《Linux内核源代码情景分析》----毛德操【著】 阅读笔记。
1、GCC编译器吸收了很多C++特性对ANSI C做了很多扩充。【ADI 的Visual DSP使用的就是GCC内核,也支持以下扩展特性】
1.1 增加inline关键字,并且为兼容其他以编程将“inline”作为变量的代码,还扩充了“__inline__”作为关键字,等价于“inline”。用inline定义的函数需要写在“.h”文件,用于打开编译器优化时自动将代码插入到调用的地方。
1.2 扩充了数据长度,增加“long long int”类型,定义64bit整数,增加“double double”定义64bit float数据。
2、常见以下形式宏定义:
#define DUMP_WRITE(addr, n) do{memcpy(buff, addr, n); buff+=n;}while(0)
表面上看,使用一个do..while循环,可是循环条件却永远为假,等价于不用循环。但是这样定义的好处是这个宏定义可以插在代码的任何地方而不用担心编译器报错或编译出错。比如:
if( addr ) DUMP_WRITE; else do_something_else();
就可以很好的工作,这种宏定义方式巧妙的利用了C的特性实现了宏定义一段代码的方法。
3、利用一个独立定义的链表指针结构体来供各种需要双向指针的结构体包含,以提供双向链节点。
struct list_head{ struct list_head * next ; struct list_head * prev ; };
任何需要链表的结构体只需包含上述结构体即可,上述结构体还可以单独用于定义一个链表头类型变量或指针。
4、对于以上结构体,如果一个结构体包含了以上的结构体用于构成链表。那么怎么通过链表指针计算出其宿主结构体变量的首地址呢?Linux这么做的:
假如有如下定义: struct page{ ...... struct list_head list ; //不确定处于page结构体的某个偏移地址的位置上 ...... } 【取自mm/page_alloc.c】 page = memlist_entry( pcurr, struct page, list ); memlist_entry函数实现将一个list_head类型的指针pcurr换算成其宿主结构体变量的起始地址。 并且文件中根本没有定义list变量。第二个参数还仅仅是个类型,而不是变量。 但是同一文件中,有以下宏定义: #define memlist_entry list_entry list_entry则在【include/linux/list.h】中定义如下: #define list_entry(ptr, type, member) ((type *)((char*)(ptr)-(unsigned long)(&((type *)0)->member))) 由此可见,以上“page = memlist_entry( pcurr, struct page, list );”语句将被预处理替换成如下格式: page = ((struct page *)((char*)(curr)-(unsigned long)(&((struct page *)0)->list))) ; 这样就很好理解了,用链表指针的地址减去list成员在结构体中的偏移。