Linux内核(二)——内核编译与安装
内核获取与分析
- 1. 获取内核源码
- 2. 内核编译与安装
- 3. 内核开发的特点
1. 获取内核源码
Linux内核的官方网站:http://www.kernel.org
可以看到,最新的Linux内核发布版本为6.12。我们选一个长期稳定版本的6.6.62来学习。
由于使用Git命令(git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-6.6.git
)会下载非常多修订版本,因此我们直接下载6.6.62的tar文件:
wget https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.6.62.tar.xz
下载完成后解压:``
查看Linux6.6内核的大致结构:
fle@fle-virtual-machine:/tmp/linux-6.6.62$ cd linux-6.6.62/
fle@fle-virtual-machine:/tmp/linux-6.6.62/linux-6.6.62$ ls
arch CREDITS fs ipc lib mm samples tools
block crypto include Kbuild LICENSES net scripts usr
certs Documentation init Kconfig MAINTAINERS README security virt
COPYING drivers io_uring kernel Makefile rust sound
目录 | 描述 |
---|---|
arch | 存放与硬件架构相关的代码,例如 x86、ARM、MIPS 等。不同架构的特定实现文件都在这个目录中,支持多种硬件平台。 |
block | 与块设备相关的核心代码,如磁盘调度程序、I/O 子系统等。块设备是存储设备(如硬盘)的抽象 |
crypto | 实现加密算法和加密框架的模块,包括 AES、SHA 等常见的加密和哈希算法 |
Documentation | 内核开发的文档目录,包含关于代码结构、API 使用、驱动开发等方面的说明,非常适合开发人员参考 |
drivers | 设备驱动程序代码的存放目录,按设备类型进一步细分:net/:网络设备驱动。gpu/:图形处理单元驱动。input/:输入设备(如键盘、鼠标)驱动。usb/:USB 设备驱动。 |
fs | 文件系统相关代码,包括 VFS(虚拟文件系统)和具体的文件系统实现(如 ext4、XFS、NTFS 等) |
include | 内核使用的头文件,提供各种宏定义、数据结构和函数原型。常见子目录:linux/:Linux 核心头文件。uapi/:用户空间可访问的头文件。 |
init | 内核初始化代码,如启动流程和早期初始化逻辑。此目录中的代码用于引导操作系统进入正常运行状态 |
ipc | 进程间通信(IPC)机制的代码实现,例如信号量、共享内存和消息队列 |
kernel | 内核核心部分的代码,处理调度器、信号、线程管理等内容 |
lib | 通用的库代码,用于支持内核各个模块。包括字符串处理、数学运算等基础功能 |
mm | 内存管理相关代码,包括分页管理、内存分配和虚拟内存机制 |
net | 网络协议栈的实现代码,包括 TCP/IP、UDP、IPv4/IPv6,以及各种网络相关的功能模块 |
scripts | 辅助脚本和工具,用于编译、配置和生成内核镜像等任务 |
security | 与内核安全相关的代码,包括 SELinux 和其他访问控制框架 |
sound | 音频子系统代码,支持声卡和音频设备 |
tools | 开发工具目录,包括性能分析工具、调试工具等 |
usr | 存放用户空间的代码,主要用于内核初始镜像(initramfs)的生成 |
virt | 虚拟化相关代码,支持虚拟机和虚拟化技术(如 KVM) |
总共19个目录,涉及到调度(kernel)、内存管理(mm)、文件系统(fs)、设备支持(drivers)和网络通信(net)等。
2. 内核编译与安装
本小节在Ubuntu22.04上新装一个内核,实现一个系统双内核,这样就可以用其中一个内核来学习(改飞也不要紧)。
----------------------------------------------------------错误示例-------------------------------------------------------------------------
下载的只是内核的源码,需要进行编译形成二进制可执行文件才可以使用。在源码根目录执行以下命令进行编译:
make defconfig
make
下面是编译后会形成的文件:
fle@fle-virtual-machine:/tmp/linux-6.6.62/linux-6.6.62$ ls
arch Documentation Kbuild mm rust usr
block drivers Kconfig modules.builtin samples virt
built-in.a fs kernel modules.builtin.modinfo scripts vmlinux
certs include lib modules.order security vmlinux.a
COPYING init LICENSES Module.symvers sound vmlinux.o
CREDITS io_uring MAINTAINERS net System.map
crypto ipc Makefile README tools
接下来是进行内核的安装:
使用Ubuntu 22.04.4 LTS来进行安装,查看现有的系统内核版本:
fle@fle-virtual-machine:/tmp/linux-6.6.62/linux-6.6.62$ uname -r
6.8.0-48-generic
可以看到Ubuntu系统使用的内核版本是6.8.0。
①将编译生成的压缩内核文件(如 bzImage 或 zImage)复制到 /boot 目录
fle@fle-virtual-machine:/tmp/linux-6.6.62/linux-6.6.62$ sudo cp arch/x86/boot/bzImage /boot/vmlinuz-custom
将bzImage复制到boot下并重新命名为vmlinuz-custom。
②安装编译生成的内核模块到系统
fle@fle-virtual-machine:/tmp/linux-6.6.62/linux-6.6.62$ sudo make modules_install
SYMLINK /lib/modules/6.6.62/build
INSTALL /lib/modules/6.6.62/modules.order
INSTALL /lib/modules/6.6.62/modules.builtin
INSTALL /lib/modules/6.6.62/modules.builtin.modinfo
INSTALL /lib/modules/6.6.62/kernel/fs/efivarfs/efivarfs.ko
INSTALL /lib/modules/6.6.62/kernel/drivers/thermal/intel/x86_pkg_temp_thermal.ko
INSTALL /lib/modules/6.6.62/kernel/net/netfilter/nf_log_syslog.ko
INSTALL /lib/modules/6.6.62/kernel/net/netfilter/xt_mark.ko
INSTALL /lib/modules/6.6.62/kernel/net/netfilter/xt_nat.ko
INSTALL /lib/modules/6.6.62/kernel/net/netfilter/xt_LOG.ko
INSTALL /lib/modules/6.6.62/kernel/net/netfilter/xt_MASQUERADE.ko
INSTALL /lib/modules/6.6.62/kernel/net/netfilter/xt_addrtype.ko
INSTALL /lib/modules/6.6.62/kernel/net/ipv4/netfilter/iptable_nat.ko
DEPMOD /lib/modules/6.6.62
模块会被复制到 /lib/modules/<内核版本号>/:
fle@fle-virtual-machine:/tmp/linux-6.6.62/linux-6.6.62$ ls /lib/modules
6.5.0-18-generic 6.5.0-27-generic 6.5.0-41-generic 6.8.0-48-generic
6.5.0-21-generic 6.5.0-28-generic 6.6.62 6.8.0-49-generic
6.5.0-26-generic 6.5.0-35-generic 6.8.0-45-generic
可以看到,/lib/modules
中有了6.6.62版本的。
③更新 GRUB 配置文件以添加新内核
更改/boot/grub/grub.cfg
文件之前先看一下其关于vmlinuz的内容:
fle@fle-virtual-machine:/tmp/linux-6.6.62/linux-6.6.62$ cat /boot/grub/grub.cfg | grep 'vmlinuz' linux /boot/vmlinuz-6.8.0-49-generic root=UUID=8d98b873-ec04-41de-9476-6cef0431f7c7 ro quiet splash $vt_handoff
linux /boot/vmlinuz-6.8.0-49-generic root=UUID=8d98b873-ec04-41de-9476-6cef0431f7c7 ro quiet splash $vt_handoff
linux /boot/vmlinuz-6.8.0-49-generic root=UUID=8d98b873-ec04-41de-9476-6cef0431f7c7 ro recovery nomodeset dis_ucode_ldr
linux /boot/vmlinuz-6.8.0-48-generic root=UUID=8d98b873-ec04-41de-9476-6cef0431f7c7 ro quiet splash $vt_handoff
linux /boot/vmlinuz-6.8.0-48-generic root=UUID=8d98b873-ec04-41de-9476-6cef0431f7c7 ro recovery nomodeset dis_ucode_ldr
更新文件:
fle@fle-virtual-machine:/tmp/linux-6.6.62/linux-6.6.62$ sudo update-grub
Sourcing file `/etc/default/grub'
Sourcing file `/etc/default/grub.d/init-select.cfg'
Generating grub configuration file ...
dpkg: 警告: 版本号 'custom' 语法错误: 版本号没有以数字开头
Found linux image: /boot/vmlinuz-custom
Found linux image: /boot/vmlinuz-6.8.0-49-generic
Found initrd image: /boot/initrd.img-6.8.0-49-generic
Found linux image: /boot/vmlinuz-6.8.0-48-generic
Found initrd image: /boot/initrd.img-6.8.0-48-generic
Found memtest86+ image: /boot/memtest86+.elf
Found memtest86+ image: /boot/memtest86+.bin
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
done
可以看到上面有一个警告,所以建议命名的时候还是把custom改为具体的版本号。
重新查看配置文件:
fle@fle-virtual-machine:/tmp/linux-6.6.62/linux-6.6.62$ cat /boot/grub/grub.cfg | grep 'vmlinuz-custom'
linux /boot/vmlinuz-custom root=/dev/sda3 ro quiet splash $vt_handoff
linux /boot/vmlinuz-custom root=/dev/sda3 ro quiet splash $vt_handoff
linux /boot/vmlinuz-custom root=/dev/sda3 ro recovery nomodeset dis_ucode_ldr
可以看到已经有vmlinuz-custom的相关配置了。
④重启系统并选择内核
reboot
选择Advanced options for Ubuntu
.
选择Linux custom
.
安装到这里说明内核有地方设置出错了,之前的gurb配置文件也没备份,完蛋。但是旧的内核没有问题,还是可以进入的,先进入旧内核把之前的文件删了(Tips:内核源码之前放在/tmp目录下,重启后/tmp目录是会被清空的):
fle@fle-virtual-machine:/usr/src$ sudo passwd root
[sudo] fle 的密码:
新的密码:
重新输入新的密码:
passwd:已成功更新密码
fle@fle-virtual-machine:/usr/src$ su -
密码:
root@fle-virtual-machine:~# cd /boot
root@fle-virtual-machine:/boot# ls
config-6.8.0-48-generic memtest86+.elf
config-6.8.0-49-generic memtest86+_multiboot.bin
efi System.map-6.8.0-48-generic
grub System.map-6.8.0-49-generic
initrd.img vmlinuz
initrd.img-6.8.0-48-generic vmlinuz-6.8.0-48-generic
initrd.img-6.8.0-49-generic vmlinuz-6.8.0-49-generic
initrd.img.old vmlinuz-custom
memtest86+.bin vmlinuz.old
root@fle-virtual-machine:/boot# rm -rf ./vmlinuz-custom
root@fle-virtual-machine:/boot# ls
config-6.8.0-48-generic memtest86+.elf
config-6.8.0-49-generic memtest86+_multiboot.bin
efi System.map-6.8.0-48-generic
grub System.map-6.8.0-49-generic
initrd.img vmlinuz
initrd.img-6.8.0-48-generic vmlinuz-6.8.0-48-generic
initrd.img-6.8.0-49-generic vmlinuz-6.8.0-49-generic
initrd.img.old vmlinuz.old
memtest86+.bin
由于Ubuntu默认是打不开root的,所以需要先设置密码再进入,用普通用户身份+sudo是删除不了/boot中的文件的。接下来删除 /lib/modules/
中的文件:
root@fle-virtual-machine:/boot# cd /lib/modules
root@fle-virtual-machine:/lib/modules# ls
6.5.0-18-generic 6.5.0-27-generic 6.5.0-41-generic 6.8.0-48-generic
6.5.0-21-generic 6.5.0-28-generic 6.6.62 6.8.0-49-generic
6.5.0-26-generic 6.5.0-35-generic 6.8.0-45-generic
root@fle-virtual-machine:/lib/modules# rm -rf 6.6.62
root@fle-virtual-machine:/lib/modules# ls
6.5.0-18-generic 6.5.0-27-generic 6.5.0-41-generic 6.8.0-49-generic
6.5.0-21-generic 6.5.0-28-generic 6.8.0-45-generic
6.5.0-26-generic 6.5.0-35-generic 6.8.0-48-generic
现在重新配置gurb启动文件:
root@fle-virtual-machine:/lib/modules# sudo update-grub
Sourcing file `/etc/default/grub'
Sourcing file `/etc/default/grub.d/init-select.cfg'
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-6.8.0-49-generic
Found initrd image: /boot/initrd.img-6.8.0-49-generic
Found linux image: /boot/vmlinuz-6.8.0-48-generic
Found initrd image: /boot/initrd.img-6.8.0-48-generic
Found memtest86+ image: /boot/memtest86+.elf
Found memtest86+ image: /boot/memtest86+.bin
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
done
root@fle-virtual-machine:/lib/modules# cat /etc/default/grub | grep 'vmlinuz-custom'
root@fle-virtual-machine:/lib/modules#
可以看到/etc/default/grub
文件中已经没有vmlinuz-custom了,重启系统即可恢复到之前的状态。
----------------------------------------------------------正确示例-------------------------------------------------------------------------
下面重新开始做:
** ①下载源码**
Linux 核心源代码一般建议放置于 /usr/src/kernels/ 目录下面。
# 下载源码
fle@fle-virtual-machine:~/桌面$ wget https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.6.62.tar.xz
# 解压缩到/usr/src/kernels目录
fle@fle-virtual-machine:~/桌面$ sudo mkdir /usr/src/kernels
fle@fle-virtual-machine:~/桌面$ sudo tar -x -f linux-6.6.62.tar.xz -C /usr/src/kernels/
# clean一下,make clean 仅会删除类似目标文件之类的编译过程产生的中间文件, 而不会删除配置文件。
fle@fle-virtual-machine:~/桌面$ cd /usr/src/kernels/
fle@fle-virtual-machine:/usr/src/kernels$ ls
linux-6.6.62
fle@fle-virtual-machine:/usr/src/kernels$ cd linux-6.6.62/
fle@fle-virtual-machine:/usr/src/kernels/linux-6.6.62$ make clean
②生成config-6.6.62文件
接下来是要生成config文件,如Ubuntu系统的/boot/config-6.8.0-48-generic文件。核心功能的挑选, 最后会在/usr/src/kernels/linux-6.6.62/ 下面产生一个名为 .config 的隐藏文件, 这个文件就是/boot/config-xxx 的文件啦。常见的核心功能的挑选方法有如下几种:
这里我们使用make oldconfig
,使用Ubuntu系统的6.8.0内核来配置6.6.62内核:
fle@fle-virtual-machine:/usr/src/kernels/linux-6.6.62$ ls /boot/
config-6.8.0-48-generic memtest86+.elf
config-6.8.0-49-generic memtest86+_multiboot.bin
efi System.map-6.8.0-48-generic
grub System.map-6.8.0-49-generic
initrd.img vmlinuz
initrd.img-6.8.0-48-generic vmlinuz-6.8.0-48-generic
initrd.img-6.8.0-49-generic vmlinuz-6.8.0-49-generic
initrd.img.old vmlinuz.old
memtest86+.bin
fle@fle-virtual-machine:/usr/src/kernels/linux-6.6.62$ sudo cp /boot/config-6.8.0-48-generic ./config
[sudo] fle 的密码:
fle@fle-virtual-machine:/usr/src/kernels/linux-6.6.62$ sudo make oldconfig
fle@fle-virtual-machine:/usr/src/kernels/linux-6.6.62$ ls -a
. COPYING include .mailmap scripts
.. CREDITS init MAINTAINERS security
arch crypto io_uring Makefile sound
block Documentation ipc mm tools
certs drivers Kbuild net usr
.clang-format fs Kconfig README virt
.cocciconfig .get_maintainer.ignore kernel rust
.config .gitattributes lib .rustfmt.toml
config .gitignore LICENSES samples
③生成bzImage和modules
接下来进行核心编译,我们常见的在 /boot/ 下面的核心文件, 都是经过压缩过的核心文件, 因此, 上述的动作中比较常用的是 modules 与 bzImage 这两个。
# 编译生成bzImage,默认放置在/usr/src/kernels/linux-6.6.62/arch/x86/boot/bzImage
fle@fle-virtual-machine:/usr/src/kernels/linux-6.6.62$ sudo make bzImage
......
CALL scripts/checksyscalls.sh
DESCEND objtool
INSTALL libsubcmd_headers
make[3]: *** 没有规则可制作目标“debian/canonical-certs.pem”,由“certs/x509_certificate_list” 需求。 停止。
make[2]: *** [scripts/Makefile.build:480:certs] 错误 2
make[1]: *** [/usr/src/kernels/linux-6.6.62/Makefile:1921:.] 错误 2
make: *** [Makefile:234:__sub-make] 错误 2
使用sudo vim .config
打开配置文件,并把debian/canonical-certs.pem
所在行注释掉(在vim中使用debian/canonical-certs.pem
来搜索):
遇到debian/canonical-revoked-certs.pem
也是一样的操作。
编译完成后会出现下面这些东西:
......
OBJCOPY arch/x86/boot/setup.bin
OBJCOPY arch/x86/boot/vmlinux.bin
HOSTCC arch/x86/boot/tools/build
BUILD arch/x86/boot/bzImage
Kernel: arch/x86/boot/bzImage is ready (#1)
# 把bzImage拷贝到/boot目录下
fle@fle-virtual-machine:/usr/src/kernels/linux-6.6.62$ sudo cp ./arch/x86/boot/bzImage /boot/vmlinuz-6.6.62
[sudo] fle 的密码:
fle@fle-virtual-machine:/usr/src/kernels/linux-6.6.62$ ls /boot/
config-6.8.0-48-generic memtest86+.elf
config-6.8.0-49-generic memtest86+_multiboot.bin
efi System.map-6.8.0-48-generic
grub System.map-6.8.0-49-generic
initrd.img vmlinuz
initrd.img-6.8.0-48-generic vmlinuz-6.6.62
initrd.img-6.8.0-49-generic vmlinuz-6.8.0-48-generic
initrd.img.old vmlinuz-6.8.0-49-generic
memtest86+.bin vmlinuz.old
# 最好把.config文件也拷贝过来
fle@fle-virtual-machine:/usr/src/kernels/linux-6.6.62$ sudo cp .config /boot/config-6.6.62
接下来编译模块文件:
# 编译生成modules,默认放置在arch/x86/crypto/
fle@fle-virtual-machine:/usr/src/kernels/linux-6.6.62$ sudo make modules
......
CC [M] net/qrtr/qrtr-mhi.mod.o
LD [M] net/qrtr/qrtr-mhi.ko
CC [M] virt/lib/irqbypass.mod.o
LD [M] virt/lib/irqbypass.ko
fle@fle-virtual-machine:/usr/src/kernels/linux-6.6.62$ sudo make modules_install
fle@fle-virtual-machine:/usr/src/kernels/linux-6.6.62$ ls /lib/modules
6.5.0-18-generic 6.5.0-27-generic 6.5.0-41-generic 6.8.0-48-generic
6.5.0-21-generic 6.5.0-28-generic 6.6.62 6.8.0-49-generic
6.5.0-26-generic 6.5.0-35-generic 6.8.0-45-generic
④创建Initial Ram Disk(initrd)
# 赋予Fle普通用户对/boot的全部权限,也可以直接切换为root身份
fle@fle-virtual-machine:~/桌面$ sudo chmod 777 /boot
# 创建initrd文件
fle@fle-virtual-machine:~/桌面$ dracut -v /boot/initramfs-6.6.62.img 6.6.62
......
dracut: ========================================================================
dracut: *** Creating initramfs image file '/boot/initramfs-6.6.62.img' done ***
# 查验initramfs-6.6.62.img文件
fle@fle-virtual-machine:~/桌面$ ls /boot
config-6.6.62 memtest86+.bin
config-6.8.0-48-generic memtest86+.elf
config-6.8.0-49-generic memtest86+_multiboot.bin
efi System.map-6.8.0-48-generic
grub System.map-6.8.0-49-generic
initramfs-6.6.62.img vmlinuz
initrd.img vmlinuz-6.6.62
initrd.img-6.8.0-48-generic vmlinuz-6.8.0-48-generic
initrd.img-6.8.0-49-generic vmlinuz-6.8.0-49-generic
initrd.img.old vmlinuz.old
⑤修改启动文件/boot/grub/grub.cfg
# 备份grub.cfg文件
fle@fle-virtual-machine:/usr/src/kernels/linux-6.6.62$ sudo tar -jc -f grub.cfg.tar.bz2 /boot/grub/grub.cfg
tar: 从成员名中删除开头的“/”
# 配置/boot/grub/grub.cfg开机引导文件,实现双系统
fle@fle-virtual-machine:/usr/src/kernels/linux-6.6.62$ sudo update-grub
Sourcing file `/etc/default/grub'
Sourcing file `/etc/default/grub.d/init-select.cfg'
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-6.8.0-49-generic
Found initrd image: /boot/initrd.img-6.8.0-49-generic
Found linux image: /boot/vmlinuz-6.8.0-48-generic
Found initrd image: /boot/initrd.img-6.8.0-48-generic
Found linux image: /boot/vmlinuz-6.6.62
Found memtest86+ image: /boot/memtest86+.elf
Found memtest86+ image: /boot/memtest86+.bin
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
done
默认情况下,GRUB 菜单可能是隐藏的,您需要修改 GRUB 配置文件以显示内核选择界面:
sudo vim /etc/default/grub
GRUB的到时模式为菜单,选择内核的时间为20s。从上面sudo update-grub
命令的输出结果可以看到,/boot/vmlinuz-6.8.0-49-generic
为默认内核。
然后更新/boot/grub/grub.cfg文件并重启系统:
fle@fle-virtual-machine:~/桌面$ sudo update-grub
[sudo] fle 的密码:
Sourcing file `/etc/default/grub'
Sourcing file `/etc/default/grub.d/init-select.cfg'
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-6.8.0-49-generic
Found initrd image: /boot/initrd.img-6.8.0-49-generic
Found linux image: /boot/vmlinuz-6.8.0-48-generic
Found initrd image: /boot/initrd.img-6.8.0-48-generic
Found linux image: /boot/vmlinuz-6.6.62
Found memtest86+ image: /boot/memtest86+.elf
Found memtest86+ image: /boot/memtest86+.bin
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
done
fle@fle-virtual-machine:~/桌面$ reboot
进入系统后查看内核版本:
fle@fle-virtual-machine:~/桌面$ uname -r
6.6.62
fle@fle-virtual-machine:~/桌面$
3. 内核开发的特点
内核开发不同于应用程序开发,具有以下特点:
①内核编程时既不能访问C库也不能访问标准的C头文件
在C编程时,可以通过# include <stdio.h>
来导入C库函数,但是这些库函数对于Linux内核来说太大了,所以Linux只实现了大部分常用的C库函数。比如操作字符串的函数组就位于 1ib/string.c文件中。只要包含<linux/string.h>头文件,就可以使用它们。
Tips:基本的头文件都放在include/linux/中,这样就可以通过<linux/string.h>来引用;体系结构相关的头文件集位于内核源代码树的 arch/< architecture >/include/asm目录下。例如,如果编译的是x86体系结构,则体系结构相关的头文件就是arch/x86/include/asm。内核代码通过以 asm/为前缀的方式包含这些头文件,例如<asm/ioctl.h>。
此外,C的printf在内核中并没有实现,取而代之的是printk函数:
printk(KERN_WARNING "Warning: Something might be wrong!\n");
②内核编程时必须使用 GNU C
Linux内核使用gcc作为编译器,其C语法用到了一些扩展语法:
a.内联函数inline
C语言中的内联函数(inline function)是为了提高程序运行效率的一种机制。它通过将函数的代码直接插入到调用处,而不是通过传统的函数调用机制来减少函数调用的开销(例如压栈和跳转的开销)。但由于函数是直接插入到调用处的,所以整个代码文件的大小就会变大。内核开发者通常把那些对时间要求比较高,而本身长度又比较短的函数定义成内联函数。如果一个函数较大,会被反复调用,且没有特别的时间上的限制,我们并不赞成把它做成内联函数。内联函数必须在使用前就将其定义好,否则编译器就没法将这个函数展开。
#include <stdio.h>
inline int add(int a, int b) {
return a + b;
}
int main() {
int result = add(3, 4);
printf("Result: %d\n", result);
return 0;
}
b.内联汇编
gcc编译器支持在C函数中嵌入汇编指令。当然,在内核编程的时候,只有知道对应的体系结构,才能使用这个功能。我们通常使用 asm()
指令嵌入汇编代码。例如,下面这条内联汇编指令用于执行 x86 处理器的rdtsc指令,返回时间戳(tsc)寄存器的值:
unsigned int low, high;
asm volatile("rdtsc": "=a" (low), "=d" (high));
Linux的内核混合使用了C语言和汇编语言。在偏近体系结构的底层或对执行时间要求严格的地方,一般使用的是汇编语言。而内核其他部分的大部分代码是用C语言编写的。
c. 分支声明
在C语言(特别是使用GCC或Clang编译器)中,likely 和 unlikely 是两种宏或关键字,它们用于提示编译器某个条件分支更可能或更不可能发生。这种提示可以帮助编译器优化代码的分支预测,从而提高性能。
③内核编程时缺乏像用户空间那样的内存保护机制
如果一个用户程序试图进行一次非法的内存访问,内核就会发现这个错误,发送SIGSEGV信号,并结束整个进程。然而,如果是内核自己非法访问了内存,那后果就很难控制了。(毕竟,有谁能照顾内核呢?)内核中发生的内存错误会导致oops,这是内核中出现的最常见的一类错误。在内核中,不应该去做访问非法的内存地址,引用空指针之类的事情,否则它可能会死掉却根本不告诉你一声–在内核里,风险常常会比外面大一些。
④内核编程时难以执行浮点运算
在 Linux 内核中,通常不允许直接使用浮点数运算,因为内核代码运行在内核态,而浮点运算需要使用浮点处理单元(FPU),涉及复杂的上下文切换,可能导致性能下降或系统不稳定。
⑤内核给每个进程只有一个很小的定长堆栈
用户空间的程序可以从栈上分配大量的空间来存放变量,甚至巨大的结构体或者是包含数以千计的数据项的数组都没有问题。之所以可以这么做,是因为用户空间的栈本身比较大,而且还能动态地增长(年长的开发者回想一下DOS那个年代,这种低级的操作系统即使在用户空间也只有固定大小的栈)。内核栈的准确大小随体系结构而变。在x86上,栈的大小在编译时配置,可以是4KB也可以是8KB。从历史上说,内核栈的大小是两页,这就意味着,32位机的内核是8KB,而64位机是16KB,这是固定不变的。每个处理器都有自己的栈。
⑥由于内核支持异步中断、抢占和 SMP,因此必须时刻注意同步和并发
⑦要考虑可移植性的重要性
尽管用户空间的应用程序不太注意移植问题,然而Linux却是一个可移植的操作系统,并且要一直保持这种特点。也就是说,大部分C代码应该与体系结构无关,在许多不同体系结构的计算机上都能够编译和执行,因此,必须把与体系结构相关的代码从内核代码树的特定目录中适当地分离出来。诸如保持字节序、64位对齐、不假定字长和页面长度等一系列准则都有助于移植性。