Linux 内核list_head 学习

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

  list->next = list;

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

一、父进程的访问方法

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

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

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

(
(size_t) & ((struct file_node*)0)-> node );这样看的还是不很清楚,我们再变变:

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

 

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

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

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

   

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

3)、&((TYPE *) 0) -> MEMBER
:取数据成员MEMBER的地址(不是按位与,不要看错了);

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

  1. ( (TYPE *)0 ) 将零转型为TYPE类型指针; 
  2. ((TYPE *)0)->MEMBER 访问结构中的数据成员; 
  3. &( ( (TYPE *)0 )->MEMBER )取出数据成员的地址; 
    4.(size_t)(&(((TYPE*)0)->MEMBER))结果转换类型。
struct list_head{
    struct list_head *next,*prev;  
};

  prev->next = new;

  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) );}) 

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

  struct list_head node;

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

 

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

[cpp]

 

刚说了__mptr是结构体中list_head节点的地址,offset宏求的是list_head节点MEMBER在结构体TYPE中的偏移量,那么__mptr减去它所在结构体中的偏移量,就是结构体的地址。

[cpp]

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

#define
list_for_each(pos, head)

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

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

{

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

  list_entry 实际上就是 container_of。

struct list_head
name = LIST_HEAD_INIT(name)

图片 1

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

  return head->next == head;

二、子进程的访问方法

}

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

双向链表的遍历——list_for_each

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

所以list_entry(ptr,type,member)宏的功能就是,由结构体成员地址求结构体地址。其中ptr 是所求结构体中list_head成员指针,type是所求结构体类型,member是结构体list_head成员名。通过下图来总结一下:

/**
 * 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))); })

};

/*
 * 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堆栈指针与栈底对齐    
}    

struct file_node{

/* 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 */

container_of的值是两个地址相减,

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

static inline void
list_del(struct list_head *entry)

  list_for_each:
在linux/list.h 中定义

将实参代入 offset( struct file_node, node
);最终将变成这样:

4)、((size_t) &((TYPE *) 0) -> MEMBER): 强制类型转换成 size_t
类型。 

  char c;

struct task_struct *my_parent = current -> parent;

 

生成双向链表的头结点——LIST_HEAD()

/**
 * 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_head
其实就是一个双向链表,而且一般来说,都是双向循环链表。

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

  分析一下 offsetof 宏:

}

struct task_struct *task;
struct list_head *list;

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

继续列举一些双链表的常用操作:

 

  char c;

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

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

  list_head: 在 linux/types.h
中定义

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

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

Linux 内核list_head 学习(一)

 

{

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图