Linux 下的工做都是依靠進程來執行的,控制了進程就至關於控制了 Linux 系統了。這篇博客將經過 Linux 系統的啓動登陸來探討進程管理機制,看這種機制如何支撐和左右進程的命運。linux
先來了解了解什麼是進程,程序這個詞比較好理解,一般的程序是靜態實體,進程是正在運行的程序實體,而且包括這個運行的程序中佔據的全部系統資源,好比說CPU(寄存器),IO,內存,網絡資源等。進程描述符(PID)是惟一用來標識進程的。c++
在運行級別3下啓動 Linux,出現命令行界面須要在「login: 」提示符處輸入用戶名登陸,能夠另外找一臺機子ssh遠程鏈接,查看一下mingetty
進程的執行狀況:git
# ps -ef|grep mingett[y] root 14450 1 0 14:10 tty1 00:00:00 /sbin/minagetty --noclear tty1 linux root 14566 1 0 14:13 tty2 00:00:00 /sbin/minagetty --noclear tty2 linux root 14589 1 0 14:16 tty3 00:00:00 /sbin/minagetty --noclear tty3 linux root 14591 1 0 14:16 tty4 00:00:00 /sbin/minagetty --noclear tty4 linux root 14593 1 0 14:16 tty5 00:00:00 /sbin/minagetty --noclear tty5 linux root 14595 1 0 14:16 tty6 00:00:00 /sbin/minagetty --noclear tty6 linux
這裏有6個mingetty
進程,對應CTR
L+ALT
+F1~F6
六個虛擬控制檯。github
在tty1輸入用戶名並按回車,這裏先不要輸入密碼,回到ssh遠程登陸終端上,再看看mingetty
進程,會發現少了PID爲14450的mingetty
進程,能夠利用ps命令檢索PID。shell
# ps -ef|grep 1445[0] root 14450 1 0 15:36 tty1 00:00:00 /bin/login --
PID爲14450的進程變成了login進程了。這是由於mingetty
進程在exec()
系統調用的做用下,轉變成了login
進程。bash
exec
的做用是捨棄進程原先攜帶的信息,在進程執行時用新的程序代碼替代調用進程的內容。網絡
能夠分析一下mingetty
進程中運行exec
的部分源碼:session
exec(loginprog, loginprog, autologin? "-f" : "--", logname, NULL);
mingetty
進程的工做是接收登陸用戶名,以後的密碼驗證處理工做則是 login 進程的工做,當驗證結束後,便啓動用戶的bash進程。ssh
一樣的再次檢索同一個PID會發現 login 進程保留了原先的相同的進程,並且還多了一個 bash 進程。這是由於 bash 進程的父進程ID是14450,這說明bash進程是做爲 login 進程的子進程開始啓動的。函數
┌─────┐ │進程 │PID=X │程序=A └─────┘ │ ↓ ┌─────┐ │進程 │PID=X │程序=B └─────┘
┌─────┐ │父進程 │PID=X ──┓ │程序=A │ └─────┘ │ fork │ │ ↓ ↓ ┌─────┐ ┌─────┐ │父進程 │子進程 │PID=X │ PID=Y │程序=A │程序=A └─────┘ └─────┘
一般fork一個進程是指經過父進程建立一個子進程,生成的子進程與父進程只有PID不同,login 進程經過fork
生成一個自身的副本後,還會在子進程經過exec
啓動 bash 。這樣的機制叫作「fork-exec」
。
childPid = fork();//建立子進程 if (childPid < 0) { int errsv = errno; fprintf(stderr, _("login: failure forking: %s"), strerror(errsv)); PAM_END; exit(0); } if (childPid) {//父進程,等待子進程退出 /* parent - wait for child to finish, then cleanup session */ signal(SIGHUP, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); signal(SIGTSTP, SIG_IGN); signal(SIGTTIN, SIG_IGN); signal(SIGTTOU, SIG_IGN);//忽略以上信號 wait(NULL);//等待子進程結束 PAM_END;//PAM結束 exit(0); } //如下是子進程 /* child */ //(省略部分源碼) childArgv[childArgc++] = NULL; //登陸成功,執行/bin/sh進入shell execvp(childArgv[0], childArgv + 1);
上面是login.c的源碼,能夠知道父進程會一直等待子進程結束(wait),父進程纔會結束。
在已登陸的控制檯上輸入 exit
進行用戶註銷,此時exit()
系統調用,bash進程會被終止,同時發送CHLD
信號給父進程login。接收到CHLD
信號的父進程login會退出wait
函數,同時結束進程。wait
是一個函數,它讓父進程在接收子進程CHLD
信號以前一直保持休眠狀態。
另外一方面,子進程在向父進程發送CHLD
信號,直到父進程接收爲止,子進程一直保持殭屍狀態。