本文地址:https://www.ebpf.top/post/ubuntu_2104_bpf_env

1. 系统安装

1.1 Vagrant

Vagrant 是一款用于构建及配置虚拟开发环境的软件,基于 Ruby,主要以命令行的方式运行。Vagrant 由 HashiCorp 官方出品,相信提到大名鼎鼎的 HashiCorp 公司,大家还能够联想到著名的安全密码存储 Valut、服务注册发现 Consul、大规模调度系统 Normad 等等。

Vagrant 可基于官方提供的各种虚拟机打包文件(比如 VirtualBox 的 Box 文件)快速搭建各种系统的验证环境,也可以灵活通过配置式的文件管理多 VM 环境,同时具有平台适配性,是非常好的 VM 管理工具。但其自身并不提供虚拟化环境,需要配合 VirtualBox、WMware、KVM 等虚拟化技术,1.6 版本底层也支持使用容器,可以将容器替代完整的虚拟系统。

Vagrant 的架构使用 “Provisioners” 和 “Providers” 作为开发环境的构建模块。

|--vagrant
|--Providers        如:VirtualBox、Hyper-V、Docker、VMware、AWS
|--Boxex            如:Centos7。与镜像类似
|--Provisioners     如:'yum intall -y python' 等自定义自动化脚本

Vagrant 作为最外层的虚拟软件,目的是帮助开发者更容易地与 Providers 互动。Vagrantfile 记录 Providers 和 Provisioners 的相关信息。

Providers 作为服务,帮助 vagrant 使用 Boxes 建立和创建虚拟环境。Vagrant 提供的内嵌的 Provider 有 VirtualBox、Hyper-V、Docker、VMware 等。

在 Mac 系统中如果使用 Brew 管理包,则可以通过 brew 命令直接安装。其他系统的安装方式可参考 下载 Vagrant,查看详情。

1
$ brew install vagrant

在安装 vagrant 成功后,我们还需要安装底层的虚拟化软件(即上述架构中的 Providers)才能正常工作,这里我们使用 VirtualBox。

1.2 VirtualBox

Oracle VirtualBox 是德国公司 InnoTek 出品的虚拟机软件,现在由 Oracle 公司管理和发行,适用于多平台系统。用户可以在 VirtualBox 上安装并且运行 Solaris/Windows/Linux 等系统作为客户端操作系统。

在 Mac 系统上可以通过下载 dmg 文件进行安装。其他系统的安装参见 Oracle VM VirtualBox 基础包

在 Vagrant 和 VirtualBox 安装完成后,我们可以使用以下命令快速搭建一个 Ubuntu 的 VM:

1
2
$ vagrant init ubuntu/hirsute64
$ vagrant up

在启动成功后,我们可以通过 vagrant ssh 直接登录到 VM 系统中。

1.3 系统环境搭建

2021 年 4 月 22 号,Ubuntu 发布了 21.04 Hirsute Hippo 版本 1,内核采用 5.11.0 版本。这里选择最新的 Ubuntu 发行版本,主要考虑 BPF 技术演进较快,新功能基本都需要高版本内核,采用最新发行的 Ubuntu 发行版本,方便后续的 BPF 功能学习和内核版本升级。更多 Ubuntu 版本发布的详情可参见官方 Wiki

这里我们使用 Vagrant 虚拟机的方式快速搭建环境,Hirsute Box 镜像 可在官方提供的 Vagrant Boxs 市场 中查询,ubuntu/hirsute64 Box 的镜像大小为 600M 左右。

1
2
3
4
5
$ vagrant init ubuntu/hirsute64
$ vagrant up

# 如果 box 有更新,可以使用 update 子命令更新
#$ vagrant box update

如果从国外下载镜像过慢,可以通过其他方式下载 Box 文件,使用命令: vagrant box add <name> <boxpath> 然后导入到本地后再启动上述命令。为方便大家快速下载我已经上传了一份到 百度网盘 ,提取码【bgfg】。

1
2
3
$ vagrant box add ubuntu/hirsute64 ~/Downloads/ubuntu_2104.box
$ vagrant box list
ubuntu/hirsute64   (virtualbox, 20210923.0.0)

另外,我们也可以使用以下命令将正在运行的 VM 重新打包成 Box 文件:

1
$ vagrant package --output hirsute64_my.box

如果上述安装顺利,我们可以通过 ssh 的方式登录到新创建的 VM 机器中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ vagrant ssh
Welcome to Ubuntu 21.04 (GNU/Linux 5.11.0-36-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sat Sep 25 11:22:52 UTC 2021

  System load:  0.0               Processes:               105
  Usage of /:   3.4% of 38.71GB   Users logged in:         0
  Memory usage: 18%               IPv4 address for enp0s3: 10.0.2.15
  Swap usage:   0%

 * Super-optimized for small spaces - read how we shrank the memory
   footprint of MicroK8s to make it the smallest full K8s around.

   https://ubuntu.com/blog/microk8s-memory-optimisation

0 updates can be applied immediately.


Last login: Sat Sep 25 08:13:09 2021 from 10.0.2.2
vagrant@ubuntu-hirsute:~$ uname -a
Linux ubuntu-hirsute 5.11.0-36-generic #40-Ubuntu SMP Fri Sep 17 18:15:22 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

通过检查 CONFIG_DEBUG_INFO_BTF 内核编译选项确认系统是否已经支持 BTF 能力,这是 BPF CO-RE (Compile Once – Run Everywhere) 能力的基础。

1
2
3
$ grep CONFIG_DEBUG_INFO_BTF /boot/config-5.11.0-36-generic
CONFIG_DEBUG_INFO_BTF=y
CONFIG_DEBUG_INFO_BTF_MODULES=y

至此,我们的 BPF VM 环境已基本准备完成。

2. BPF 程序编译

对于我们编译 BPF 程序而言需要系统已经安装了必备的 linux-headers 包。在 Ubuntu/Debian/Arch/Manjaro 发行版系统中需要安装 linux-headers 包,而在 CentOS/Fedora 发行版系统中需要安装 kernel-headers 和 kernel-devel 两个包。

首先我们在 VM 中安装 linux-headers 包:

1
2
3
4
5
6
$ sudo apt update
$ sudo apt install linux-headers-$(uname -r)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
linux-headers-5.11.0-36-generic is already the newest version (5.11.0-36.40).

如果是通过内核升级版本的场景,务必确认 linux-headers 版本与当前运行的内核版本一致。

为了能够编译 BPF 程序,我们还需要安装编译所依赖的工具包:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ sudo apt install -y bison flex build-essential git cmake make libelf-dev  clang llvm strace tar libfl-dev libssl-dev libedit-dev zlib1g-dev  python  python3-distutils

$ clang --version
Ubuntu clang version 12.0.0-3ubuntu1~21.04.2
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin 

$ llc --version
LLVM (http://llvm.org/):
  LLVM version 12.0.0

  Optimized build.
  Default target: x86_64-pc-linux-gnu
  Host CPU: haswell
  
  Registered Targets:
    ...
    bpf        - BPF (host endian)
    bpfeb      - BPF (big endian)
    bpfel      - BPF (little endian)
    ...

通过上述 clang/llvm 版本信息查看,我们可以知道在 Ubuntu 21.04 版本中,安装的为 12.0 的版本,这可以满足我们对于 CO-RE 能力的要求(>= 9.0 版本)。

我们使用 libbpf-bootstrap 来进行编译验证,同时使用 --recursive 参数将 libbpf-bootstrap 仓库中的依赖子模块 libbpf 同时下载到本地。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
$ git clone --recursive https://github.com/libbpf/libbpf-bootstrap.git
Cloning into 'libbpf-bootstrap'...
remote: Enumerating objects: 260, done.
remote: Counting objects: 100% (172/172), done.
remote: Compressing objects: 100% (96/96), done.
remote: Total 260 (delta 74), reused 149 (delta 64), pack-reused 88
Receiving objects: 100% (260/260), 1.65 MiB | 280.00 KiB/s, done.
Resolving deltas: 100% (118/118), done.
Submodule 'libbpf' (https://github.com/libbpf/libbpf.git) registered for path 'libbpf'   # 子模块 libbpf 下载
Cloning into '/home/vagrant/libbpf-bootstrap/libbpf'...
remote: Enumerating objects: 6174, done.
remote: Counting objects: 100% (1222/1222), done.
remote: Compressing objects: 100% (336/336), done.
remote: Total 6174 (delta 965), reused 942 (delta 866), pack-reused 4952
Receiving objects: 100% (6174/6174), 3.59 MiB | 547.00 KiB/s, done.
Resolving deltas: 100% (4087/4087), done.
Submodule path 'libbpf': checked out '8bf016110e683df2727a22ed90c9c9860c966544'

$ cd libbpf-bootstrap/examples/c
~/libbpf-bootstrap/examples/c$ make

如没有错误提示,且通过下述的 ls 命令可查看到对应的二进制文件,则表明编译成功。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ ls -hl
-rwxrwxr-x 1 vagrant vagrant 1.4M Sep 25 13:30 bootstrap
-rwxrwxr-x 1 vagrant vagrant 1.3M Sep 25 13:30 fentry
-rwxrwxr-x 1 vagrant vagrant 1.3M Sep 25 13:30 kprobe
-rwxrwxr-x 1 vagrant vagrant 1.3M Sep 25 13:30 minimal
-rwxrwxr-x 1 vagrant vagrant 1.3M Sep 25 13:30 uprobe

# 通过 bootstrap 程序运行验证
~/libbpf-bootstrap/examples/c$ sudo ./bootstrap
TIME     EVENT COMM             PID     PPID    FILENAME/EXIT CODE
13:41:14 EXEC  ls               16473   7633    /usr/bin/ls
13:41:14 EXIT  ls               16473   7633    [0] (1ms)

# 用户空间程序与内核中的 BPF 通信的唯一入口是 bpf() 系统调用,可以通过 strace 查看整个交互流程
~/libbpf-bootstrap/examples/c$ sudo strace -e bpf ./bootstrap
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_SOCKET_FILTER, insn_cnt=2, insns=0x7fffd2d6e3c0, license="GPL", log_level=0, log_size=0, log_buf=NULL, kern_version=KERNEL_VERSION(0, 0, 0), prog_flags=0, prog_name="", prog_ifindex=0, expected_attach_type=BPF_CGROUP_INET_INGRESS, prog_btf_fd=0, func_info_rec_size=0, func_info=NULL, func_info_cnt=0, line_info_rec_size=0, line_info=NULL, line_info_cnt=0, attach_btf_id=0, attach_prog_fd=0}, 128) = 3
bpf(BPF_BTF_LOAD, {btf="\237\353\1\0\30\0\0\0\0\0\0\0\20\0\0\0\20\0\0\0\5\0\0\0\1\0\0\0\0\0\0\1"..., btf_log_buf=NULL, btf_size=45, btf_log_size=0, btf_log_level=0}, 128) = 3
...

至此我们可以将 VM 导出为 Box 文件以便作为后续的基础,需要在 VM Vagrant 文件所在的目录操作运行,导出过程会自动关闭 VM 虚拟机。

1
2
3
4
ubuntu_21_04$ vagrant package --output ubuntu_21_04_bpf.box
==> default: Attempting graceful shutdown of VM...
==> default: Clearing any previously set forwarded ports...
==> default: Exporting VM...

ubuntu_21_04_bpf.box 文件已经上传 百度云盘 ,提取码【if2j】,文件大小 1.1G。

3. 内核代码安装和 sample/bpf 模块编译

安装 Ubuntu 21.04 源代码,并解压源代码:

1
2
3
4
5
6
7
8
$ sudo apt-cache search linux-source
linux-source - Linux kernel source with Ubuntu patches
linux-source-5.11.0 - Linux kernel source for version 5.11.0 with Ubuntu patches

$ sudo apt install linux-source-5.11.0
$ sudo cd /usr/src
$ sudo tar -jxvf linux-source-5.11.0.tar.bz2
$ sudo cd /usr/src/linux-source-5.11.0

源码编译 sample/bpf 模块:( 为方便编译,统一切换成 root 用户 )

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# cp -v /boot/config-$(uname -r) .config
# make oldconfig && make prepare
# make headers_install
# apt-get install libcap-dev   // 解决 sys/capability.h: No such file or directory
# make M=samples/bpf          // 编译最后,可能会报部分 WARNING,可以忽略
samples/bpf/Makefile:231: WARNING: Detected possible issues with include path.
samples/bpf/Makefile:232: WARNING: Please install kernel headers locally (make headers_install).
WARNING: Symbol version dump "Module.symvers" is missing.
         Modules may not have dependencies or modversions.
  MODPOST samples/bpf/Module.symvers
WARNING: modpost: Symbol info of vmlinux is missing. Unresolved symbol check will be entirely skipped.

# ls -hl samples/bpf/          // 查看是否生产需要的样例代码,如果生成了可执行文件,这表明编译成功

# ./tracex1
libbpf: elf: skipping unrecognized data section(4) .rodata.str1.1
libbpf: elf: skipping unrecognized data section(17) .eh_frame
libbpf: elf: skipping relo section(18) .rel.eh_frame for section(17) .eh_frame
           <...>-36521   [000] d.s1  2056.935795: bpf_trace_printk: skb 00000000a32b8f51 len 84
           <...>-36521   [000] d.s1  2056.935817: bpf_trace_printk: skb 0000000094918e19 len 84

4. 附加 BPF 样例编译

在 samples/bpf 目录包含接近 60 个相关的程序,给与我们学习提供了便利,同时也提供了一个组织编译的 Makefile 框架。如果我们用 C 语言编写的 BPF 程序编译可以直接在该目录提供的环境中进行编译。

如果是需要单独编译的场景,也可以参考 BCC 仓库中的 libbpf-tools 下的样例。

samples/bpf 下的程序一般组成方式是 xyz_user.c 和 xyz_kern.c:

  • xyz_user.c 为用户空间的程序用于设置 BPF 程序的相关配置、加载 BPF 程序至内核、设置 BPF 程序中的 map 值和读取 BPF 程序运行过程中发送至用户空间的消息等。目前 xyz_user.c 与 xyz_kern.c 程序在交互实现都是基于 bpf() 系统调用完成的。直接使用 bpf() 系统调用涉及的参数和细节比较多,使用门槛较高,因此为了方便用户空间程序更加易用,内核提供了 libbpf 库封装了对于 bpf() 系统调用的细节。
  • xyz_kern.c 为 BPF 程序代码,通过 clang 编译成字节码加载至内核中,在对应事件触发的时候运行,可以接受用户空间程序发送的各种数据,并将运行时产生的数据发送至用户空间程序。

完整的样例代码参见 hello_world_bpf_ex

hello_user.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <unistd.h>
#include <bpf/libbpf.h>
#include "trace_helpers.h"

int main(int ac, char **argv)
{
	struct bpf_link *link = NULL;
	struct bpf_program *prog;
	struct bpf_object *obj;
	char filename[256];

	snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
	obj = bpf_object__open_file(filename, NULL);
	if (libbpf_get_error(obj)) {
		fprintf(stderr, "ERROR: opening BPF object file failed\n");
		return 0;
	}

	prog = bpf_object__find_program_by_name(obj, "bpf_hello");
	if (!prog) {
		fprintf(stderr, "ERROR: finding a prog in obj file failed\n");
		goto cleanup;
	}

	/* load BPF program */
	if (bpf_object__load(obj)) {
		fprintf(stderr, "ERROR: loading BPF object file failed\n");
		goto cleanup;
	}

	link = bpf_program__attach(prog);
	if (libbpf_get_error(link)) {
		fprintf(stderr, "ERROR: bpf_program__attach failed\n");
		link = NULL;
		goto cleanup;
	}

	read_trace_pipe();

cleanup:
	bpf_link__destroy(link);
	bpf_object__close(obj);
	return 0;
}

hello_kern.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <uapi/linux/bpf.h>
#include <linux/version.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog1(struct pt_regs *ctx)
{
  char fmt[] = "Hello %s !\n";
	char comm[16];
	bpf_get_current_comm(&comm, sizeof(comm));  
  bpf_trace_printk(fmt, sizeof(fmt), comm);
  
	return 0;
}

char _license[] SEC("license") = "GPL";
u32 _version SEC("version") = LINUX_VERSION_CODE;

Makefile 文件修改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# diff -u Makefile.old Makefile
--- Makefile.old	2021-09-26 03:16:16.883348130 +0000
+++ Makefile	2021-09-26 03:20:46.732277872 +0000
@@ -55,6 +55,7 @@
 tprogs-y += xdp_sample_pkts
 tprogs-y += ibumad
 tprogs-y += hbm
+tprogs-y += hello

 # Libbpf dependencies
 LIBBPF = $(TOOLS_PATH)/lib/bpf/libbpf.a
@@ -113,6 +114,7 @@
 xdp_sample_pkts-objs := xdp_sample_pkts_user.o
 ibumad-objs := ibumad_user.o
 hbm-objs := hbm.o $(CGROUP_HELPERS)
+hello-objs := hello_user.o $(TRACE_HELPERS)

 # Tell kbuild to always build the programs
 always-y := $(tprogs-y)
@@ -174,6 +176,7 @@
 always-y += hbm_out_kern.o
 always-y += hbm_edt_kern.o
 always-y += xdpsock_kern.o
+always-y += hello_kern.o

 ifeq ($(ARCH), arm)
 # Strip all except -D__LINUX_ARM_ARCH__ option needed to handle linux

在当前目录重新运行 make 命令即可。编译完成后,启动命令后在其他窗口执行 ls -hl ,显示效果如下:

1
2
3
4
5
6
7
8
9
$ sudo ./hello 
           <...>-46054   [000] d...  7627.000940: bpf_trace_printk: Hello bash !
                      
$ ldd hello
	linux-vdso.so.1 (0x00007ffd71306000)
	libelf.so.1 => /lib/x86_64-linux-gnu/libelf.so.1 (0x00007f99d67fd000)
	libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f99d67e1000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f99d65f5000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f99d6cf1000)

5. 系统内核升级

5.1 Ubuntu 内核官方包升级

mainline 为 Ubuntu 提供可视化的内核升级工具,早期版本为 ukuu 。该工具支持的特性如下:

  • 从 Ubuntu Mainline PPA 中获取可用的内核列表
  • 当有新的内核更新时,可以选择观察并显示通知
  • 自动下载和安装软件包
  • 方便地显示可用和已安装的内核
  • 从界面上安装 / 卸载内核
  • 对于每个内核,相关的软件包(头文件和模块)会同时安装或卸载

使用 Vagrant 快速搭建一个 VM 环境:

1
2
3
4
$ vagrant box add  ubuntu/hirsute64-ukuu ~/Downloads/ubuntu_21_04_bpf.box
$ mkdir ubuntu_21_04_ukuu && cd ubuntu_21_04_ukuu
$ vagrant init ubuntu/hirsute64-ukuu
$ vagrant up

可以通过以下命令自动安装:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ sudo add-apt-repository ppa:cappelikan/ppa
$ sudo apt update
$ sudo apt install mainline

$  mainline -h
mainline 1.0.15
Distribution: Ubuntu 21.04
Architecture: amd64
Running kernel: 5.11.0-36-generic

mainline 1.0.15 - Ubuntu Mainline Kernel Installer

list 子命令会显示出来当前系统支持升级的或者已经安装的系统:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
$ mainline --list
mainline 1.0.15
Distribution: Ubuntu 21.04
Architecture: amd64
Running kernel: 5.11.0-36-generic
Fetching index from kernel.ubuntu.com...
OK

Fetching index...

▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 100 %

----------------------------------------------------------------------
Found installed: 5.11.0.36.38
Found installed: 5.11.0-36.40
----------------------------------------------------------------------
----------------------------------------------------------------------

======================================================================
Available Kernels
======================================================================
5.14.8
5.14.7

$ sudo mainline --install 5.14.7  # 则会启动对应的内核安装
mainline 1.0.15
Distribution: Ubuntu 21.04
Architecture: amd64
Running kernel: 5.11.0-36-generic
Fetching index from kernel.ubuntu.com...
OK

Fetching index...

▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 100 %

----------------------------------------------------------------------
Found installed: 5.11.0.36.38
Found installed: 5.11.0-36.40
----------------------------------------------------------------------
----------------------------------------------------------------------

Downloading: 'amd64/linux-headers-5.14.7-051407-generic_5.14.7-051407.202109221210_amd64.deb'...
OK

Downloading: 'amd64/linux-image-unsigned-5.14.7-051407-generic_5.14.7-051407.202109221210_amd64.deb'...
OK

Downloading: 'amd64/linux-headers-5.14.7-051407_5.14.7-051407.202109221210_all.deb'...
OK

Downloading: 'amd64/linux-modules-5.14.7-051407-generic_5.14.7-051407.202109221210_amd64.deb'...
OK
...

通过上述输出我们也可以看出,和手工安装的过程类似,同样需要 4 个主要文件,( 手动安装可以到 http://kernel.ubuntu.com/~kernel-ppa/mainline/ 这里下载 ):

  • linux-headers-5.14.7-051407-generic_5.14.7-051407.202109221210_amd64.deb(通用头文件) 和 amd64/linux-headers-5.14.7-051407_5.14.7-051407.202109221210_all.deb(基础头文件)
  • amd64/linux-image-unsigned-5.14.7-051407-generic_5.14.7-051407.202109221210_amd64.deb
  • amd64/linux-modules-5.14.7-051407-generic_5.14.7-051407.202109221210_amd64.deb

在本次升级到 5.14.7 的过程中,modules 和 linux images 成功,但是安装 linux-headers-5.14.7-051407-generic 头文件的时候,遇到了以下的问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ ...
dpkg: dependency problems prevent configuration of linux-headers-5.14.7-051407-generic:
 linux-headers-5.14.7-051407-generic depends on libc6 (>= 2.34); however:
  Version of libc6:amd64 on system is 2.33-0ubuntu5.

dpkg: error processing package linux-headers-5.14.7-051407-generic (--install):
 dependency problems - leaving unconfigured
....
Errors were encountered while processing:
 linux-headers-5.14.7-051407-generic
E: Installation completed with errors

错误提示的大意是我们安装的 linux-headers-5.14.7-051407 需要 libc 的版本 >= 2.34,而我们系统的 libc 版本为 2.33。而 linux-headers 是我们 BPF 需要的环境 linux-headers,所以还需要手动修复安装。

1
2
3
4
$ sudo reboot
...
$ uname -a
Linux ubuntu-hirsute 5.14.7-051407-generic #202109221210 SMP Wed Sep 22 15:15:48 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

系统重启后已经证明我们成功升级了系统,这里我们继续手工修复 linux-headers 问题,首先尝试使用手工安装:

1
2
3
4
5
6
7
8
9
$ sudo apt install linux-headers-$(uname -r)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
linux-headers-5.14.7-051407-generic is already the newest version (5.14.7-051407.202109221210).
You might want to run 'apt --fix-broken install' to correct these.
The following packages have unmet dependencies:
 linux-headers-5.14.7-051407-generic : Depends: libc6 (>= 2.34) but 2.33-0ubuntu5 is to be installed
E: Unmet dependencies. Try 'apt --fix-broken install' with no packages (or specify a solution).

错误提示我们使用 apt --fix-broken install 来修订错误,该命令其实做的事情就是删除存在问题的头文件包:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ sudo apt --fix-broken install
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Correcting dependencies... Done
The following packages will be REMOVED:
  linux-headers-5.14.7-051407-generic
0 upgraded, 0 newly installed, 1 to remove and 4 not upgraded.
1 not fully installed or removed.
After this operation, 23.3 MB disk space will be freed.
Do you want to continue? [Y/n] y
(Reading database ... 162397 files and directories currently installed.)
Removing linux-headers-5.14.7-051407-generic (5.14.7-051407.202109221210) ...

$ dpkg -l |grep -i  linux-headers-5.14.7
ii  linux-headers-5.14.7-051407                5.14.7-051407.202109221210                                           all          Header files related to Linux kernel version 5.14.7

从网上搜索 libc-2.34 版本的安装包 ,下载并安装:

1
2
3
4
5
6
7
$ wget http://launchpadlibrarian.net/560614488/libc6_2.34-0ubuntu3_amd64.deb

# 因为本地已经安装了 libc6_2.33,安装会提示冲突,这里使用 --force-all 参数
$ sudo dpkg -i --force-all libc6_2.34-0ubuntu3_amd64.deb

$ dpkg -l|grep libc6
ii  libc6:amd64                                2.34-0ubuntu3                                                        amd64        GNU C Library: Shared libraries

Ubunt 5.14.7 主线 下载安装出错的 linux-headers-5.14.7-051407-generic,然后手工进行安装:

1
2
3
4
5
6
$sudo dpkg -i linux-headers-5.14.7-051407-generic_5.14.7-051407.202109221210_amd64.deb
Selecting previously unselected package linux-headers-5.14.7-051407-generic.
(Reading database ... 152977 files and directories currently installed.)
Preparing to unpack linux-headers-5.14.7-051407-generic_5.14.7-051407.202109221210_amd64.deb ...
Unpacking linux-headers-5.14.7-051407-generic (5.14.7-051407.202109221210) ...
Setting up linux-headers-5.14.7-051407-generic (5.14.7-051407.202109221210) ...

这种强制安装的 pkg 的方式,会影响到其他依赖包的管理,一般不推荐。 这种情况一般是我们升级的内核版本比较新,其他的对应组件还未能完全更新。如果是升级到 Ubuntu 的官方新发布版本(比如从 20.04 升级到 21.04),建议通过官方提供的方式升级,内核和其他依赖包都可以一起升级,保证整体完整型,可参考 如何升级到 Ubuntu 20.04

5.2 内核源码编译升级内核(进阶版)

某些情况我们需要更新版本的内核,如果 Ubuntu 官方还未提供该版本的安装包及内核源码,那么我们就需要通过源码编译和安装的方式进行。本文从 Ubuntu 环境中编译,其他的 Linux 发行版可以参考 内核源码编译指南

5.2.1 源码下载及编译工具安装

基于 Ubuntu 21.04 的系统,我们从 内核网站 上选择 5.14.7 版本进行编译。

1
2
3
$ wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.14.7.tar.xz
$ xz -d linux-5.14.7.tar.xz
$ tar xvf linux-5.14.7.tar

安装必要的内核编译工具:

1
$ sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev -y

Linux BTF 功能需要 pahole 工具 (>= 1.16) ,该工具位于 dwarves 包中,需要提前进行安装。

1
$ sudo apt install dwarves -y

5.2.2 源码编译

编译前需要将系统中运行的 config 文件复制到内核编译目录中为 .config 文件。

1
2
3
4
5
$ cd linux-5.14.7/
~/linux-5.14.7$ cp -v /boot/config-5.11.0-36-generic .config
'/boot/config-5.11.0-36-generic' -> '.config'

$ make menuconfig 

因为当前目录已经存在 .config 文件,make menuconfig 会直接使用 .config 作为默认值,通过图形界面修改值以后,默认会将当前的 .config 备份成 .config.old,并生成新的 .config 文件。

这里如果采用 make oldconfig,那么则是通过命令界面配置内核,会自动载入既有的 .config 配置文件,并且只有在遇到先前没有设定过的选项时,才会要求我们手动设定。同样也会将老的 .config 备份成 .config.old 文件。

运行 make 命令编译, -j 参数可指定并行 CPU 核数:

1
$ make -j $(getconf _NPROCESSORS_ONLN)

如果编译过程中遇到 No rule to make target 'debian/canonical-certs.pem' 的错误,可以通过禁用证书或者将运行系统中源码的相关证书复制到当前目录下的 debian 目录。

方案 1:复制系统中的证书

将当前运行系统源码中的 /usr/src/linux-source-5.11.0/debian/canonical-certs.pem 和 /usr/src/linux-source-5.11.0/debian/canonical-revoked-certs.pem 复制到当前 debian 目录中:

1
2
3
$ mkdir debian
$ cp /usr/src/linux-source-5.11.0/debian/canonical-certs.pem ./debain/
$ cp /usr/src/linux-source-5.11.0/debian/canonical-revoked-certs.pem ./debain/

方案 2:采用禁用 SYSTEM_TRUSTED_KEYS 的方式

1
$ scripts/config --disable SYSTEM_TRUSTED_KEYS  # 关闭禁止证书,修订 No rule to make target 'debian/canonical-certs.pem'

待证书设置以后,就可以运行 make 命令进行编译。

编译的时长使用的 CPU 核数相关,编译大概占用 20G 左右的产品空间。

5.2.3 内核安装和启动选择

编译成功后,使用以下命令安装内核:

1
2
3
4
5
$ sudo make modules_install && make install && make headers_install 
$ sudo reboot
[....]
$ uname -a
Linux ubuntu-hirsute 5.14.7 #1 SMP Sun Sep 26 11:32:44 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

reboot 重启后,我们使用使用 uname 查看,发现系统内核已经更新为了 5.14.7 版本。

Ubuntu 21.04 采用 GRUB2 来管理启动菜单,对应的文件为 /boot/grub/grub.cfg ,该文件为 update-grub 命令依据 /etc/default/grub 文件自动生成。如果需要调整菜单选项,这需要通过 /etc/default/grub 文件修改,然后通过运行 update-grub 命令生效。

/etc/default/grub 文件中的:

1
2
3
GRUB_DEFAULT=0
GRUB_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=0

GRUB_DEFAULT 指定了默认启动的菜单选项,0 表示第一个菜单选项。

GRUB_TIMEOUT 选项表明了启动菜单显示的时间,方便用于用户选择,默认为 0,在调试新的内核时建议设置成一个非 0 值,同时需要将 GRUB_TIMEOUT_STYLE 的值从 hidden 调整为 menu:

1
2
GRUB_TIMEOUT_STYLE=menu
GRUB_TIMEOUT=5

然后通过 update-grub 命令更新生效,再重新启动就可以看到启动菜单。其他参数详细配置可参见 Grub 配置

todo: 在 VirtualBox 中能看到停顿,但是未能够看到启动界面

5.2.4 其他 - 编译内核以后磁盘管理

Virtualbox 底层使用 vmdk 的磁盘格式,当前会存在只是单向增大不会自动收缩的问题(即使我们在编译内核后,删除了编译相关的问题),请参见 VirtualBox VM 空间瘦身记(vmdk)


  1. Ubuntu 21.04 is here ↩︎