来自 澳门新葡亰官网 2019-11-06 09:28 的文章
当前位置: 澳门新葡亰app > 澳门新葡亰官网 > 正文

澳门新葡亰官网APP:虽说linux内核是用C语言写的

  Linux系统中,进度之间有四个刚烈的继续关系,全体进程都以 PID 为1的 init 进度的儿孙。内核在系统运营的结尾阶段运行 init 进程。该进程读取系统的早先化脚本(initscript卡塔 尔(英语:State of Qatar)并进行其余的相干程序,最后成就系统运行的全方位经过。

Linux 内核list_head 学习(一)

  系统中各种进程必有二个父进度,相应的,各类进程也足以由零个可能七个子进度。具备同三个父进度的装有进程被堪当兄弟。进度之间的涉嫌存放在经过描述符 task_struct 中。每个 task_struct 都包含八个针对其父进度 task_struct 的指针 parent,还会有一个被称作 children 的子进度链表。

 

在Linux内核中,提供了三个用来创立双向循环链表的组织 list_head。纵然linux内核是用C语言写的,然则list_head的引进,使得内核数据结构也得以具有面向对象的风味,通过应用操作list_head 的通用接口超级轻巧实今世码的录取,有一些雷同于C++的三番一回机制(希望有空子写篇小说钻探一下C语言的面向对象机制卡塔 尔(英语:State of Qatar)。下边正是kernel中的list_head结构定义:

生机勃勃、父进度的拜会方法

struct list_head {

  对于近期路程,能够应用上边代码访谈其父进度,拿到其进程描述符:

  struct list_head *next, *prev;

struct task_struct *my_parent = current -> parent;

};

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

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

/* 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是不接纳的,那点必要小心。

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

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

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

   

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

澳门新葡亰官网APP 1

 

list_head那一个协会看起来怪怪的,它竟从未数据域!所以看见这么些布局的人首先反馈正是我们怎么访谈数据?

二、子进度的走访方法

其实list_head不是拿来单独用的,它日常被嵌到别的组织中,如:

  基本上能用以下方式访问子进度:

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卡塔 尔(英语:State of Qatar)-> MEMBE福特Explorer 也正是从0址址找到TYPE 的成员MEMBEPAJERO 。

  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) -> MEMBELacrosse: 访谈结构中的成员MEMBER,三个针对性 0 的 TYPE 类型的结构;分明,MEMBE揽胜极光 之处便是偏移地址;

};

3)、&((TYPE *) 0) -> MEMBE奥迪Q5:取多少成员MEMBE本田UR-V之处(不是按位与,不要看错了卡塔 尔(阿拉伯语:قطر‎;

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

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

( (size_t) & ((struct file_node*)0卡塔 尔(英语:State of Qatar)-> node );那样看的依然不很清楚,我们再变变:

 

struct file_node *p = NULL;

& p->node;

这么应该比较清楚了,即求 p 的积极分子 node的地方,只可是p 为0地址,从0地址发轫算成员node的地址,也正是成员 node 在协会体 struct file_node中的偏移量。offset宏正是算MEMBE大切诺基在TYPE中的偏移量的。

我们再看第贰个宏

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

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

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

以此宏是由七个语句组成,最终container_of再次来到的结果便是第1个表达式的值。这里__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成员名。通过下图来总计一下:

   

澳门新葡亰官网APP 2

   

继续列举部分双链表的常用操作:

双向链表的遍历——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内核是用C语言写的

关键词: