Linux內核分析(六)

Linux內核分析——【實驗六:進程的描述與創建】

一 進程的概念
進程是程序執行的一個實例,它是最小的系統資源分配基本單元,在Linux內核代碼中,常把進程稱爲任務(task)或線程(thread)。當然,一個進程可以包含多個線程,線程是系統調度最小的基本單元。

二 進程描述符
對於多任務處理的操作系統而言,通常系統上都會運行着多個進程(任務),爲了管理進程,系統必須對每個進程所做的事情進行詳細地描述。在window系統中,進程控制塊(PCB)就是用來記錄進程的相關信息,每一個進程都有一個進程控制塊,它記錄了pid,進程狀態,堆棧大小,進程優先級,進程存儲信息,pc等等。在linux系統中,也有一個類似的數據結構——進程描述符,它包含了進程的所有信息,進程描述符是由內核定義的一個task_struct類型數據結構,它在/include/linux/sched.h文件中定義,它的內容非常複雜,由四百多行的c代碼組成,下面盡列出少量的代碼:

struct task_struct {
    //進程狀態
    volatile long state;    /*  -1 unrunnable, 0 runnable, >0 stopped */
    //進程內核堆棧,包括:thread_info和stack
    void *stack;
    //進程標誌符
    unsigned int flags; /* per process flags, defined below */
    //優先級
    int prio, static_prio, normal_prio;
    unsigned int rt_priority;
    //進程鏈表,雙向鏈表
    struct list_head tasks;
    //內存管理
    struct mm_struct *mm, *active_mm;
    //進程標識符
    pid_t pid;
    //線程組標識符
    pid_t tgid;
    //金絲雀,用於防止棧溢出
    unsigned long stack_canary;/* Canary value for the -fstack-protector gcc feature */
    //創建該進程的父進程
    struct task_struct __rcu *real_parent; /* real parent process */
    //當前父進程
    struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
    /*
     * children/sibling forms the list of my natural children
     */
     //子進程,是一個鏈表
    struct list_head children;  /* list of my children */
    //兄弟進程
    struct list_head sibling;   /* linkage in my parent's children list */
    //該進程中cpu相關寄存器信息
    struct thread_struct thread;/* CPU-specific state of this task */
    //文件系統,相關目錄
    struct fs_struct *fs;/* filesystem information */
    //打開的文件信息--文件描述符
    struct files_struct *files;/* open file information */
    //相關信號
    struct signal_struct *signal;
    //信號處理
    struct sighand_struct *sighand;
    ... ...
};

1.進程狀態
(1)TASK_RUNNING(可運行狀態)
進程是可執行的;它或者正在執行,或者在運行隊列中等待執行。
(2)TASK_INTERRUPTIBLE(可中斷等待狀態)
進程正在睡眠(被阻塞),等待某些條件的達成。一旦這些條件達成,內核就會把進程狀態設置爲運行。處於此狀態的進程也會因爲接收到信號而提前被喚醒並隨時準備投入運行。
(3)TASK_UNINTERRUPTIBLE(不可中斷等待狀態)
除了就算是接收到信號也不會被喚醒或準備投入運行外,這個狀態與可打斷狀態相同。這個狀態通常在進程必須在等待時不受干擾或等待事件很快就會發生時出現。由於處於此狀態的任務對信號不做響應,
(4)TASK_TRACED(跟蹤狀態)
被其他進程跟蹤的進程,例如通過ptrace對調試程序進行跟蹤。
(5)TASK_STOPPED(停止狀態)
進程停止執行;進程沒有投入運行也不能投入運行。通常這種狀態發生在接收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信號的時候。此外,在調試期間接收到任何信號,都會使進程進入這種狀態。

2.進程描述符thread_info和內核棧
對於每個進程來說,系統都會爲這兩個數據結構分配8k的內存空間,他們與進程描述符關係如下:
6-1
在這個圖中,esp寄存器是CPU棧指針,用來存放棧頂單元的地址。在80x86系統中,棧起始於頂端,並朝着這個內存區開始的方向增長。從用戶態剛切換到內核態以後,進程的內核棧總是空的。因此,esp寄存器指向這個棧的頂端。
一旦數據寫入堆棧,esp的值就遞減。在Linux3.5.4內核中,thread_info結構是72個字節長,因此內核棧能擴展到8120個字節。thread_info結構的定義如下:
6-2
Linux內核中使用一個聯合體來表示一個進程的線程描述符和內核棧:

union thread_union {
    struct thread_info thread_info;
    unsigned long stack[2048];
};

通過esp棧指針很容易獲取當前在CPU上正在運行進程的thread_info結構。實際上,thread_info結構和內核態堆棧是緊密結合在一起的,佔據兩個頁框的物理內存空間。而且,這兩個頁框的起始起始地址是2的13次冪對齊的。所以,內核通過簡單的屏蔽掉esp的低13位有效位就可以獲得thread_info結構的基地址了。

3.進程間關係
6-3

4.進程鏈表
進程鏈表把所有進程的描述符連接起來。
6-4

三 進程創建
在linux中,fork(),vfork(),clone都可以創建一個新進程,並且它們都是通過do_fork()函數(文件路徑:linux-3.18.6/kernel/fork.c)實現的,如下圖:
6-5

下面,將進行實驗,對fork()函數創建新進程的過程進行跟蹤分析:
1.實驗環境
與實驗三相同,只不過把test.c文件換爲test_fork.c文件,這裏不再贅訴。
6-6
2.調試並設置斷點
(1)在gdb終端:

(gdb) b sys_clone
(gdb) c

(2)在QEMU模擬器中輸入

MemuOS>>fork

(3)在gdb終端設置斷點,如下
6-7
3.跟蹤執行過程
(1) 執行

    (gdb) c   

6-8
(2)繼續
6-9
(3)繼續
6-10
(4)繼續
6-11

由此可見,fork()的執行過程大致可以描述爲
6-12
當然,上圖中忽略了很多細節的東西,只是一個粗略的過程。

一個新的進程創建的時候,從用戶態來看,fork語句的下一條語句即爲子進程執行的開始;從內核態看,子進程的執行從ret_from_fork(文件路徑:/arch/x86/kernel/entry_32.S)處開始。

附錄
(1)函數arch_dup_task_struct
6-13
(2)數據結構struct pt_regs
6-14
(3)數據結構struct thread_info
6-15
(4)數據結構struct thread_struct
6-16

=========== 王傑 原創作品轉載請註明出處==============
《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章