Linux內核源碼流水筆記之進程的創建

Linux內核源碼流水筆記之進程的創建

基於linux.2.0 源碼

內核通進調用do_fork(clone_flags, newsp, &regs);創建一個進程

參數clone_flags表明了子進程要與父進程共享的資源,參數struct pt_regs結構體保存了用戶態下進程使用的cpu寄存器信息,在內核態父進程會從do_fork返回到用戶態,子進程不從do_fork返回,但父子進程最終會從ret_from_sys_call返回到用戶態的同一點繼續運行即fork處

 

do_fork()函數

  1. struct task_struct *p = (struct task_struct *) kmalloc(sizeof(*p), GFP_KERNEL);//申請一塊內存用於保存子進程的task_struct
  2. unsigned long new_stack = alloc_kernel_stack();//申請一頁內存1kb,做爲內核棧用於,用戶空間調用系統調用,處理信號,陷入內核時使用,指針就是一個整型
  3. int nr = find_empty_process();//從task_struct * task[NR_TASKS=512]數組中找出一個空閒的位置用於存放此進程的task_struct
  4. *p = *current;複製父進程的task_struct,淺拷貝
  5. p->state = TASK_UNINTERRUPTIBLE;設定子進程爲不可中斷的睡眠狀態,因爲正在創建,使其不可被調度不可被信號中斷,
  6. p->kernel_stack_page = new_stack;把內核棧地址保存到task_struct中
  7. p->pid = get_pid(clone_flags);如是vfork flags & CLONE_PID則父子進程共享pid,返回父進程的pid,反之爲進新進程尋找一個可用的進程pid,進程pid是一個有符號int整型,從1開始遞增,遞增至4294934528再從1開始,0是啓動內核的那個進程的pid,1是內核第一個啓動的進程init進程的pid,
  8. p->signal = 0;清空從父進程繼承過來的信號,linux內核使用一個32位的int表示信號集,每一位表示一個信號,信號範圍0-31,向進程發送信號就是置該信號值位爲1,使用kill向進程發送信號0可以檢測進程是否存在,使用pthread_kill向線程發送信號0可以檢測線程是否存在
  9. task[nr] = p;把新進程的task_struct存放到task_struct * task[NR_TASKS=512]數組中
  10.        SET_LINKS(p);  nr_tasks++;把新進程插入到一個以init_task開始的全局鏈表中已便可隨時遍歷所有存活的進程,並增加任務數
  11. copy_files(clone_flags, p) 如是vfork clone_flags & CLONE_FILES則增加引用計數,反之爲子進程分配一個struct file * fd[NR_OPEN=256]數組拷貝父進程打開的所有文件描述符struct file * fd[NR_OPEN=256]到子進程中,此爲淺拷貝,所以父進程與子進程共享父進程打開的所有文件描述符,struct file結構體保存了文件的inode,文件讀寫偏移量,及操作此inode所對應文件的open,read,write,close等函數,因此inode可以表示任何文件系統中的一個磁盤文件,也可表示一個socket或管道
  12. copy_sighand(clone_flags, p) 如是vfork clone_flags & CLONE_SIGHAND則增加引用計數,反之拷貝父進程註冊的信號處理函數,所以子進程會繼承父進程註冊的信號處理函數
  13. copy_mm(clone_flags, p) 如是vfork clone_flags & CLONE_VM則增加引用計數,把子進程頁表指針指向父進程的頁表即可,反之拷貝父進程的整個虛擬地址空間。linux內核1.0之前是使用單鏈表來管理進程的虛擬地址空間,對於頻繁訪問的虛擬地址,當32,64位系統的到來,伴隨着的是虛擬地址空間的爆增,鏈表的查找變的十分低效。linux2.0中使用紅黑樹來管理虛擬地址空間,拷貝虛擬地址空間就是爲子進程構建一棵紅黑樹把父進程的各個虛擬地址段節點插入到子進程的樹中,同時在此函數中會爲子進程申請頁表,拷貝父進程的頁表到子進程的頁表,並標記整個虛擬地址空間爲讀共享,父子進程以只讀的方式共享父進程的整個虛擬地址空間,此爲fork的copyOnWrite,當有任何一方對共享空間中的一頁內存進行寫操作(就算1字節也會)將產生內存寫錯誤然後會調用do_wp_page函數申請一頁內存,拷貝此頁到新申請的內存中,並把此內存的地址通過虛擬地址找到對應的頁表項,放到發起寫操作進程的頁表項中
  14. copy_thread(nr, clone_flags, usp, p, regs);初始化子進程struct thread_struct tss結構體,設置子進程堆棧信息及各個寄存器的值,在此函數內部會設定子進程的tss.eip = (unsigned long) ret_from_sys_call; eax = 0;當子進程被調度運行時,會從ret_from_sys_call返回0到用戶空間,完成fork調用的從子進程返回,設定子進程內核態堆棧指針p->tss.esp0 = p->kernel_stack_page + PAGE_SIZE;
  15. wake_up_process(p);    設置子進程狀態爲p->state = TASK_RUNNING;並把子進程加入可運行隊列,以便可以被schedule調度運行
  16. ++total_forks; 用於計算系統負載時使用
  17. return p->pid;父進程直接從do_fork返回,返回子進程pid,
  18. end

 

子進程與父進程僅共享父進程打開的文件描述符和代碼段,對於父進程持有的鎖資源,子進程並不會持有,對於用於進程間同步的信號量,文件鎖,及通信的共享內存,消息隊列子進程會獲得這些資源的引用拷貝,可以不用在子進程中重新創建而直接使用

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