进程管理
可以通过execl函数把新的程序加载到内存中,把当前进程的映像替换成新的映像。path指向程序的位置,arg是新程序的第一个参数,省略号表示数目不定的参数,但是必须以NULL结束:
int execl(const char *path, const char *arg, ...);
下面是该函数的一个例子,该段代码加载了ls对应的程序,ls运行结束之后返回,而不再执行main中在其后面的代码:
int main(){
int ret = execl("/bin/ls","ls", NULL);
if(ret == -1){
printf("调用execl函数失败\n");
printf("%s\n", strerror(errno));
}
printf("main");//不会执行
return 0;
}
execl的一次成功执行不仅仅是会离开旧程序、执行新程序,还有以下影响:
- 任何没有处理的信号会被丢掉,而新的程序捕捉到的信号都被调用默认的处理函数。
- 任何对内存的锁定都会被丢掉。
- 多数进程重置统计数据。
- 多数进程属性回归默认值。
- 与进程的内存有关的任何东西会被丢掉。
- 单独存在用户空间的任何东西会被丢掉。
fork调用可以产生一个当前进程的副本并继续执行,有点真假孙悟空的味道。子进程和父进程不同的地方在于fork调用的返回值,子进程返回0,而父进程返回子进程的PID。至于vfork与fork有什么区别相信大家都知道了,这里就不罗嗦,一个简单的例子:
int main(){
pid_t pid = vfork();
if(pid > 0){
printf("我是父进程!\n");
}else if(pid == 0){
printf("我是子进程!\n");
int ret = execl("/bin/ls", "ls", NULL);
if(ret == -1){
printf("execl调用失败!\n");
printf("%s\n", strerror(errno));
}
}else{
printf("fork调用失败!\n");
}
return 0;
}
再看进程的结束,进程的结束并不是想象的那么简单,至少比我想的复杂:
- 按照与注册相反的顺序调用以atexit或on_exit注册的任何函数。
- 刷新所有已打开的标准I/O流。
- 移除任何由tmpfile所创建的临时文件。
关于第一点可以用下面的程序看到效果:
void func_1(){
printf("func_1\n");
}
void func_2(){
printf("func_2\n");
}
int main(){
atexit(func_1);
atexit(func_2);
return 0;
}
不同的结束方式对第二点的现象是不同的,下面的两个程序的输出的差异就能说明这点:
int main(){
for(int i = 0; i < 10000; i++){
printf("%d ", i);
}
_exit(0);
}
该段代码不会输出到9999,说明没有刷新标准输出流,但是在exit的时候会完整第输出。如下:
int main(){
for(int i = 0; i < 10000; i++){
printf("%d ", i);
}
exit(0);
}
exit(stats)是无法返回任何值的,其中stats参数用来表示进程的结束状态,特别的是stats&0377会被返回给父进程。
如果子进程在父进程之前死亡,则内核让子进程进入一个特殊的进程状态。进入该状态的进程为僵尸进程,此时进程只会保留最小的骨架。处于此状态的进程会等待它的父进程来打听它的状态。只有在父进程取得保留的关于子进程的信息时,子进程才会正式结束并且不再存在。
wait的用法可以看下面的例子:
int main(){
pid_t pid = fork();
if(pid == 0){
exit(1);
}
sleep(2);
int stats;
pid = wait(&stats);
printf("%d %d\n", stats, (int)pid);
return 0;
}
这个是在等待所有的子进程,在取到一个子进程的结束状态之后就返回。可以在下面的例子中看到如何等待特定的子进程:
int main(){
pid_t pid = fork();
if(pid == 0)
exit(0);
pid = fork();
if(pid == 0)
exit(2);
printf("第二次产生进程的pid:%d\n", (int)pid);
int stats;
pid = waitpid(pid, &stats, WUNTRACED);
printf("等待的子进程的pid:%d\n", (int)pid);
return 0;
}
下面是一种更灵活的等待方式:
int waitid(idtpe_t idtype, id_t id, siginfo_t *infop, int options);
其中idtype可以的取值为下面的三种:
- P_PID:所要等待的是其进程ID与id相符的子进程。
- P_GID:所等待的是进程组ID域id相符的子进程。
- P_ALL:等待所有的子进程,忽略id参数。
而options可以是下面几组的一种或者组合:
- WEXITED:等待已终止的子进程。
- WSTOPPED:等待已停止执行的子进程。
- WCONTINUED:等待已继续执行的子进程。
- WNOHANG:不会阻挡。
- WNOWAIT:不会把进程从僵尸进程状态移除。
下面是waitid的一个简单的例子,虽然和上面waitpid的效果是一样的:
int main(){
pid_t pid = fork();
if(pid == 0)
exit(0);
pid = fork();
if(pid == 0)
exit(2);
printf("第二次产生进程的pid:%d\n", (int)pid);
int stats;
siginfo_t sig;
waitid(P_PID, pid, &sig, WEXITED);
printf("等待的子进程的pid:%d\n", sig.si_pid);
return 0;
}
system可以用来创建新进程并等待它的终止,可以视为同步进程的创建:
int main(){
system("ls");
return 0;
}
命令执行期间,SIGCHLD会被阻挡,而SIGINT和SIGQUIT会被忽略。忽略SIGINT和SIGQUIT会有几个问题,特别是system在一个循环中被调用的时候,这时应该确保程序会妥善检查子进程的结束状态。