Linux系统中,进程之间有一个明显的继承关系,所有进程都是 PID 为1的
init 进程的后代。内核在系统启动的最后阶段启动 init
进程。该进程读取系统的初始化脚本(initscript)并执行其他的相关程序,最终完成系统启动的整个过程。

Linux 内核list_head 学习(一)

关于container_of的用法,可参考
http://www.linuxidc.com/Linux/2012-02/53700.htm 。其实就是解决了”如何通过结构中的某个变量的地址获取结构本身的指针“这样的问题。container_of实现了根据一个结构体变量中的一个成员变量的指针来获取指向整个结构体变量的指针的功能。

  系统中每个进程必有一个父进程,相应的,每个进程也可以由零个或者多个子进程。拥有同一个父进程的所有进程被称为兄弟。进程之间的关系存放在进程描述符
task_struct 中。每个 task_struct 都包含一个指向其父进程 task_struct
的指针 parent,还有一个被称为 children 的子进程链表。

首先container_of出现在linux/kernel.h中。定义如下:

 

在Linux内核中,提供了一个用来创建双向循环链表的结构 list_head。虽然linux内核是用C语言写的,但是list_head的引入,使得内核数据结构也可以拥有面向对象的特性,通过使用操作list_head 的通用接口很容易实现代码的重用,有点类似于C++的继承机制(希望有机会写篇文章研究一下C语言的面向对象机制)。下面就是kernel中的list_head结构定义:

[cpp]

一、父进程的访问方法

struct list_head {

  1. /** 
  2.  * container_of – cast a member of a structure out to the containing structure 
  3.  * @ptr:    the pointer to the member. 
  4.  * @type:   the type of the container struct this is embedded in. 
  5.  * @member: the name of the member within the struct. 
  6.  * 
  7.  */  
  8. #define container_of(ptr, type, member) ({          
      
  9.     const typeof( ((type *)0)->member ) *__mptr = (ptr);   
  10.     (type *)( (char *)__mptr – offsetof(type,member) );}) 

  对于当前进程,可以使用下面代码访问其父进程,获得其进程描述符:

  struct list_head *next, *prev;

typeof( ((type *)0)->member )
*__mptr 就是声明了一个指向其
我们在看一下offset的定义,在linux/stddef.h中。定义如下:

struct task_struct *my_parent = current -> parent;

};

[cpp]

   其中,current 是一个宏,在
linux/asm-generic/current.h中有定义:

#define
LIST_HEAD_INIT(name) { &(name), &(name) }

  1. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __ASM_GENERIC_CURRENT_H
#define __ASM_GENERIC_CURRENT_H

#include <linux/thread_info.h>

#define get_current() (current_thread_info()->task)
#define current get_current()

#endif /* __ASM_GENERIC_CURRENT_H */

需要注意的一点是,头结点head是不使用的,这点需要注意。

offset顾名思义就是获得该成员变量基于其包含体地址的偏移量。先分析一下这个宏:

  而 current_thread_info() 函数在
arch/arm/include/asm/thread_info.h 中有定义:

使用list_head组织的链表的结构如下图所示:

  1. ( (TYPE *)0 ) 将零转型为TYPE类型指针; 
  2. ((TYPE *)0)->MEMBER 访问结构中的数据成员; 
  3. &( ( (TYPE *)0 )->MEMBER )取出数据成员的地址; 
    4.(size_t)(&(((TYPE*)0)->MEMBER))结果转换类型。
/*
 * how to get the thread information struct from C
 */
static inline struct thread_info *current_thread_info(void) __attribute_const__;

static inline struct thread_info *current_thread_info(void)
{
    return (struct thread_info *)
        (current_stack_pointer & ~(THREAD_SIZE - 1));        // 让SP堆栈指针与栈底对齐    
}    

   

有人可能感觉很不适应(TYPE
*)0这种用法,把一个0转换成一个结构体指针是什么意思呢?其实就是声明了一个指向无物的指针,并告诉编译器这个指针式指向TYPE类型的,然后成员地址自然为偏移地址,因为成员地址-0还是成员地址。

   可以看到,current 实际上是指向当前执行进程的 task_struct 指针的。

图片 1

最后把__mptr
强制类型转换为char*类型,保证指针相减时是以一个字节为单位进行相减的,保证了程序的正确性。

 

list_head这个结构看起来怪怪的,它竟没有数据域!所以看到这个结构的人第一反应就是我们怎么访问数据?

这样我们就可以利用container_of来根据一个成员变量的指针来获取指向整个结构体变量的指针,对于应用层程序而言,这种机制完全没有必要,但是对于设备驱动程序而言,运用container_of就很有必要了。

二、子进程的访问方法

其实list_head不是拿来单独用的,它一般被嵌到其它结构中,如:

图片 2

  可以使用以下方法访问子进程:

struct file_node{

struct task_struct *task;
struct list_head *list;

list_for_each(list,&current->children){
    task = list_entry(list,struct task_struct,sibling);      
}

  char c;

  可以看到,这里使用的是链表相关的操作来访问子进程。我们知道,
task_struct 是存放在一个双向循环链表 task_list(任务队列)中的,而一个
task_struct 包含了一个具体进程的所有信息,因此,我们只需要找到子进程的
task_struct
即可以访问子进程了,上面代码就是这么做的。那么,具体是如何找到子进程的进程描述符
task_struct的呢?下面对上面的代码进行详细分析:

  struct list_head node;

  list_head: 在 linux/types.h
中定义

};

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

此时list_head就作为它的父结构中的一个成员了,当我们知道list_head的地址(指针)时,我们可以通过list.c提供的宏 list_entry 来获得它的父结构的地址。下面我们来看看list_entry的实现:

  显然,list_head
其实就是一个双向链表,而且一般来说,都是双向循环链表。

#define
list_entry(ptr,type,member)

 

  container_of(ptr,type,member)

  list_for_each:
在linux/list.h 中定义

   

#define list_for_each(pos, head) 
    for (pos = (head)->next; pos != (head); pos = pos->next)

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

  这是一个宏定义。其中,pos 是指向 list_head 的指针,而 head 是双链表
list_head
中的指针,定义了从哪里开始遍历这个链表。这个宏的作用就是对一个双向循环链表进行遍历。

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

 

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

  list_entry: 在 linux/list.h
中定义,也是一个宏定义

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

/**
 * 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_head within the struct.
 */
#define list_entry(ptr, type, member) 
    container_of(ptr, type, member)

   

  list_entry 实际上就是 container_of。

这里涉及到三个宏,还是有点复杂的,我们一个一个来看:

 

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

  container_of : 在 linux/kernel.h
中定义

我们知道 0 地址内容是不能访问的,但 0地址的地址我们还是可以访问的,这里用到一个取址运算符

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:    the pointer to the member.
 * @type:    the type of the container struct this is embedded in.
 * @member:    the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({                
    void *__mptr = (void *)(ptr);                    
    BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&    
             !__same_type(*(ptr), void),            
             "pointer type mismatch in container_of()");    
    ((type *)(__mptr - offsetof(type, member))); })

(TYPE
*)0 它表示将 0地址强制转换为TYPE类型,((TYPE *)0)-> MEMBER 也就是从0址址找到TYPE 的成员MEMBER 。

  container_of
实现了根据一个结构中的一个成员变量的指针来获取指向整个结构的指针的功能。其中,offsetof
也是一个宏,它的功能是获得成员变量基于其所在结构的地址的偏移量,定义如下:

我们结合上面的结构来看

#define offsetof(TYPE,MEMBER)  ((size_t) &((TYPE *)0) -> MEMBER)        // 获得成员变量member基于其所在结构的地址的偏移量,该宏在 linux/stddef.h 中有定义

struct file_node{

  分析一下 offsetof 宏:

  char c;

1)、((TYPE *) 0) : 将 0 转换成 TYPE 类型的指针。这声明了一个指向 0
的指针,且这个指针是 TYPE 类型的;

  struct list_head node;

发表评论

电子邮件地址不会被公开。 必填项已用*标注