《内核设计与实现》读书笔记(二)- 从内核出发

在尝试内核开发之前,需要对内核有个整体的了解。

主要内容:

  • 获取内核源码
  • 内核源码树
  • 编译内核
  • 内核开发的特点

1. 获取内核源码

内核是开源的,所有获取源码特别方便,参照以下的网址,可以通过git或者直接下载压缩好的源码包。

http://www.kernel.org

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 虚拟化基础结构

 

3. 编译内核

3.1 配置内核

得到Linux源码后,在编译它之前可以配置和定制。可以把自己需要的特殊功能和驱动程序编译进内核。

配置选项可以用来决定哪些文件编译进内核,也可以通过预处理命令处理代码。

这些配置选项要么是二选一(yes或no),要么是三选一(yes、no或module)。module意味着该配置项选定了,但编译时这部分功能的实现代码是以模块(一种可以动态安装的独立代码段)的形式生成。yes选项表示把代码编译进主内核映像中,而不是作为一个模块。驱动程序一般都用三选一的配置项。

3.1.1内核提供的内核配置工具

$ make config  --->  逐一遍历所有配置项,要求用户选择yes、no或module,耗时长,不推荐

$ make menuconfig  --->  基于ncurse库编制的图形界面工具,推荐

$ make gconfig  --->  基于gtk+的图形工具

$ make defconfig  --->  会基于默认的配置为你的体系结构创建一个配置

配置后这些配置项会保存在内核代码树根目录下的.config文件中,可以在.config文件中查找和修改内核选项。

3.2 编译内核

一旦配置好了内核(即.config文件按自己要求生成完),就可以用一个简单明了编译它:

$ make

3.2.1 减少编译的垃圾信息

尽少看到垃圾信息,又不希望错过错误和警告信息,可以使用下面这个命令来对输出进行重定向:

$ make > .. /detritus

这样一旦需要查看编译的输出信息,你可以查看detritus这个文件,不过,因为错误和警告都在屏幕上显示,所以查看的可能性不大。我们可以输入以下命令,让输出信息重定向到永无返回值的黑洞/dev/null

$ make > /dev/null

 

4. 内核开发的特点

  • 内核编程时既不能访问C库也不能访问标准的C头文件
  • 内核编程时必须使用GNU C
  • 内核编程时缺乏像用户空间那样的内存保护机制
  • 内核编程时难以执行浮点运算
  • 内核给每个进程只有一个很小的定长堆栈
  • 由于内核支持异步中断、抢占和SMP,因此必须时刻注意同步和并发
  • 要考虑可移植性的重要性

4.1 内核编程时既不能访问C库也不能访问标准的C头文件

为了保证内核的小和高效,内核开发中不能使用C标准库,所以连最常用的printf函数也没有,但是还好有个printk函数来代替。

4.2 内核编程时必须使用GNU C,推荐使用gcc 4.4或以后的版本来编译内核

因为使用GNU C,所以内核中常使用GNU C中的一些扩展:

4.2.1 内联(inline)函数

内联函数在编译时会在它被调用的地方展开,减少函数调用和返回的开销,性能较好。但是,频繁使用内联函数也会是代码变长,从而在运行时占用更多的内存。

所以内联函数使用时最好满足以下几点:函数较小,不被反复调用,对程序时间要求比较严格。

定义一个内联函数时,需要使用static作为关键字,并用inline限定它。

内联函数示例:static inline void sample();

4.2.2 内联汇编

内联汇编用于偏近底层或对执行时间严格要求的地方。示例如下:

unsigned int low, high;
asm volatitle("rdtsc" : "=a" (low), "=d" (high));
/* low和high分别包含64位时间戳的低32位和高32位*/  

4.2.3 分支声明

在条件选择语句中,如果一个条件经常出现或条件很少出现时,那么可以用likely和unlikely来优化这段判断的代码。

/* 下面是一个条件选择语句 */
if (error) {
    /* ... */
}

/* 如果error绝大多数时间都会为0 */
if (unlikely(error)) {
    /* ... */
}

/* 如果success在绝大多数情况下不为0(真) */
if (likely(success)) {
    /* ... */
}  

4.3 内核编程时缺乏像用户空间那样的内存保护机制

因为内核是最底层的程序,所以如果内核访问的非法内存,那么整个系统都会挂掉!!所以内核开发的风险比用户程序开发的风险要大。

而且,内核中的内存是不分页的,每用一个字节的内存,物理内存就少一个字节。所以内核中使用内存一定要谨慎。

4.4 内核编程时难以执行浮点运算

内核不能完美的支持浮点操作,使用浮点数时,需要人工保存和恢复浮点寄存器及其他一些繁琐的操作。

4.5 内核给每个进程只有一个很小的定长堆栈

内核栈的大小有编译内核时决定的,对于不用的体系结构,内核栈的大小虽然不一样,但都是固定的。

查看内核栈大小的方法:

ulimit -a | grep "stack size"  

4.6 由于内核支持异步中断、抢占和SMP,因此必须时刻注意同步和并发

Linux是多用户的操作系统,所以必须处理好同步和并发操作,防止因竞争而出现死锁。

4.7 要考虑可移植性的重要性

Linux内核可用于不用的体现结构,支持多种硬件。所以开发时要时刻注意可移植性,尽量使用体系结构无关的代码。

 

posted @ 2017-02-12 21:08  大海中的一粒沙  阅读(288)  评论(0编辑  收藏  举报