1. 系统与内核的关系
在计算机科学语境中,内核即操作系统,kernel = OS
,它直接与硬件交互、管理各种资源、调度其他进程。
在日常语境中,操作系统是内核加上其他一些工具套件(如Windows的文件浏览器、控制面板、IE)组成的集合kernel ⊂ OS
。
虽然从用户的角度,那些“系统自带”的套件和普通的用户软件在信任程度、控制权限以及重要性方面都有很大差别,但是从内核的角度来说,这些套件其实和普通的程序其实是平等的,都只是内核调度之下的普通用户态程序。
2. Linux内核及相关部件
以下内容皆以Ubuntu
为代表的发行版为例。
2.1. vmlinuz
内核一般位于/boot/vmlinuz-*
位置。
- vm即virtual memory,即支持虚拟内存的意思
- linuz表示是linux的gzip压缩形式,而内核头部自带一段自解压代码,在加载后自动自我展开。
- 后接版本号与系统信息等
系统中可以安置多个版本的内核,但引导程序只会选择其一进行实际启动。常用的引导程序是grub,他从设备固件(BIOS、UEFI)接过执行权,加载Linux内核并将之放置在系统物理内存的低地址处,然后将执行权交给内核功成身退。
Linux内核一般也就几十兆,gzip压缩后的vmlinuz
一般不足十兆,相对动辄以GB计的“操作系统”,真实的内核其实并不大。
2.2. System.map
System.map-*
位于内核同目录下。
它是内核的符号表,与内核一同编译产生,保存了每个符号的链接地址,对于系统调试有用。
与nm
命令的结果格式一直,大致如下:
0000000000000000 D __per_cpu_start |
2.3. initrd.img
initrd.img-*
也位于内核同目录下。
其名称表示initial ramdisk,意义是充当内核启动初期阶段的文件系统。
内核一般除了核心常用功能外,很多功能可以做成模块外置,在启动后按需动态加载。以文件相关为例,实际的存储介质(磁盘、网络)、总线类型(USB、SATA、PCI-E)、文件系统(ext4、zfs)千差万别,甚至可能涉及加密等问题,内核肯定无法将之全部纳入,一般将之做成模块。但问题是要加载模块就要先挂载根目录,读取模块文件,二者就成了一个鸡生蛋、蛋生鸡的死结。
initrd.img
就是为了解开这个死结而生,其本质是一个非常精简的系统镜像:
. |
它和内核一起被引导程序加载,然后内核将其用作早期的根文件系统,利用其中的系统环境,内核就可以挂载真正的系统根目录了。
也正因如此,通常可以只在内核中包含一些通用的功能,而差异化的功能只需要做成模块,必要时还需要置入initrd.img
。所以在Ubuntu上,内核是从软件仓库下载的,因为它相对来说是通用的,而initrd.img
是下载内核后本地自动生成的,因为它需要依据本地的环境和配置特殊生成。
值得一提,现今的initrd.img
实际上已经是initramfs
了,前者模拟一个磁盘块设备,后者直接模拟一个文件系统,少绕了一个弯路,因此性能更好,只不过在名称上出于历史原因继续沿用前者罢了。
3. Linux内核编译
3.1. 下载源码
Linux内核早有对应的Github仓库,不过鉴于其体积和网络速度,还是从清华镜像上下载比较快。
安全方便考虑,可以先克隆一个仓库到本地,之后有需要的话,就从本地仓库再克隆出一个仓库来使用。
克隆一个裸仓库(其没有工作目录、体积更小)到本地/opt/linux.git
:
sudo git clone --bare https://mirrors.tuna.tsinghua.edu.cn/git/linux.git /opt/linux.git |
从本地仓库再克隆出5.0版本的仓库,--depth 1
选项告诉git不需要历史commits,更小更快
git clone file:///opt/linux.git --depth 1 -b v5.0 linux-v5.0.0 |
3.2. 编译
内核编译一般有三步骤,
配置
通过
make ???config
系列命令在源码根目录下生成一个.config
文件,控制内核应该包含的功能。编译
生成编译出二进制文件,
make -j 16
,耗时最长,给make
命令加上-j
参数指定并行数量。以x86为例,完成后的内核会在arch/x86/boot/bzImage
,这个bzImage
就是/boot/vmlinuz-*
,也就是真正的内核。安装
其实就是复制编译结果到目标路径
mkdir -p ../install/boot
make install INSTALL_PATH=../install/boot
make modules_install INSTALL_MOD_PATH=../install. ├── boot │ ├── config-5.0.0 │ ├── System.map-5.0.0 │ └── vmlinuz-5.0.0 └── lib └── modules └── 5.0.0 ├── build ├── kernel ├── modules.alias ├── modules.alias.bin ├── modules.builtin ├── modules.builtin.bin ├── modules.dep ├── modules.dep.bin ├── modules.devname ├── modules.order ├── modules.softdep ├── modules.symbols ├── modules.symbols.bin └── source
需要注意,不过内核的编译与gcc
的版本依赖性很强,比如4.0版本的内核在Ubuntu上用7.0版本的gcc无法通过编译,需要切换到gcc 4.8版本才行。