list_for_each 详解

 Linux系统中的每个进程都有一个父进程(init进程除外);每个进程还有0个或多个子进程。在进程描述符中parent指针指向其父进程还有一个名为children的子进程链表(父进程task_struct中的children相当于链表的表头)。

 

而我们可以使用list_for_each(/include/linux/list.h)来依次遍历访问子进程

 

struct task_struct *task;

 

struct list_head *list;

 

list_for_each(list, ¤t->children) 

{

 

      task = list_entry(list, struct task_struct, sibling);

 

}

 

其中task即为某个子进程的地址

 

首先需要说明:

1)task_struct中的children指针指向其某个子进程的进程描述符task_structchildren的地址

而非直接指向某个子进程的地址,也就是说

2)子进程链表中存放的仅仅是各个task_struct成员children的地址

 

我们查看源文件找到list_for_each的定义:

 

#define list_for_each(pos, head) \

 

    for (pos = (head)->next; prefetch(pos->next), pos != (head); pos = pos->next)

 

 

从上可以看出list_for_each其实就是一个for循环

for()实现的就是一个children链表的遍历,而由children的地址如何取到task_struct的地址呢,它是由list_entry宏来实现的。

知道父进程children的地址,如何得到子进程的task_struct地址,这是目的

 

我们先给出所需函数或宏的源代码

 

list_entry(/include/linux/list.h)

//task = list_entry(list, struct task_struct, sibling); 加入做对比

#define list_entry(ptr, type, member) \

 

    container_of(ptr, type, member)

 

---------------------------------------------------

 

container_of(include/linux/kernel.h)

 

#define container_of(ptr, type, member) ({          \

 

    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \

 

    (type *)( (char *)__mptr - offsetof(type,member) );})

 

-------------------------------------------

 

offsetof(/include/linux/stddef.h)

 

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

 

 ((size_t) &((TYPE *)0)->MEMBER) 

 

 

对于list_entry宏来说ptr在这里为指向children链表的指针,typetask_struct结构体的类型,member为链表成员的变量名,即children

 

container_of()思路为先求出结构体成员member(children)在结构体(task_struct)中的偏移量,然后再根据member的地址(ptr)来求出结构体(task_struct)的地址。

 

 

哇哈哈 下面是我觉得最经典的地方((type *)0)->member,他将地址0强制转换为type类型的指针,然后再指向成员member,此时((type *)0)->member的地址即为member成员相对于结构体的位移。

 

其中typeof()相当于Csizeof()(char *)__mptr这个强制转换用来计算偏移字节量,size_t被定义为unsigned int 类型。

 

 

 

 

哈哈 这样这个过程就不难理解了吧

 

 

 

PS:网上找到的list_entry宏定义的另一个版本(有人说是老版本kernel里面的),其实是一样的,大家自己理解吧。^_^

 

#define list_entry(ptr, type, member) \ 

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

**********************************************************************************************

**********************************************************************************************

关于container_ofkernel.h:

 

#define container_of(ptr, type, member) ({ /

const typeof( ((type *)0)->member ) *__mptr = (ptr); /

(type *)( (char *)__mptr - offsetof(type,member) );})

container_ofLinux Kernel中的应用非常广泛,它用于获得某结构中某成员的入口地址.


关于offsetofstddef.h:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

TYPE是某struct的类型 0是一个假想TYPE类型struct,MEMBER是该struct中的一个成员由于该struct的基地址为0, MEMBER的地址就是该成员相对与struct头地址的偏移量.

关于typeof,这是gccC语言扩展保留字,用于声明变量类型.

const typeof( ((type *)0->member ) *__mptr = (ptr);意思是声明一个与member同一个类型的指针常量*__mptr,并初始化为ptr.

(type *)( (char *)__mptr - offsetof(type,member) );意思是__mptr的地址减去member在该struct中的偏移量得到的地址再转换成type型指针该指针就是member的入口地址了.



例一;


container_of宏定义在[include/linux/kernel.h]中:


#define container_of(ptr, type, member) /


const typeof( ((type *)0)->member ) *__mptr = (ptr); /


(type *)( (char *)__mptr - offsetof(type,member) );


offsetof宏定义在[include/linux/stddef.h]中:


#define offsetof(type, member) ((size_t) &((type *)0)->member)

下面用一个测试程序test.c来说明


#include

struct student{

char name[20];

char sex;

}stu={"zhangsan",'m'};

s

main()

{

struct student *stu_ptr; //存储container_of宏的返回值

int offset; //存储offsetof宏的返回值

//下面三行代码等同于 container_of(&stu.sex,struct student, sex )参数带入的情形


const typeof(((struct student*)0)->sex) *_mptr = &stu.sex;

//首先定义一个 _mptr指针, 类型为struct student结构体中sex成员的类型

//typeof 为获取(((struct student*)0->sex)的类型,此处此类型为char

//((struct student*)0)在offsetof处讲解


offset = (int)(&((struct student *)0)->sex);

 


stu_ptr = (struct student *)((char*)_mptr - offset);

 


printf("offsetof stu.sex = %d/n",offset);

printf("stu_ptr->name:%s/tstu_ptr->sex:%c/n", stu_ptr->name, stu_ptr->sex);

return 0;

}


例二:


它的作用显而易见,那就是根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针。比如,有一个结构体变量,其定义如下:


1. struct demo_struct {

2. type1 member1;

3. type2 member2;

4. type3 member3;

5. type4 member4;

6. };

7.

8. struct demo_struct demo;


同时,在另一个地方,获得了变量demo中的某一个域成员变量的指针,比如:


1. type3 *memp = get_member_pointer_from_somewhere();


此时,如果需要获取指向整个结构体变量的指针,而不仅仅只是其某一个域成员变量的指针,我们就可以这么做:


1. struct demo_struct *demop = container_of(memp, struct demo_struct, member3);

首先,我们将container_of(memp, struct demo_struct, type3)根据宏的定义进行展开如下:


1. struct demo_struct *demop = ({ /

2. const typeof( ((struct demo_struct *)0)->member3 ) *__mptr = (memp); /

3. (struct demo_struct *)( (char *)__mptr - offsetof(struct demo_struct, member3) );})


其中,typeofGNU C对标准C的扩展,它的作用是根据变量获取变量的类型。因此,上述代码中的第2行的作用是首先使用typeof获取结构体域变量member3的类型为 type3,然后定义了一个type3指针类型的临时变量__mptr,并将实际结构体变量中的域变量的指针memp的值赋给临时变量__mptr


假设结构体变量demo在实际内存中的位置如下图所示:

demo

+-------------+ 0xA000

| member1 |

+-------------+ 0xA004

| member2 |

| |

+-------------+ 0xA010

| member3 |

| |

+-------------+ 0xA018

| member4 |

+-------------+


则,在执行了上述代码的第2行之后__mptr的值即为0xA010


再看上述代码的第3行,其中需要说明的是offsetof,它定义在include/linux/stddef.h中,其定义如下:


1. 24#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)


同样,我们将上述的offsetof调用展开,即为:


1. (struct demo_struct *)( (char *)__mptr - ((size_t) &((struct demo_struct *)0)->member3) );


可见,offsetof的实现原理就是取结构体中的域成员相对于地址0的偏移地址,也就是域成员变量相对于结构体变量首地址的偏移。


因此,offsetof(struct demo_struct, member3)调用返回的值就是member3相对于demo变量的偏移。结合上述给出的变量地址分布图可知,offsetof(struct demo_struct, member3)将返回0x10


于是,由上述分析可知,此时,__mptr==0xA010offsetof(struct demo_struct, member3)==0x10

因此, (char *)__mptr - ((size_t) &((struct demo_struct *)0)->member3) == 0xA010 - 0x10 == 0xA000,也就是结构体变量demo的首地址(如上图所示)。


由此,container_of实现了根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针的功能。

你可能感兴趣的:(linux)