<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          內(nèi)核該怎么學(xué)?Linux進(jìn)程管理工作原理(代碼演示)

          共 46795字,需瀏覽 94分鐘

           ·

          2021-11-15 16:57

          前言:Linux內(nèi)核里大部分都是C語言。建議先看《Linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)(Linux Kernel Development)》,Robert Love,也就是LKD。

          Linux是一種動(dòng)態(tài)系統(tǒng),能夠適應(yīng)不斷變化的計(jì)算需求。Linux計(jì)算需求的表現(xiàn)是以進(jìn)程的通用抽象為中心的。進(jìn)程可以是短期的(從命令行執(zhí)行的一個(gè)命令),也可以是長(zhǎng)期的(一種網(wǎng)絡(luò)服務(wù))。因此,對(duì)進(jìn)程及其調(diào)度進(jìn)行一般管理就顯得極為重要。

          在用戶空間,進(jìn)程是由進(jìn)程標(biāo)識(shí)符(PID)表示的。從用戶的角度來看,一個(gè) PID 是一個(gè)數(shù)字值,可惟一標(biāo)識(shí)一個(gè)進(jìn)程。一個(gè) PID 在進(jìn)程的整個(gè)生命期間不會(huì)更改,但 PID 可以在進(jìn)程銷毀后被重新使用,所以對(duì)它們進(jìn)行緩存并不見得總是理想的。在用戶空間,創(chuàng)建進(jìn)程可以采用幾種方式。可以 執(zhí)行一個(gè)程序(這會(huì)導(dǎo)致新進(jìn)程的創(chuàng)建),也可以 在程序內(nèi),調(diào)用一個(gè) fork或 exec 系統(tǒng)調(diào)用。fork調(diào)用會(huì)導(dǎo)致創(chuàng)建一個(gè)子進(jìn)程,而exec調(diào)用則會(huì)用新程序代替當(dāng)前進(jìn)程上下文。這里將對(duì)這幾種方法進(jìn)行討論以便您能很好地理解它們的工作原理。

          這里將按照下面的順序展開對(duì)進(jìn)程的介紹,首先展示進(jìn)程的內(nèi)核表示以及它們是如何在內(nèi)核內(nèi)被管理的,然后來看看進(jìn)程創(chuàng)建和調(diào)度的各種方式(在一個(gè)或多個(gè)處理器上),最后介紹進(jìn)程的銷毀。內(nèi)核的版本為2.6.32.45。

          一,進(jìn)程描述符

          在Linux內(nèi)核內(nèi),進(jìn)程是由相當(dāng)大的一個(gè)稱為 task_struct 的結(jié)構(gòu)表示的。此結(jié)構(gòu)包含所有表示此進(jìn)程所必需的數(shù)據(jù),此外,還包含了大量的其他數(shù)據(jù)用來統(tǒng)計(jì)(accounting)和維護(hù)與其他進(jìn)程的關(guān)系(如父和子)。task_struct 位于
          ./linux/include/linux/sched.h(注意./linux/指向內(nèi)核源代碼樹)。

          下面是task_struct結(jié)構(gòu):

            struct task_struct {  
          volatile long state; /* -1 不可運(yùn)行, 0 可運(yùn)行, >0 已停止 */
          void *stack; /* 堆棧 */
          atomic_t usage;
          unsigned int flags; /* 一組標(biāo)志 */
          unsigned int ptrace;
          /* ... */

          int prio, static_prio, normal_prio; /* 優(yōu)先級(jí) */
          /* ... */

          struct list_head tasks; /* 執(zhí)行的線程(可以有很多) */
          struct plist_node pushable_tasks;

          struct mm_struct *mm, *active_mm; /* 內(nèi)存頁(yè)(進(jìn)程地址空間) */

          /* 進(jìn)行狀態(tài) */
          int exit_state;
          int exit_code, exit_signal;
          int pdeath_signal; /* 當(dāng)父進(jìn)程死亡時(shí)要發(fā)送的信號(hào) */


          /* ... */?

                pid_t pid;  /* 進(jìn)程號(hào) */  
          pid_t tgid;

          /* ... */
          struct task_struct *real_parent; /* 實(shí)際父進(jìn)程real parent process */
          struct task_struct *parent; /* SIGCHLD的接受者,由wait4()報(bào)告 */
          struct list_head children; /* 子進(jìn)程列表 */
          struct list_head sibling; /* 兄弟進(jìn)程列表 */
          struct task_struct *group_leader; /* 線程組的leader */
          /* ... */

          char comm[TASK_COMM_LEN]; /* 可執(zhí)行程序的名稱(不包含路徑) */
          /* 文件系統(tǒng)信息 */
          int link_count, total_link_count;
          /* ... */

          /* 特定CPU架構(gòu)的狀態(tài) */
          struct thread_struct thread;
          /* 進(jìn)程當(dāng)前所在的目錄描述 */
          struct fs_struct *fs;
          /* 打開的文件描述信息 */
          struct files_struct *files;
          /* ... */
          };

          在task_struct結(jié)構(gòu)中,可以看到幾個(gè)預(yù)料之中的項(xiàng),比如執(zhí)行的狀態(tài)、堆棧、一組標(biāo)志、父進(jìn)程、執(zhí)行的線程(可以有很多)以及開放文件。state 變量是一些表明任務(wù)狀態(tài)的比特位。最常見的狀態(tài)有:TASK_RUNNING 表示進(jìn)程正在運(yùn)行,或是排在運(yùn)行隊(duì)列中正要運(yùn)行;TASK_INTERRUPTIBLE 表示進(jìn)程正在休眠、TASK_UNINTERRUPTIBLE表示進(jìn)程正在休眠但不能叫醒;TASK_STOPPED 表示進(jìn)程停止等等。這些標(biāo)志的完整列表可以在
          ./linux/include/linux/sched.h 內(nèi)找到。

          flags 定義了很多指示符,表明進(jìn)程是否正在被創(chuàng)建(PF_STARTING)或退出(PF_EXITING),或是進(jìn)程當(dāng)前是否在分配內(nèi)存(PF_MEMALLOC)??蓤?zhí)行程序的名稱(不包含路徑)占用 comm(命令)字段。每個(gè)進(jìn)程都會(huì)被賦予優(yōu)先級(jí)(稱為 static_prio),但進(jìn)程的實(shí)際優(yōu)先級(jí)是基于加載以及其他幾個(gè)因素動(dòng)態(tài)決定的。優(yōu)先級(jí)值越低,實(shí)際的優(yōu)先級(jí)越高。tasks字段提供了鏈接列表的能力。它包含一個(gè) prev 指針(指向前一個(gè)任務(wù))和一個(gè) next 指針(指向下一個(gè)任務(wù))。

          進(jìn)程的地址空間由mm 和 active_mm 字段表示。mm代表的是進(jìn)程的內(nèi)存描述符,而 active_mm 則是前一個(gè)進(jìn)程的內(nèi)存描述符(為改進(jìn)上下文切換時(shí)間的一種優(yōu)化)。thread_struct thread結(jié)構(gòu)則用來標(biāo)識(shí)進(jìn)程的存儲(chǔ)狀態(tài),此元素依賴于Linux在其上運(yùn)行的特定架構(gòu)。例如對(duì)于x86架構(gòu),在
          ./linux/arch/x86/include/asm/processor.h的thread_struct結(jié)構(gòu)中可以找到該進(jìn)程自執(zhí)行上下文切換后的存儲(chǔ)(硬件注冊(cè)表、程序計(jì)數(shù)器等)。

          代碼如下:

          struct thread_struct {
          /* Cached TLS descriptors: */
          struct desc_struct tls_array[GDT_ENTRY_TLS_ENTRIES];
          unsigned long sp0;
          unsigned long sp;
          #ifdef CONFIG_X86_32
          unsigned long sysenter_cs;
          #else
          unsigned long usersp; /* Copy from PDA */
          unsigned short es;
          unsigned short ds;
          unsigned short fsindex;
          unsigned short gsindex;
          #endif
          #ifdef CONFIG_X86_32
          unsigned long ip;
          #endif
          /* ... */
          #ifdef CONFIG_X86_32
          /* Virtual 86 mode info */
          struct vm86_struct __user *vm86_info;
          unsigned long screen_bitmap;
          unsigned long v86flags;
          unsigned long v86mask;
          unsigned long saved_sp0;
          unsigned int saved_fs;
          unsigned int saved_gs;
          #endif
          /* IO permissions: */
          unsigned long *io_bitmap_ptr;
          unsigned long iopl;
          /* Max allowed port in the bitmap, in bytes: */
          unsigned io_bitmap_max;
          /* MSR_IA32_DEBUGCTLMSR value to switch in if TIF_DEBUGCTLMSR is set. */
          unsigned long debugctlmsr;
          /* Debug Store context; see asm/ds.h */
          struct ds_context *ds_ctx;
          };

          二,進(jìn)程管理

          在很多情況下,進(jìn)程都是動(dòng)態(tài)創(chuàng)建并由一個(gè)動(dòng)態(tài)分配的 task_struct 表示。一個(gè)例外是init 進(jìn)程本身,它總是存在并由一個(gè)靜態(tài)分配的task_struct表示,參看
          ./linux/arch/x86/kernel/init_task.c。

          代碼如下:

          static struct signal_struct init_signals = INIT_SIGNALS(init_signals);
          static struct sighand_struct init_sighand = INIT_SIGHAND(init_sighand);

          /*
          * 初始化線程結(jié)構(gòu)
          */

          union thread_union init_thread_union __init_task_data =
          { INIT_THREAD_INFO(init_task) };

          /*
          * 初始化init進(jìn)程的結(jié)構(gòu)。所有其他進(jìn)程的結(jié)構(gòu)將由fork.c中的slabs來分配
          */

          struct task_struct init_task = INIT_TASK(init_task);
          EXPORT_SYMBOL(init_task);

          /*
          * per-CPU TSS segments.
          */

          DEFINE_PER_CPU_SHARED_ALIGNED(struct tss_struct, init_tss) = INIT_TSS;

          注意進(jìn)程雖然都是動(dòng)態(tài)分配的,但還是需要考慮最大進(jìn)程數(shù)。在內(nèi)核內(nèi)最大進(jìn)程數(shù)是由一個(gè)稱為max_threads的符號(hào)表示的,它可以在 ./linux/kernel/fork.c 內(nèi)找到??梢酝ㄟ^
          /proc/sys/kernel/threads-max 的 proc 文件系統(tǒng)從用戶空間更改此值。

          Linux 內(nèi)所有進(jìn)程的分配有兩種方式。第一種方式是通過一個(gè)哈希表,由PID 值進(jìn)行哈希計(jì)算得到;第二種方式是通過雙鏈循環(huán)表。循環(huán)表非常適合于對(duì)任務(wù)列表進(jìn)行迭代。由于列表是循環(huán)的,沒有頭或尾;但是由于 init_task 總是存在,所以可以將其用作繼續(xù)向前迭代的一個(gè)錨點(diǎn)。讓我們來看一個(gè)遍歷當(dāng)前任務(wù)集的例子。任務(wù)列表無法從用戶空間訪問,但該問題很容易解決,方法是以模塊形式向內(nèi)核內(nèi)插入代碼。下面給出一個(gè)很簡(jiǎn)單的程序,它會(huì)迭代任務(wù)列表并會(huì)提供有關(guān)每個(gè)任務(wù)的少量信息(name、pid和 parent 名)。注意,在這里,此模塊使用 printk 來發(fā)出結(jié)果。要查看具體的結(jié)果,可以通過 cat 實(shí)用工具(或?qū)崟r(shí)的 tail -f/var/log/messages)查看 /var/log/messages 文件。next_task函數(shù)是 sched.h 內(nèi)的一個(gè)宏,它簡(jiǎn)化了任務(wù)列表的迭代(返回下一個(gè)任務(wù)的 task_struct 引用)。

          如下:

          #define next_task(p) \
          list_entry_rcu((p)->tasks.next, struct task_struct, tasks)

          查詢?nèi)蝿?wù)列表信息的簡(jiǎn)單內(nèi)核模塊:

          "code" class="cpp">#include 
          #include
          #include

          int init_module(void)
          {
          /* Set up the anchor point */
          struct task_struct *task=&init_task;
          /* Walk through the task list, until we hit the init_task again */
          do {
          printk(KERN_INFO "=== %s [%d] parent %s\n",
          task->comm,task->pid,task->parent->comm);
          } while((task=next_task(task))!=&init_task);

          printk(KERN_INFO "Current task is %s [%d]\n", current->comm,current->pid);
          return 0;
          }

          void cleanup_module(void)
          {
          return;
          }

          編譯此模塊的Makefile文件如下:

          obj-m += procsview.o

          KDIR := /lib/modules/$(shell uname -r)/build
          PWD := $(shell pwd)

          default:
          $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

          在編譯后,可以用insmod procsview.ko 插入模塊對(duì)象,也可以用 rmmod procsview 刪除它。插入后,/var/log/messages可顯示輸出,如下所示。從中可以看到,這里有一個(gè)空閑任務(wù)(稱為 swapper)和init 任務(wù)(pid 1)。

          Dec 28 23:18:16 ubuntu kernel: [12128.910863]=== swapper [0] parent swapper
          Dec 28 23:18:16 ubuntu kernel: [12128.910934]=== init [1] parent swapper
          Dec 28 23:18:16 ubuntu kernel: [12128.910945]=== kthreadd [2] parent swapper
          Dec 28 23:18:16 ubuntu kernel: [12128.910953]=== migration/0 [3] parent kthreadd
          ......
          Dec 28 23:24:12 ubuntu kernel: [12485.295015]Current task is insmod [6051]

          Linux 維護(hù)一個(gè)稱為current的宏,標(biāo)識(shí)當(dāng)前正在運(yùn)行的進(jìn)程(類型是 task_struct)。模塊尾部的那行prink用于輸出當(dāng)前進(jìn)程的運(yùn)行命令及進(jìn)程號(hào)。注意到當(dāng)前的任務(wù)是 insmod,這是因?yàn)?init_module 函數(shù)是在insmod 命令執(zhí)行的上下文運(yùn)行的。current 符號(hào)實(shí)際指的是一個(gè)函數(shù)(get_current),可在一個(gè)與 arch 有關(guān)的頭部中找到它。比如
          ./linux/arch/x86/include/asm/current.h,
          如下:

          #include 
          #include

          #ifndef __ASSEMBLY__
          struct task_struct;

          DECLARE_PER_CPU(struct task_struct *, current_task);

          static __always_inline struct task_struct *get_current(void)
          {
          return percpu_read_stable(current_task);
          }

          #define current get_current()

          #endif /* __ASSEMBLY__ */

          #endif /* _ASM_X86_CURRENT_H */

          三,進(jìn)程創(chuàng)建

          用戶空間內(nèi)可以通過執(zhí)行一個(gè)程序、或者在程序內(nèi)調(diào)用fork(或exec)系統(tǒng)調(diào)用來創(chuàng)建進(jìn)程,fork調(diào)用會(huì)導(dǎo)致創(chuàng)建一個(gè)子進(jìn)程,而exec調(diào)用則會(huì)用新程序代替當(dāng)前進(jìn)程上下文。一個(gè)新進(jìn)程的誕生還可以分別通過vfork()和clone()。fork、vfork和clone三個(gè)用戶態(tài)函數(shù)均由libc庫(kù)提供,它們分別會(huì)調(diào)用Linux內(nèi)核提供的同名系統(tǒng)調(diào)用fork,vfork和clone。下面以fork系統(tǒng)調(diào)用為例來介紹。

          傳統(tǒng)的創(chuàng)建一個(gè)新進(jìn)程的方式是子進(jìn)程拷貝父進(jìn)程所有資源,這無疑使得進(jìn)程的創(chuàng)建效率低,因?yàn)樽舆M(jìn)程需要拷貝父進(jìn)程的整個(gè)地址空間。更糟糕的是,如果子進(jìn)程創(chuàng)建后又立馬去執(zhí)行exec族函數(shù),那么剛剛才從父進(jìn)程那里拷貝的地址空間又要被清除以便裝入新的進(jìn)程映像。為了解決這個(gè)問題,內(nèi)核中提供了上述三種不同的系統(tǒng)調(diào)用。

          1. 內(nèi)核采用寫時(shí)復(fù)制技術(shù)對(duì)傳統(tǒng)的fork函數(shù)進(jìn)行了下面的優(yōu)化。即子進(jìn)程創(chuàng)建后,父子以只讀的方式共享父進(jìn)程的資源(并不包括父進(jìn)程的頁(yè)表項(xiàng))。當(dāng)子進(jìn)程需要修改進(jìn)程地址空間的某一頁(yè)時(shí),才為子進(jìn)程復(fù)制該頁(yè)。采用這樣的技術(shù)可以避免對(duì)父進(jìn)程中某些數(shù)據(jù)不必要的復(fù)制。

          2. 使用vfork函數(shù)創(chuàng)建的子進(jìn)程會(huì)完全共享父進(jìn)程的地址空間,甚至是父進(jìn)程的頁(yè)表項(xiàng)。父子進(jìn)程任意一方對(duì)任何數(shù)據(jù)的修改使得另一方都可以感知到。為了使得雙方不受這種影響,vfork函數(shù)創(chuàng)建了子進(jìn)程后,父進(jìn)程便被阻塞直至子進(jìn)程調(diào)用了exec()或exit()。由于現(xiàn)在fork函數(shù)引入了寫時(shí)復(fù)制技術(shù),在不考慮復(fù)制父進(jìn)程頁(yè)表項(xiàng)的情況下,vfork函數(shù)幾乎不會(huì)被使用。

          3. clone函數(shù)創(chuàng)建子進(jìn)程時(shí)靈活度比較大,因?yàn)樗梢酝ㄟ^傳遞不同的clone標(biāo)志參數(shù)來選擇性的復(fù)制父進(jìn)程的資源。

          大部分系統(tǒng)調(diào)用對(duì)應(yīng)的例程都被命名為 sys_* 并提供某些初始功能以實(shí)現(xiàn)調(diào)用(例如錯(cuò)誤檢查或用戶空間的行為),實(shí)際的工作常常會(huì)委派給另外一個(gè)名為 do_* 的函數(shù)。


          ./linux/include/asm-generic/unistd.h中記錄了所有的系統(tǒng)調(diào)用號(hào)及名稱。注意fork實(shí)現(xiàn)與體系結(jié)構(gòu)相關(guān),對(duì)32位的x86系統(tǒng)會(huì)使用./linux/arch/x86/include/asm/unistd_32.h中的定義,fork系統(tǒng)調(diào)用編號(hào)為2。

          fork系統(tǒng)調(diào)用在unistd.h中的宏關(guān)聯(lián)如下:

          #define __NR_fork 1079
          #ifdef CONFIG_MMU
          __SYSCALL(__NR_fork, sys_fork)
          #else
          __SYSCALL(__NR_fork, sys_ni_syscall)
          #endif

          在unistd_32.h中的調(diào)用號(hào)關(guān)聯(lián)為:?#define __NR_fork 2

          在很多情況下,用戶空間任務(wù)和內(nèi)核任務(wù)的底層機(jī)制是一致的。系統(tǒng)調(diào)用fork、vfork和clone在內(nèi)核中對(duì)應(yīng)的服務(wù)例程分別為sys_fork(),sys_vfork()和sys_clone()。它們最終都會(huì)依賴于一個(gè)名為do_fork 的函數(shù)來創(chuàng)建新進(jìn)程。例如在創(chuàng)建內(nèi)核線程時(shí),內(nèi)核會(huì)調(diào)用一個(gè)名為 kernel_thread 的函數(shù)(對(duì)32位系統(tǒng))參見
          ./linux/arch/x86/kernel/process_32.c,注意process.c是包含32/64bit都適用的代碼,process_32.c是特定于32位架構(gòu),process_64.c是特定于64位架構(gòu)),此函數(shù)執(zhí)行某些初始化后會(huì)調(diào)用 do_fork。創(chuàng)建用戶空間進(jìn)程的情況與此類似。在用戶空間,一個(gè)程序會(huì)調(diào)用fork,通過int $0x80之類的軟中斷會(huì)導(dǎo)致對(duì)名為sys_fork的內(nèi)核函數(shù)的系統(tǒng)調(diào)用(參見 ./linux/arch/x86/kernel/process_32.c),如下:

          int sys_fork(struct pt_regs *regs)
          {
          return do_fork(SIGCHLD, regs->sp, regs, 0, NULL, NULL);
          }

          最終都是直接調(diào)用do_fork。進(jìn)程創(chuàng)建的函數(shù)層次結(jié)構(gòu)如下圖:

          進(jìn)程創(chuàng)建的函數(shù)層次結(jié)構(gòu)

          從圖中可以看到 do_fork 是進(jìn)程創(chuàng)建的基礎(chǔ)。可以在 ./linux/kernel/fork.c 內(nèi)找到 do_fork 函數(shù)(以及合作函數(shù) copy_process)。

          當(dāng)用戶態(tài)的進(jìn)程調(diào)用一個(gè)系統(tǒng)調(diào)用時(shí),CPU切換到內(nèi)核態(tài)并開始執(zhí)行一個(gè)內(nèi)核函數(shù)。在X86體系中,可以通過兩種不同的方式進(jìn)入系統(tǒng)調(diào)用:執(zhí)行int $0×80匯編命令和執(zhí)行sysenter匯編命令。后者是Intel在Pentium II中引入的指令,內(nèi)核從2.6版本開始支持這條命令。這里將集中討論以int $0×80方式進(jìn)入系統(tǒng)調(diào)用的過程。

          通過int $0×80方式調(diào)用系統(tǒng)調(diào)用實(shí)際上是用戶進(jìn)程產(chǎn)生一個(gè)中斷向量號(hào)為0×80的軟中斷。當(dāng)用戶態(tài)fork()調(diào)用發(fā)生時(shí),用戶態(tài)進(jìn)程會(huì)保存調(diào)用號(hào)以及參數(shù),然后發(fā)出int $0×80指令,陷入0x80中斷。CPU將從用戶態(tài)切換到內(nèi)核態(tài)并開始執(zhí)行system_call()。這個(gè)函數(shù)是通過匯編命令來實(shí)現(xiàn)的,它是0×80號(hào)軟中斷對(duì)應(yīng)的中斷處理程序。對(duì)于所有系統(tǒng)調(diào)用來說,它們都必須先進(jìn)入system_call(),也就是所謂的系統(tǒng)調(diào)用處理程序。再通過系統(tǒng)調(diào)用號(hào)跳轉(zhuǎn)到具體的系統(tǒng)調(diào)用服務(wù)例程處。32位x86系統(tǒng)的系統(tǒng)調(diào)用處理程序在
          ./linux/arch/x86/kernel/entry_32.S中,代碼如下:

          .macro SAVE_ALL
          cld
          PUSH_GS
          pushl %fs
          CFI_ADJUST_CFA_OFFSET 4
          /*CFI_REL_OFFSET fs, 0;*/
          pushl %es
          CFI_ADJUST_CFA_OFFSET 4
          /*CFI_REL_OFFSET es, 0;*/
          pushl %ds
          CFI_ADJUST_CFA_OFFSET 4
          /*CFI_REL_OFFSET ds, 0;*/
          pushl %eax
          CFI_ADJUST_CFA_OFFSET 4
          CFI_REL_OFFSET eax, 0
          pushl %ebp
          CFI_ADJUST_CFA_OFFSET 4
          CFI_REL_OFFSET ebp, 0
          pushl %edi
          CFI_ADJUST_CFA_OFFSET 4
          CFI_REL_OFFSET edi, 0
          pushl %esi
          CFI_ADJUST_CFA_OFFSET 4
          CFI_REL_OFFSET esi, 0
          pushl %edx
          CFI_ADJUST_CFA_OFFSET 4
          CFI_REL_OFFSET edx, 0
          pushl %ecx
          CFI_ADJUST_CFA_OFFSET 4
          CFI_REL_OFFSET ecx, 0
          pushl %ebx
          CFI_ADJUST_CFA_OFFSET 4
          CFI_REL_OFFSET ebx, 0
          movl $(__USER_DS), %edx
          movl %edx, %ds
          movl %edx, %es
          movl $(__KERNEL_PERCPU), %edx
          movl %edx, %fs
          SET_KERNEL_GS %edx
          .endm
          /* ... */
          ENTRY(system_call)
          RING0_INT_FRAME # 無論如何不能進(jìn)入用戶空間
          pushl %eax # 將保存的系統(tǒng)調(diào)用編號(hào)壓入棧中
          CFI_ADJUST_CFA_OFFSET 4
          SAVE_ALL
          GET_THREAD_INFO(%ebp)
          # 檢測(cè)進(jìn)程是否被跟蹤
          testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
          jnz syscall_trace_entry
          cmpl $(nr_syscalls), %eax
          jae syscall_badsys
          syscall_call:
          call *sys_call_table(,%eax,4) # 跳入對(duì)應(yīng)服務(wù)例程
          movl %eax,PT_EAX(%esp) # 保存進(jìn)程的返回值
          syscall_exit:
          LOCKDEP_SYS_EXIT
          DISABLE_INTERRUPTS(CLBR_ANY) # 不要忘了在中斷返回前關(guān)閉中斷
          TRACE_IRQS_OFF
          movl TI_flags(%ebp), %ecx
          testl $_TIF_ALLWORK_MASK, %ecx # current->work
          jne syscall_exit_work
          restore_all:
          TRACE_IRQS_IRET
          restore_all_notrace:
          movl PT_EFLAGS(%esp), %eax # mix EFLAGS, SS and CS
          # Warning: PT_OLDSS(%esp) contains the wrong/random values if we
          # are returning to the kernel.
          # See comments in process.c:copy_thread() for details.
          movb PT_OLDSS(%esp), %ah
          movb PT_CS(%esp), %al
          andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax
          cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax
          CFI_REMEMBER_STATE
          je ldt_ss # returning to user-space with LDT SS
          restore_nocheck:
          RESTORE_REGS 4 # skip orig_eax/error_code
          CFI_ADJUST_CFA_OFFSET -4
          irq_return:
          INTERRUPT_RETURN
          .section .fixup,"ax"

          分析:

          (1)在system_call函數(shù)執(zhí)行之前,CPU控制單元已經(jīng)將eflags、cs、eip、ss和esp寄存器的值自動(dòng)保存到該進(jìn)程對(duì)應(yīng)的內(nèi)核棧中。隨之,在 system_call內(nèi)部首先將存儲(chǔ)在eax寄存器中的系統(tǒng)調(diào)用號(hào)壓入棧中。接著執(zhí)行SAVE_ALL宏。該宏在棧中保存接下來的系統(tǒng)調(diào)用可能要用到的所有CPU寄存器。

          (2)通過GET_THREAD_INFO宏獲得當(dāng)前進(jìn)程的thread_inof結(jié)構(gòu)的地址;再檢測(cè)當(dāng)前進(jìn)程是否被其他進(jìn)程所跟蹤(例如調(diào)試一個(gè)程序時(shí),被調(diào)試的程序就處于被跟蹤狀態(tài)),也就是thread_info結(jié)構(gòu)中flag字段的_TIF_ALLWORK_MASK被置1。如果發(fā)生被跟蹤的情況則轉(zhuǎn)向syscall_trace_entry標(biāo)記的處理命令處。

          (3)對(duì)用戶態(tài)進(jìn)程傳遞過來的系統(tǒng)調(diào)用號(hào)的合法性進(jìn)行檢查。如果不合法則跳入到syscall_badsys標(biāo)記的命令處。

          (4)如果系統(tǒng)調(diào)用好合法,則根據(jù)系統(tǒng)調(diào)用號(hào)查找
          ./linux/arch/x86/kernel/syscall_table_32.S中的系統(tǒng)調(diào)用表sys_call_table,找到相應(yīng)的函數(shù)入口點(diǎn),跳入sys_fork這個(gè)服務(wù)例程當(dāng)中。由于 sys_call_table表的表項(xiàng)占4字節(jié),因此獲得服務(wù)例程指針的具體方法是將由eax保存的系統(tǒng)調(diào)用號(hào)乘以4再與sys_call_table表的基址相加。

          syscall_table_32.S中的代碼如下:

          ENTRY(sys_call_table)
          .long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */
          .long sys_exit
          .long ptregs_fork
          .long sys_read
          .long sys_write
          .long sys_open /* 5 */
          .long sys_close
          /* ... */

          sys_call_table是系統(tǒng)調(diào)用多路分解表,使用 eax 中提供的索引來確定要調(diào)用該表中的哪個(gè)系統(tǒng)調(diào)用。

          (5)當(dāng)系統(tǒng)調(diào)用服務(wù)例程結(jié)束時(shí),從eax寄存器中獲得當(dāng)前進(jìn)程的的返回值,并把這個(gè)返回值存放在曾保存用戶態(tài)eax寄存器值的那個(gè)棧單元的位置上。這樣,用戶態(tài)進(jìn)程就可以在eax寄存器中找到系統(tǒng)調(diào)用的返回碼。

          經(jīng)過的調(diào)用鏈為fork()--->int$0×80軟中斷--->ENTRY(system_call)--->ENTRY(sys_call_table)--->sys_fork()--->do_fork()。實(shí)際上fork、vfork和clone三個(gè)系統(tǒng)調(diào)最終都是調(diào)用do_fork()。只不過在調(diào)用時(shí)所傳遞的參數(shù)有所不同,而參數(shù)的不同正好導(dǎo)致了子進(jìn)程與父進(jìn)程之間對(duì)資源的共享程度不同。因此,分析do_fork()成為我們的首要任務(wù)。在進(jìn)入do_fork函數(shù)進(jìn)行分析之前,很有必要了解一下它的參數(shù)。

          clone_flags:該標(biāo)志位的4個(gè)字節(jié)分為兩部分。最低的一個(gè)字節(jié)為子進(jìn)程結(jié)束時(shí)發(fā)送給父進(jìn)程的信號(hào)代碼,通常為SIGCHLD;剩余的三個(gè)字節(jié)則是各種clone標(biāo)志的組合(本文所涉及的標(biāo)志含義詳見下表),也就是若干個(gè)標(biāo)志之間的或運(yùn)算。通過 clone標(biāo)志可以有選擇的對(duì)父進(jìn)程的資源進(jìn)行復(fù)制。

          本文所涉及到的clone標(biāo)志詳見下表:


          • stack_start:子進(jìn)程用戶態(tài)堆棧的地址。

          • regs:指向pt_regs結(jié)構(gòu)體的指針。當(dāng)系統(tǒng)發(fā)生系統(tǒng)調(diào)用,即用戶進(jìn)程從用戶態(tài)切換到內(nèi)核態(tài)時(shí),該結(jié)構(gòu)體保存通用寄存器中的值,并被存放于內(nèi)核態(tài)的堆棧中。

          • stack_size:未被使用,通常被賦值為0。

          • parent_tidptr:父進(jìn)程在用戶態(tài)下pid的地址,該參數(shù)在CLONE_PARENT_SETTID標(biāo)志被設(shè)定時(shí)有意義。

          • child_tidptr:子進(jìn)程在用戶態(tài)下pid的地址,該參數(shù)在CLONE_CHILD_SETTID標(biāo)志被設(shè)定時(shí)有意義。

          • do_fork函數(shù)在./linux/kernel/fork.c中,主要工作就是復(fù)制原來的進(jìn)程成為另一個(gè)新的進(jìn)程,它完成了整個(gè)進(jìn)程創(chuàng)建中的大部分工作。

          代碼如下:

          long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          struct pt_regs *regs,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)
          {
          struct task_struct *p;
          int trace = 0;
          long nr;

          /*
          * 做一些預(yù)先的參數(shù)和權(quán)限檢查
          */

          if (clone_flags & CLONE_NEWUSER) {
          if (clone_flags & CLONE_THREAD)
          return -EINVAL;
          /* 希望當(dāng)用戶名稱被支持時(shí),這里的檢查可去掉
          */

          if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SETUID) ||
          !capable(CAP_SETGID))
          return -EPERM;
          }

          /*
          * 希望在2.6.26之后這些標(biāo)志能實(shí)現(xiàn)循環(huán)
          */

          if (unlikely(clone_flags & CLONE_STOPPED)) {
          static int __read_mostly count = 100;

          if (count > 0 && printk_ratelimit()) {
          char comm[TASK_COMM_LEN];

          count--;
          printk(KERN_INFO "fork(): process `%s' used deprecated "
          "clone flags 0x%lx\n",
          get_task_comm(comm, current),
          clone_flags & CLONE_STOPPED);
          }
          }

          /*
          * 當(dāng)從kernel_thread調(diào)用本do_fork時(shí),不使用跟蹤
          */

          if (likely(user_mode(regs))) /* 如果從用戶態(tài)進(jìn)入本調(diào)用,則使用跟蹤 */
          trace = tracehook_prepare_clone(clone_flags);

          p = copy_process(clone_flags, stack_start, regs, stack_size,
          child_tidptr, NULL, trace);
          /*
          * 在喚醒新線程之前做下面的工作,因?yàn)樾戮€程喚醒后本線程指針會(huì)變成無效(如果退出很快的話)
          */

          if (!IS_ERR(p)) {
          struct completion vfork;

          trace_sched_process_fork(current, p);

          nr = task_pid_vnr(p);

          if (clone_flags & CLONE_PARENT_SETTID)
          put_user(nr, parent_tidptr);

          if (clone_flags & CLONE_VFORK) {
          p->vfork_done = &vfork;
          init_completion(&vfork);
          }

          audit_finish_fork(p);
          tracehook_report_clone(regs, clone_flags, nr, p);

          /*
          * 我們?cè)趧?chuàng)建時(shí)設(shè)置PF_STARTING,以防止跟蹤進(jìn)程想使用這個(gè)標(biāo)志來區(qū)分一個(gè)完全活著的進(jìn)程
          * 和一個(gè)還沒有獲得trackhook_report_clone()的進(jìn)程?,F(xiàn)在我們清除它并且設(shè)置子進(jìn)程運(yùn)行
          */

          p->flags &= ~PF_STARTING;

          if (unlikely(clone_flags & CLONE_STOPPED)) {
          /*
          * 我們將立刻啟動(dòng)一個(gè)即時(shí)的SIGSTOP
          */

          sigaddset(&p->pending.signal, SIGSTOP);
          set_tsk_thread_flag(p, TIF_SIGPENDING);
          __set_task_state(p, TASK_STOPPED);
          } else {
          wake_up_new_task(p, clone_flags);
          }

          tracehook_report_clone_complete(trace, regs,
          clone_flags, nr, p);

          if (clone_flags & CLONE_VFORK) {
          freezer_do_not_count();
          wait_for_completion(&vfork);
          freezer_count();
          tracehook_report_vfork_done(p, nr);
          }
          } else {
          nr = PTR_ERR(p);
          }
          return nr;
          }

          (1)在一開始,該函數(shù)定義了一個(gè)task_struct類型的指針p,用來接收即將為新進(jìn)程(子進(jìn)程)所分配的進(jìn)程描述符。trace表示跟蹤狀態(tài),nr表示新進(jìn)程的pid。接著做一些預(yù)先的參數(shù)和權(quán)限檢查。

          (2)接下來檢查clone_flags是否設(shè)置了CLONE_STOPPED標(biāo)志。如果設(shè)置了,則做相應(yīng)處理,打印消息說明進(jìn)程已過時(shí)。通常這樣的情況很少發(fā)生,因此在判斷時(shí)使用了unlikely修飾符。使用該修飾符的判斷語句執(zhí)行結(jié)果與普通判斷語句相同,只不過在執(zhí)行效率上有所不同。正如該單詞的含義所表示的那樣,當(dāng)前進(jìn)程很少為停止?fàn)顟B(tài)。因此,編譯器盡量不會(huì)把if內(nèi)的語句與當(dāng)前語句之前的代碼編譯在一起,以增加cache的命中率。與此相反,likely修飾符則表示所修飾的代碼很可能發(fā)生。tracehook_prepare_clone用于設(shè)置子進(jìn)程是否被跟蹤。所謂跟蹤,最常見的例子就是處于調(diào)試狀態(tài)下的進(jìn)程被debugger進(jìn)程所跟蹤。進(jìn)程的ptrace字段非0說明debugger程序正在跟蹤它。如果調(diào)用是從用戶態(tài)進(jìn)來的(而不從kernel_thread進(jìn)來的),且當(dāng)前進(jìn)程(父進(jìn)程)被另外一個(gè)進(jìn)程所跟蹤,那么子進(jìn)程也要設(shè)置為被跟蹤,并且將跟蹤標(biāo)志CLONE_PTRACE加入標(biāo)志變量clone_flags中。如果父進(jìn)程不被跟蹤,則子進(jìn)程也不會(huì)被跟蹤,設(shè)置好后返回trace。

          (3)接下來的這條語句要做的是整個(gè)創(chuàng)建過程中最核心的工作:通過copy_process()創(chuàng)建子進(jìn)程的描述符,分配pid,并創(chuàng)建子進(jìn)程執(zhí)行時(shí)所需的其他數(shù)據(jù)結(jié)構(gòu),最終則會(huì)返回這個(gè)創(chuàng)建好的進(jìn)程描述符p。該函數(shù)中的參數(shù)意義與do_fork函數(shù)相同。注意原來內(nèi)核中為子進(jìn)程分配pid的工作是在do_fork中完成,現(xiàn)在新的內(nèi)核已經(jīng)移到copy_process中了。

          (4)如果copy_process函數(shù)執(zhí)行成功,那么將繼續(xù)執(zhí)行if(!IS_ERR(p))部分。首先定義了一個(gè)完成量vfork,用task_pid_vnr(p)從p中獲取新進(jìn)程的pid。如果clone_flags包含CLONE_VFORK標(biāo)志,那么將進(jìn)程描述符中的vfork_done字段指向這個(gè)完成量,之后再對(duì)vfork完成量進(jìn)行初始化。完成量的作用是,直到任務(wù)A發(fā)出信號(hào)通知任務(wù)B發(fā)生了某個(gè)特定事件時(shí),任務(wù)B才會(huì)開始執(zhí)行,否則任務(wù)B一直等待。我們知道,如果使用vfork系統(tǒng)調(diào)用來創(chuàng)建子進(jìn)程,那么必然是子進(jìn)程先執(zhí)行。究其原因就是此處vfork完成量所起到的作用。當(dāng)子進(jìn)程調(diào)用exec函數(shù)或退出時(shí)就向父進(jìn)程發(fā)出信號(hào),此時(shí)父進(jìn)程才會(huì)被喚醒,否則一直等待。此處的代碼只是對(duì)完成量進(jìn)行初始化,具體的阻塞語句則在后面的代碼中有所體現(xiàn)。

          (5)如果子進(jìn)程被跟蹤或者設(shè)置了CLONE_STOPPED標(biāo)志,那么通過sigaddset函數(shù)為子進(jìn)程增加掛起信號(hào),并將子進(jìn)程的狀態(tài)設(shè)置為TASK_STOPPED。signal對(duì)應(yīng)一個(gè)unsignedlong類型的變量,該變量的每個(gè)位分別對(duì)應(yīng)一種信號(hào)。具體的操作是將SIGSTOP信號(hào)所對(duì)應(yīng)的那一位置1。如果子進(jìn)程并未設(shè)置CLONE_STOPPED標(biāo)志,那么通過wake_up_new_task將進(jìn)程放到運(yùn)行隊(duì)列上,從而讓調(diào)度器進(jìn)行調(diào)度運(yùn)行。wake_up_new_task()在./linux/kernel/sched.c中,用于喚醒第一次新創(chuàng)建的進(jìn)程,它將為新進(jìn)程做一些初始的必須的調(diào)度器統(tǒng)計(jì)操作,然后把進(jìn)程放到運(yùn)行隊(duì)列中。一旦當(dāng)然正在運(yùn)行的進(jìn)程時(shí)間片用完(通過時(shí)鐘tick中斷來控制),就會(huì)調(diào)用schedule(),從而進(jìn)行進(jìn)程調(diào)度。

          代碼如下:

          void wake_up_new_task(struct task_struct *p, unsigned long clone_flags)
          {
          unsigned long flags;
          struct rq *rq;
          int cpu = get_cpu();

          #ifdef CONFIG_SMP
          rq = task_rq_lock(p, &flags);
          p->state = TASK_WAKING;

          /*
          * Fork balancing, do it here and not earlier because:
          * - cpus_allowed can change in the fork path
          * - any previously selected cpu might disappear through hotplug
          *
          * We set TASK_WAKING so that select_task_rq() can drop rq->lock
          * without people poking at ->cpus_allowed.
          */

          cpu = select_task_rq(rq, p, SD_BALANCE_FORK, 0);
          set_task_cpu(p, cpu);

          p->state = TASK_RUNNING;
          task_rq_unlock(rq, &flags);
          #endif

          rq = task_rq_lock(p, &flags);
          update_rq_clock(rq);
          activate_task(rq, p, 0);
          trace_sched_wakeup_new(rq, p, 1);
          check_preempt_curr(rq, p, WF_FORK);
          #ifdef CONFIG_SMP
          if (p->sched_class->task_woken)
          p->sched_class->task_woken(rq, p);
          #endif
          task_rq_unlock(rq, &flags);
          put_cpu();
          }

          這里先用get_cpu()獲取CPU,如果是對(duì)稱多處理系統(tǒng)(SMP),先設(shè)置我為TASK_WAKING狀態(tài),由于有多個(gè)CPU(每個(gè)CPU上都有一個(gè)運(yùn)行隊(duì)列),需要進(jìn)行負(fù)載均衡,選擇一個(gè)最佳CPU并設(shè)置我使用這個(gè)CPU,然后設(shè)置我為TASK_RUNNING狀態(tài)。這段操作是互斥的,因此需要加鎖。注意TASK_RUNNING并不表示進(jìn)程一定正在運(yùn)行,無論進(jìn)程是否正在占用CPU,只要具備運(yùn)行條件,都處于該狀態(tài)。Linux把處于該狀態(tài)的所有PCB組織成一個(gè)可運(yùn)行隊(duì)列run_queue,調(diào)度程序從這個(gè)隊(duì)列中選擇進(jìn)程運(yùn)行。事實(shí)上,Linux是將就緒態(tài)和運(yùn)行態(tài)合并為了一種狀態(tài)。然后用
          ./linux/kernel/sched.c:activate_task()把當(dāng)前進(jìn)程插入到對(duì)應(yīng)CPU的runqueue上,最終完成入隊(duì)的函數(shù)是active_task()--->enqueue_task(),其中核心代碼行為:p->sched_class->enqueue_task(rq, p,wakeup, head);sched_class在./linux/include/linux/sched.h中,是調(diào)度器一系列操作的面向?qū)ο蟪橄螅@個(gè)類包括進(jìn)程入隊(duì)、出隊(duì)、進(jìn)程運(yùn)行、進(jìn)程切換等接口,用于完成對(duì)進(jìn)程的調(diào)度運(yùn)行。

          (6)
          tracehook_report_clone_complete函數(shù)用于在進(jìn)程復(fù)制快要完成時(shí)報(bào)告跟蹤情況。如果父進(jìn)程被跟蹤,則將子進(jìn)程的pid賦值給父進(jìn)程的進(jìn)程描述符的pstrace_message字段,并向父進(jìn)程的父進(jìn)程發(fā)送SIGCHLD信號(hào)。

          (7)如果CLONE_VFORK標(biāo)志被設(shè)置,則通過wait操作將父進(jìn)程阻塞,直至子進(jìn)程調(diào)用exec函數(shù)或者退出。

          (8)如果copy_process()在執(zhí)行的時(shí)候發(fā)生錯(cuò)誤,則先釋放已分配的pid,再根據(jù)PTR_ERR()的返回值得到錯(cuò)誤代碼,保存于nr中。

          四,copy_process: 進(jìn)程描述符的處理

          copy_process函數(shù)也在./linux/kernel/fork.c中。它會(huì)用當(dāng)前進(jìn)程的一個(gè)副本來創(chuàng)建新進(jìn)程并分配pid,但不會(huì)實(shí)際啟動(dòng)這個(gè)新進(jìn)程。它會(huì)復(fù)制寄存器中的值、所有與進(jìn)程環(huán)境相關(guān)的部分,每個(gè)clone標(biāo)志。新進(jìn)程的實(shí)際啟動(dòng)由調(diào)用者來完成。

          對(duì)于每一個(gè)進(jìn)程而言,內(nèi)核為其單獨(dú)分配了一個(gè)內(nèi)存區(qū)域,這個(gè)區(qū)域存儲(chǔ)的是內(nèi)核棧和該進(jìn)程所對(duì)應(yīng)的一個(gè)小型進(jìn)程描述符thread_info結(jié)構(gòu)。在
          ./linux/arch/x86/include/asm/thread_info.h中,如下:

          struct thread_info {
          struct task_struct *task; /* 主進(jìn)程描述符 */
          struct exec_domain *exec_domain; /* 執(zhí)行域 */
          __u32 flags; /* 低級(jí)別標(biāo)志 */
          __u32 status; /* 線程同步標(biāo)志 */
          __u32 cpu; /* 當(dāng)前CPU */
          int preempt_count; /* 0 => 可搶占, <0 => BUG */
          mm_segment_t addr_limit;
          struct restart_block restart_block;
          void __user *sysenter_return;
          #ifdef CONFIG_X86_32
          unsigned long previous_esp; /* 先前棧的ESP,以防嵌入的(IRQ)棧 */
          __u8 supervisor_stack[0];
          #endif
          int uaccess_err;
          };

          /* ... */

          /* 怎樣從C獲取當(dāng)前棧指針 */
          register unsigned long current_stack_pointer asm("esp") __used;

          /* 怎樣從C獲取當(dāng)前線程信息結(jié)構(gòu) */
          static inline struct thread_info *current_thread_info(void)
          {
          return (struct thread_info *)
          (current_stack_pointer & ~(THREAD_SIZE - 1));
          }

          之所以將線程信息結(jié)構(gòu)稱之為小型的進(jìn)程描述符,是因?yàn)樵谶@個(gè)結(jié)構(gòu)中并沒有直接包含與進(jìn)程相關(guān)的字段,而是通過task字段指向具體某個(gè)進(jìn)程描述符。通常這塊內(nèi)存區(qū)域的大小是8KB,也就是兩個(gè)頁(yè)的大?。ㄓ袝r(shí)候也使用一個(gè)頁(yè)來存儲(chǔ),即4KB)。

          一個(gè)進(jìn)程的內(nèi)核棧和thread_info結(jié)構(gòu)之間的邏輯關(guān)系如下圖所示:

          進(jìn)程內(nèi)核棧和thread_info結(jié)構(gòu)的存儲(chǔ)

          從上圖可知,內(nèi)核棧是從該內(nèi)存區(qū)域的頂層向下(從高地址到低地址)增長(zhǎng)的,而thread_info結(jié)構(gòu)則是從該區(qū)域的開始處向上(從低地址到高地址)增長(zhǎng)。內(nèi)核棧的棧頂?shù)刂反鎯?chǔ)在esp寄存器中。所以,當(dāng)進(jìn)程從用戶態(tài)切換到內(nèi)核態(tài)后,esp寄存器指向這個(gè)區(qū)域的末端。從代碼的角度來看,內(nèi)核棧和thread_info結(jié)構(gòu)是被定義在
          ./linux/include/linux/sched.h中的一個(gè)聯(lián)合體當(dāng)中的:

          union thread_union {
          struct thread_info thread_info;
          unsigned long stack[THREAD_SIZE/sizeof(long)];
          };

          其中,THREAD_SIZE的值取8192時(shí),stack數(shù)組的大小為2048;THREAD_SIZE的值取4096時(shí),stack數(shù)組的大小為1024。

          現(xiàn)在我們應(yīng)該思考,為何要將內(nèi)核棧和thread_info(其實(shí)也就相當(dāng)于task_struct,只不過使用thread_info結(jié)構(gòu)更節(jié)省空間)緊密的放在一起?最主要的原因就是內(nèi)核可以很容易的通過esp寄存器的值獲得當(dāng)前正在運(yùn)行進(jìn)程的thread_info結(jié)構(gòu)的地址,進(jìn)而獲得當(dāng)前進(jìn)程描述符的地址。在上面的current_thread_info函數(shù)中,定義current_stack_pointer的這條內(nèi)聯(lián)匯編語句會(huì)從esp寄存器中獲取內(nèi)核棧頂?shù)刂?,和~(THREAD_SIZE - 1)做與操作將屏蔽掉低13位(或12位,當(dāng)THREAD_SIZE為4096時(shí)),此時(shí)所指的地址就是這片內(nèi)存區(qū)域的起始地址,也就剛好是thread_info結(jié)構(gòu)的地址。但是,thread_info結(jié)構(gòu)的地址并不會(huì)對(duì)我們直接有用。我們通常可以輕松的通過 current宏獲得當(dāng)前進(jìn)程的task_struct結(jié)構(gòu),前面已經(jīng)列出過get_current()函數(shù)的代碼。current宏返回的是thread_info結(jié)構(gòu)task字段,而task正好指向與thread_info結(jié)構(gòu)關(guān)聯(lián)的那個(gè)進(jìn)程描述符。得到 current后,我們就可以獲得當(dāng)前正在運(yùn)行進(jìn)程的描述符中任何一個(gè)字段了,比如我們通常所做的current->pid。

          下面看copy_process的實(shí)現(xiàn):

          static struct task_struct *copy_process(unsigned long clone_flags,
          unsigned long stack_start,
          struct pt_regs *regs,
          unsigned long stack_size,
          int __user *child_tidptr,
          struct pid *pid,
          int trace)
          {
          int retval;
          struct task_struct *p;
          int cgroup_callbacks_done = 0;

          if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
          return ERR_PTR(-EINVAL);

          /*
          * Thread groups must share signals as well, and detached threads
          * can only be started up within the thread group.
          */

          if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
          return ERR_PTR(-EINVAL);

          /*
          * Shared signal handlers imply shared VM. By way of the above,
          * thread groups also imply shared VM. Blocking this case allows
          * for various simplifications in other code.
          */

          if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
          return ERR_PTR(-EINVAL);

          /*
          * Siblings of global init remain as zombies on exit since they are
          * not reaped by their parent (swapper). To solve this and to avoid
          * multi-rooted process trees, prevent global and container-inits
          * from creating siblings.
          */

          if ((clone_flags & CLONE_PARENT) &&
          current->signal->flags & SIGNAL_UNKILLABLE)
          return ERR_PTR(-EINVAL);

          retval = security_task_create(clone_flags);
          if (retval)
          goto fork_out;

          retval = -ENOMEM;
          p = dup_task_struct(current);
          if (!p)
          goto fork_out;

          ftrace_graph_init_task(p);

          rt_mutex_init_task(p);

          #ifdef CONFIG_PROVE_LOCKING
          DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled);
          DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
          #endif
          retval = -EAGAIN;
          if (atomic_read(&p->real_cred->user->processes) >=
          p->signal->rlim[RLIMIT_NPROC].rlim_cur) {
          if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) &&
          p->real_cred->user != INIT_USER)
          goto bad_fork_free;
          }

          retval = copy_creds(p, clone_flags);
          if (retval < 0)
          goto bad_fork_free;

          /*
          * If multiple threads are within copy_process(), then this check
          * triggers too late. This doesn't hurt, the check is only there
          * to stop root fork bombs.
          */

          retval = -EAGAIN;
          if (nr_threads >= max_threads)
          goto bad_fork_cleanup_count;

          if (!try_module_get(task_thread_info(p)->exec_domain->module))
          goto bad_fork_cleanup_count;

          p->did_exec = 0;
          delayacct_tsk_init(p); /* Must remain after dup_task_struct() */
          copy_flags(clone_flags, p);
          INIT_LIST_HEAD(&p->children);
          INIT_LIST_HEAD(&p->sibling);
          rcu_copy_process(p);
          p->vfork_done = NULL;
          spin_lock_init(&p->alloc_lock);

          init_sigpending(&p->pending);

          p->utime = cputime_zero;
          p->stime = cputime_zero;
          p->gtime = cputime_zero;
          p->utimescaled = cputime_zero;
          p->stimescaled = cputime_zero;
          p->prev_utime = cputime_zero;
          p->prev_stime = cputime_zero;

          p->default_timer_slack_ns = current->timer_slack_ns;

          task_io_accounting_init(&p->ioac);
          acct_clear_integrals(p);

          posix_cpu_timers_init(p);

          p->lock_depth = -1; /* -1 = no lock */
          do_posix_clock_monotonic_gettime(&p->start_time);
          p->real_start_time = p->start_time;
          monotonic_to_bootbased(&p->real_start_time);
          p->io_context = NULL;
          p->audit_context = NULL;
          cgroup_fork(p);
          #ifdef CONFIG_NUMA
          p->mempolicy = mpol_dup(p->mempolicy);
          if (IS_ERR(p->mempolicy)) {
          retval = PTR_ERR(p->mempolicy);
          p->mempolicy = NULL;
          goto bad_fork_cleanup_cgroup;
          }
          mpol_fix_fork_child_flag(p);
          #endif
          #ifdef CONFIG_TRACE_IRQFLAGS
          p->irq_events = 0;
          #ifdef __ARCH_WANT_INTERRUPTS_ON_CTXSW
          p->hardirqs_enabled = 1;
          #else
          p->hardirqs_enabled = 0;
          #endif
          p->hardirq_enable_ip = 0;
          p->hardirq_enable_event = 0;
          p->hardirq_disable_ip = _THIS_IP_;
          p->hardirq_disable_event = 0;
          p->softirqs_enabled = 1;
          p->softirq_enable_ip = _THIS_IP_;
          p->softirq_enable_event = 0;
          p->softirq_disable_ip = 0;
          p->softirq_disable_event = 0;
          p->hardirq_context = 0;
          p->softirq_context = 0;
          #endif
          #ifdef CONFIG_LOCKDEP
          p->lockdep_depth = 0; /* no locks held yet */
          p->curr_chain_key = 0;
          p->lockdep_recursion = 0;
          #endif

          #ifdef CONFIG_DEBUG_MUTEXES
          p->blocked_on = NULL; /* not blocked yet */
          #endif

          p->bts = NULL;

          /* Perform scheduler related setup. Assign this task to a CPU. */
          sched_fork(p, clone_flags);

          retval = perf_event_init_task(p);
          if (retval)
          goto bad_fork_cleanup_policy;

          if ((retval = audit_alloc(p)))
          goto bad_fork_cleanup_policy;
          /* copy all the process information */
          if ((retval = copy_semundo(clone_flags, p)))
          goto bad_fork_cleanup_audit;
          if ((retval = copy_files(clone_flags, p)))
          goto bad_fork_cleanup_semundo;
          if ((retval = copy_fs(clone_flags, p)))
          goto bad_fork_cleanup_files;
          if ((retval = copy_sighand(clone_flags, p)))
          goto bad_fork_cleanup_fs;
          if ((retval = copy_signal(clone_flags, p)))
          goto bad_fork_cleanup_sighand;
          if ((retval = copy_mm(clone_flags, p)))
          goto bad_fork_cleanup_signal;
          if ((retval = copy_namespaces(clone_flags, p)))
          goto bad_fork_cleanup_mm;
          if ((retval = copy_io(clone_flags, p)))
          goto bad_fork_cleanup_namespaces;
          retval = copy_thread(clone_flags, stack_start, stack_size, p, regs);
          if (retval)
          goto bad_fork_cleanup_io;

          if (pid != &init_struct_pid) {
          retval = -ENOMEM;
          pid = alloc_pid(p->nsproxy->pid_ns);
          if (!pid)
          goto bad_fork_cleanup_io;

          if (clone_flags & CLONE_NEWPID) {
          retval = pid_ns_prepare_proc(p->nsproxy->pid_ns);
          if (retval < 0)
          goto bad_fork_free_pid;
          }
          }

          p->pid = pid_nr(pid);
          p->tgid = p->pid;
          if (clone_flags & CLONE_THREAD)
          p->tgid = current->tgid;

          if (current->nsproxy != p->nsproxy) {
          retval = ns_cgroup_clone(p, pid);
          if (retval)
          goto bad_fork_free_pid;
          }

          p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
          /*
          * Clear TID on mm_release()?
          */

          p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr: NULL;
          #ifdef CONFIG_FUTEX
          p->robust_list = NULL;
          #ifdef CONFIG_COMPAT
          p->compat_robust_list = NULL;
          #endif
          INIT_LIST_HEAD(&p->pi_state_list);
          p->pi_state_cache = NULL;
          #endif
          /*
          * sigaltstack should be cleared when sharing the same VM
          */

          if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM)
          p->sas_ss_sp = p->sas_ss_size = 0;

          /*
          * Syscall tracing should be turned off in the child regardless
          * of CLONE_PTRACE.
          */

          clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);
          #ifdef TIF_SYSCALL_EMU
          clear_tsk_thread_flag(p, TIF_SYSCALL_EMU);
          #endif
          clear_all_latency_tracing(p);

          /* ok, now we should be set up.. */
          p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 : (clone_flags & CSIGNAL);
          p->pdeath_signal = 0;
          p->exit_state = 0;

          /*
          * Ok, make it visible to the rest of the system.
          * We dont wake it up yet.
          */

          p->group_leader = p;
          INIT_LIST_HEAD(&p->thread_group);

          /* Now that the task is set up, run cgroup callbacks if
          * necessary. We need to run them before the task is visible
          * on the tasklist. */

          cgroup_fork_callbacks(p);
          cgroup_callbacks_done = 1;

          /* Need tasklist lock for parent etc handling! */
          write_lock_irq(&tasklist_lock);

          /* CLONE_PARENT re-uses the old parent */
          if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {
          p->real_parent = current->real_parent;
          p->parent_exec_id = current->parent_exec_id;
          } else {
          p->real_parent = current;
          p->parent_exec_id = current->self_exec_id;
          }

          spin_lock(¤t->sighand->siglock);

          /*
          * Process group and session signals need to be delivered to just the
          * parent before the fork or both the parent and the child after the
          * fork. Restart if a signal comes in before we add the new process to
          * it's process group.
          * A fatal signal pending means that current will exit, so the new
          * thread can't slip out of an OOM kill (or normal SIGKILL).
          */

          recalc_sigpending();
          if (signal_pending(current)) {
          spin_unlock(¤t->sighand->siglock);
          write_unlock_irq(&tasklist_lock);
          retval = -ERESTARTNOINTR;
          goto bad_fork_free_pid;
          }

          if (clone_flags & CLONE_THREAD) {
          atomic_inc(¤t->signal->count);
          atomic_inc(¤t->signal->live);
          p->group_leader = current->group_leader;
          list_add_tail_rcu(&p->thread_group, &p->group_leader->thread_group);
          }

          if (likely(p->pid)) {
          list_add_tail(&p->sibling, &p->real_parent->children);
          tracehook_finish_clone(p, clone_flags, trace);

          if (thread_group_leader(p)) {
          if (clone_flags & CLONE_NEWPID)
          p->nsproxy->pid_ns->child_reaper = p;

          p->signal->leader_pid = pid;
          tty_kref_put(p->signal->tty);
          p->signal->tty = tty_kref_get(current->signal->tty);
          attach_pid(p, PIDTYPE_PGID, task_pgrp(current));
          attach_pid(p, PIDTYPE_SID, task_session(current));
          list_add_tail_rcu(&p->tasks, &init_task.tasks);
          __get_cpu_var(process_counts)++;
          }
          attach_pid(p, PIDTYPE_PID, pid);
          nr_threads++;
          }

          total_forks++;
          spin_unlock(¤t->sighand->siglock);
          write_unlock_irq(&tasklist_lock);
          proc_fork_connector(p);
          cgroup_post_fork(p);
          perf_event_fork(p);
          return p;

          bad_fork_free_pid:
          if (pid != &init_struct_pid)
          free_pid(pid);
          bad_fork_cleanup_io:
          put_io_context(p->io_context);
          bad_fork_cleanup_namespaces:
          exit_task_namespaces(p);
          bad_fork_cleanup_mm:
          if (p->mm)
          mmput(p->mm);
          bad_fork_cleanup_signal:
          if (!(clone_flags & CLONE_THREAD))
          __cleanup_signal(p->signal);
          bad_fork_cleanup_sighand:
          __cleanup_sighand(p->sighand);
          bad_fork_cleanup_fs:
          exit_fs(p); /* blocking */
          bad_fork_cleanup_files:
          exit_files(p); /* blocking */
          bad_fork_cleanup_semundo:
          exit_sem(p);
          bad_fork_cleanup_audit:
          audit_free(p);
          bad_fork_cleanup_policy:
          perf_event_free_task(p);
          #ifdef CONFIG_NUMA
          mpol_put(p->mempolicy);
          bad_fork_cleanup_cgroup:
          #endif
          cgroup_exit(p, cgroup_callbacks_done);
          delayacct_tsk_free(p);
          module_put(task_thread_info(p)->exec_domain->module);
          bad_fork_cleanup_count:
          atomic_dec(&p->cred->user->processes);
          exit_creds(p);
          bad_fork_free:
          free_task(p);
          fork_out:
          return ERR_PTR(retval);
          }
          • (1)定義返回值亦是retval和新的進(jìn)程描述符task_struct結(jié)構(gòu)p。

          • (2)標(biāo)志合法性檢查。對(duì)clone_flags所傳遞的標(biāo)志組合進(jìn)行合法性檢查。當(dāng)出現(xiàn)以下三種情況時(shí),返回出錯(cuò)代號(hào):

          1)CLONE_NEWNS和CLONE_FS同時(shí)被設(shè)置。前者標(biāo)志表示子進(jìn)程需要自己的命名空間,而后者標(biāo)志則代表子進(jìn)程共享父進(jìn)程的根目錄和當(dāng)前工作目錄,兩者不可兼容。在傳統(tǒng)的Unix系統(tǒng)中,整個(gè)系統(tǒng)只有一個(gè)已經(jīng)安裝的文件系統(tǒng)樹。每個(gè)進(jìn)程從系統(tǒng)的根文件系統(tǒng)開始,通過合法的路徑可以訪問任何文件。在2.6版本中的內(nèi)核中,每個(gè)進(jìn)程都可以擁有屬于自己的已安裝文件系統(tǒng)樹,也被稱為命名空間。通常大多數(shù)進(jìn)程都共享init進(jìn)程所使用的已安裝文件系統(tǒng)樹,只有在clone_flags中設(shè)置了CLONE_NEWNS標(biāo)志時(shí),才會(huì)為此新進(jìn)程開辟一個(gè)新的命名空間。

          2)CLONE_THREAD被設(shè)置,但CLONE_SIGHAND未被設(shè)置。如果子進(jìn)程和父進(jìn)程屬于同一個(gè)線程組(CLONE_THREAD被設(shè)置),那么子進(jìn)程必須共享父進(jìn)程的信號(hào)(CLONE_SIGHAND被設(shè)置)。

          3)CLONE_SIGHAND被設(shè)置,但CLONE_VM未被設(shè)置。如果子進(jìn)程共享父進(jìn)程的信號(hào),那么必須同時(shí)共享父進(jìn)程的內(nèi)存描述符和所有的頁(yè)表(CLONE_VM被設(shè)置)。

          • (3)安全性檢查。通過調(diào)用security_task_create()和后面的security_task_alloc()執(zhí)行所有附加的安全性檢查。詢問 Linux Security Module (LSM) 看當(dāng)前任務(wù)是否可以創(chuàng)建一個(gè)新任務(wù)。LSM是SELinux的核心。

          • (4)復(fù)制進(jìn)程描述符。通過dup_task_struct()為子進(jìn)程分配一個(gè)內(nèi)核棧、thread_info結(jié)構(gòu)和task_struct結(jié)構(gòu)。注意,這里將當(dāng)前進(jìn)程描述符指針作為參數(shù)傳遞到此函數(shù)中。

          函數(shù)代碼如下:

          int __attribute__((weak)) arch_dup_task_struct(struct task_struct *dst,
          struct task_struct *src)
          {
          *dst = *src;
          return 0;
          }

          static struct task_struct *dup_task_struct(struct task_struct *orig)
          {
          struct task_struct *tsk;
          struct thread_info *ti;
          unsigned long *stackend;

          int err;

          prepare_to_copy(orig);

          tsk = alloc_task_struct();
          if (!tsk)
          return NULL;

          ti = alloc_thread_info(tsk);
          if (!ti) {
          free_task_struct(tsk);
          return NULL;
          }

          err = arch_dup_task_struct(tsk, orig);
          if (err)
          goto out;

          tsk->stack = ti;

          err = prop_local_init_single(&tsk->dirties);
          if (err)
          goto out;

          setup_thread_stack(tsk, orig);
          stackend = end_of_stack(tsk);
          *stackend = STACK_END_MAGIC; /* 用于溢出檢測(cè) */

          #ifdef CONFIG_CC_STACKPROTECTOR
          tsk->stack_canary = get_random_int();
          #endif

          /* One for us, one for whoever does the "release_task()" (usually parent) */
          atomic_set(&tsk->usage,2);
          atomic_set(&tsk->fs_excl, 0);
          #ifdef CONFIG_BLK_DEV_IO_TRACE
          tsk->btrace_seq = 0;
          #endif
          tsk->splice_pipe = NULL;

          account_kernel_stack(ti, 1);

          return tsk;

          out:
          free_thread_info(ti);
          free_task_struct(tsk);
          return NULL;
          }

          首先,該函數(shù)分別定義了指向task_struct和thread_info結(jié)構(gòu)體的指針。接著,prepare_to_copy為正式的分配進(jìn)程描述符做一些準(zhǔn)備工作。主要是將一些必要的寄存器的值保存到父進(jìn)程的thread_info結(jié)構(gòu)中。這些值會(huì)在稍后被復(fù)制到子進(jìn)程的thread_info結(jié)構(gòu)中。執(zhí)行alloc_task_struct宏,該宏負(fù)責(zé)為子進(jìn)程的進(jìn)程描述符分配空間,將該片內(nèi)存的首地址賦值給tsk,隨后檢查這片內(nèi)存是否分配正確。執(zhí)行alloc_thread_info宏,為子進(jìn)程獲取一塊空閑的內(nèi)存區(qū),用來存放子進(jìn)程的內(nèi)核棧和thread_info結(jié)構(gòu),并將此會(huì)內(nèi)存區(qū)的首地址賦值給ti變量,隨后檢查是否分配正確。

          上面已經(jīng)說明過orig是傳進(jìn)來的current宏,指向當(dāng)前進(jìn)程描述符的指針。arch_dup_task_struct直接將orig指向的當(dāng)前進(jìn)程描述符內(nèi)容復(fù)制到當(dāng)前里程描述符tsk。接著,用atomic_set將子進(jìn)程描述符的使用計(jì)數(shù)器設(shè)置為2,表示該進(jìn)程描述符正在被使用并且處于活動(dòng)狀態(tài)。最后返回指向剛剛創(chuàng)建的子進(jìn)程描述符內(nèi)存區(qū)的指針。

          通過dup_task_struct可以看到,當(dāng)這個(gè)函數(shù)成功操作之后,子進(jìn)程和父進(jìn)程的描述符中的內(nèi)容是完全相同的。在稍后的copy_process代碼中,我們將會(huì)看到子進(jìn)程逐步與父進(jìn)程區(qū)分開來。

          • (5)一些初始化。通過諸如ftrace_graph_init_task,rt_mutex_init_task完成某些數(shù)據(jù)結(jié)構(gòu)的初始化。調(diào)用copy_creds()復(fù)制證書(應(yīng)該是復(fù)制權(quán)限及身份信息)。

          • (6)檢測(cè)系統(tǒng)中進(jìn)程的總數(shù)量是否超過了max_threads所規(guī)定的進(jìn)程最大數(shù)。

          • (7)復(fù)制標(biāo)志。通過copy_flags,將從do_fork()傳遞來的的clone_flags和pid分別賦值給子進(jìn)程描述符中的對(duì)應(yīng)字段。

          • (8)初始化子進(jìn)程描述符。初始化其中的各個(gè)字段,使得子進(jìn)程和父進(jìn)程逐漸區(qū)別出來。這部分工作包含初始化子進(jìn)程中的children和sibling等隊(duì)列頭、初始化自旋鎖和信號(hào)處理、初始化進(jìn)程統(tǒng)計(jì)信息、初始化POSIX時(shí)鐘、初始化調(diào)度相關(guān)的統(tǒng)計(jì)信息、初始化審計(jì)信息。它在copy_process函數(shù)中占據(jù)了相當(dāng)長(zhǎng)的一段的代碼,不過考慮到task_struct結(jié)構(gòu)本身的復(fù)雜性,也就不足為奇了。

          • (9)調(diào)度器設(shè)置。調(diào)用sched_fork函數(shù)執(zhí)行調(diào)度器相關(guān)的設(shè)置,為這個(gè)新進(jìn)程分配CPU,使得子進(jìn)程的進(jìn)程狀態(tài)為TASK_RUNNING。并禁止內(nèi)核搶占。并且,為了不對(duì)其他進(jìn)程的調(diào)度產(chǎn)生影響,此時(shí)子進(jìn)程共享父進(jìn)程的時(shí)間片。

          • (10)復(fù)制進(jìn)程的所有信息。根據(jù)clone_flags的具體取值來為子進(jìn)程拷貝或共享父進(jìn)程的某些數(shù)據(jù)結(jié)構(gòu)。比如copy_semundo()、復(fù)制開放文件描述符(copy_files)、復(fù)制符號(hào)信息(copy_sighand 和copy_signal)、復(fù)制進(jìn)程內(nèi)存(copy_mm)以及最終復(fù)制線程(copy_thread)。

          • (11)復(fù)制線程。通過copy_threads()函數(shù)更新子進(jìn)程的內(nèi)核棧和寄存器中的值。在之前的dup_task_struct()中只是為子進(jìn)程創(chuàng)建一個(gè)內(nèi)核棧,至此才是真正的賦予它有意義的值。

          當(dāng)父進(jìn)程發(fā)出clone系統(tǒng)調(diào)用時(shí),內(nèi)核會(huì)將那個(gè)時(shí)候CPU中寄存器的值保存在父進(jìn)程的內(nèi)核棧中。這里就是使用父進(jìn)程內(nèi)核棧中的值來更新子進(jìn)程寄存器中的值。特別的,內(nèi)核將子進(jìn)程eax寄存器中的值強(qiáng)制賦值為0,這也就是為什么使用fork()時(shí)子進(jìn)程返回值是0。而在do_fork函數(shù)中則返回的是子進(jìn)程的pid,這一點(diǎn)在上述內(nèi)容中我們已經(jīng)有所分析。另外,子進(jìn)程的對(duì)應(yīng)的thread_info結(jié)構(gòu)中的esp字段會(huì)被初始化為子進(jìn)程內(nèi)核棧的基址。

          • (12)分配pid。用alloc_pid函數(shù)為這個(gè)新進(jìn)程分配一個(gè)pid,Linux系統(tǒng)內(nèi)的pid是循環(huán)使用的,采用位圖方式來管理。簡(jiǎn)單的說,就是用每一位(bit)來標(biāo)示該位所對(duì)應(yīng)的pid是否被使用。分配完畢后,判斷pid是否分配成功。成功則賦給p->pid。

          • (13)更新屬性和進(jìn)程數(shù)量。根據(jù)clone_flags的值繼續(xù)更新子進(jìn)程的某些屬性。將 nr_threads加一,表明新進(jìn)程已經(jīng)被加入到進(jìn)程集合中。將total_forks加一,以記錄被創(chuàng)建進(jìn)程數(shù)量。

          • (14)如果上述過程中某一步出現(xiàn)了錯(cuò)誤,則通過goto語句跳到相應(yīng)的錯(cuò)誤代碼處;如果成功執(zhí)行完畢,則返回子進(jìn)程的描述符p。

          至此,copy_process()的大致執(zhí)行過程分析完畢。

          copy_process()執(zhí)行完后返回do_fork(),do_fork()執(zhí)行完畢后,雖然子進(jìn)程處于可運(yùn)行狀態(tài),但是它并沒有立刻運(yùn)行。至于子進(jìn)程何時(shí)執(zhí)行這完全取決于調(diào)度程序,也就是schedule()的事了。

          五,進(jìn)程調(diào)度

          創(chuàng)建好的進(jìn)程最后被插入到運(yùn)行隊(duì)列中,它會(huì)通過 Linux 調(diào)度程序來調(diào)度。Linux調(diào)度程序維護(hù)了針對(duì)每個(gè)優(yōu)先級(jí)別的一組列表,其中保存了 task_struct 引用。當(dāng)正在運(yùn)行的進(jìn)程時(shí)間片用完時(shí),時(shí)鐘tick產(chǎn)生中斷,調(diào)用
          kernel/sched.c:scheduler_tick()進(jìn)程調(diào)度器的中斷處理,中斷返回后就會(huì)調(diào)用schedule()。運(yùn)行隊(duì)列中的任務(wù)通過 schedule 函數(shù)(在./linux/kernel/sched.c 內(nèi))來調(diào)用,它根據(jù)加載及進(jìn)程執(zhí)行歷史決定最佳進(jìn)程。這里并不涉及此函數(shù)的分析。

          六,進(jìn)程銷毀

          進(jìn)程銷毀可以通過幾個(gè)事件驅(qū)動(dòng)、通過正常的進(jìn)程結(jié)束(當(dāng)一個(gè)C程序從main函數(shù)返回時(shí)startuproutine調(diào)用exit)、通過信號(hào)或是通過顯式地對(duì) exit 函數(shù)的調(diào)用。不管進(jìn)程如何退出,進(jìn)程的結(jié)束都要借助對(duì)內(nèi)核函數(shù) do_exit(在./linux/kernel/exit.c 內(nèi))的調(diào)用。

          函數(shù)的層次結(jié)構(gòu)如下圖:

          進(jìn)程銷毀的函數(shù)層次結(jié)構(gòu)

          exit()調(diào)用通過0x80中斷跳到sys_exit內(nèi)核例程處,這個(gè)例程名稱可以在
          ./linux/include/linux/syscalls.h中找到(syscalls.h中導(dǎo)出所有平臺(tái)無關(guān)的系統(tǒng)調(diào)用名稱),定義為asmlinkage long sys_exit(int error_code); 它會(huì)直接調(diào)用do_exit。

          代碼如下:

          NORET_TYPE void do_exit(long code)
          {
          struct task_struct *tsk = current;
          int group_dead;

          profile_task_exit(tsk);

          WARN_ON(atomic_read(&tsk->fs_excl));

          if (unlikely(in_interrupt()))
          panic("Aiee, killing interrupt handler!");
          if (unlikely(!tsk->pid))
          panic("Attempted to kill the idle task!");

          /*
          * If do_exit is called because this processes oopsed, it's possible
          * that get_fs() was left as KERNEL_DS, so reset it to USER_DS before
          * continuing. Amongst other possible reasons, this is to prevent
          * mm_release()->clear_child_tid() from writing to a user-controlled
          * kernel address.
          */

          set_fs(USER_DS);

          tracehook_report_exit(&code);

          validate_creds_for_do_exit(tsk);

          /*
          * We're taking recursive faults here in do_exit. Safest is to just
          * leave this task alone and wait for reboot.
          */

          if (unlikely(tsk->flags & PF_EXITING)) {
          printk(KERN_ALERT
          "Fixing recursive fault but reboot is needed!\n");
          /*
          * We can do this unlocked here. The futex code uses
          * this flag just to verify whether the pi state
          * cleanup has been done or not. In the worst case it
          * loops once more. We pretend that the cleanup was
          * done as there is no way to return. Either the
          * OWNER_DIED bit is set by now or we push the blocked
          * task into the wait for ever nirwana as well.
          */

          tsk->flags |= PF_EXITPIDONE;
          set_current_state(TASK_UNINTERRUPTIBLE);
          schedule();
          }

          exit_irq_thread();

          exit_signals(tsk); /* sets PF_EXITING */
          /*
          * tsk->flags are checked in the futex code to protect against
          * an exiting task cleaning up the robust pi futexes.
          */

          smp_mb();
          spin_unlock_wait(&tsk->pi_lock);

          if (unlikely(in_atomic()))
          printk(KERN_INFO "note: %s[%d] exited with preempt_count %d\n",
          current->comm, task_pid_nr(current),
          preempt_count());

          acct_update_integrals(tsk);

          group_dead = atomic_dec_and_test(&tsk->signal->live);
          if (group_dead) {
          hrtimer_cancel(&tsk->signal->real_timer);
          exit_itimers(tsk->signal);
          if (tsk->mm)
          setmax_mm_hiwater_rss(&tsk->signal->maxrss, tsk->mm);
          }
          acct_collect(code, group_dead);
          if (group_dead)
          tty_audit_exit();
          if (unlikely(tsk->audit_context))
          audit_free(tsk);

          tsk->exit_code = code;
          taskstats_exit(tsk, group_dead);

          exit_mm(tsk);

          if (group_dead)
          acct_process();
          trace_sched_process_exit(tsk);

          exit_sem(tsk);
          exit_files(tsk);
          exit_fs(tsk);
          check_stack_usage();
          exit_thread();
          cgroup_exit(tsk, 1);

          if (group_dead && tsk->signal->leader)
          disassociate_ctty(1);

          module_put(task_thread_info(tsk)->exec_domain->module);

          proc_exit_connector(tsk);

          /*
          * Flush inherited counters to the parent - before the parent
          * gets woken up by child-exit notifications.
          */

          perf_event_exit_task(tsk);

          exit_notify(tsk, group_dead);
          #ifdef CONFIG_NUMA
          mpol_put(tsk->mempolicy);
          tsk->mempolicy = NULL;
          #endif
          #ifdef CONFIG_FUTEX
          if (unlikely(current->pi_state_cache))
          kfree(current->pi_state_cache);
          #endif
          /*
          * Make sure we are holding no locks:
          */

          debug_check_no_locks_held(tsk);
          /*
          * We can do this unlocked here. The futex code uses this flag
          * just to verify whether the pi state cleanup has been done
          * or not. In the worst case it loops once more.
          */

          tsk->flags |= PF_EXITPIDONE;

          if (tsk->io_context)
          exit_io_context();

          if (tsk->splice_pipe)
          __free_pipe_info(tsk->splice_pipe);

          validate_creds_for_do_exit(tsk);

          preempt_disable();
          exit_rcu();
          /* causes final put_task_struct in finish_task_switch(). */
          tsk->state = TASK_DEAD;
          schedule();
          BUG();
          /* Avoid "noreturn function does return". */
          for (;;)
          cpu_relax(); /* For when BUG is null */
          }

          EXPORT_SYMBOL_GPL(do_exit);
          (1)為進(jìn)程銷毀做一系列準(zhǔn)備。用set_fs設(shè)置USER_DS。注意如果do_exit是因?yàn)楫?dāng)前進(jìn)程出現(xiàn)不可預(yù)知的錯(cuò)誤而被調(diào)用,這時(shí)get_fs()有可能得到的仍然是KERNEL_DS狀態(tài),因此我們要重置它為USER_DS狀態(tài)。還有一個(gè)可能原因是這可以防止mm_release()->clear_child_tid()寫一個(gè)被用戶控制的內(nèi)核地址。

          (2)清除所有信號(hào)處理函數(shù)。exit_signals函數(shù)會(huì)設(shè)置PF_EXITING標(biāo)志來表明進(jìn)程正在退出,并清除所有信息處理函數(shù)。內(nèi)核的其他方面會(huì)利用PF_EXITING來防止在進(jìn)程被刪除時(shí)還試圖處理此進(jìn)程。


          (3)清除一系列的進(jìn)程資源。比如比如 exit_mm刪除內(nèi)存頁(yè)、exit_files關(guān)閉所有打開的文件描述符,這會(huì)清理I/O緩存,如果緩存中有數(shù)據(jù),就會(huì)將它們寫入相應(yīng)的文件,以防止文件數(shù)據(jù)的丟失。exit_fs清除當(dāng)前目錄關(guān)聯(lián)的inode、exit_thread清除線程信息、等等。


          (4)發(fā)出退出通知。調(diào)用exit_notify執(zhí)行一系列通知。例如通知父進(jìn)程我正在退出。

          如下:

          static void exit_notify(struct task_struct *tsk, int group_dead)
          {
          int signal;
          void *cookie;

          /*
          * This does two things:
          *
          * A. Make init inherit all the child processes
          * B. Check to see if any process groups have become orphaned
          * as a result of our exiting, and if they have any stopped
          * jobs, send them a SIGHUP and then a SIGCONT. (POSIX 3.2.2.2)
          */

          forget_original_parent(tsk);
          exit_task_namespaces(tsk);

          write_lock_irq(&tasklist_lock);
          if (group_dead)
          kill_orphaned_pgrp(tsk->group_leader, NULL);

          /* Let father know we died
          *
          * Thread signals are configurable, but you aren't going to use
          * that to send signals to arbitary processes.
          * That stops right now.
          *
          * If the parent exec id doesn't match the exec id we saved
          * when we started then we know the parent has changed security
          * domain.
          *
          * If our self_exec id doesn't match our parent_exec_id then
          * we have changed execution domain as these two values started
          * the same after a fork.
          */

          if (tsk->exit_signal != SIGCHLD && !task_detached(tsk) &&
          (tsk->parent_exec_id != tsk->real_parent->self_exec_id ||
          tsk->self_exec_id != tsk->parent_exec_id))
          tsk->exit_signal = SIGCHLD;

          signal = tracehook_notify_death(tsk, &cookie, group_dead);
          if (signal >= 0)
          signal = do_notify_parent(tsk, signal);

          tsk->exit_state = signal == DEATH_REAP ? EXIT_DEAD : EXIT_ZOMBIE;

          /* mt-exec, de_thread() is waiting for us */
          if (thread_group_leader(tsk) &&
          tsk->signal->group_exit_task &&
          tsk->signal->notify_count < 0)
          wake_up_process(tsk->signal->group_exit_task);

          write_unlock_irq(&tasklist_lock);

          tracehook_report_death(tsk, signal, cookie, group_dead);

          /* If the process is dead, release it - nobody will wait for it */
          if (signal == DEATH_REAP)
          release_task(tsk);
          }

          exit_notify將當(dāng)前進(jìn)程的所有子進(jìn)程的父進(jìn)程ID設(shè)置為1(init),讓init接管所有這些子進(jìn)程。如果當(dāng)前進(jìn)程是某個(gè)進(jìn)程組的組長(zhǎng),其銷毀導(dǎo)致進(jìn)程組變?yōu)椤盁o領(lǐng)導(dǎo)狀態(tài)“,則向每個(gè)組內(nèi)進(jìn)程發(fā)送掛起信號(hào)SIGHUP,然后發(fā)送SIGCONT。這是遵循POSIX3.2.2.2標(biāo)準(zhǔn)。接著向自己的父進(jìn)程發(fā)送SIGCHLD信號(hào),然后調(diào)用do_notify_parent通知父進(jìn)程。若返回DEATH_REAP(這個(gè)意思是不管是否有其他進(jìn)程關(guān)心本進(jìn)程的退出信息,自動(dòng)完成進(jìn)程退出和PCB銷毀),就直接進(jìn)入EXIT_DEAD狀態(tài),如果不是,則就需要變?yōu)镋XIT_ZOMBIE狀態(tài)。

          注意在最初父進(jìn)程創(chuàng)建子進(jìn)程時(shí),如果調(diào)用了waitpid()等待子進(jìn)程結(jié)束(表示它關(guān)心子進(jìn)程的狀態(tài)),子進(jìn)程結(jié)束時(shí)父進(jìn)程會(huì)處理它發(fā)來的SIGCHILD信號(hào)。如果不調(diào)用wait(表示它不關(guān)心子進(jìn)程的死活狀態(tài)),則不會(huì)處理子進(jìn)程的SIGCHILD信號(hào)。參看
          ./linux/kernel/signal.c:do_notify_parent(),代碼如下:

          int do_notify_parent(struct task_struct *tsk, int sig)
          {
          struct siginfo info;
          unsigned long flags;
          struct sighand_struct *psig;
          int ret = sig;

          BUG_ON(sig == -1);

          /* do_notify_parent_cldstop should have been called instead. */
          BUG_ON(task_is_stopped_or_traced(tsk));

          BUG_ON(!task_ptrace(tsk) &&
          (tsk->group_leader != tsk || !thread_group_empty(tsk)));

          info.si_signo = sig;
          info.si_errno = 0;
          /*
          * we are under tasklist_lock here so our parent is tied to
          * us and cannot exit and release its namespace.
          *
          * the only it can is to switch its nsproxy with sys_unshare,
          * bu uncharing pid namespaces is not allowed, so we'll always
          * see relevant namespace
          *
          * write_lock() currently calls preempt_disable() which is the
          * same as rcu_read_lock(), but according to Oleg, this is not
          * correct to rely on this
          */

          rcu_read_lock();
          info.si_pid = task_pid_nr_ns(tsk, tsk->parent->nsproxy->pid_ns);
          info.si_uid = __task_cred(tsk)->uid;
          rcu_read_unlock();

          info.si_utime = cputime_to_clock_t(cputime_add(tsk->utime,
          tsk->signal->utime));
          info.si_stime = cputime_to_clock_t(cputime_add(tsk->stime,
          tsk->signal->stime));

          info.si_status = tsk->exit_code & 0x7f;
          if (tsk->exit_code & 0x80)
          info.si_code = CLD_DUMPED;
          else if (tsk->exit_code & 0x7f)
          info.si_code = CLD_KILLED;
          else {
          info.si_code = CLD_EXITED;
          info.si_status = tsk->exit_code >> 8;
          }

          psig = tsk->parent->sighand;
          spin_lock_irqsave(&psig->siglock, flags);
          if (!task_ptrace(tsk) && sig == SIGCHLD &&
          (psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN ||
          (psig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDWAIT))) {
          /*
          * We are exiting and our parent doesn't care. POSIX.1
          * defines special semantics for setting SIGCHLD to SIG_IGN
          * or setting the SA_NOCLDWAIT flag: we should be reaped
          * automatically and not left for our parent's wait4 call.
          * Rather than having the parent do it as a magic kind of
          * signal handler, we just set this to tell do_exit that we
          * can be cleaned up without becoming a zombie. Note that
          * we still call __wake_up_parent in this case, because a
          * blocked sys_wait4 might now return -ECHILD.
          *
          * Whether we send SIGCHLD or not for SA_NOCLDWAIT
          * is implementation-defined: we do (if you don't want
          * it, just use SIG_IGN instead).
          */

          ret = tsk->exit_signal = -1;
          if (psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN)
          sig = -1;
          }
          if (valid_signal(sig) && sig > 0)
          __group_send_sig_info(sig, &info, tsk->parent);
          __wake_up_parent(tsk, tsk->parent);
          spin_unlock_irqrestore(&psig->siglock, flags);

          return ret;
          }

          我們可以看到,如果父進(jìn)程顯示指定對(duì)子進(jìn)程的SIGCHLD信號(hào)處理為SIG_IGN,或者標(biāo)志為SA_NOCLDWAIT,則返回的是ret=-1,即DEATH_REAP(這個(gè)宏在
          ./linux/include/tracehook.h中定義為-1),這時(shí)在exit_notify中子進(jìn)程馬上變?yōu)镋XIT_DEAD,表示我已退出并且死亡,最后被后面的release_task回收,將不會(huì)再有進(jìn)程等待我。否則返回值與傳入的信號(hào)值相同,子進(jìn)程變成EXIT_ZOMBIE,表示已退出但還沒死。不管有沒有處理SIGCHLD,do_notify_parent最后都會(huì)用__wake_up_parent來喚醒正在等待的父進(jìn)程。

          可見子進(jìn)程在結(jié)束前不一定都需要經(jīng)過一個(gè)EXIT_ZOMBIE過程。如果父進(jìn)程調(diào)用了waitpid等待子進(jìn)程,則會(huì)顯示處理它發(fā)來的SIGCHILD信號(hào),子進(jìn)程結(jié)束時(shí)會(huì)自我清理(在do_exit中自己用release_task清理);如果父進(jìn)程沒有調(diào)用waitpid等待子進(jìn)程,則不會(huì)處理SIGCHLD信號(hào),子進(jìn)程不會(huì)馬上被清理,而是變成EXIT_ZOMBIE狀態(tài),成為著名的僵尸進(jìn)程。還有一種特殊情形,如果子進(jìn)程退出時(shí)父進(jìn)程恰好正在睡眠(sleep),導(dǎo)致沒來得急處理SIGCHLD,子進(jìn)程也會(huì)成為僵尸,只要父進(jìn)程在醒來后能調(diào)用waitpid,也能清理僵尸子進(jìn)程,因?yàn)閣ait系統(tǒng)調(diào)用內(nèi)部有清理僵尸子進(jìn)程的代碼。因此,如果父進(jìn)程一直沒有調(diào)用waitpid,那么僵尸子進(jìn)程就只能等到父進(jìn)程退出時(shí)被init接管了。init進(jìn)程會(huì)負(fù)責(zé)清理這些僵尸進(jìn)程(init肯定會(huì)調(diào)用wait)。

          我們可以寫個(gè)簡(jiǎn)單的程序來驗(yàn)證,父進(jìn)程創(chuàng)建10個(gè)子進(jìn)程,子進(jìn)程sleep一段時(shí)間后退出。第一種情況,父進(jìn)程只對(duì)1~9號(hào)子進(jìn)程調(diào)用waitpid(),1~9號(hào)子進(jìn)程都正常結(jié)束,而在父進(jìn)程結(jié)束前,pids[0]為EXIT_ZOMBIE。第二種情況,父進(jìn)程創(chuàng)建10個(gè)子進(jìn)程后,sleep()一段時(shí)間,在這段時(shí)間內(nèi)_exit()的子進(jìn)程都成為EXIT_ZOMBIE。父進(jìn)程sleep()結(jié)束后,依次調(diào)用waitpid(),子進(jìn)程馬上變?yōu)镋XIT_DEAD被清理。

          為了更好地理解怎么清理僵尸進(jìn)程,我們簡(jiǎn)要地分析一下wait系統(tǒng)調(diào)用。wait族的系統(tǒng)調(diào)用如waitpid,wait4等,最后都會(huì)進(jìn)入
          ./linux/kernel/exit.c:do_wait()內(nèi)核例程,而后函數(shù)鏈為do_wait()--->do_wait_thread()--->wait_consider_task(),在這里,如果子進(jìn)程在exit_notify中設(shè)置的tsk->exit_state為EXIT_DEAD,就返回0,即wait系統(tǒng)調(diào)用返回,說明子進(jìn)程不是僵尸進(jìn)程,會(huì)自己用release_task進(jìn)行回收。如果它的exit_state是EXIT_ZOMBIE,進(jìn)入wait_task_zombie()。在這里使用xchg嘗試把它的exit_state設(shè)置為EXIT_DEAD,可見父進(jìn)程的wait4調(diào)用會(huì)把子進(jìn)程由EXIT_ZOMBIE設(shè)置為EXIT_DEAD。最后wait_task_zombie()在末尾調(diào)用release_task()清理這個(gè)僵尸進(jìn)程。

          (5)設(shè)置銷毀標(biāo)志并調(diào)度新的進(jìn)程。在do_exit的最后,用exit_io_context清除IO上下文、preempt_disable禁用搶占,設(shè)置進(jìn)程狀態(tài)為TASK_DEAD,然后調(diào)用
          ./linux/kernel/sched.c:schedule()來選擇一個(gè)將要執(zhí)行的新進(jìn)程。注意進(jìn)程在退出并回收之后,其位于調(diào)度器的進(jìn)程列表中的進(jìn)程描述符(PCB)并沒有立即釋放,必須在設(shè)置task_struct的state為TASK_DEAD之后,由schedule()中的finish_task_switch()--->put_task_struct()把它的PCB重新放回到freelist(可用列表)中,這時(shí)PCB才算釋放,然后切換到新的進(jìn)程。

          七,exit與_exit的差異

          為了理解這兩個(gè)系統(tǒng)調(diào)用的差異,先來討論文件內(nèi)存緩存區(qū)的問題。在linux中,標(biāo)準(zhǔn)輸入輸出(I/O)函數(shù)都是作為文件來處理。對(duì)應(yīng)于打開的每個(gè)文件,在內(nèi)存中都有對(duì)應(yīng)的緩存,每次讀取文件時(shí),會(huì)多讀一些記錄到緩存中,這樣在下次讀文件時(shí),就在緩存中讀?。煌瑯?,在寫文件時(shí)也是寫在文件對(duì)應(yīng)的緩存中,并不是直接寫入硬盤的文件中,等滿足了一定條件(如達(dá)到一定數(shù)量,遇到換行符\n或文件結(jié)束標(biāo)志EOF)才將數(shù)據(jù)真正的寫入文件。這樣做的好處就是加快了文件讀寫的速度。但這樣也帶來了一些問題,比如有一些數(shù)據(jù),我們認(rèn)為已經(jīng)寫入了文件,但實(shí)際上沒有滿足一定條件而任然駐留在內(nèi)存的緩存中,這樣,如果我們直接用_exit()函數(shù)直接終止進(jìn)程,將導(dǎo)致數(shù)據(jù)丟失。如果改成exit,就不會(huì)有數(shù)據(jù)丟失的問題出現(xiàn)了,這就是它們之間的區(qū)別了.要解釋這個(gè)問題,就要涉及它們的工作步驟了。

          1. exit():通過前面源代碼分析可知,在執(zhí)行該函數(shù)時(shí),進(jìn)程會(huì)檢查文件打開情況,清理I/O緩存,如果緩存中有數(shù)據(jù),就會(huì)將它們寫入相應(yīng)的文件,這樣就防止了文件數(shù)據(jù)的丟失,然后終止進(jìn)程。

          2. _exit():在執(zhí)行該函數(shù)時(shí),并不清理標(biāo)準(zhǔn)輸入輸出緩存,而是直接清除內(nèi)存空間,當(dāng)然也就把文件緩存中尚未寫入文件的數(shù)據(jù)給銷毀了。由此可見,使用exit()函數(shù)更加安全。

          此外,對(duì)于它們兩者的區(qū)別還有各自的頭文件不同。exit()在stdlib.h中,_exit()在unistd.h中。一般情況下exit(0)表示正常退出,exit(1),exit(-1)為異常退出,0、1、-1是返回值,具體含義可以自定。還要注意return是返回函數(shù)調(diào)用,如果返回的是main函數(shù),則為退出程序 。exit是在調(diào)用處強(qiáng)行退出程序,運(yùn)行一次程序就結(jié)束。

          下面是完整的Linux進(jìn)程運(yùn)行流程:

          arch/x86/include/asm/unistd_32.h:fork()        用戶空間來調(diào)用(如C程序)
          --->int $0×80 產(chǎn)生0x80軟中斷
          --->arch/x86/kernel/entry_32.S:ENTRY(system_call) 中斷處理程序system_call()
          --->執(zhí)行SAVE_ALL宏 保存所有CPU寄存器值
          --->arch/x86/kernel/syscall_table_32.S:ENTRY(sys_call_table) 系統(tǒng)調(diào)用多路分解表
          --->arch/x86/kernel/process_32.c:sys_fork()
          --->kernel/fork.c:do_fork() 復(fù)制原來的進(jìn)程成為另一個(gè)新的進(jìn)程
          --->kernel/fork.c:copy_process()
          --->struct task_struct *p; 定義新的進(jìn)程描述符(PCB)
          --->clone_flags標(biāo)志的合法性檢查
          --->security_task_create() 安全性檢查(SELinux機(jī)制)
          --->kernel/fork.c:dup_task_struct() 復(fù)制進(jìn)程描述符
          --->struct thread_info *ti; 定義線程信息結(jié)構(gòu)
          --->alloc_task_struct() 為新的PCB分配內(nèi)存
          --->kernel/fork.c:arch_dup_task_struct() 復(fù)制父進(jìn)程的PCB
          --->atomic_set(&tsk->usage,2) 將PCB使用計(jì)數(shù)器設(shè)置為2,表示活動(dòng)狀態(tài)
          --->copy_creds() 復(fù)制權(quán)限及身份信息
          --->檢測(cè)進(jìn)程總數(shù)是否超過max_threads
          --->初始化PCB中各個(gè)字段
          --->sched_fork() 調(diào)度器相關(guān)設(shè)置
          --->復(fù)制進(jìn)程所有信息copy_semundo(), copy_files(),
          --->copy_signal(), copy_mm()
          --->copy_thread() 復(fù)制線程
          --->alloc_pid() 分配pid
          --->更新屬性和進(jìn)程數(shù)量計(jì)數(shù)
          --->kernel/sched.c:wake_up_new_task() 把進(jìn)程放到運(yùn)行隊(duì)列上,讓調(diào)度器進(jìn)行調(diào)度
          --->kernel/sched.c:select_task_rq() 選擇最佳的CPU(SMP中有多個(gè)CPU)
          --->p->state = TASK_RUNNING 設(shè)置成TASK_RUNNING狀態(tài)
          --->activate_task()
          --->enqueue_task() 把當(dāng)前進(jìn)程插入到對(duì)應(yīng)CPU的runqueue上
          --->有CLONE_VFORK標(biāo)志:wait_for_completion() 讓父進(jìn)程阻塞,等待子進(jìn)程結(jié)束
          --->返回分配的pid
          kernel/sched.c:schedule() 調(diào)度新創(chuàng)建的進(jìn)程
          進(jìn)程運(yùn)行中
          exit() 用戶空間來調(diào)用(如C程序)
          --->0x80中斷跳轉(zhuǎn)到include/linux/syscalls.h:sys_exit()
          --->kernel/exit.c:do_exit() 負(fù)責(zé)進(jìn)程的退出
          --->struct task_struct *tsk = current; 獲取我的PCB
          --->set_fs(USER_DS) 設(shè)置使用的文件系統(tǒng)模式
          --->exit_signals() 清除信號(hào)處理函數(shù)并設(shè)置PF_EXITING標(biāo)志
          --->清除進(jìn)程一系列資源exit_mm(), exit_files()
          --->exit_fs(), exit_thread()
          --->kernel/exit.c:exit_notify() 退出通知
          --->forget_original_parent() 把我的所有子進(jìn)程過繼給init進(jìn)程
          --->kill_orphaned_pgrp() 向進(jìn)程組內(nèi)各進(jìn)程發(fā)送掛起信號(hào)SIGHUP及SIGCONT
          --->tsk->exit_signal = SIGCHLD; 向我的父進(jìn)程發(fā)送SIGCHLD信號(hào)
          --->kernel/exit.c:do_notify_parent() 通知父進(jìn)程
          --->如果父進(jìn)程處理SIGCHLD信號(hào),返回DEATH_REAP
          --->如果父進(jìn)程不處理SIGCHLD信號(hào),返回傳入時(shí)的信號(hào)值
          --->__wake_up_parent() 喚醒父進(jìn)程
          --->通知返回DEATH_REAP,設(shè)置exit_state為EXIT_DEAD 我退出并且死亡
          --->否則設(shè)置我為EXIT_ZOMBIE 我退出但沒死亡,成為僵尸進(jìn)程
          --->如果為DEATH_REAP:release_task() 我自己清理相關(guān)資源
          --->如果為僵尸,在我的父進(jìn)程退出時(shí)我會(huì)過繼給init進(jìn)程,由init負(fù)責(zé)清理
          --->exit_io_context() 清理IO上下文
          --->preempt_disable() 禁用搶占
          --->tsk->state = TASK_DEAD; 設(shè)置我為進(jìn)程死亡狀態(tài)
          --->kernel/sched.c:schedule() 釋放我的PCB,調(diào)度另一個(gè)新的進(jìn)程

          清理僵尸進(jìn)程:wait系統(tǒng)調(diào)用 等待子進(jìn)程結(jié)束
          --->0x80中斷最后到達(dá)kernel/exit.c:do_wait()
          --->do_wait_thread()
          --->wait_consider_task()
          --->如果子進(jìn)程為EXIT_DEAD,返回0,wait調(diào)用返回,子進(jìn)程自己清理自己
          --->如果子進(jìn)程為EXIT_ZOMBIE:wait_task_zombie()
          --->xchg() 設(shè)置僵尸子進(jìn)程為EXIT_DEAD
          --->release_task() 清理僵尸子進(jìn)程

          下面是基本的執(zhí)行流程圖:






          推薦閱讀:

          專輯|Linux文章匯總
          專輯|程序人生
          專輯|C語言
          我的知識(shí)小密圈

          關(guān)注公眾號(hào),后臺(tái)回復(fù)「1024」獲取學(xué)習(xí)資料網(wǎng)盤鏈接。

          歡迎點(diǎn)贊,關(guān)注,轉(zhuǎn)發(fā),在看,您的每一次鼓勵(lì),我都將銘記于心~


          瀏覽 44
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  极品少妇被猛得白浆直流草莓视频 | 亚洲AV成人精品一区二区三区 | 俺去啦在线观看 | 在线观看黄色视频网站 | 欧老太做爱 亚洲性猛交 |