Linux內核學習筆記(2)-- 父進程和子進程及它們的訪問方法
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) #definecurrent 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 類型。
Linux內核學習筆記(2)-- 父進程和子進程及它們的訪問方法