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
信号,直到父进程接收为止,子进程一直保持僵尸状态。