分类:
2010-07-28 12:20:57
第二章 从内核出发
内核源码树:
arch:特定体系结构的源码;
crypto:Crypto API;
drivers:设备驱动程序;
fs:VFS和各种文件系统;
include:内核头文件;
init:内核引导和初始化;
ipc:进程间通信代码;
kernel:内核核心代码(这部分很小);
lib:通用内核函数;
mm:内存管理子系统和VM;
net:网络子系统,主要是网络协议;
scripts:编译内核使用的脚本;
usr:用户空间代码;
sound:声音驱动的支持;
security:Linux安全模块。
2.3 编译内核
Linux源码随手可得,那就意味着在编译它之前可以配置和定制。的确,我们可以把自己需要的功能和驱动程序编译进内核。编译前,首先要配置它。由于内核 提供了数不胜数的功能,支持了难以计数的硬件,因而有许多东西需要配置。可以配置的各种不同的选项表示,其前缀为CONFIG。配置选项既可以用来决定哪 些文件编译进内核,也可以通过预处理命令处理代码。
这些配置项要么是二选一,要么是三选一,二选一就是yes或no,三选一就是yes、no或Module。Module表示该配置项被选定了,但编译的时 候这部分功能的实现代码是以模块(一种可以动态安装的代码段)的形式生成。驱动程序一般都是三选一的配置项。所有的配置选项会放置在根目录下config文件中。
一个小技巧:减少编译时的垃圾信息可以如下所示:make >../some_other_files。
安装内核
在内核编译好之后,还需要安装它。如何安装就和体系结构及启动引导工具息息相关了-----查阅启动引导工具的说明,按照它的指导将内核映像拷贝到合适的位置,并且按照启动要求安装它。
模块的安装是自动的,也是独立于体系结构的,以root身份登录,运行:make modules_install,就可以把所有已编译的模块安装到正确的主目录/lib 下。
system.map文件:编译时创建,这是一份符号对照表,用以将内核符号和它们的起始地址对应起来。调试时,如果把内存地址翻译成容易理解的函数名以及变量名会很有用。
2.4 内核开发的特点
GNU C
Linux内核是用C编写的,但内核并不完全符合ANSI C标准。实际上,只要有可能,内核开发者总是要用到gcc提供的许多语言扩展部分。而标准C有区别且通常是人们不熟悉的那些变化,多数集中在GNU C上。下面我们就研究一下内核代码中所使用的C语言扩展中让人感兴趣的那部分吧。
(1)内联(inline)函数:inline直译应该是“在字里行间展开”,意为函数会在被调用的 位置展开。定义一个内联函数的时候,需要使用static作为关键字,并用inline限定它。 如: static inline void dog(unsigned long tail_size) 内联函数必须在使用之前就定义好,否则编译器就没法把这个函数展开。实践中一般在头文件中定义内联函数。
(2)内联汇编:gcc编译器支持在C函数中嵌入汇编指令。
(3)分支声明:说白了就是用likely()来声明一个经常出现的条件(所对应的变量),用unlikely()来声明一个很少出现的条件(对应的变量),以达到优化的目的。
并发、同步和竞争
下面一段描述了上述几个概念之间的大致关系,这种关系在内核中同样适用。
对于多线程程序的开发者来说,往往会利用多线程访问共享数据,避免繁琐的进程间通讯。但是多线程对共享数据的并发访问有可能产生竞争,使得数据处于不一致状态,所以需要一些同步方法来保护共享数据。多线程的并发执行是由于线程被抢占式的调度——一个线程在对共享数据访问期间(还未完成)被调度程序中断,将另一个线程投入运行——如果新被调度的线程也要对这个共享数据进行访问,就将产生竞争。为了避免竞争产生,需要使线程串行地访问共享数据 ,也就是说访问需要同步——在一方对数据访问结束后,另一方才能对同一数据进行访问。