1、获取内核源码
1.1 下载内核
登录Linux内核官方网站,获取Linux源代码。
1.2 Git
使用Git来下载和管理Linux内核源代码。
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
使用Git获取最新提交到Linux版本树的一个副本。
$ git pull
更新自己的分支到Linux的最新分支。
1.3 使用补丁
可以以补丁的形式发布对代码的修改,也可以以补丁的形式接收其他人所做的修改。
$ patch -pl < ../patch-x.y.z
从内核源码树开始运行命令,应用增量补丁,进行版本转移。
2、内核源码树
目录 | 描述 |
---|---|
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 | 虚拟化基础结构 |
Linux源码树中的其他文件:COPYING文件是内核许可证(GNU GPL v2)。CREDITS是开发了很多内核代码的开发者列表。MAINTAINERS是维护者列表,负责维护内核子系统和驱动程序。Makefile是基本内核的Makefile。
3、编译内核
3.1 配置内核
$ make config
逐一遍历所有配置项,要求用户进行选择,耗费时间很长。
$ make menuconfig
$ make gconfig
前者是基于ncurse库编制的图形界面工具,后者是基于gtk+的图形工具。将所有配置项分门别类放置。
$ make defconfig
基于默认的配置为你的体系结构创建一个配置。
配置项会被存放在内核代码树根目录下的.config文件中,可以从中查找和修改内核选项。
$ make oldconfig
在修改过配置文件后,或者用已有的配置文件配置新的代码树时,应该进行验证和更新。
$ zcat /proc/config.gz > .config
$ make oldconfig
配置选项CONFIG_IKCONFIG_PROC把完整的压缩过的内核配置文件存放在/proc/config.gz下,编译一个新内核时可以方便地克隆当前的配置。
内核配置好后,使用make
命令进行编译。
3.2 减少编译的垃圾信息
$ make > ../detritus
对输出进行重定向。
$ make > /dev/null
将输出信息重定向到永无返回值的黑洞/dev/null。
3.3 衍生多个编译作业
$ make -jn
默认情况make只衍生一个作业,因为Makefile常会出现不正确的依赖信息,对于不正确的依赖,多个作业可能会互相踩踏,导致编译过程出错。但是内核的Makefile没有这样的编码错误。
make程序能把编译过程拆分成多个并行的作业,每个作业独立并发地运行,极大地加快多处理器系统上的编译过程,改善处理器的利用率。
3.4 安装内核
内核的安装和体系结以及启动引导工具(bootloader)相关。查阅启动引导工具的说明,按照他的指导将内核映像拷贝到合适的位置。
模块的安装是自动的,独立于体系结构。以root身份运行命令,将所有已编译的模块安装到目录/lib/modules下。
$ make modules_install
编译时会在内核代码树的根目录下一个符号对照表System.map文件。
4、内核开发特点
- 内核编程时既不能访问C库也不能访问标准的C头文件。
- 内核编程时必须使用GNU C。
- 内核编程时缺乏像用户空间那样的内存保护机制。
- 内核编程时难以执行浮点运算。
- 内核给每个进程只有一个很小的定长堆栈。
- 由于内核支持异步中断、抢占和SMP(对称多处理器),必须时刻注意同步和并发。
- 要考虑可移植性的重要性。
4.1 无libc库或无标准头文件
与用户空间的应用程序不同,内核不能链接使用标准C函数库或者其他的库。主要原因是速度和大小。完整的C库对内核来说都太大且低效。不过大部分常用的C库函数在内核中都已经得到了实现。
头文件指的是组成内核源代码树的内核头文件。内核源代码文件不能包含外部头文件,就想他们不能用外部库一样。
printk(KERN_ERR "this is an error\n");
内核代码使用的printk函数与printf函数相似,负责把格式化好的字符串拷贝到内核日志缓冲区,syslog程序通过读取该缓冲区来获取内核信息。此外,printk可以指定一个标志来设置优先级,syslogd根据这个优先级标志决定在什么地方显示这条系统消息。
4.2 GNU C
内核开发使用的C语言涵盖了ISO C99标准和GNU C扩展特性。
内联函数
static inline void wolf(unsigned long tail_size)
内联函数会在它所调用的位置上展开,消除函数调用和返回带来的开销(寄存器存储和恢复),可以使编译器进一步优化代码。但是,代码会变长,占用更多的内存空间或指令缓存。
内联函数需要使用static作为关键字,并用inline限定它。通常把对时间要求比较高且本身长度较短的函数定义成内联函数。
内嵌汇编
unsigned int low, high;
asm volatile("rdtsc" : "=a" (low), "=d" (high));
/* low和high分别包含64位时间戳的低32位和高32位 */
内核编程时,知道对应的体系结构,可以使用内联汇编功能,使用asm()指令嵌入汇编代码。
分支声明
if (unlikely(error))
if (likely(error))
unlikely()宏表示绝少发生的分支。
likely()宏表示通常为真的分支。
4.3 没有内存保护机制
在内核中不应该访问非法的内存地址,否则内核可能会死掉却没有通知。
内核中的内存不分页,每用掉一个字节,物理内存就减少一个字节。
4.4 不要轻易在内核中使用浮点数
内核不能完美地支持浮点操作,因为内核本身不能陷入。使用浮点数时,处理要人工保存和恢复浮点寄存器,还有其他琐碎的事情要做。
4.5 容积小而固定的栈
用户空间的程序可以从栈上分配大量的空间存放变量。内核栈的准确大小随体系结构体而变。
在x86上,栈的大小在编译时配置,可以使4KB也可以是8KB。内核栈的大小是两页,所以32位机的内核栈是8KB,64位机是16KB,每个处理器都有自己的栈。
4.6 同步和并发
内核的许多特性都要求能够并发地访问共享数据,要有同步机制保证不出现竞争条件。
4.7 可移植性
大部分C代码应与体系结构无关,在不同体系结构和计算机上都能够编译和执行。必须把与体系结构相关的代码从内核代码树的特定目录中适当地分离出来。
保持字节序、64位对其、不假定字长和页面长度等准则有助于移植性。