linux内核c语言代码,Linux内核源代码中的C语言代码(转)

Linux 内核的主体是以 GNU 的 C 语言编写的, GNU 为此提供了编译工具 gcc 。

一、 inline 函数大量的使用:

Gcc 从 C++ 语言中吸收了“ inline ”和“ const ”。其实, GNU 的 C 和 C++ 是合为一体的, gcc 既是 C 编译又是 C++ 编译,所以从 C++ 中吸收一些东西到 C 中是很自然的。从功能上说, inline 函数的使用与 #define 宏定义相似,但更有相对的独立性,也更安全。使用 inline 函数也有利于程序调试。如果编译时不加优化,则这些 inline 函数就是普通的,独立的函数,更便于调试。调试好以后,再采用优化重新编译一次,这些 inline 函数就像宏操作一样融入了引用处的代码中,有利于提高运行效率。由于 inline 函数的大量使用,相当一部分代码从 .c 文件移入了 .h 文件中。

二、宏操作定义:

Linux 内核代码中使用了大量的 inline 函数,但这并未消除对宏操作的使用,内核中仍有许多宏操作定义。并常对内核代码中一些宏操作定义方式感到迷惑不解,先看一个实例,取自 fs/proc/kcore.c:

163#define DUMP_WRITE(add,nr)do {memcpy(bufp,addr,nr);buf +=nr;} while(0)

这个循环体只执行一次,为什么要这样通过一个 do-while 循环来定义呢?首先能不能定义成如下式样:

163#define DUMP_WRITE(add,nr)memcpy(bufp,addr,nr);buf +=nr;

不行。如果有一段程序在一个 if 语句中引用这个宏操作就会出问题:

if (add)

DUMP_WRITE(addr,nr);

else

Do_something_else();

经过预处理以后,这段代码就会变成这样:

if (add)

memcpy(bufp,addr,nr);buf +=nr;

Else

Do_something_else();

编译这段代码 gcc 会失败,并报语法出错。因为 gcc 认为 if 语句在 memcpy() 以后就结束了,然后却又碰到了一个 else 。如果把 DUMP_WRITE(addr,nr) 和 Do_something_else() 换一下位置,编译倒是可以通过,但问题却更严重了,因为不管条件满足与否 bufp+=nr 都会得到执行。马上会想到要在定义中加上花括号,成为这样:

163#define DUMP_WRITE(add,nr){memcpy(bufp,addr,nr);buf +=nr;}

可是,上面那段程序是通不过编译,因为经过预处理后就变成这样:

if (add)

{memcpy(bufp,addr,nr);buf +=nr;};

else

Do_something_else();

同样, gcc 在碰到 else 前面的“ ; ”时就认为 if 语句已经结束了,因而后面的 else 不在 if 语句中。相比之下,采用 do-while 的定义在任何情况下都没有问题。

三、队列的使用:

内核中大量地使用着队列和队列操作。

如果我们有一种数据结构 foo ,并且需要维持一个这种数据结构的双链队列,最简单的、也是最常用的办法就是在这个数据结构的类型定义中加入两个指针,例如:

typedefstruct foo

{

struct foo *prev;

struct foo *next;

……

}foo_t;

然后为这种数据结构写一套用于各种队列操作的子程序。由于用来维持队列的这两个指针的类型是固定的(都是指向 foo 数据结构),这些子程序不能用于其它数据结构的队列操作。换言之,需要维持多少种数据结构的队列,就得有多少套的队列操作子程序。对于使用队列较少的应用程序或许不是个大问题,但对于使用大量队列的内核就成问题了。所以, Linux 内核中采用了一套能用的、一般的、可以用到各种不同数据结构的队列操作。为此,代码的作者们把指针 prev 和 next 从具体的“宿主”数据结构中抽象出来成为一种数据结构 list_head, 这种数据结构既可以“寄宿”在具体的宿主结构内部,成为该数据结构的一个“连接件” ; 也可以独立存在而成为一个队列的头。这个数据结构定义在 include/linux/list.h 中。

16struct list_head {

17struct list_head *next, *prev;

18};

如果需要某种数据结构的队列,就在这种结构内部放上一个 list_head 数据结构。以用于内存页面管理的 page 数据结构为例,其定义为:(见 include/linux/mm.h )

134typedef struct page {

135struct list_head list;

……

138struct page *next_hash;

……

141struct list_head lru;

……

148}mem_map_t;

可见,在 page 数据结构中寄宿了两个 list_head 结构,或者说有两个队列操作的连接件,所以 page 结构可以同时存在于两个双链队列中。此外,结构中还有个单链指针 next_hash, 用来维持一个单链的杂凑队列。

对于宿主数据结构内部了每个 list_head 数据结构都要加以初始化,可以通过一个宏操作 INIT_LIST_HEAD 进行,要将一个 page 结构通过其“队列头”链入(有时候也说“挂入”)一个队列时,可以使用 list_add() 。从队列中脱链用 list_del() 。

但这里存在一个问题:队列操作都是通过 list_head 进行的,但那不过是个连接件,如果我们手上有个宿主结构,那当然就知道了它的某个 list_head 在那里,从而以此为参数调用 list_add() 或 list_del(); 可是,反过来,当我们顺着一个队列取得其中一项 list_head 结构时,又怎样找到其宿主结构呢?在 list_head 结构中并没有指向宿主结构的指针呀。毕竟,我们真正关心的是宿主结构,而不是连接件。

下面通过一个实例来看这个问题是如何解决的。下面是取自 mm/page_alloc.c 中的一行代码:

[rmqueue()]

188page = memlist_entry(curr,struct page,list);

这里的 memlist_entry() 将一个 list_head 指针 curr 换算成其宿主结构的起始地址,也就是取得指向其宿主结构的指针。那 memlist_entry() 是如何实现的呢?因为其调用参数 page 是个类型,而不是具体的数据。如果看一下函数 rmqueue() 的整个代码,就可以发现在那里 list 竟是无定义的。

事实上,在同一文件中将 memlist_entry 定义成 list_entry ,所以实际引用的是 list_entry():

48#define memlist_entry list_entry

而 list_entry 的定义则在 include/linux/list.h 中:

135/**

136* list_entry : get the struct for this entry

137* @ptr:the &struct list_head pointer

138* @type:the type of the struct this is embedded in

139* @member:the name of the list_struct within the struct

140*/

141#define list_entry(ptr, type, member) \

142((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

将前面的 188 行与此对照,就可以看出其中的奥秘:经过 C 预处理的文字替换,这一行的内容就成为:

page=((struct page*) ((char )(curr)-(unsigned long)(&((struct page*)0)->list)));

这里的 curr 是一个 page 结构内部的成分 list 的地址,而我们所需要的却是那个 page 结构本身的地址,所以要从地址 curr 减去一个位移量,即成分 list 在 page 内部的位移量,才能达到要求。那么,这个位移量到底是多少呢?& ((struct page*)0)->list 就表示当结构 page 正好在地址 0 上时其成分 list 的地址,这就是位移。同样道理,如果是在 page 结构的 lru 队列里,则传下来的 member 为 lru ,一样能算出宿主结构的地址。

可见,这一套操作既普遍适用,又保持了较高效率。但是,对于阅读代码的人却是有个缺点,那就是光从代码中不容易看出一个 list_head 的宿主结构是什么,而以前只要看一下 next 的类型就知道了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本PDF电子书包含上下两册,共1576页,带目录,高清非扫描版本。 作者: 毛德操 胡希明 丛书名: Linux内核源代码情景分析 出版社:浙江大学出版社 目录 第1章 预备知识 1.1 Linux内核简介. 1.2 Intel X86 CPU系列的寻址方式 1.3 i386的页式内存管理机制 1.4 Linux内核源代码C语言代码 1.5 Linux内核源代码的汇编语言代码 第2章 存储管理 2.1 Linux内存管理的基本框架 2.2 地址映射的全过程 2.3 几个重要的数据结构和函数 2.4 越界访问 2.5 用户堆栈的扩展 2.6 物理页面的使用和周 2.7 物理页面的分配 2.8 页面的定期换出 2.9 页面的换入 2.10 内核缓冲区的管理 2.11 外部设备存储空间的地址映射 2.12 系统调用brk() 2.13 系统调用mmap() 第3章 断、异常和系统调用 3.1 X86 CPU对断的硬件支持 3.2 断向量表IDT的初始化 3.3 断请求队列的初始化 3.4 断的响应和服务 3.5 软断与Bottom Half 3.6 页面异常的进入和返回 3.7 时钟断 3.8 系统调用 3.9 系统调用号与跳表 第4章 进程与进程调度 4.1 进程四要素 4.2 进程三部曲:创建、执行与消亡 4.3 系统调用fork()、vfork()与clone() 4.4 系统调用execve() 4.5 系统调用exit()与wait4() 4.6 进程的调度与切换 4.7 强制性调度 4.8 系统调用nanosleep()和pause() 4.9 内核的互斥操作 第5章 文件系统 5.1 概述 5.2 从路径名到目标节点 5.3 访问权限与文件安全性 5.4 文件系统的安装和拆卸 5.5 文件的打开与关闭 5.6 文件的写与读 5.7 其他文件操作 5.8 特殊文件系统/proc 第6章 传统的Unix进程间通信 6.1 概述 6.2 管道和系统调用pipe() 6.3 命名管道 6.4 信号 6.5 系统调用ptrace()和进程跟踪 6.6 报文传递 6.7 共享内存 6.8 信号量 第7章基于socket的进程间通信 7.1系统调用socket() 7.2函数sys—socket()——创建插口 7.3函数sys—bind()——指定插口地址 7.4函数sys—listen()——设定server插口 7.5函数sys—accept()——接受连接请求 7.6函数sys—connect()——请求连接 7.7报文的接收与发送 7.8插口的关闭 7.9其他 第8章设备驱动 8.1概述 8.2系统调用mknod() 8.3可安装模块 8.4PCI总线 8.5块设备的驱动 8.6字符设备驱动概述 8.7终端设备与汉字信息处理 8.8控制台的驱动 8.9通用串行外部总线USB 8.10系统调用select()以及异步输入/输出 8.11设备文件系统devfs 第9章多处理器SMP系统结构 9.1概述 9.2SMP结构的互斥问题 9.3高速缓存与内存的一致性 9.4SMP结构断机制 9.5SMP结构的进程调度 9.6SMP系统的引导 第10章系统引导和初始化 10.1系统引导过程概述 10.2系统初始化(第一阶段) 10.3系统初始化(第二阶段) 10.4系统初始化(第三阶段) 10.5系统的关闭和重引导

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值