1、進程有哪些狀態?什麼是進程的可中斷等待狀態?進程退出後為什麼要等待調度器刪除其task_struct結構?進程的退出狀態有哪些?
TASK_RUNNING可運行狀態)
TASK_INTERRUPTIBLE可中斷等待狀態)
TASK_UNINTERRUPTIBLE不可中斷等待狀態)
TASK_STOPPED進程被其它進程設定為暫停狀態)
TASK_TRACED進程被調試器設定為暫停狀態)
TASK_DEAD退出狀態)
進程由於所需資源得不到滿足,從而進入等待隊列,但是該狀態能夠被訊號中斷。比如當一個正在啟動並執行進程因進行磁碟I/O操作而進入可中斷等待狀態時,在I/O操作完成之前,使用者可以向該進程發送SIGKILL,從而使該進程提前結束等待狀態,進入可運行態,以便響應SIGKILL,執行進程結束代碼,從而結束該進程。
當進程退出時例如調用exit或者從main函數返回),需要向父進程發送訊號,父進程進行訊號處理時,需要擷取子進程的資訊,因此這時不能刪除子進程的task_struct。另外每個進程都有一個核心態的堆棧,當進程調用exit()時,在切換到另外一個進程前,總是要使用核心態堆棧,因此當進程調用exit()時,完成必要的處理後,就把state設定為TASK_DEAD,並切換到其他進程。當順利地切換到其他進程後,由於該進程的狀態設定為TASK_DEAD,因此這個進程不會被調度,之後當調度器檢查到狀態為TASK_DEAD的進程時,就會刪除這個進程的task_struct結構,這樣這個進程就徹底的消失了。
EXIT_ZOMBIE僵死進程):父進程等待子進程結束時發送的SIGCHLD訊號預設情況下,建立進程都會設定在進程退出的時候向父進程發送訊號的標誌,除非建立的是輕權進程),此時子進程已退出,並且SIGCHLD訊號已經發送,但是父進程還沒有被調度運行;EXIT_DEAD僵死撤銷狀態):父進程對子進程的退出訊號“沒興趣”,或者在子進程退出時,父進程通過waitpid()調用等待子進程的SIGCHLD訊號。
2、殭屍進程
1) 怎麼產生殭屍進程
一個進程在調用exit命令結束自己的時候,其實它並沒有真正的被銷毀,只是進程不能被調度並處於EXIT_ZOMBIE狀態,它佔用的所有記憶體就是核心棧、thread_info結構和task_struct結構。此時進程存在的唯一目的就是向它的父進程提供資訊,如果它的父進程沒有調用wait或waitpid等待子進程結束,又沒有顯示地忽略該訊號,那麼它就一直保持EXIT_ZOMBIE狀態。
2) 怎麼查看殭屍進程
利用命令ps,看到有標記為Z的進程就是殭屍進程。
3) 怎麼清理殭屍進程
- 父進程可以調用waitpid、wait函數來等待子進程結束
- 把父進程殺掉,父進程死後,殭屍進程成為“孤兒進程”,過繼給init進程,init進程始終負責清理殭屍進程,它產生的所有殭屍進程也跟著消失。
3、PID管理
在Linux系統中用pid結構體來標識一個進程,通過pidmap位元影像來管理所有的進程號(即pid:與前面的pid結構體不是同一個意思),目的就是要更快的找到目標進程。用pid結構體來表示進程的優點:比直接用數字pid_t更容易管理(進程退出時pid回收再分配效率高),比直接用task_struct標識進程佔用空間小。
pid結構體如下所示:
- struct pid
- {
- atomic_t count;
- int nr; /*存放pid數值*/
- struct hlist_node pid_chain; /*把該pid鏈到雜湊表中*/
- struct hlist_head tasks[PIDTYPE_MAX];
- struct rcu_head rcu;
- };
因為對於32位系統來說,預設最大的pid為32768,由於pidmap位元影像中每一位表示這個pid是否可用,共需要32768位,正好一個物理頁的大小(4*1024*8)。
pidmap結構體如下所示:
- struct pidmap {
- /*
- *這個變數用來統計這個結構體對應的一頁實體記憶體中有多少個位
- *是0的,即空閑pid的數量
- */
- atomic_t nr_free;
- void *page; /*這個就是指向存放這個位元影像的記憶體頁的指標*/
- };
下面首先來看Linux核心啟動之初在start_kernel函數中對pidmap位元影像的初始化函數pidmap_init如下所示:
- void __init pidmap_init(void)
- {
- /*申請一頁實體記憶體,並初始化為0*/
- init_pid_ns.pidmap[0].page = kzalloc(PAGE_SIZE, GFP_KERNEL);
- /*將第0位設定為1,表示當前進程使用pid為0,即現在就是0號進程*/
- set_bit(0, init_pid_ns.pidmap[0].page);
- /*同時更新nr_free統計空閑pid的值*/
- atomic_dec(&init_pid_ns.pidmap[0].nr_free);
- pid_cachep = KMEM_CACHE(pid, SLAB_PANIC);
- }
再來看Linux核心啟動之初在start_kernel函數中對pid hash表的初始化函數pidhash_init如下所示:
- void __init pidhash_init(void)
- {
- int i, pidhash_size;
- /*
- *nr_kernel_pages表示核心記憶體總頁數,就是系統DMA和NORMAL內
- *存頁區的實際實體記憶體總頁數
- *megabytes:統計的是核心記憶體有多少MB
- */
- unsigned long megabytes = nr_kernel_pages >> (20 - PAGE_SHIFT);
- /*從下面兩行代碼可以看出pidhash_shift是在4~12之間的*/
- pidhash_shift = max(4, fls(megabytes * 4));
- pidhash_shift = min(12, pidhash_shift);
- pidhash_size = 1 << pidhash_shift;
- printk("PID hash table entries: %d (order: %d, %Zd bytes)\n",
- pidhash_size, pidhash_shift,
- pidhash_size * sizeof(struct hlist_head));
- /*
- *由alloc_bootmem可知pid_hash是在低端實體記憶體申請的,由於
- *pidhash_init函數是在mem_init函數執行之前被調用的,所以這裡申請
- *的記憶體是不會被回收的
- */
- pid_hash = alloc_bootmem(pidhash_size * sizeof(*(pid_hash)));
- if (!pid_hash)
- panic("Could not alloc pidhash!\n");
- for (i = 0; i < pidhash_size; i++)
- /*初始化每個表的每個表項的鏈表*/
- INIT_HLIST_HEAD(&pid_hash[i]);
- }
總結:核心維護兩個資料結構來維護進程號pid,一個是雜湊表pid_hash,還有一個位元影像pidmap。在do_fork()中每調用一次alloc_pid(),首先會通過調用alloc_pidmap()修改相應的位元影像,該函數的主要思想是:last記錄上次分配的pid,此次分配的pid為last+1,如果pid超出最大值,那麼就迴圈回到最初值(RESERVED_PIDS),然後測試pidmap上該pid所對應的bit是否為0,直到找到為止。其次通過hlist_add_head_rcu函數在pid_hash表中增加一項。