今天学习到进程等待wait()和进程结束exit(),遇到很多不懂的问题,写一个示例代码研究了大半天。
首先还是贴几个前辈的链接,以后自己复习也好找资料。
http://blog.sina.com.cn/s/blog_759803690101aqeq.html --->wait()
http://hi.baidu.com/shoujiban/item/760791a881201c93151073bc --->kill()
http://blog.csdn.net/zzyoucan/article/details/9235685 --->kill()
http://wenku.baidu.com/view/ef2ad7ecaeaad1f346933ffe.html --->exit()
进程等待就是为了同步父进程和子进程,如把运算放到子进程,赋值放到父进程,可能需要让父进程等待子进程运算结束。
通常需要通过父进程调用wait()等待函数,如果父进程没有调用等待函数,子进程就会进入“Zombie”状态。所谓的“Zombie”状态,简单的说,
就是进程调用exit()结束运行后,它并没有完全被销毁,而是成为一个僵尸进程。还保存少量的信息,可被其它进程取得。
wait():
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
在终端输入命令:man wait 可看到除了wait(),还有waitpid()等函数。这两个的区别是:wait()函数用于等待所有子进程的结束,而waitpid()函数仅用于等待某个特定进程的结束,这个特定的进程是指其pid与函数中的参数pid相关时。详解请查看终端下的解释。提示:遇到不是很明白的函数或参数什么的,在终端输入 man 函数名或参数名 即可看到详解,如:man wait() . 挑对自己需要的信息看,如参数,返回值。不是很懂英文的,复制贴到谷歌翻译里看也能大概明白。但技术类的东西不能全信翻译,译过来就可能有问题了。
wait()函数的工作过程是:先判断子进程是否存在,即是否成功创建了一个子进程。如果创建失败,则会直接退出并提示相关错误信息;
如果创建成功,wait()将父进程挂起,直到子进程结束,并返回子进程结束时的状态和PID。
如果不存在子进程,提示的错误信息为ECHLD,表示wait()系统调用的进程没有可以等待的子进程。
如果有子进程,退出时的结束状态(status)有以下两种:
1、子进程正常结束:如调用exit(0)。
2、信号引起子进程结束:如调用kill(pid_t pid, int sig);
关于exit()函数,它是用于终止或结束一个进程的。系统调用除了exit()还有_exit()。
exit():
#include<stdlib.h>
void exit(int status);
exit()函数终止进程后,会把
status&0377 运算后的值返回给父进程,在父进程中通过wait()函数可获得该值。这里可以看出
wait()返回的那个整形值其实是保存了子进程结束时的信息的。我们很难读取里面的含义。所以人们又定义了一套宏来让我们更直观的去理解。这个宏后面会解释一下。
注意:exit()函数调用成功与失败都没有返回值,并且没有出错信息提示。
_exit():
#include<unistd.h>
void _exit(int status);
_exit()与exit()功能类似,都用来终止进程。也是没有返回信息。
exit()与_exit()的区别:
首先我们回忆一下,在使用vfork()函数创建的子进程在退出时只能使用_exit()函数退出而不能用exit()。这是因为,在调用exit()函数时,会对输入/输出流进行刷新,释放所占用的资源以及清空缓冲区等;而_exit()函数没有刷新缓冲区的功能。
提醒:如果在fork()函数和vfork()函数中使用exit()终止子进程,会清空标准输入/输出流,可能造成临时文件丢失。而且如果是用vfork()函数创建子进程的话,父子进程是共享虚拟内存的,如果在子进程中使用exit()函数会严重影响到父进程,所以少用exit()函数终止子进程。后面的代码中,没有那么复杂,所以直接用exit();
关于kill()函数,是用于给一个进程或进程组发送信号的函数的。原型如下:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数:pid
pid > 0时,pid是信号欲送达的进程的标识
pid = 0时,信号送往所有与调用kill()的那个进程同属一个进程组的进程。就是发给一个进程组。
pid = -1时,信号送往所有调用进程有权给其发送信号的进程,除了进程1(init)。
pid < -1时,信号送往以-pid为组标识的进程。
参数:sig
准备发送的信号代码,假如其值为0则没有任何信号送出,但是系统会执行错误检查,通常会利用sig为0来检验某个进程是不否仍在执行。
在终端下输入:kill -l 命令可看到32种信号类型,每种类型可用其英文名表示,也可以用其信号对应的数字表示。当然如果是在终端执行kill命令的话,sig就要用信号ID,也就是用数字。而后面的示例代码中kill()用两种方法都可以,作用都一样。
解释几个后面示例代码中用到的信号类型
9)SIGKILL,该信号用来立即结束进程的运行,并且不能被阻塞、处理或忽略。默认操作:终止。
17)SIGCHLD,子进程改变状态时,父进程会收到这个信号。默认操作:忽略。
19)SIGSTOP,信号用于暂停一个进程,且不能被阻塞、处理或忽略。默认操作:暂停进程。
好了,其它的类型可以自己在网上找,或直接在终端查看。建议了解个大概之后,再去终端用man命令查看原始解释,会有收获。
返回值说明:成功则返回0,成功就是说至少一个进程被发送。失败返回-1,errno被适当设置。在终端用 man errno 命令看详解。
现在说说关于示例中定义的功能函数exit_s()用到的宏定义
WIFEXITED(status):该宏定义的作用是当子进程正常退出时,返回真值。正常退出是指系统通过调用exit()和_exit()在main()函数中返回。
WIFSIGNALED(status):表示当子进程被没有捕获的信号终止时,返回真值。
WIFSTOPPED(status):当子进程接收到停止信号时,返回真值。这种情况仅出现在调用waitpid()函数时使用了WUNTRACED选项。
WIFCONTINUED(status):该宏表示当子进程接收到信号SIGCONT时,继续运行。
WEXITSTATUS(status):返回子进程正常退出时的状态,该宏定义只适用于当WIFEXITED为真值时。
WTERMSIG(status):用于子进程被信号终止的情况,返回此信号类型,该宏用于WIFSIGNALED为真值时。
WSTOPSIG(status):返回使子进程停止的信号类型,该宏用于WIFSTOPPED为真值时。
最后一个函数,getpid()的作用时返回调用进程的PID。这时下面示例中所用到的全部知识都解释完了,如果有时间运行一下示例,对照解释研究一遍就应该明白了。
下面是我的示例代码,有点乱,因为我验证的时候为了把父子进程完整的运行过程和各个函数弄明白,就用了很多printf()来输出。
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
#include <errno.h>
/*定义一个功能函数,通过返回的状态,判断进程是正常退出还是信号导致退出*/
void exit_s(int status)
{
if(WIFEXITED(status))
{
printf("normal exit, status=%d\n", WEXITSTATUS(status));
}
else if(WIFSIGNALED(status))
{
printf("signal exit! status=%d\n", WTERMSIG(status));
}
}
int main(void)
{
printf("\n");
pid_t pid, pid1, pidt01, pidt02;
int status;
pid = fork();//创建一个子进程,在子进程中,返回0.在父进程中返回子进程的ID,所以有以下结果
printf("创建一个子进程。%d\n", pid);
if(pid<0)
{
printf("child process error!\n");
exit(0);
}
else if(pid==0) //子进程
{
printf("the child process! %d\n",getpid());
exit(2); //调用exit()退出函数正常退出
}
else
printf("the parent process! chileID= %d parentID=%d\n", pid,getpid());
pidt01 = wait(&status);
/*子进程已经调用exit(2)正常结束了,wait()调用成功,子进程被销毁,返回被销毁的子进程的PID。下面两句printf()是父进程运行的。*/
printf("pidt01=%d\n", pidt01);
printf("pid=%d\n", pid);
/* 由于上面调用了wait(),所以子进程已经销毁,这时执行代码的是父进程,所以这里的pid是在父进程中输出的,
* 父进程中pid存放了调用fork()返回的子进程的ID
* 所以是子进程的ID。如果wait()失败的话,则还是在子进程中,下面的if就会成立。*/
if(pidt01!=pid)
{
printf("this is a parent process! %d\nwait error!\n", getpid());
exit(0);
}
printf("status=%d\n",status);
exit_s(status); //wait()函数调用成功,调用自定义的功能函数,判断退出类型
/*又一次创建子进程,在子进程中,使用kill()函数发送信号,导致退出*/
printf("\n\n第一次创建的子进程已完全结束,再一次创建子进程!\n");
pid = fork();//创建子进程
printf("1pid=%d\n", pid);
if(pid<0)
{
printf("child process error!\n");
exit(0);
}
else if(pid==0)
{
printf("the child process! %d\n", getpid());
pid1=getpid();
printf("pid1=%d\n", pid1);
/*使用kill()函数发送信号*/
//kill(pid1,SIGKILL); /*SIGKILL对应9,用来立即结束进程,并且不能被阻塞、处理或忽略。默认操作为 终止*/
kill(pid1,SIGCHLD); /*SIGCHLD对应17,子进程改变状态时,父进程会收到这个信号。默认操作 忽略*/
//kill(pid1,SIGSTOP); /*SIGSTOP对应19,暂停一个进程,且不能被阻塞、处理或忽略。默认操作 暂停进程*/
/*下面三句与上面三句对应,作用一样。*/
//kill(pid1,9); /*结束进程*/
//kill(pid1,17); /*进入父进程*/
//kill(pid1,19); /*终止进程*/
}
else
printf("the parent process! parentID=%d childID=%d\n", getpid(), pid);
printf("2pid=%d\n", pid);
printf("1error: %s\n.\n", strerror(errno));
pidt02 = wait(&status); //开始等待子进程结束
printf("2error: %s\n.\n", strerror(errno));
printf("status=%d pid02=%d pid=%d\n", status, pidt02, pid);
if(pidt02!=pid)
{
printf("3error: %s\n.\n", strerror(errno));
printf("this is a parent process! %d\nwait error\n", getpid());
exit(0); //子进程到这时才结束。上面一句的parent应该改为child才对
}
exit_s(status);
printf("\n");
exit(0);
}
上面代码主要的语句都有注释,如有不恰当之处,希望有人指出并指导改正!。