图片 2

父进程和子进程及它们的访问方法

By admin in 新闻公告 on 2019年10月16日

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

Linux 内核list_head 学习(一)

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

 

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

图片 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)-> 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;

2)、((TYPE *) 0) -> MEMBER: 访问结构中的成员MEMBER,一个指向 0 的
TYPE 类型的结构;显然,MEMBER 的地址就是偏移地址;

};

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

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

4)、((size_t) &((TYPE *) 0) -> MEMBER): 强制类型转换成 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宏就是算MEMBER在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节点MEMBER在结构体TYPE中的偏移量,那么__mptr减去它所在结构体中的偏移量,就是结构体的地址。

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

   

图片 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

 

发表评论

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

网站地图xml地图
Copyright @ 2010-2019 大奖888网页版登陆 版权所有