Linux内核源代码中的C语言代码(Linux内核源代码情景分析读书笔记连载)

1 Linux内核的主体是以 GNU 的 C 语言编写的,GNU为此提供了编译工具gcc。GNU对C语言本身作了不少扩充。

首先,gcc从C++语言中吸收了“inline”和“const”。其实,GNU的C和C++是合为一体的,gcc即是C编译又是C++编译,所以从C++中吸收一些东西是很自然的。

还有,为了支持64位的CPU结构,gcc增加了一种新的基本数据类型“long long int”,该类型在内核代码中常常用到。

许多C语言都支持一些“属性描述符”,如“aligned”、“packed”等;gcc也支持不少这样的描述符。这些描述符的使用等于是在C语言中增加了一些新的保留字。可是,在原来的C语言中,这些词并非保留字,这样就有可能产生一些冲突。例如,gcc支持保留字inline,可是由于“inline”原非保留字,所以在老的代码中可能已经有一些变量名为“inline”,这样就产生了冲突。为了解决这个问题,gcc允许在作为保留字使用的“inline”前、后都加上“_ _”,因而,“_ _ inline_ _”等价于保留字“inline”。同样的道理,“asm”等价于“_ _asm_ _”。这就是我们在代码中有时候看到“asm”,而有时候又看到“_ _asm_ _”的原因。

gcc还支持一个保留字“attribute”,用来作属性描述符。

2 由于在Linux内核中使用了gcc对C的扩充,很自然地Linux内核就只能用gcc编译。不仅如此,由于gcc和Linux内核在平行地发展,一旦在Linux内核中使用了gcc,在其较新版本中有了新增加新扩充,就不能再使用较老版本的gcc来编译。也就是说,Linux内核的各种版本有着对gcc版本的依赖关系。

3 如前所述,Linux内核的代码中使用了大量的inline函数。不过,这并未消除对宏操作的使用,内核中仍有许多宏操作定义。先看一个实例,取自fs/proc/kcore.c。

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

为什么要这样通过一个do-while循环来定义呢?这似乎有点怪。不妨看看其他几种可能。首先,能不能定义成如下这样?

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

通过一个假想的例子来说明:

if(addr)

DUMP_WRITE(addr,nr);

else

do_something_else();

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

if(addr)

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

else

do_something_else();

gcc在碰到else前面的“;”时就认为if语句已经结束,因而后面的else不在if语句中。

4 来看对“空操作”的定义。由于Linux内核的代码要考虑到各种不同的CPU和不同的系统配置,所以常常需要在一定的条件下把某些宏操作定义为空操作。

例如在,include/asm-i386/system.h中的prepare_to_switch();

14                                                                #define prepare_to_switch() do { } while(0)

内核在调度一个进程运行,进行切换之际,在有些CPU上需要调用prepare_to_switch()作些准备,而在另一些CPU上就不需要,所以要把它定位空操作。

5 内核中大量使用队列和队列操作,在这里作一些介绍。如果我们有一种数据结构foo,并且需要维持一个这种数据结构的双链队列,最简单的、也是最常用的办法就是在这个数据结构的类型定义中加入两个指针,例如:

typedef struct foo

{

struct foo *prev;

struct foo *next;

.........

} foo_t;

然后为这种数据结构写一套用于各种队列操作的子程序。由于用来维持队列的这两个指针的类型都是固定的(都指向foo数据结构),这些子程序不能用于其他数据结构的队列操作。换言之,需要维持多少种数据结构的队列,就得有多少套的队列操作子程序。对于使用大量队列的内核效率太低。所以,Linux内核中采用了一套通用的、一般的、可以用到各种不同数据结构的队列操作。

为此,代码的作者们把指针prev和next从具体的“宿主”数据结构中抽象出来成为一种数据结构 list_head,这种数据结构既可以“寄宿”在具体的宿主数据结构内部,成为该数据结构的一个“连接件”;也可以独立存在而成为一个队列的头。这个数据结构的定义在include/linux/list.h中。

struct list_head {
struct list_head *next, *prev;
};

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

typedef struct page {
struct list_head list;
struct address_space *mapping;
unsigned long index;
struct page *next_hash;
atomic_t count;
unsigned long flags; /* atomic flags, some possibly updated asynchronously */
struct list_head lru;
unsigned long age;
wait_queue_head_t wait;
struct page **pprev_hash;
struct buffer_head * buffers;
void *virtual; /* non-NULL if kmapped */
struct zone_struct *zone;
} mem_map_t;

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

对于宿主数据结构内部的每个list_head数据结构都要加以初始化,可以通过一个宏操作INIT_LIST_HEAD进行:

# define INIT_LIST_HEAD(ptr) do{\

(ptr)->nex = (ptr);(ptr)->prev = (ptr);\

} while(0)

参数ptr为指向需要初始化的list_head结构。可见初始化以后两个指针都指向该list_head结构自身。

要将一个page结构通过其“队列头”list链入(有时候也说挂入)一个队列时,可以使用list_add(),这是一个inline函数,其代码在include/linux/list.h中:

static __inline__ void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}

参数new指向欲链入队列的宿主数据结构内部的list_head数据结构。参数head则指向链入点,也是个list_head结构,它可以是个独立的、真正意义上的队列头,也可以在另一个宿主数据结构内部。这个inline函数调用另一个inline函数_ _list_add()来完成操作:

static __inline__ void __list_add(struct list_head * new,
struct list_head * prev,
struct list_head * next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}

再来看从队列中脱链的操作list_del();

/**
 * list_del - deletes entry from list.
 * @entry: the element to delete from the list.
 * Note: list_empty on entry does not return true after this, the entry is in an undefined state.
 */
static __inline__ void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
}

同样,这里也是调用另一个inline函数__list_del()来完成操作:

static __inline__ void __list_del(struct list_head * prev,
 struct list_head * next)
{
next->prev = prev;
prev->next = next;
}

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

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

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

这里的memlist_entry将一个list_head指针curr换算成其宿主结构的起始地址,也就是取得指向其宿主page结构的指针。事实上,在同一文件中将memlist_entry定义成list_entry,所以实际引用的是list_entry():

#define memlist_entry list_entry

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


/**
 * list_entry - get the struct for this entry
 * @ptr: the &struct list_head pointer.
 * @type: the type of the struct this is embedded in.
 * @member: the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

替换上述page,得到:

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

这里的curr是一个page结构内部的成分list的地址,而我们所需要的却是那个page结构本身的地址,所以要从地址curr减去一个位移量,即成分list在内部的位移量,才能达到要求。

那么,这个位移量到底是多少呢?&((struct page*)0)->list就表示当结构page正好在地址0上时其成分list的地址,这就是位移。




  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值