理解進程的退出
- 2020 年 3 月 12 日
- 筆記
當一個進程調用exit的時候,就意味著他退出了。我們看一下他退出的時候,都做了什麼操作。
int sys_exit(int error_code) { return do_exit((error_code&0xff)<<8); } int do_exit(long code) { int i; // 釋放程式碼段和數據段頁表,頁目錄,物理地址 free_page_tables(get_base(current->ldt[1]),get_limit(0x0f)); free_page_tables(get_base(current->ldt[2]),get_limit(0x17)); for (i=0 ; i<NR_TASKS ; i++) // 找出當前進程的子進程 if (task[i] && task[i]->father == current->pid) { // 子進程的新父進程是進程id為1的進程 task[i]->father = 1; /* 父進程沒有調wait,子進程退出了,然後父進程也退出了。沒人回收子進程的pcb,給init進程發 */ if (task[i]->state == TASK_ZOMBIE) /* assumption task[1] is always init */ (void) send_sig(SIGCHLD, task[1], 1); } // 關閉文件 for (i=0 ; i<NR_OPEN ; i++) if (current->filp[i]) sys_close(i); // 回寫inode到硬碟 iput(current->pwd); current->pwd=NULL; iput(current->root); current->root=NULL; iput(current->executable); current->executable=NULL; // 是會話首進程並打開了終端 if (current->leader && current->tty >= 0) tty_table[current->tty].pgrp = 0; if (last_task_used_math == current) last_task_used_math = NULL; // 是會話首進程,則通知會話里的所有進程會話結束 if (current->leader) kill_session(); // 更新狀態 current->state = TASK_ZOMBIE; current->exit_code = code; // 通知父進程 tell_father(current->father); // 重新調度進程 schedule(); return (-1); /* just to suppress warnings */ }
釋放地址
// from是線性地址。釋放from開始,連續的n個大小為4MB的頁面對應的物理地址。最後釋放頁表、頁目錄項 int free_page_tables(unsigned long from,unsigned long size) { unsigned long *pg_table; unsigned long * dir, nr; // 判斷是否按4MB對齊 if (from & 0x3fffff) panic("free_page_tables called with wrong alignment"); if (!from) panic("Trying to free up swapper memory space"); // 算出size包含多少個MB,比如size是0 - 1>>22,則電腦後是1 size = (size + 0x3fffff) >> 22; /* 頁目錄在地址0開始的地方,首先右移得到頁目錄索引, 根據索引得到頁目錄項內容,因為頁目錄項的內容佔4個位元組, 其中高20位是頁表地址,低12位是標記位,,所以要乘以4得到 from對應的頁目錄項的地址。即dir = from >> 22 << 2 = from >> 20, 但是程式碼里是直接右移20位,所以需要和0xffc與,把低兩位置0,最後得到from 對應的頁目錄項的地址 */ dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */ for ( ; size-->0 ; dir++) { // 低位是1說明該頁目錄項有效 if (!(1 & *dir)) continue; // *dir為頁表首地址,與0xfffff000是因為高二十位是有效地址,低12位是標記位 pg_table = (unsigned long *) (0xfffff000 & *dir); // 釋放每個頁表指向的物理地址 for (nr=0 ; nr<1024 ; nr++) { // 頁表是否有效,有效則釋放*pg_table指向物理地址,以4kb對齊 if (1 & *pg_table) // 與0xfffff000是因為高二十位是有效地址,低12位是標記位 free_page(0xfffff000 & *pg_table); // 置頁表無效 *pg_table = 0; // 下一個頁表 pg_table++; } // 釋放頁表佔據的物理地址 free_page(0xfffff000 & *dir); // 置頁目錄項為無效 *dir = 0; } invalidate(); return 0; }
結束會話
// 結束會話,給該會話的所有進程發SIGHUP訊號,因為子進程會繼承父進程的sessionid,所以if可能會多次成立 static void kill_session(void) { struct task_struct **p = NR_TASKS + task; while (--p > &FIRST_TASK) { if (*p && (*p)->session == current->session) (*p)->signal |= 1<<(SIGHUP-1); } }
通知父進程
// 子進程退出,通知進程id是pid的父進程 static void tell_father(int pid) { int i; if (pid) for (i=0;i<NR_TASKS;i++) { if (!task[i]) continue; if (task[i]->pid != pid) continue; // 根據pid找到父進程,設置子進程退出的訊號 task[i]->signal |= (1<<(SIGCHLD-1)); return; } /* if we don't find any fathers, we just release ourselves */ /* This is not really OK. Must change it to make father 1 */ printk("BAD BAD - no father foundnr"); // 釋放pcb結構 release(current); }
我們發生子進程釋放了一系列的資源,但是沒有釋放pcb。即task_struct結構體。這時候子進程是殭屍進程。需要等待父進程處理。
// 等待pid進程退出,並且把退出碼寫到stat_addr變數 int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options) { int flag, code; struct task_struct ** p; verify_area(stat_addr,4); repeat: flag=0; for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) { // 過濾不符合條件的 if (!*p || *p == current) continue; // 不是當前進程的子進程則跳過 if ((*p)->father != current->pid) continue; // pid大於0說明等待某一個子進程 if (pid>0) { // 不是等待的子進程則跳過 if ((*p)->pid != pid) continue; } else if (!pid) { // pid等於0則等待進程組中的進程,不是當前進程組的進程則跳過 if ((*p)->pgrp != current->pgrp) continue; } else if (pid != -1) { // 不等於-1說明是等待某一個組的,但不是當前進程的組,組id是-pid的組,不是該組則跳過 if ((*p)->pgrp != -pid) continue; } // else { // 等待所有進程 // } // 找到了一個符合條件的進程 switch ((*p)->state) { // 子進程已經退出,這個版本沒有這個狀態 case TASK_STOPPED: if (!(options & WUNTRACED)) continue; put_fs_long(0x7f,stat_addr); return (*p)->pid; case TASK_ZOMBIE: // 子進程已經退出,則返回父進程 current->cutime += (*p)->utime; current->cstime += (*p)->stime; flag = (*p)->pid; code = (*p)->exit_code; release(*p); put_fs_long(code,stat_addr); return flag; default: // flag等於1說明子進程還沒有退出 flag=1; continue; } } // 還沒有退出的進程 if (flag) { // 設置了非阻塞則返回 if (options & WNOHANG) return 0; // 否則父進程掛起 current->state=TASK_INTERRUPTIBLE; // 重新調度 schedule(); /* 在schedule函數里,如果當前進程收到了訊號,會變成running狀態, 如果current->signal &= ~(1<<(SIGCHLD-1)))為0,即...0000000100000... & ...111111110111111... 說明當前需要處理的訊號是SIGCHLD,因為signal不可能為全0,否則進程不可能被喚醒, 即有子進程退出,跳到repeat找到該退出的進程,否則說明是其他訊號導致了進程變成可執行狀態, 阻塞的進程被訊號喚醒,返回EINTR */ if (!(current->signal &= ~(1<<(SIGCHLD-1)))) goto repeat; else return -EINTR; } return -ECHILD; } //父進程在wait_pid調用時釋放pcb的一頁記憶體,重新調度進程 void release(struct task_struct * p) { int i; if (!p) return; for (i=1 ; i<NR_TASKS ; i++) if (task[i]==p) { task[i]=NULL; free_page((long)p); schedule(); return; } panic("trying to release non-existent task"); }