blog
blog copied to clipboard
Linux Process Manage
Linux 下的工作都是依靠进程来执行的,控制了进程就相当于控制了 Linux 系统了。这篇博客将通过 Linux 系统的启动登录来探讨进程管理机制,看这种机制如何支撑和左右进程的命运。
什么是进程
先来了解了解什么是进程,程序这个词比较好理解,通常的程序是静态实体,进程是正在运行的程序实体,并且包括这个运行的程序中占据的所有系统资源,比如说CPU(寄存器),IO,内存,网络资源等。进程描述符(PID)是唯一用来标识进程的。
进程的创建
fork 和 exec 分别是进程的分身和变身
在运行级别3下启动 Linux,出现命令行界面需要在“login: ”提示符处输入用户名登录,可以另外找一台机子ssh远程连接,查看一下mingetty进程的执行情况:
# 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进程,对应CTRL+ALT+F1~F6六个虚拟控制台。
在tty1输入用户名并按回车,这里先不要输入密码,回到ssh远程登录终端上,再看看mingetty进程,会发现少了PID为14450的mingetty进程,可以利用ps命令检索PID。
# ps -ef|grep 1445[0]
root 14450 1 0 15:36 tty1 00:00:00 /bin/login --
PID为14450的进程变成了login进程了。这是因为mingetty进程在exec()系统调用的作用下,转变成了login进程。
exec的作用是舍弃进程原先携带的信息,在进程执行时用新的程序代码替代调用进程的内容。
可以分析一下mingetty进程中运行exec的部分源码:
exec(loginprog, loginprog, autologin? "-f" : "--", logname, NULL);
mingetty进程的工作是接收登录用户名,之后的密码验证处理工作则是 login 进程的工作,当验证结束后,便启动用户的bash进程。
同样的再次检索同一个PID会发现 login 进程保留了原先的相同的进程,而且还多了一个 bash 进程。这是因为 bash 进程的父进程ID是14450,这说明bash进程是作为 login 进程的子进程开始启动的。
exec 和 fork 中进程的变化
┌─────┐
│进程
│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信号,直到父进程接收为止,子进程一直保持僵尸状态。
不错,作者写的很简洁,表述的很清楚
@renwotao :smile:
不错是不错,能不能举一个例子。稍微完整一点的。 fork多个,等待结束的例子最好。
@skyformat99 简单写个例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void) {
pid_t pid;
int status;
printf("current pid = %d\n", getpid());
printf("fork one:\n");
if ((pid = fork()) < 0)
fprintf(stderr, "fork error\n");
else if (pid == 0) { /* child */
printf("fork child #1, pid = %d\n", getpid());
exit(0);
}
/* parent */
if (wait(&status) != pid)
fprintf(stderr, "wait error\n");
else
printf("child #1 exit\n");
printf("\n");
printf("fork two:\n");
if ((pid = fork()) < 0)
fprintf(stderr, "fork error\n");
else if (pid == 0) { /* child */
printf("fork child #2, pid = %d\n", getpid());
exit(0);
}
printf("no wait\n");
if (wait(&status) != pid)
fprintf(stderr, "wait error\n");
else
printf("child #2 exit\n");
printf("\nprocess exit\n");
}
并不太复杂,两次fork,第一次fork之后会在父进程中阻塞等待第一个子进程结束才进行第二次fork,而第二次fork会在阻塞之前打印“no wait”,所以输出的情况你可能看到这样子的:
current pid = 20405
fork one:
fork child #1, pid = 20406
child #1 exit
fork two:
no wait
fork child #2, pid = 20407
child #2 exit
process exit
“no wait"可能会在打印第二个子进程之前输出。