[关闭]
@yiltoncent 2015-05-19T09:37:11.000000Z 字数 3512 阅读 3259

Linux内核学习摘录

LINUX


系统初始化 —— 内核引导程序

initrd是一种基于内存的文件系统,启动过程中,系统在访问真正的根文件系统时,会先访问initrd文件系统。比如,使用的是scsi硬盘,而内核vmlinuz中并没有这个scsi硬件的驱动,那么在装入scsi模块之前,内核不能加载根文件系统,但scsi模块存储在根文件系统的/lib/modules下。为了解决这个问题,能引导一个能够读实际内核的initrd内核并用initrd修正scsi引导问题。initrd-2.6.18-194.el5.img是用gzip压缩的文件,initrd实现加载一些模块和安装文件系统等功能。

initrd的作用在于解决一个矛盾:即内核想访问真正的根文件系统,如在硬盘上,但是内核中并没有硬盘的驱动,驱动存储在根文件系统中,因此这形成了一种相互依赖却没办法解开的循环。initrd打破了这种循环,它能提供硬盘的驱动,在内核访问文件系统之前,将文件系统安装好。

内核映像的形成 —— KBuild体系(一)

Kbuild Makefile的最核心的部分是内核目标的定义。主要是定义需要编译的文件,所有的选项,以及到哪些子目录去执行递归操作。
例如,源代码某目录中有一个foo.c,要编译成一对象,那么该目录中的Kbuild Makefile其中必有一行形式如下:
obj-$(CONFIG_FOO) += foo.o
$(CONFIG_FOO)来自.config内核配置文件,可以为y(编译进内核) 或m(编译成模块)。如果CONFIG_FOO不是ym,那么该文件就不会被编译联接了。

(1)编译进内核
Kbuild Makefile 规定所有编译进内核的目标文件都存在$(obj-y)列表中。而这些列表依赖内核的配置。
Kbuild编译所有的$(obj-y)文件。然后,调用"$(LD) -r"将它们合并到一个build-in.o文件中。稍后,该build-in.o会被顶层Makefile链接进vmlinux中。
注意,$(obj-y)中的文件是有顺序的。列表中有重复项是可以的,因为当第一个文件被联接到built-in.o中后,其余文件就被忽略了。
另外,链接也是有顺序的,那是因为有些函数(module_init()/__initcall)将会在启动时按照他们出现的顺序进行调用。所以,记住改变链接的顺序可能改变你系统顺序,从而导致你的硬件损害,这一点千万要注意。
(2)编译成模块
编译成模块和内核的区别不用我多说,因为模块是可装载,通过insmod等命令。$(obj-m) 列举出了哪些文件要编译成可装载模块。一个模块可以由一个文件或多个文件编译而成。如果是一个源文件,KbuildMakefile只需简单的将其加到$(obj-m)中去就可以了。
如果内核模块是由多个源文件编译而成,那你就要采用一个方法去声明你所要编译的模块:
obj-$(CONFIG_FOO) += isdn.o
解释一下这个方法,很简单,Kbuild需要知道你所编译的模块是基于哪些文件,所以你需要通过变量$(<module_name>-objs)来告诉它:

  1. #drivers/isdn/i4l/Makefile(表示以下是这个文件里面部分内容)
  2. obj-$(CONFIG_ FOO) += isdn.o
  3. isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o

上面的例子中,isdn.o就是模块名,Kbuild将编译在$(isdn-objs)中列出的所有文件,然后使用"$(LD) -r"生成isdn.o
Kbuild能够识别用于组成目标文件的后缀-objs和后缀-y。这就让KbuildMakefile可以通过使用 CONFIG_ 符号来判断该对象是否是用来组合对象的:

  1. #fs/ext2/Makefile
  2. obj-$(CONFIG_EXT2_FS) += ext2.o
  3. ext2-y := balloc.o bitmap.o
  4. ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o

在这个例子中,如果 $(CONFIG_EXT2_FS_XATTR)'y'xattr.o将是复合对象 ext2.o的一部分。
注意:当然,当你要将其编译进内核时,上面的语法同样适用。所以,如果你的 CONFIG_EXT2_FS=y,那Kbuild会按你所期望的那样,生成 ext2.o文件,然后将其联接到 built-in.o中。

(4)递归向下访问目录
一个Kbuild Makefile只对编译所在目录的对象负责。在子目录中的文件的编译要由其所在的子目录的Makefile来管理。只要你让Kbuild知道它应该递归操作,那么该系统就会在其子目录中自动的调用 make 递归操作。这就是obj-yobj-m 的作用。
例如,ext2 被放的一个单独的目录下,在fs目录下的Makefile会告诉Kbuild使用下面的赋值进行向下递归操作

  1. #fs/Makefile
  2. obj-$(CONFIG_EXT2_FS) += ext2/

如果 CONFIG_EXT2_FS 被设置为 'y'(编译进内核)或是'm'(编译成模块),相应的 obj- 变量就会被设置,并且Kbuild就会递归向下访问 ext2 目录。Kbuild只是用这些信息来决定它是否需要访问该目录,而具体怎么编译由该目录中的Makefile来决定。
注意,将CONFIG_变量设置成目录名是一个好的编程习惯。这让Kbuild在完全忽略那些相应的CONFIG_值不是'y''m'的目录。

内核映像的形成 —— KBuild体系(三)

KBuild体系还有一个重要的概念,那就是编译标志。我们可以在一些Makefile中看到如下标志:
EXTRA_CFLAGS、 EXTRA_AFLAGS、 EXTRA_LDFLAGS、EXTRA_ARFLAGS
这些EXTRA_开头的大写字母变量都是编译标志,所有的 EXTRA_ 变量只在所定义的Kbuild Makefile中起作用。EXTRA_ 变量可以在Kbuild Makefile中所有命令中使用。
$(EXTRA_CFLAGS) 是用 $(CC) 编译C源文件时的选项,例如:

  1. # drivers/sound/emu10kl/Makefile
  2. EXTRA_CFLAGS += -I$(obj)
  3. ifdef DEBUG
  4. EXTRA_CFLAGS += -DEMU10KL_DEBUG
  5. endif

由于顶层Makefile的$(CC)拥有变量 $(CFLAGS) 用来作为整个源代码树的编译选项,所以在这里做这么一个设置就是将$(CFLAGS)替换成EXTRA_CFLAGS

内核映像的形成——链接vmlinux

在2.6内核中,为了更好地调试内核,引入了kallsyms 。kallsyms 抽取内核用到的所有函数地址(全局的,静态的)和非栈数据变量地址,生成一个数据小块( data blog ),作为只读数据链接进kernel image 。相当于内核中存在了一份System.map .内核可以根据符号名查出函数的地址。当系统发生oops 时,(不懂oops?好吧,内核崩溃总懂了吧)kallsyms的print_symbol 函数会显示相关信息,以此形式跟踪栈的状态。

内核映像的形成——制作bzImage

我们知道,C和汇编源文件被编译成ELF 文件后,通常会包含有连接器(Linker)或加载器(Loader)所需要的ELF header,Program header table或Section header table。这些ELF header 和一些section 的作用是告诉内核ELF Loader 如何载入ELF 可执行文件。但是, Linux内核作为一种特殊的ELF 文件,没有ELF Loader的帮助,需要特殊辅助程序去装载它。它的装载地址是固定的,前面讲了的,0xC0100000。
这时,为了保证通用性而存在的ELF header 和一些section对内核的装载就没有意义了。为了使内核尽可能小,可以把这些信息去掉。所以通常采用objcopy命令来去掉原来ELF文件中的header和section,同时转化为raw binary格式的文件。即便如此,通过objcopy 处理过的vmlinux 一般需要压缩以后再重新链接,当然必须把解压缩的程序也同时链接进来。
这个压缩的过程着实奇怪:压缩后,然后再解压缩,岂不是浪费启动时间?这还是因为当x86系列处理器启动初期,处于实模式状态,可以寻得的地址空间十分有限,仅仅是1MB,如果内核过大,就无法加载。更新的内核(从2.6.30开始)甚至提供了更高压缩率的格式:bzip或LZMA。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注