20135239益西拉姆 Linux內核分析 進程的描述和進程的建立

【益西拉姆 原創做品轉載請註明出處 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000】node

第六週 進程的描述和進程的建立

1、 進程的描述

  • 進程控制塊PCB——task_struct編程

  • 爲了管理進程,內核必須對每一個進程進行清晰的描述,進程描述符提供了內核所需瞭解的進程信息。數據結構

  • struct task_struct數據結構很龐大
  • Linux進程的狀態與操做系統原理中的描述的進程狀態彷佛有所不一樣,好比就緒狀態和運行狀態都是TASK_RUNNING,爲何呢?
  • 進程的標示pid
  • 全部進程鏈表struct list_head tasks;
    • 內核的雙向循環鏈表的實現方法 - 一個更簡略的雙向循環鏈表
  • 程序建立的進程具備父子關係,在編程時每每須要引用這樣的父子關係。進程描述符中有幾個域用來表示這樣的關係
  • Linux爲每一個進程分配一個8KB大小的內存區域,用於存放該進程兩個不一樣的數據結構:Thread_info和進程的內核堆棧
    • 進程處於內核態時使用, 不一樣於用戶態堆棧,即PCB中指定了內核棧,那爲何PCB中沒有用戶態堆棧?用戶態堆棧是怎麼設定的?
    • 內核控制路徑所用的堆棧 不多,所以對棧和Thread_info 來講,8KB足夠了
  • struct thread_struct thread; //CPU-specific state of this task
  • 文件系統和文件描述符
  • 內存管理——進程的地址空間

進程的建立

  • 進程的建立概覽及fork一個進程的源代碼
  • 回顧:
    • startkernel建立了cpuidle,也就是0號進程。而0號進程又建立了兩個線程,一個是kernel_init,也就是1號進程,這個進程最終啓動了用戶態;
    • 另外一個是kthreadd。這就是「道生一,一輩子二」。0號進程是固定的代碼;
    • 1號進程是經過複製0號進程PCB以後在此基礎上作修改獲得的。
  • iret與int 0x80指令對應,一個是彈出寄存器值,一個是壓入寄存器的值
  • 若是將系統調用類比於fork();那麼就至關於系統調用建立了一個子進程,而後子進程返回以後將在內核態運行,而返回到父進程後仍然在用戶態運行

fork的一個子進程代碼

`#include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 int main(int argc, char * argv[])
 {
      int pid;
       / * fork another process */
      pid = fork();
      if (pid < 0) 
      { 
          /* error occurred */
          fprintf(stderr,"Fork Failed!");
          exit(-1);
      } 
      else if (pid == 0) 
      {
           /* child process */
          printf("This is Child Process!\n");
      } 
      else 
      {  
           /* parent process  */
           printf("This is Parent Process!\n");
           /* parent will wait for the child to complete*/
           wait(NULL);
           printf("Child Complete!\n");
      }  
}

`框架

建立一個新進程在內核中的執行過程

  1. fork、vfork和clone三個系統調用均可以建立一個新進程,並且都是經過調用do_fork來實現進程的建立;
  2. Linux經過複製父進程來建立一個新進程,那麼這就給咱們理解這一個過程提供一個想象的框架:函數

    • 複製一個PCB——task_struct
    • err = arch_dup_task_struct(tsk, orig);
    • 要給新進程分配一個新的內核堆棧
    • `ti = allocthreadinfo_node(tsk, node);this

      tsk->stack = ti;操作系統

      setupthreadstack(tsk, orig); //這裏只是複製,而非複製內核堆`線程

  3. 要修改複製過來的進程數據,好比pid、進程鏈表等等都要改改吧,見copy_process內部。
  4. 從用戶態的代碼看fork();函數返回了兩次,即在父子進程中各返回一次,父進程從系統調用中返回比較容易理解,子進程從系統調用中返回,那它在系統調用處理過程當中的哪裏開始執行的呢?這就涉及子進程的內核堆棧數據狀態和taskstruct中thread記錄的sp和ip的一致性問題,這是在哪裏設定的?copythread in copy_process
  5. *childregs = *current_pt_regs(); //複製內核堆棧
  6. childregs->ax = 0; //爲何子進程的fork返回0,這裏就是緣由!
  7. p->thread.sp = (unsigned long) childregs; //調度到子進程時的內核棧頂
  8. p->thread.ip = (unsigned long) ret_from_fork; //調度到子進程時的第一條指令地址

使用gdb跟蹤建立新進程的過程

  • 更新menu內核,而後刪除test_fork.c以及test.c(以減小對以後實驗的影響
  • 編譯內核,能夠看到fork命令
  • 啓動gdb調試,並對主要的函數設置斷點
  • 在MenuOS中執行fork,就會發現fork函數停在了父進程中
  • 繼續執行以後,停在了dofork的位置。而後n單步執行,依次進入copyprocess、duptaskstruct。按s進入該函數,能夠看到dst = src(也就是複製父進程的struct)
  • 在copythread中,能夠看到把taskpg_regs(p)也就是內核堆棧特定的地址找到並初始化
  • 到了15九、160行的代碼就是把壓入的代碼再放到子進程中:3d

    `*children = *current_pt_regs();
    
      childregs->ax = 0;`
  • 164行,是肯定返回地址 p->thread.ip = (unsigned long) ret_from_fork;
  • 最後輸入finish運行完畢。

總結

本週主要就是課本的進程一章的拓展,經過實踐來更加的運用完整,頗有趣。調試

相關文章
相關標籤/搜索