LINUX内核设计与实现第三周读书笔记
第一章 LINUX内核简介
1.1 Unix的历史
- 1969年的夏天,贝尔实验室的程序员们在一台PDR-7型机上实现了Unix这个全新的操作系统。
- 1973年,整个Unix系统用C语言进行了重写,给后来Unix系统的广泛移植铺平了道路。
- 伯克利的第一个Unix演化版是1977年推出的1BSD系统。
- 伯克利真正独立开发的Unix系统是于1979年推出的3BSD系统,支持虚拟内存。
- 1994年重写了虚拟内存子系统,推出了最终官方版,即4.4BSD。
Unix内核的特点:
- 首先,Unix很简洁,仅仅提供几百个系统调用并且有一个非常明确的设计目的。
- 在Unix中,所有的东西都被当做文件对待。
- Unix内核和相关的系统工具软件是用C语言编写而成的,使得其在各种硬件体系架构面前都具备令人惊异的移植能力。
- Unix的进程创建非常迅速,并且有一个非常独特的fork系统调用。
- Unix提供了一套非常简单但又很稳定的进程间通信元语,把目标放在一次执行保质保量地完成一个任务上。
1.2Linux简介
Linux是类Unix系统,没有直接使用Unix的源代码,但也没有抛弃Unix的设计目标并且保证了应用程序编程接口的一致性。
Linux内核也是自由(公开)软件。
1.3操作系统和内核简介
操作系统:整个系统中负责完成最基本功能和系统管理的部分。
内核(管理者或者操作系统核心):由负责响应中断的中断服务程序,负责管理多个进程从而分享处理器时间的调度程序,负责管理进程地址空间的内存管理程序和网络、进程间通信等系统服务程序共同组成。
内核空间:系统态和被保护起来的内存空间。
在系统中运行的应用程序通过系统调用来与内核通信。
将每个处理器在任何指定时间点上的活动必然概括为:
- 运行于用户空间,执行用户进程
- 运行于内核空间,处于进程上下文,代表某个特定的进程执行
- 运行于内核空间,处于中断上下文,与任何进程无关,处理某个特定的中断
1.4Linux内核和传统Unix内核的比较
单内核:把它从整体上作为一个单独的大过程来实现,同是也运行在一个单独的地址空间上。
微内核:功能被划分成各个独立的过程。每个过程叫做一个服务器。
Linux是一个单内核,汲取了微内核的精华:模块化设计、抢占式内核、支持内核线程以及动态装载内核模块的能力。让所有事情运行在内核态,直接调用函数,无需消息传递。
- 支持动态加载内核模块
- 支持对称多处理(SMP)
- 内核可以抢占(preemptive),允许内核运行的任务有优先执行的能力
- 不区分线程和其他的一般进程
- 提供具有设备类的面向对象的设备模型、热插拔事件,以及用户空间的设备文件系统。
1.5Linux内核版本
版本号为2.6.30.1的内核,主版本号是2,从版本号是6,修订版本号是30,稳定版本号是1。
第二章 从内核出发
2.1获取内核源码
2.1.1使用Git
获取最新提交到版本树的一个副本
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
下载代码后,更新自己的分支到最新分支
$ git pull
2.1.2安装内核源代码
- 压缩形式为bzip2:
$ tar xvjf linux-x.y.z.tar.bz2
- 压缩形式为zip:
$ tar xvzf linux-x.y.z.tar.gz
2.1.3使用补丁
从内部源码树开始,只需运行$ patch -p1 < ../patch-x,y,z
2.2内核源码树
-
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是维护者列表(维护内核子系统和驱动程序)
2.3编译内核
2.3.1 配置内核
make config
:遍历所有配置项,并让用户选择make deconfig
:按默认的配置make oldconfig
:先将/boot目录下的配置文件写进.config文件中,采用的是注释的形式写进新增加的功能。zcat /proc/config.gz > .config
:配置选项CONFIG_IKCONFIG_PROC
会把完整的压缩过的内核配置文件存放在/proc/config.gz
中,再次编译时可以方便地克隆当前的配置。make
:默认的Makefile
自动化编译。
2.3.2减少编译的垃圾信息
$ make > ../detritus
将错误报告和警告信息重定向到文件中
$ make > /dev/null
将无用的输出信息重定向到/dev/null中
2.3.3衍生多个编译作业
make程序能把编译过程拆分成多个并行的作业。其中每个作业独立并发地运行,有助于加快多处理器系统上的编译过程,也有利于改善处理器的利用率。默认情况下,make只衍生一个作业。
$ make -jn
以多个作业编译内核
2.3.4安装新内核
% make modules_install
把所有已编译的模块安装到正确的主目录/lib/modules下
System.map文件:编译时在内核代码树的根目录下创建的符号对照表。用来将内核符号与它们的起始地址对应起来。
2.4内核开发的特点
2.4.1无libc库抑或无标准头文件
原因:(速度与大小)保证内核高效和简练。
内核源代码文件不能包含外部头文件。
基本头文件:内核源代码顶级目录下的include
中。
体系结构相关头文件:内核源代码树的arch/<architecture>/include/asm
目录下。
printk()
函数:把格式化好的字符串拷贝到内核日志缓冲区上,syslog
程序可以通过读取该缓冲区来获取内核信息。
2.4.2GNU 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内建的一条指令对条件分支选择进行优化。
内核把这条指令封装成了宏。
2.4.3没有内存保护机制
内核自己非法访问内存的风险。
内核中的内存都不分页:每用掉一个字节,物理内存都减少一个。
2.4.4不要轻易在内核中使用浮点数
使用浮点数时,需要人工保存和恢复浮点寄存器及其他一些繁琐的操作。
不建议使用。
2.4.5容积小而固定的栈
内核栈的大小是编译内核时决定的,对于不用的体系结构,内核栈的大小不一样,但都是固定的。(不像用户空间的栈可以动态增长)
2.4.6同步和并发
原因:
- Linux是抢占多任务操作系统
- 内核支持对称多处理器系统(SMP)
- 中断异步到来
- 内核可以抢占
常用解决方法:自旋锁和信号量
2.4.7可移植性的重要性
需要保持的特点:大部分C语言代码与体系结构无关。