'Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?'

Linux C語言 操作系統 UNIX ARM 文章 嵌入式時代 2019-08-07
"

上一節簡要討論了下Linux操作系統中進程的概念,其實簡單來說,進程無非就是處於運行期的程序及其相關資源的總和。這裡讀者應該注意“相關資源”一詞,Linux 在內核中是如何記錄進程的資源的呢?

"

上一節簡要討論了下Linux操作系統中進程的概念,其實簡單來說,進程無非就是處於運行期的程序及其相關資源的總和。這裡讀者應該注意“相關資源”一詞,Linux 在內核中是如何記錄進程的資源的呢?

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

神祕的Linux系統是如何記錄和描述進程的?

Linux內核如何記錄進程的資源?

首先應該明白,Linux 內核大都是採用C語言編寫的,因此要弄清楚內核如何記錄進程資源,只需要查看相關的C語言代碼就可以了。事實上,Linux 內核是使用 task_struct 結構體描述進程的資源的,它的C語言部分代碼如下,請看:

"

上一節簡要討論了下Linux操作系統中進程的概念,其實簡單來說,進程無非就是處於運行期的程序及其相關資源的總和。這裡讀者應該注意“相關資源”一詞,Linux 在內核中是如何記錄進程的資源的呢?

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

神祕的Linux系統是如何記錄和描述進程的?

Linux內核如何記錄進程的資源?

首先應該明白,Linux 內核大都是採用C語言編寫的,因此要弄清楚內核如何記錄進程資源,只需要查看相關的C語言代碼就可以了。事實上,Linux 內核是使用 task_struct 結構體描述進程的資源的,它的C語言部分代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體很長

task_struct 結構體很長,在我手中的 Linux 內核C語言源代碼中,它佔用了280行。當然了,這其中包含很多條件編譯部分,在 32 位機器上,task_struct 大約要佔用 1.7 KB 的內存空間,不過考慮到它可以管理完整的進程,1.7kB 其實並不算大了。

鑑於 task_struct 結構體過長,這裡不可能將其成員一一介紹清楚。如果讀者和我一樣好奇,粗略的瀏覽 task_struct 結構體,應該能夠發現一些比較令人熟悉的成員,例如:

"

上一節簡要討論了下Linux操作系統中進程的概念,其實簡單來說,進程無非就是處於運行期的程序及其相關資源的總和。這裡讀者應該注意“相關資源”一詞,Linux 在內核中是如何記錄進程的資源的呢?

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

神祕的Linux系統是如何記錄和描述進程的?

Linux內核如何記錄進程的資源?

首先應該明白,Linux 內核大都是採用C語言編寫的,因此要弄清楚內核如何記錄進程資源,只需要查看相關的C語言代碼就可以了。事實上,Linux 內核是使用 task_struct 結構體描述進程的資源的,它的C語言部分代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體很長

task_struct 結構體很長,在我手中的 Linux 內核C語言源代碼中,它佔用了280行。當然了,這其中包含很多條件編譯部分,在 32 位機器上,task_struct 大約要佔用 1.7 KB 的內存空間,不過考慮到它可以管理完整的進程,1.7kB 其實並不算大了。

鑑於 task_struct 結構體過長,這裡不可能將其成員一一介紹清楚。如果讀者和我一樣好奇,粗略的瀏覽 task_struct 結構體,應該能夠發現一些比較令人熟悉的成員,例如:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體令人熟悉的成員

通過C語言註釋以及成員的變量名,能夠看到 task_struct 結構體包含了文件系統,線程結構體,以及進程打開的文件等信息,這就與上一節文章的內容對應上了。其他成員在我之後的文章中會涉及到,這裡暫不贅述。

在創建進程時,Linux 通過 slab 分配器分配 task_struct 結構,這樣可以避免動態分配和釋放帶來的開銷,提高內存的使用效率。

那麼創建 task_struct 結構後,內核如何訪問它呢?

根據我手上的內核C語言源代碼,Linux 中還有一個結構體 thread_info,它的其中一個成員 task 指針正好適合用於索引 task_struct 結構體,在X86_64平臺上,thread_info 的相關C語言代碼如下,請看:

"

上一節簡要討論了下Linux操作系統中進程的概念,其實簡單來說,進程無非就是處於運行期的程序及其相關資源的總和。這裡讀者應該注意“相關資源”一詞,Linux 在內核中是如何記錄進程的資源的呢?

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

神祕的Linux系統是如何記錄和描述進程的?

Linux內核如何記錄進程的資源?

首先應該明白,Linux 內核大都是採用C語言編寫的,因此要弄清楚內核如何記錄進程資源,只需要查看相關的C語言代碼就可以了。事實上,Linux 內核是使用 task_struct 結構體描述進程的資源的,它的C語言部分代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體很長

task_struct 結構體很長,在我手中的 Linux 內核C語言源代碼中,它佔用了280行。當然了,這其中包含很多條件編譯部分,在 32 位機器上,task_struct 大約要佔用 1.7 KB 的內存空間,不過考慮到它可以管理完整的進程,1.7kB 其實並不算大了。

鑑於 task_struct 結構體過長,這裡不可能將其成員一一介紹清楚。如果讀者和我一樣好奇,粗略的瀏覽 task_struct 結構體,應該能夠發現一些比較令人熟悉的成員,例如:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體令人熟悉的成員

通過C語言註釋以及成員的變量名,能夠看到 task_struct 結構體包含了文件系統,線程結構體,以及進程打開的文件等信息,這就與上一節文章的內容對應上了。其他成員在我之後的文章中會涉及到,這裡暫不贅述。

在創建進程時,Linux 通過 slab 分配器分配 task_struct 結構,這樣可以避免動態分配和釋放帶來的開銷,提高內存的使用效率。

那麼創建 task_struct 結構後,內核如何訪問它呢?

根據我手上的內核C語言源代碼,Linux 中還有一個結構體 thread_info,它的其中一個成員 task 指針正好適合用於索引 task_struct 結構體,在X86_64平臺上,thread_info 的相關C語言代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task 指針

Linux 通常會在內核棧底或者棧頂保留 thread_info 結構,而內核棧通常大小都是可知的,因此每個進程都能方便的從自己的棧中找到 thread_info 結構,進而找到 task_struct 結構。

查找當前進程的 thread_info 結構,可以調用 current_thread_info() 函數,它的C語言代碼如下,請看:

static inline struct thread_info *current_thread_info(void)
{
register unsigned long sp asm ("sp");
return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
}\t
"

上一節簡要討論了下Linux操作系統中進程的概念,其實簡單來說,進程無非就是處於運行期的程序及其相關資源的總和。這裡讀者應該注意“相關資源”一詞,Linux 在內核中是如何記錄進程的資源的呢?

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

神祕的Linux系統是如何記錄和描述進程的?

Linux內核如何記錄進程的資源?

首先應該明白,Linux 內核大都是採用C語言編寫的,因此要弄清楚內核如何記錄進程資源,只需要查看相關的C語言代碼就可以了。事實上,Linux 內核是使用 task_struct 結構體描述進程的資源的,它的C語言部分代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體很長

task_struct 結構體很長,在我手中的 Linux 內核C語言源代碼中,它佔用了280行。當然了,這其中包含很多條件編譯部分,在 32 位機器上,task_struct 大約要佔用 1.7 KB 的內存空間,不過考慮到它可以管理完整的進程,1.7kB 其實並不算大了。

鑑於 task_struct 結構體過長,這裡不可能將其成員一一介紹清楚。如果讀者和我一樣好奇,粗略的瀏覽 task_struct 結構體,應該能夠發現一些比較令人熟悉的成員,例如:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體令人熟悉的成員

通過C語言註釋以及成員的變量名,能夠看到 task_struct 結構體包含了文件系統,線程結構體,以及進程打開的文件等信息,這就與上一節文章的內容對應上了。其他成員在我之後的文章中會涉及到,這裡暫不贅述。

在創建進程時,Linux 通過 slab 分配器分配 task_struct 結構,這樣可以避免動態分配和釋放帶來的開銷,提高內存的使用效率。

那麼創建 task_struct 結構後,內核如何訪問它呢?

根據我手上的內核C語言源代碼,Linux 中還有一個結構體 thread_info,它的其中一個成員 task 指針正好適合用於索引 task_struct 結構體,在X86_64平臺上,thread_info 的相關C語言代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task 指針

Linux 通常會在內核棧底或者棧頂保留 thread_info 結構,而內核棧通常大小都是可知的,因此每個進程都能方便的從自己的棧中找到 thread_info 結構,進而找到 task_struct 結構。

查找當前進程的 thread_info 結構,可以調用 current_thread_info() 函數,它的C語言代碼如下,請看:

static inline struct thread_info *current_thread_info(void)
{
register unsigned long sp asm ("sp");
return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
}\t
Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

current_thread_info() 函數

可見,current_thread_info() 函數其實就是通過進程棧計算的,因此它的實現與平臺架構有關,上述C語言代碼其實只是 arm 平臺的實現方法,其他平臺的實現方法,讀者可自行查閱。

此時,要獲取當前進程的資源,可以通過 current_thread_info()->task 索引。

進程 PID

Linux 內核為每一個進程分配獨一無二的進程標識(process identification,PID)用於區分不同的進程。PID 是一個整數,在內核的C語言源碼中表示為 pid_t 類型(其實就是 int 類型)。在Linux命令行輸入 ps 命令,即可查看進程的 PID,例如:

"

上一節簡要討論了下Linux操作系統中進程的概念,其實簡單來說,進程無非就是處於運行期的程序及其相關資源的總和。這裡讀者應該注意“相關資源”一詞,Linux 在內核中是如何記錄進程的資源的呢?

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

神祕的Linux系統是如何記錄和描述進程的?

Linux內核如何記錄進程的資源?

首先應該明白,Linux 內核大都是採用C語言編寫的,因此要弄清楚內核如何記錄進程資源,只需要查看相關的C語言代碼就可以了。事實上,Linux 內核是使用 task_struct 結構體描述進程的資源的,它的C語言部分代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體很長

task_struct 結構體很長,在我手中的 Linux 內核C語言源代碼中,它佔用了280行。當然了,這其中包含很多條件編譯部分,在 32 位機器上,task_struct 大約要佔用 1.7 KB 的內存空間,不過考慮到它可以管理完整的進程,1.7kB 其實並不算大了。

鑑於 task_struct 結構體過長,這裡不可能將其成員一一介紹清楚。如果讀者和我一樣好奇,粗略的瀏覽 task_struct 結構體,應該能夠發現一些比較令人熟悉的成員,例如:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體令人熟悉的成員

通過C語言註釋以及成員的變量名,能夠看到 task_struct 結構體包含了文件系統,線程結構體,以及進程打開的文件等信息,這就與上一節文章的內容對應上了。其他成員在我之後的文章中會涉及到,這裡暫不贅述。

在創建進程時,Linux 通過 slab 分配器分配 task_struct 結構,這樣可以避免動態分配和釋放帶來的開銷,提高內存的使用效率。

那麼創建 task_struct 結構後,內核如何訪問它呢?

根據我手上的內核C語言源代碼,Linux 中還有一個結構體 thread_info,它的其中一個成員 task 指針正好適合用於索引 task_struct 結構體,在X86_64平臺上,thread_info 的相關C語言代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task 指針

Linux 通常會在內核棧底或者棧頂保留 thread_info 結構,而內核棧通常大小都是可知的,因此每個進程都能方便的從自己的棧中找到 thread_info 結構,進而找到 task_struct 結構。

查找當前進程的 thread_info 結構,可以調用 current_thread_info() 函數,它的C語言代碼如下,請看:

static inline struct thread_info *current_thread_info(void)
{
register unsigned long sp asm ("sp");
return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
}\t
Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

current_thread_info() 函數

可見,current_thread_info() 函數其實就是通過進程棧計算的,因此它的實現與平臺架構有關,上述C語言代碼其實只是 arm 平臺的實現方法,其他平臺的實現方法,讀者可自行查閱。

此時,要獲取當前進程的資源,可以通過 current_thread_info()->task 索引。

進程 PID

Linux 內核為每一個進程分配獨一無二的進程標識(process identification,PID)用於區分不同的進程。PID 是一個整數,在內核的C語言源碼中表示為 pid_t 類型(其實就是 int 類型)。在Linux命令行輸入 ps 命令,即可查看進程的 PID,例如:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

查看進程的 PID

task_struct 結構體使用成員 pid 記錄進程的 PID 值,相關的C語言代碼如下,請看:

"

上一節簡要討論了下Linux操作系統中進程的概念,其實簡單來說,進程無非就是處於運行期的程序及其相關資源的總和。這裡讀者應該注意“相關資源”一詞,Linux 在內核中是如何記錄進程的資源的呢?

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

神祕的Linux系統是如何記錄和描述進程的?

Linux內核如何記錄進程的資源?

首先應該明白,Linux 內核大都是採用C語言編寫的,因此要弄清楚內核如何記錄進程資源,只需要查看相關的C語言代碼就可以了。事實上,Linux 內核是使用 task_struct 結構體描述進程的資源的,它的C語言部分代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體很長

task_struct 結構體很長,在我手中的 Linux 內核C語言源代碼中,它佔用了280行。當然了,這其中包含很多條件編譯部分,在 32 位機器上,task_struct 大約要佔用 1.7 KB 的內存空間,不過考慮到它可以管理完整的進程,1.7kB 其實並不算大了。

鑑於 task_struct 結構體過長,這裡不可能將其成員一一介紹清楚。如果讀者和我一樣好奇,粗略的瀏覽 task_struct 結構體,應該能夠發現一些比較令人熟悉的成員,例如:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體令人熟悉的成員

通過C語言註釋以及成員的變量名,能夠看到 task_struct 結構體包含了文件系統,線程結構體,以及進程打開的文件等信息,這就與上一節文章的內容對應上了。其他成員在我之後的文章中會涉及到,這裡暫不贅述。

在創建進程時,Linux 通過 slab 分配器分配 task_struct 結構,這樣可以避免動態分配和釋放帶來的開銷,提高內存的使用效率。

那麼創建 task_struct 結構後,內核如何訪問它呢?

根據我手上的內核C語言源代碼,Linux 中還有一個結構體 thread_info,它的其中一個成員 task 指針正好適合用於索引 task_struct 結構體,在X86_64平臺上,thread_info 的相關C語言代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task 指針

Linux 通常會在內核棧底或者棧頂保留 thread_info 結構,而內核棧通常大小都是可知的,因此每個進程都能方便的從自己的棧中找到 thread_info 結構,進而找到 task_struct 結構。

查找當前進程的 thread_info 結構,可以調用 current_thread_info() 函數,它的C語言代碼如下,請看:

static inline struct thread_info *current_thread_info(void)
{
register unsigned long sp asm ("sp");
return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
}\t
Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

current_thread_info() 函數

可見,current_thread_info() 函數其實就是通過進程棧計算的,因此它的實現與平臺架構有關,上述C語言代碼其實只是 arm 平臺的實現方法,其他平臺的實現方法,讀者可自行查閱。

此時,要獲取當前進程的資源,可以通過 current_thread_info()->task 索引。

進程 PID

Linux 內核為每一個進程分配獨一無二的進程標識(process identification,PID)用於區分不同的進程。PID 是一個整數,在內核的C語言源碼中表示為 pid_t 類型(其實就是 int 類型)。在Linux命令行輸入 ps 命令,即可查看進程的 PID,例如:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

查看進程的 PID

task_struct 結構體使用成員 pid 記錄進程的 PID 值,相關的C語言代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體使用成員 pid 記錄進程的 PID 值

在Linux系統中,PID 的最大值是可以調整的,早期為了兼容老版本的 Unix 和 Linux,默認最大值為 32768(short int 類型能夠表示的最大值),這個值可以通過 cat 命令查看:

# cat /proc/sys/kernel/pid_max 
32768

PID 的最大值對於Linux系統的運行是有影響的,因為 PID 值是獨一無二的,所以它的最大值實際上就表示系統可以同時運行的最多進程數目。對於普通的個人用戶來說,32768 足夠多,但是對於大型服務器來說,32768 可能就遠遠不夠了,這時可以修改 pid_max 解決這一問題。

進程的狀態

現在知道了Linux內核是如何描述和記錄進程資源,以及如何區分不同進程的了。那麼進程有哪些狀態呢?讀者應該注意到 task_struct 結構體的第一個成員 state 了,它就是用於記錄進程狀態的。進程的狀態在C語言源代碼中是使用幾個宏定義的:

"

上一節簡要討論了下Linux操作系統中進程的概念,其實簡單來說,進程無非就是處於運行期的程序及其相關資源的總和。這裡讀者應該注意“相關資源”一詞,Linux 在內核中是如何記錄進程的資源的呢?

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

神祕的Linux系統是如何記錄和描述進程的?

Linux內核如何記錄進程的資源?

首先應該明白,Linux 內核大都是採用C語言編寫的,因此要弄清楚內核如何記錄進程資源,只需要查看相關的C語言代碼就可以了。事實上,Linux 內核是使用 task_struct 結構體描述進程的資源的,它的C語言部分代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體很長

task_struct 結構體很長,在我手中的 Linux 內核C語言源代碼中,它佔用了280行。當然了,這其中包含很多條件編譯部分,在 32 位機器上,task_struct 大約要佔用 1.7 KB 的內存空間,不過考慮到它可以管理完整的進程,1.7kB 其實並不算大了。

鑑於 task_struct 結構體過長,這裡不可能將其成員一一介紹清楚。如果讀者和我一樣好奇,粗略的瀏覽 task_struct 結構體,應該能夠發現一些比較令人熟悉的成員,例如:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體令人熟悉的成員

通過C語言註釋以及成員的變量名,能夠看到 task_struct 結構體包含了文件系統,線程結構體,以及進程打開的文件等信息,這就與上一節文章的內容對應上了。其他成員在我之後的文章中會涉及到,這裡暫不贅述。

在創建進程時,Linux 通過 slab 分配器分配 task_struct 結構,這樣可以避免動態分配和釋放帶來的開銷,提高內存的使用效率。

那麼創建 task_struct 結構後,內核如何訪問它呢?

根據我手上的內核C語言源代碼,Linux 中還有一個結構體 thread_info,它的其中一個成員 task 指針正好適合用於索引 task_struct 結構體,在X86_64平臺上,thread_info 的相關C語言代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task 指針

Linux 通常會在內核棧底或者棧頂保留 thread_info 結構,而內核棧通常大小都是可知的,因此每個進程都能方便的從自己的棧中找到 thread_info 結構,進而找到 task_struct 結構。

查找當前進程的 thread_info 結構,可以調用 current_thread_info() 函數,它的C語言代碼如下,請看:

static inline struct thread_info *current_thread_info(void)
{
register unsigned long sp asm ("sp");
return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
}\t
Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

current_thread_info() 函數

可見,current_thread_info() 函數其實就是通過進程棧計算的,因此它的實現與平臺架構有關,上述C語言代碼其實只是 arm 平臺的實現方法,其他平臺的實現方法,讀者可自行查閱。

此時,要獲取當前進程的資源,可以通過 current_thread_info()->task 索引。

進程 PID

Linux 內核為每一個進程分配獨一無二的進程標識(process identification,PID)用於區分不同的進程。PID 是一個整數,在內核的C語言源碼中表示為 pid_t 類型(其實就是 int 類型)。在Linux命令行輸入 ps 命令,即可查看進程的 PID,例如:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

查看進程的 PID

task_struct 結構體使用成員 pid 記錄進程的 PID 值,相關的C語言代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體使用成員 pid 記錄進程的 PID 值

在Linux系統中,PID 的最大值是可以調整的,早期為了兼容老版本的 Unix 和 Linux,默認最大值為 32768(short int 類型能夠表示的最大值),這個值可以通過 cat 命令查看:

# cat /proc/sys/kernel/pid_max 
32768

PID 的最大值對於Linux系統的運行是有影響的,因為 PID 值是獨一無二的,所以它的最大值實際上就表示系統可以同時運行的最多進程數目。對於普通的個人用戶來說,32768 足夠多,但是對於大型服務器來說,32768 可能就遠遠不夠了,這時可以修改 pid_max 解決這一問題。

進程的狀態

現在知道了Linux內核是如何描述和記錄進程資源,以及如何區分不同進程的了。那麼進程有哪些狀態呢?讀者應該注意到 task_struct 結構體的第一個成員 state 了,它就是用於記錄進程狀態的。進程的狀態在C語言源代碼中是使用幾個宏定義的:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

進程的狀態在C語言源代碼中是使用幾個宏定義的

Linux 系統中的進程必定處於這 5 種狀態之一。從上到下,分別表示進程處於:

  • 正在運行或者準備運行
  • 正在睡眠,但是可中斷,接收到信號會被提前喚醒
  • 正在睡眠,並且不可中斷,也即即使接收到信號也不會被喚醒
  • 被其他進程跟蹤中
  • 停止運行

現在就明白有時無法通過 kill 命令殺死 D 狀態的進程了,這是因為這些進程處於不響應信號的狀態,kill 命令本質上是發送 SIGKILL 信號,自然無法殺死該進程。

父進程和子進程

進程的父進程和子進程也屬於進程的資源,因此也被記錄在 task_struct 結構體中,請看相關C語言代碼:

"

上一節簡要討論了下Linux操作系統中進程的概念,其實簡單來說,進程無非就是處於運行期的程序及其相關資源的總和。這裡讀者應該注意“相關資源”一詞,Linux 在內核中是如何記錄進程的資源的呢?

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

神祕的Linux系統是如何記錄和描述進程的?

Linux內核如何記錄進程的資源?

首先應該明白,Linux 內核大都是採用C語言編寫的,因此要弄清楚內核如何記錄進程資源,只需要查看相關的C語言代碼就可以了。事實上,Linux 內核是使用 task_struct 結構體描述進程的資源的,它的C語言部分代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體很長

task_struct 結構體很長,在我手中的 Linux 內核C語言源代碼中,它佔用了280行。當然了,這其中包含很多條件編譯部分,在 32 位機器上,task_struct 大約要佔用 1.7 KB 的內存空間,不過考慮到它可以管理完整的進程,1.7kB 其實並不算大了。

鑑於 task_struct 結構體過長,這裡不可能將其成員一一介紹清楚。如果讀者和我一樣好奇,粗略的瀏覽 task_struct 結構體,應該能夠發現一些比較令人熟悉的成員,例如:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體令人熟悉的成員

通過C語言註釋以及成員的變量名,能夠看到 task_struct 結構體包含了文件系統,線程結構體,以及進程打開的文件等信息,這就與上一節文章的內容對應上了。其他成員在我之後的文章中會涉及到,這裡暫不贅述。

在創建進程時,Linux 通過 slab 分配器分配 task_struct 結構,這樣可以避免動態分配和釋放帶來的開銷,提高內存的使用效率。

那麼創建 task_struct 結構後,內核如何訪問它呢?

根據我手上的內核C語言源代碼,Linux 中還有一個結構體 thread_info,它的其中一個成員 task 指針正好適合用於索引 task_struct 結構體,在X86_64平臺上,thread_info 的相關C語言代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task 指針

Linux 通常會在內核棧底或者棧頂保留 thread_info 結構,而內核棧通常大小都是可知的,因此每個進程都能方便的從自己的棧中找到 thread_info 結構,進而找到 task_struct 結構。

查找當前進程的 thread_info 結構,可以調用 current_thread_info() 函數,它的C語言代碼如下,請看:

static inline struct thread_info *current_thread_info(void)
{
register unsigned long sp asm ("sp");
return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
}\t
Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

current_thread_info() 函數

可見,current_thread_info() 函數其實就是通過進程棧計算的,因此它的實現與平臺架構有關,上述C語言代碼其實只是 arm 平臺的實現方法,其他平臺的實現方法,讀者可自行查閱。

此時,要獲取當前進程的資源,可以通過 current_thread_info()->task 索引。

進程 PID

Linux 內核為每一個進程分配獨一無二的進程標識(process identification,PID)用於區分不同的進程。PID 是一個整數,在內核的C語言源碼中表示為 pid_t 類型(其實就是 int 類型)。在Linux命令行輸入 ps 命令,即可查看進程的 PID,例如:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

查看進程的 PID

task_struct 結構體使用成員 pid 記錄進程的 PID 值,相關的C語言代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體使用成員 pid 記錄進程的 PID 值

在Linux系統中,PID 的最大值是可以調整的,早期為了兼容老版本的 Unix 和 Linux,默認最大值為 32768(short int 類型能夠表示的最大值),這個值可以通過 cat 命令查看:

# cat /proc/sys/kernel/pid_max 
32768

PID 的最大值對於Linux系統的運行是有影響的,因為 PID 值是獨一無二的,所以它的最大值實際上就表示系統可以同時運行的最多進程數目。對於普通的個人用戶來說,32768 足夠多,但是對於大型服務器來說,32768 可能就遠遠不夠了,這時可以修改 pid_max 解決這一問題。

進程的狀態

現在知道了Linux內核是如何描述和記錄進程資源,以及如何區分不同進程的了。那麼進程有哪些狀態呢?讀者應該注意到 task_struct 結構體的第一個成員 state 了,它就是用於記錄進程狀態的。進程的狀態在C語言源代碼中是使用幾個宏定義的:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

進程的狀態在C語言源代碼中是使用幾個宏定義的

Linux 系統中的進程必定處於這 5 種狀態之一。從上到下,分別表示進程處於:

  • 正在運行或者準備運行
  • 正在睡眠,但是可中斷,接收到信號會被提前喚醒
  • 正在睡眠,並且不可中斷,也即即使接收到信號也不會被喚醒
  • 被其他進程跟蹤中
  • 停止運行

現在就明白有時無法通過 kill 命令殺死 D 狀態的進程了,這是因為這些進程處於不響應信號的狀態,kill 命令本質上是發送 SIGKILL 信號,自然無法殺死該進程。

父進程和子進程

進程的父進程和子進程也屬於進程的資源,因此也被記錄在 task_struct 結構體中,請看相關C語言代碼:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

訪問當前進程的父進程和子進程是方便的

所以要訪問當前進程的父進程和子進程是方便的,例如:

struct task_struct *p = current->parent;
struct task_stuck *c = current->children;

稍稍思考一下,應該能夠發現進程結構體 task_struct 中的 parent 指針和 children 指針其實構成了一條鏈表,通過這樣的鏈表,我們能夠輕易的訪問進程的父進程,祖父進程…, 以及子進程,孫進程… 等。不過應該明白,對於擁有大量進程的系統來說,重複遍歷所有進程的開銷是很大的。

小結

本節先討論了Linux內核如何記錄和描述進程資源,可以看出,內核管理進程其實就是管理 task_struct 結構體。接著,通過C語言源代碼查看了內核如何訪問 task_struct 結構體,以及如何區分進程,最後我們一起還討論了進程的狀態和家族樹,可見,Linux內核源代碼也並不是神祕到不可理解。

"

上一節簡要討論了下Linux操作系統中進程的概念,其實簡單來說,進程無非就是處於運行期的程序及其相關資源的總和。這裡讀者應該注意“相關資源”一詞,Linux 在內核中是如何記錄進程的資源的呢?

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

神祕的Linux系統是如何記錄和描述進程的?

Linux內核如何記錄進程的資源?

首先應該明白,Linux 內核大都是採用C語言編寫的,因此要弄清楚內核如何記錄進程資源,只需要查看相關的C語言代碼就可以了。事實上,Linux 內核是使用 task_struct 結構體描述進程的資源的,它的C語言部分代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體很長

task_struct 結構體很長,在我手中的 Linux 內核C語言源代碼中,它佔用了280行。當然了,這其中包含很多條件編譯部分,在 32 位機器上,task_struct 大約要佔用 1.7 KB 的內存空間,不過考慮到它可以管理完整的進程,1.7kB 其實並不算大了。

鑑於 task_struct 結構體過長,這裡不可能將其成員一一介紹清楚。如果讀者和我一樣好奇,粗略的瀏覽 task_struct 結構體,應該能夠發現一些比較令人熟悉的成員,例如:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體令人熟悉的成員

通過C語言註釋以及成員的變量名,能夠看到 task_struct 結構體包含了文件系統,線程結構體,以及進程打開的文件等信息,這就與上一節文章的內容對應上了。其他成員在我之後的文章中會涉及到,這裡暫不贅述。

在創建進程時,Linux 通過 slab 分配器分配 task_struct 結構,這樣可以避免動態分配和釋放帶來的開銷,提高內存的使用效率。

那麼創建 task_struct 結構後,內核如何訪問它呢?

根據我手上的內核C語言源代碼,Linux 中還有一個結構體 thread_info,它的其中一個成員 task 指針正好適合用於索引 task_struct 結構體,在X86_64平臺上,thread_info 的相關C語言代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task 指針

Linux 通常會在內核棧底或者棧頂保留 thread_info 結構,而內核棧通常大小都是可知的,因此每個進程都能方便的從自己的棧中找到 thread_info 結構,進而找到 task_struct 結構。

查找當前進程的 thread_info 結構,可以調用 current_thread_info() 函數,它的C語言代碼如下,請看:

static inline struct thread_info *current_thread_info(void)
{
register unsigned long sp asm ("sp");
return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
}\t
Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

current_thread_info() 函數

可見,current_thread_info() 函數其實就是通過進程棧計算的,因此它的實現與平臺架構有關,上述C語言代碼其實只是 arm 平臺的實現方法,其他平臺的實現方法,讀者可自行查閱。

此時,要獲取當前進程的資源,可以通過 current_thread_info()->task 索引。

進程 PID

Linux 內核為每一個進程分配獨一無二的進程標識(process identification,PID)用於區分不同的進程。PID 是一個整數,在內核的C語言源碼中表示為 pid_t 類型(其實就是 int 類型)。在Linux命令行輸入 ps 命令,即可查看進程的 PID,例如:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

查看進程的 PID

task_struct 結構體使用成員 pid 記錄進程的 PID 值,相關的C語言代碼如下,請看:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

task_struct 結構體使用成員 pid 記錄進程的 PID 值

在Linux系統中,PID 的最大值是可以調整的,早期為了兼容老版本的 Unix 和 Linux,默認最大值為 32768(short int 類型能夠表示的最大值),這個值可以通過 cat 命令查看:

# cat /proc/sys/kernel/pid_max 
32768

PID 的最大值對於Linux系統的運行是有影響的,因為 PID 值是獨一無二的,所以它的最大值實際上就表示系統可以同時運行的最多進程數目。對於普通的個人用戶來說,32768 足夠多,但是對於大型服務器來說,32768 可能就遠遠不夠了,這時可以修改 pid_max 解決這一問題。

進程的狀態

現在知道了Linux內核是如何描述和記錄進程資源,以及如何區分不同進程的了。那麼進程有哪些狀態呢?讀者應該注意到 task_struct 結構體的第一個成員 state 了,它就是用於記錄進程狀態的。進程的狀態在C語言源代碼中是使用幾個宏定義的:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

進程的狀態在C語言源代碼中是使用幾個宏定義的

Linux 系統中的進程必定處於這 5 種狀態之一。從上到下,分別表示進程處於:

  • 正在運行或者準備運行
  • 正在睡眠,但是可中斷,接收到信號會被提前喚醒
  • 正在睡眠,並且不可中斷,也即即使接收到信號也不會被喚醒
  • 被其他進程跟蹤中
  • 停止運行

現在就明白有時無法通過 kill 命令殺死 D 狀態的進程了,這是因為這些進程處於不響應信號的狀態,kill 命令本質上是發送 SIGKILL 信號,自然無法殺死該進程。

父進程和子進程

進程的父進程和子進程也屬於進程的資源,因此也被記錄在 task_struct 結構體中,請看相關C語言代碼:

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

訪問當前進程的父進程和子進程是方便的

所以要訪問當前進程的父進程和子進程是方便的,例如:

struct task_struct *p = current->parent;
struct task_stuck *c = current->children;

稍稍思考一下,應該能夠發現進程結構體 task_struct 中的 parent 指針和 children 指針其實構成了一條鏈表,通過這樣的鏈表,我們能夠輕易的訪問進程的父進程,祖父進程…, 以及子進程,孫進程… 等。不過應該明白,對於擁有大量進程的系統來說,重複遍歷所有進程的開銷是很大的。

小結

本節先討論了Linux內核如何記錄和描述進程資源,可以看出,內核管理進程其實就是管理 task_struct 結構體。接著,通過C語言源代碼查看了內核如何訪問 task_struct 結構體,以及如何區分進程,最後我們一起還討論了進程的狀態和家族樹,可見,Linux內核源代碼也並不是神祕到不可理解。

Linux系統是使用C語言編寫的,那麼它是如何記錄和描述進程的?

點個贊再走吧

歡迎在評論區一起討論,質疑。文章都是手打原創,每天最淺顯的介紹C語言、linux等嵌入式開發,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。

"

相關推薦

推薦中...