Linux内核源码中container_of详解,父进程和子进度及

  Linux系统中,进度之间有二个分明的后续关系,全数进度都以 PID 为1的 init 进度的遗族。内核在系统运维的结尾阶段运营 init 进度。该进度读取系统的开端化脚本(initscript)并举办其他的连锁程序,最后达成系统运转的整整经过。

Linux 内核list_head 学习(一)

关于container_of的用法,可参考 http://www.linuxidc.com/Linux/2012-02/53700.htm 。其实就是消除了”怎样通过协会中的某些变量的地址获取结构自个儿的指针“那样的难点。container_of完结了依照贰个结构体变量中的叁个分子变量的指针来获得指向任何结构体变量的指针的功用。

小说中援引的代码来源于LX大切诺基,所剖判的内核版本是v2.6.31。

  系统中各个进程必有一个父进度,相应的,每一个进程也能够由零个依然几个子进度。具备同贰个父进度的全数进程被喻为兄弟。进度之间的涉嫌贮存在经过描述符 task_struct 中。每个 task_struct 都包罗三个针对其父进程 task_struct 的指针 parent,还会有三个被称之为 children 的子进度链表。

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

linux内核通过定义list_head以致对于list_head上的一组操作完结对差异类型的循环链表的同类操作,这种做法幸免了对于分化数据类型的循环链表定义再一次的操作函数,使代码获得了丰裕的接纳,是一种百分之十蹴而就的编制程序方法。

 

在Linux内核中,提供了三个用来创建双向循环链表的结构 list_head。固然linux内核是用C语言写的,可是list_head的引进,使得内核数据结构也得以享有面向对象的特色,通过采纳操作list_head 的通用接口很轻巧完毕代码的录取,有一点类似于C 的一而再机制(希望有机遇写篇小说研商一下C语言的面向对象机制)。下边就是kernel中的list_head结构定义:

[cpp]

list_head的定义:

一、父进程的访问方法

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

19struct list_head {

  对于眼下经过,能够动用下边代码访谈其父进度,得到其经过描述符:

  struct list_head *next, *prev;

typeof( ((type *)0)->member ) *__mptr 正是声称了一个针对其
我们在看一下offset的概念,在linux/stddef.h中。定义如下:

20 struct list_head *next, *prev;

struct task_struct *my_parent = current -> parent;

};

[cpp]

21};

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

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

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

随后大家来看大肆一种数据结构的循环链表(如图1),链表的每种节点中参加了二个list_head类型的变量,节点的别样变量任性。(注意:各种指针所针对的岗位不是节点数据的开局地方,而是list_head类型变量的初步地址。)

/* 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看名称就会想到其意义正是获得该成员变量基于其蕴藉体地址的偏移量。先剖析一下这几个宏:

图片 1

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

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

  1. ( (TYPE *)0 ) 将零转型为TYPE类型指针; 
  2. ((TYPE *)0)->MEMBE大切诺基 访谈结构中的数据成员; 
  3. &( ( (TYPE *)0 )->MEMBECRUISER )抽取数据成员的地方; 
    4.(size_t)(&(((TYPE*)0)->MEMBESportage))结果调换类型。

图1

/*
 * 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依然成员地址。

由此如此一种实现方式创建的链表,节点都以经过list_head类型的变量相连接的,那么我们怎么着由list_head类型得指针获得中间某些节点类型的指针呢?咱们来看那样三个操作:list_entry(p,t,m),个中t是链表的节点类型,m是节点内list_head类型的变量名,p是指向该变量的指针,该操功能于从list_head指针获得指向链表节点的指针。

   能够看见,current 实际上是指向当前进行过程的 task_struct 指针的。

图片 2

最后把__mptr 强制类型调换为char*花色,保证指针相减时是以二个字节为单位进行相减的,保险了前后相继的不利。

334#define list_entry(ptr, type, member)

 

list_head这么些协会看起来怪怪的,它竟从未数据域!所以看到这么些结构的人首先反响便是我们怎么访问数据?

如此那般大家就足以使用container_of来依据二个分子变量的指针来获得指向任何结构体变量的指针,对于应用层程序来讲,这种机制大可不必,但是对于设备驱动程序来讲,运用container_of就很有必不可缺了。

335 container_of(ptr, type, member)

二、子进程的拜谒方法

其实list_head不是拿来单独用的,它平时被嵌到任何组织中,如:

图片 3

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

  能够应用以下办法访问子进度:

struct file_node{

651 const typeof( ((type *)0)->member ) *__mptr = (ptr); /*_mptr与ptr类型值都平等,是ptr的一个拷贝*/

struct task_struct *task;
struct list_head *list;

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

  char c;

652 (type *)( (char *)__mptr - offsetof(type,member) );}) /*地方减去偏移量(以字节为单位)就可以*/

  能够看出,这里运用的是链表相关的操作来访谈子进程。大家通晓, task_struct 是寄放在在贰个双向循环链表 task_list(职责队列)中的,而一个task_struct 富含了三个具体经过的具备新闻,因而,大家只供给找到子进程的 task_struct 即能够访谈子进度了,上边代码正是那样做的。那么,具体是什么找到子过程的经过描述符 task_struct的啊?下边临上面的代码进行详细分析:

  struct list_head node;

24#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) /*测算出变量在布局中的偏移量(以字节为单位)*/  

  list_head: 在 linux/types.h 中定义

};

linux内核通过定义list_head以致对此list_head上的一组操作完成对两样品类的循环链表...

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)-> MEMBEKuga 也等于从0址址找到TYPE 的积极分子MEMBELX570 。

  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;

2)、((TYPE *) 0) -> MEMBE卡宴: 访谈结构中的成员MEMBEEnclave,贰个针对性 0 的 TYPE 类型的构造;鲜明,MEMBE牧马人 的地点正是偏移地址;

};

3)、&((TYPE *) 0) -> MEMBEEnclave:取多少成员MEMBEMurano的地点(不是按位与,不要看错了);

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

4)、((size_t) &((TYPE *) 0) -> MEMBE凯雷德): 强制类型转变来 size_t 类型。 

( (size_t) & ((struct file_node*)0)-> node );那样看的依然不很掌握,大家再变变:

 

struct file_node *p = NULL;

& p->node;

诸如此比应有相比较清楚了,即求 p 的积极分子 node的地方,只但是p 为0地址,从0地址起先算成员node的地址,约等于成员 node 在布局体 struct file_node中的偏移量。offset宏正是算MEMBELAND在TYPE中的偏移量的。

我们再看第三个宏

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

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

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

本条宏是由五个语句组成,最后container_of重返的结果即是第二个表达式的值。这里__mptr为中等变量,那正是list_head指针类型,它被伊始化为ptr的值,而ptr正是眼下所求的结构体中list_head节点的地址。为何要用中间变量,那是考虑到安全性因素,假如传进来二个ptr ,全数ptr 放在三个说明式中会有副成效,像 (p ) (p )之类。

(char*)__mptr 之所以要强制类型转化为char是因为地点是以字节为单位的,而char的长度正是叁个字节。

container_of的值是七个地点相减,

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

所以list_entry(ptr,type,member)宏的效劳便是,由结构体成员地址求结构体地址。在那之中ptr 是所求结构体中list_head成员指针,type是所求结构体类型,member是构造体list_head成员名。通过下图来总计一下:

   

图片 4

   

接二连三列举部分双链表的常用操作:

双向链表的遍历——list_for_each

//注:这里prefetch 是gcc的一个优化增选,也得以绝不

#define list_for_each(pos, head)

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

                 pos = pos->next)

   

转换双向链表的头结点——LIST_HEAD()

LIST_HEAD() -- 生成多少个名称为name的双向链表头节点

#define LIST_HEAD(name)

struct list_head name = LIST_HEAD_INIT(name)

static inline void INIT_LIST_HEAD(struct list_head *list)

{

  list->next = list;

  list->prev = list;

}

双向链表的插入操作 -- list_add()

将new所代表的结构体插入head所管理的双向链表的头节点head之后: (即插入表头)

static inline void list_add(struct list_head *new, struct list_head *head)

{

  __list_add(new, head, head->next);

}

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中剔除结点——list_del()

static inline void list_del(struct list_head *entry)

{

  __list_del(entry->prev, entry->next);

  entry->next = LIST_POISON1;

  entry->prev = LIST_POISON2;

}

static inline void __list_del(struct list_head * prev, struct list_head * next)

{

  next->prev = prev;

  prev->next = next;

}

   

认清链表是还是不是为空(假设双向链表head为空则再次来到真,否则为假)——list_empty()

static inline int list_empty(const struct list_head *head)

{

  return head->next == head;

}

   

make it simple, make it happen

 

本文由星彩网app下载发布于星彩网app下载,转载请注明出处:Linux内核源码中container_of详解,父进程和子进度及

TAG标签: 星彩网app下载
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。