Linux系统中,进程之间有一个明显的继承关系,所有进程都是 PID 为1的 init 进程的后代。内核在系统启动的最后阶段启动 init 进程。该进程读取系统的初始化脚本(initscript)并执行其他的相关程序,最终完成系统启动的整个过程。
系统中每个进程必有一个父进程,相应的,每个进程也可以由零个或者多个子进程。拥有同一个父进程的所有进程被称为兄弟。进程之间的关系存放在进程描述符 task_struct 中。每个 task_struct 都包含一个指向其父进程 task_struct 的指针 parent,还有一个被称为 children 的子进程链表。
一、父进程的访问方法
对于当前进程,可以使用下面代码访问其父进程,获得其进程描述符:
struct task_struct *my_parent = current -> parent;
其中,current 是一个宏,在 linux/asm-generic/current.h中有定义:
/* 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 */
而 current_thread_info() 函数在 arch/arm/include/asm/thread_info.h 中有定义:
/* * 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 指针的。
二、子进程的访问方法
可以使用以下方法访问子进程:
struct task_struct *task; struct list_head *list; list_for_each(list,¤t->children){ task = list_entry(list,struct task_struct,sibling); }
可以看到,这里使用的是链表相关的操作来访问子进程。我们知道, task_struct 是存放在一个双向循环链表 task_list(任务队列)中的,而一个 task_struct 包含了一个具体进程的所有信息,因此,我们只需要找到子进程的 task_struct 即可以访问子进程了,上面代码就是这么做的。那么,具体是如何找到子进程的进程描述符 task_struct的呢?下面对上面的代码进行详细分析:
list_head: 在 linux/types.h 中定义
struct list_head{ struct list_head *next,*prev; };
显然,list_head 其实就是一个双向链表,而且一般来说,都是双向循环链表。
list_for_each: 在linux/list.h 中定义
#define list_for_each(pos, head) for (pos = (head)->next; pos != (head); pos = pos->next)
这是一个宏定义。其中,pos 是指向 list_head 的指针,而 head 是双链表 list_head 中的指针,定义了从哪里开始遍历这个链表。这个宏的作用就是对一个双向循环链表进行遍历。
list_entry: 在 linux/list.h 中定义,也是一个宏定义
/** * 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。
container_of : 在 linux/kernel.h 中定义
/** * 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))); })
container_of 实现了根据一个结构中的一个成员变量的指针来获取指向整个结构的指针的功能。其中,offsetof 也是一个宏,它的功能是获得成员变量基于其所在结构的地址的偏移量,定义如下:
#define offsetof(TYPE,MEMBER) ((size_t) &((TYPE *)0) -> MEMBER) // 获得成员变量member基于其所在结构的地址的偏移量,该宏在 linux/stddef.h 中有定义
分析一下 offsetof 宏:
1)、((TYPE *) 0) : 将 0 转换成 TYPE 类型的指针。这声明了一个指向 0 的指针,且这个指针是 TYPE 类型的;
2)、((TYPE *) 0) -> MEMBER: 访问结构中的成员MEMBER,一个指向 0 的 TYPE 类型的结构;显然,MEMBER 的地址就是偏移地址;
3)、&((TYPE *) 0) -> MEMBER :取数据成员MEMBER的地址(不是按位与,不要看错了);
4)、((size_t) &((TYPE *) 0) -> MEMBER): 强制类型转换成 size_t 类型。