一、获取内核源码
1. Git
git实际上是一种开源的分布式版本控制工具。
-
Linux作为一个开源的内核,其源代码也可以用git下载和管理
- 获取最新提交到版本树的一个副本
- $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
- 下载代码后,更新自己的分支到最新分支
- $ git pull
2.安装内核源代码
压缩形式为bzip2:
$ tar xvjf linux-x.y.z.tar.bz2
-
压缩形式为zip:
$ tar xvzf linux-x.y.z.tar.gz
关于参数:
-x 解开.tar格式的文件
-v 显示详细信息
-j 使用bzip2程序
-z 使用gzip程序
-f 使用归档文件
3. 使用补丁
从内部源码树开始,运行$ patch -p1 < ../patch-x,y,z
二、内核源码结构
详见LINUX内核分析第三周学习总结:构造一个简单的LINUX系统MENUOS中第一部分:“Linux内核源码简介”。
目 录 | 描 述 |
---|---|
arch | 特定体系结构的代码 |
block | 块设备I/O层 |
crypo | 加密API |
Documentation | 内核源码文档 |
drivers | 设备驱动程序 |
firmware | 使用某些驱动程序而需要的设备固件 |
fs | VFS和各种文件系统 |
include | 内核头文件 |
init | 内核引导和初始化 |
ipc | 进程间通信代码 |
kernel | 像调度程序这样的核心子系统 |
lib | 同样内核函数 |
mm | 内存管理子系统和VM |
net | 网络子系统 |
samples | 示例,示范代码 |
scripts | 编译内核所用的脚本 |
security | Linux 安全模块 |
sound | 语音子系统 |
usr | 早期用户空间代码(所谓的initramfs) |
tools | 在Linux开发中有用的工具 |
virt | 虚拟化基础结构 |
- COPYIN:内核许可证
- CREDITS:开发者列表
- MAINTAINTERS:维护者列表(维护内核子系统和驱动程序)
三、编译内核
1. 配置内核(关于make与config)
(1)相关
- Makefile:根据配置的情况,构造出需要编译的源文件列表,然后分别编译,并把目标代码链接到一起,最终形成 Linux 内核二进制文件。
- config.in:内核配置文件,给用户提供配置选择的功能。
- 配置工具:包括配置命令解释器(
make config
)和配置用户界面(例如:make menuconfig
:基于ncurse库的图形界面工具;make gconfig
:基于gtk+的图形工具...)。 - .config:用户用来存放内核配置后结果的文件。
- 可以配置的各种选项:用CONFIG_FEATURE形式表示,其前缀为CONFIG。
(2)命令
- make config:遍历所有配置项,并让用户选择
- make deconfig:按默认的配置
- make oldconfig:先将
/boot
目录下的配置文件写进.config
文件中,采用的是注释的形式写进新增加的功能。 - zcat /proc/config.gz > .config:配置选项
CONFIG_IKCONFIG_PROC
会把完整的压缩过的内核配置文件存放在/proc/config.gz
中,再次编译时可以方便地克隆当前的配置。 - make:默认的Makefile自动化编译。
2. 其它事项
-
减少垃圾信息
$ make > ../detritus
#将错误报告和警告信息重定向到文件中
$ make > /dev/null
#将无用的输出信息重定向到/dev/null中 - /dev/null:空设备,输入的信息直接丢弃 -
衍生多个编译作业:make程序能把编译过程拆分成多个并行的作业。其中每个作业独立并发地运行,有助于加快多处理器系统上的编译过程,也有利于改善处理器的利用率。默认情况下,make只衍生一个作业。
$ make -jn
#以多个作业编译内核 - j:指定同时执行多任务
- n:要衍生出的作业数
3. 安装新内核
make modules_install
#把所有已编译的模块安装到正确的主目录/lib/modules下
- System.map文件:编译时在内核代码树的根目录下创建的
符号对照表
。用来将内核符号与它们的起始地址对应起来。
四、内核开发特点
1. 无libc库/标准头文件
原因:(速度与大小)保证内核高效和简练。
-
内核源代码文件不能包含外部头文件。
- 基本头文件:内核源代码*目录下的
include
中 - 体系结构相关头文件:内核源代码树的
arch/<architecture>/include/asm
目录下
- 基本头文件:内核源代码*目录下的
printk()函数:把格式化好的字符串拷贝到内核日志缓冲区上,syslog程序可以通过读取该缓冲区来获取内核信息。
2. 必须使用GNU C
什么是GNU?GNU是一种操作系统,GNU提供的C编译器就是我们之前使用的gcc。
(1)内联函数
static inline void wolf(unsigned long tail_size);
- static:关键字
- inline:用于限定关键字
-
内联函数:编译时在它被调用的地方展开。
- 优点:减少了函数调用的开销,性能较好。
- 缺点:频繁的使用内联函数也会使代码变长,从而在运行时占用更多的内存。
定义内联函数特点:时间要求高,本身长度较短的函数。
使用之前就要定义好内联函数,一般在头文件中定义。
为了类型安全和易读性,优先使用内联函数而不是复杂的宏。
(2)内联汇编
unsigned int low, high;
asm volatile("rdtsc" : "=a" (low), "=d" (high));
/* low 和 high 分别包含64位时间戳的低32位和高32位 */
- asm:嵌入汇编代码
- volatile:不优化
- 汇编语言用于偏近底层或对执行时间严格要求的地方。
(3)分支声明
/* 如果error在绝大多数情况下为0(假) */
if (unlikely(error)) {
/* ... */
}
/* 如果success在绝大多数情况下不为0(真) */
if (likely(success)) {
/* ... */
}
- 对于条件选择语句,在一个条件经常/很少出现时,编译器可通过gcc内建的一条指令对条件分支选择进行优化。
- 内核把这条指令封装成了宏。
3. 没有内存保护机制
- 内核自己非法访问内存的风险
- 内核中的内存都不分页:每用掉一个字节,物理内存都减少一个
4. 难以执行浮点运算
- 使用浮点数时,需要人工保存和恢复浮点寄存器及其他一些繁琐的操作。
- 不建议使用
5. 每个进程只有一个很小的定长堆栈
- 内核栈的大小是编译内核时决定的,对于不用的体系结构,内核栈的大小不一样,但都是固定的。(不像用户空间的栈可以动态增长)
6. 必须时刻注意同步和并发
-
原因:
- Linux是抢占多任务操作系统
- 内核支持对称多处理器系统(SMP)
- 中断异步到来
- 内核可以抢占
常用解决方法:自旋锁和信号量
7. 考虑可移植性的重要性
- 需要保持的特点:大部分C语言代码与体系结构无关。
五、总结:关于Linux内核的结构与特点
1. 版本控制
- 我最早接触git是刚开始使用实验楼的时候,实验楼中的代码保存需要用到其中“我的代码库”功能,实际上就是最简单的git。
- Linux内核这种开源的代码以及很多项目使用git进行版本控制与协作都是挺方便的。
- 网上有很多教程可以参考,感觉这个比较全面:Git教程,可以在一些网站上创建自己的代码库,比如:git.oschina,操作过就会发现还是比较简单的。
2. 依据结构和特点的开发
- 通读本章之后感觉Linux内核的很多要求与一般的项目其实是差不多的,它的这些基本结构、开发的特点,对于理解它各个部分的工作过程是很有帮助的。
参考资料1:《Linux内核设计与实现》(原书第三版)
参考资料2:make config 解惑