内核源码树结构
| 目录 | 描述 |
|---|---|
| arch | 特定体系结构的源码 |
| block | 块设备I/O层 |
| crypto | 加密API |
| Documentation | 内核源码文档 |
| drivers | 设备驱动程序 |
| firmware | 使用某些驱动程序而需要的设备固件 |
| fs | VFS和各种文件系统 |
| include | 内核头文件 |
| init | 内核引导和初始化 |
| ipc | 进程间通信代码 |
| kernel | 像调度程序这样的核心子系统 |
| lib | 通用内核函数 |
| mm | 内存管理子系统和VM |
| net | 网络子系统 |
| samples | 示例,示范代码 |
| scripts | 编译内核所用的脚本 |
| security | Linux安全模块 |
| sound | 语音子系统 |
| usr | 早期用户空间代码(所谓的initramfs) |
| tools | 在Linux开发中有用的工具 |
| virt | 虚拟化基础结构 |
配置、编译及安装内核
配置内核
内核提供了各种不同的工具来简化内核配置。
一. 通过一个字符界面下的命令行工具
$ make config
这个工具会逐一遍历所有配置项,要求用户选择yes,no或module(如果是三选一的话),比较耗费时间
二. 通过基于ncurse库编制的图形界面工具(需要预先安装ncurse)
$ make menuconfig
三. 通过基于gtk+的图形工具
$ make gconfig
四. 基于默认配置为你的体系结构创建一个配置
$ make defconfig
这些配置项会被存放在内核源码树根目录下的.config文件中,你可以找到它,并可以随意修改它。在你修改过配置文件之后,或者在用已有的配置文件配置新的代码树的时候,你应该验证和更新配置:
$ make oldconfig
编译内核
一旦内核配置好了,就可以使用一个简单的命令来编译它了
$ make
或者你也可以衍生多个作业来加快编译计数
$ make -jn
这里的n是要衍生的作业数
安装内核
一. 安装内核模块
$ make modules_install
二. 安装内核
$ make install
Linux所谓的安装即是拷贝文件,修改配置文件,内核安装实际上也是如此
- 复制内核映像到
/boot中,编译成功后生成的内核映像文件bzImage放在arch/<architecture>/boot/中,该文件复制到/boot后重命名为vmlinuxz-<kernel-version> - 生成
initrd-<kernel-version>.img文件 - 配置引导程序(
GRUB方式编辑/etc/grub/grub.conf,LILO方式编辑/etc/lilo.conf) -
reboot进入新内核
内核开发的特点
- 无
libc库抑或无标准头文件
为了保证内核的小而且高效,内核开发不能使用C标准库,所以哪怕是最简单的printf函数也无法使用,不过有替代的printk - 使用
GNU C,推荐使用GNC 4.4或之后的版本
因为使用了GNC,所以Linux内核常有一些GNC的一些扩展 - 内联(
inline)函数
内核开发者通常把那些对时间要求比较高,而本身长度又比较短的函数定义成内联函数。
定义一个内联函数的时候,需要使用static作为关键字,并使用inline限定它,比如
```c
static inline void wolf(unsigned long tail_size)
```
- 内联汇编
gcc编译器支持在C函数中嵌入汇编指令,在内核编程的时候,知道对应的体系结构,可以使用这个功能。通常使用asm()指令嵌入汇编代码,例如下面这条内核指令用于执行x86处理器的rdtsc指令,返回时间戳(tsc)寄存器的值:
```c
unsigned int low, high;
asm volatile("rdtsc" : "=a" (low), "=d" (high));
/* low 和 high 分别包含64位时间戳的低32位和高32位 */
```
- 分支声明
如果事先知道一个条件经常为假,或者经常为真,我们可以使用unlikely()和likely()对条件分支选择进行优化。
```c
/* 我们认为error绝大多数时间都会为0 */
if (unlikely(error)) {
/* ... */
}
```
```c
/* 我们认为success通常都不会为0 */
if (likely(success)) {
/* ... */
}
```
- 缺乏像用户空间那样的内存保护机制
- 难以执行浮点运算
- 内核给每个进程只有一个很小的定长堆栈
内核栈的大小由编译内核时决定的,对于不用的体系结构,内核栈的大小虽然不一样,但都是固定的。
查看内核栈大小的方法:
$ ulimit -a | grep "stack size"
- 由于内核支持异步中断、抢占和SMP,因此必须时刻注意同步和并发
- 要考虑可移植性的重要性