赵炯;《Linux 内核完全注释 0.11 修正版 V3.0》
C 语言的库函数文件是一些可重用程序模块集合,而 Linux 内核库文件则是编译时专门供内核使用的一些内核常用函数的组合。
退出函数 _exit()、关闭文件函数 close()、复制文件描述符函数 dup()、文件打开函数 open()、写文件函数 write()、程序执行函数 execve()、内存分配函数 malloc()、等待子进程状态函数 wait()、创建会话系统调用 setsid()、字符串操作相关。
除了 malloc.c 程序较长之外,其余程序都很短小,有的只有一两行代码,基本上都是直接调用系统中断调用实现其功能。
在编译内核阶段,Makefile 中的相关指令会把以上这些程序编译成 .o 模块,然后组建成 lib.a 库文件形式并链接到内核模块中。与通常编译环境提供的各种库文件不同,这个库中的函数主要用于内核初始化阶段的 init/main.c 程序,为其在用户态执行的 init() 函数提供支持,因此所包含的函数很少,也特别简单,但它与一般库文件的实现方式完全相同。
创建函数库
创建函数库通常使用命令 ar
。例如要创建一个含有 3 个模块 a.o、b.o、c.o 的函数库 libmine.a,则需要执行如下命令:
ar -rc libmine.a a.o b.o c.o d.o
若要往这个库文件中添加函数模块 dup.o,则可执行以下命令:
ar -rs dup.o
malloc.c 程序
该程序中主要包括内存分配函数 malloc(),为了不与用户程序使用的 malloc() 函数相混淆,从内核 0.98 版本以后就该命名为 kmalloc(),free_s() 函数改名为 kfree_s()。
对于应用程序使用的名称相同的内存分配函数一般在开发环境的函数库中实现,例如 GCC 环境中的 libc.a 库。由于开发环境中的库函数本身链接于用户程序中,因此它们不能直接使用内核中的 get_free_page() 等函数来实现内存分配函数,当然它们也没有直接管理内存页面的必要,因为只要一个进程的逻辑空间足够大,并且其数据段尾段不会覆盖位于进程逻辑地址空间末端的堆栈和环境参数区域,那么函数库 libc.a 中的内存分配函数只要做到按照程序动态请求的内存大小调整进程数据段末尾的设定值即可,剩下的具体内存映射等操作均由内核完成。这种调整进程数据段末端位置的操作和管理即是库中内存分配函数的主要功能,并且需要调用内核系统调用 brk()。
开发环境中库中的内存分配函数与这里的内核库中的分配函数相同指出仅在于它们都需要对已分配内存空间进行动态管理。采用的管理方法基本是一样的。
malloc() 函数使用了存储桶的原理对分配的内存进行管理。基本思想是对不同请求的内存块大小(长度),使用存储桶目录分别进行处理,比如对于请求内存块的长度在 32 字节或 32 字节以下但大于 16 字节时,就使用存储桶目录第二项对应的存储桶描述符链表分配内存块。一次能分配的最大内存长度是一个内存页面。
在第一次调用 malloc() 函数时,首先要建立一个页面的空闲存储桶描述符,其中存放着还未使用或已经使用完毕而收回的描述符。如果某一时刻所有桶描述符都已占用,那么 free_bucket_desc 就会为 NULL,因此再没有桶描述符被释放的前提下,下一次需要使用空闲桶描述符时,程序就会再次申请一个页面并在其上新建一个与上图所示相同的空闲存储桶描述符链表。
malloc() 函数执行的基本步骤如下:
- 首先搜索目录,寻找适合请求内存块大小的目录项对应的描述符链表。当目录项的对象字节长度大于请求的字节长度,就算找到了相应的目录项。如果搜索完整个目录都没有找到合适的目录项,则说明用户请求的内存块太大。
- 在目录项对应的描述符链表中查找具有空闲空间的描述符。如果某个描述符的空闲内存指针 freeptr 不为 NULL,则表示找到了相应的描述符。如果没有找到具有空闲空间的描述符,那么我们就需要新建一个描述符。新建描述符的过程:
- 如果空闲描述符链表头指针还是 NULL,说明是第一次调用 malloc() 函数,或者所有的空桶描述符都已用完。此时需要 init_bucket_desc() 来创建空闲描述符链表。
- 然后从空闲描述符链表头处取一个描述符,初始化该描述符,令其对象引用计数为 0,对象大小等于对应目录项指定对象的长度值,并申请一内存页面,让描述符的页面指针 page 指向该内存页,描述符的空闲内存指针 freeptr 也指向页开始位置。
- 对该内存页面根据本目录项所用对象长度进行页面初始化,建立所有对象的一个链表,也即每个对象的头部都存放一个指向下一个对象的指针,最后一个对象的开始处存放一个 NULL 指针值。
- 然后将该描述符插入到对应目录项的描述符链表开始处。
- 将该描述符的空闲内存指针 freeptr 复制为返回给用户的内存指针,然后调整该 freeptr 指向描述符对应内存页面中下一个空闲对象位置,并使该描述符引用计数增 1。
回收内存 —— free_s():
基本原理是首先根据该内存块的地址换算出该内存块对应页面的地址,然后搜索目录中的所有描述符,找到对应该页面的描述符。将该释放的内存块链入 freeptr() 所指向的空闲对象链表中,并将描述符的对象引用计数值减少 1。如果引用计数值等于零,则表示该描述符对应的页面已经完全空出,可以释放该内存页面并将该描述符收回到空闲描述符链表中。