当一个进程终止了,我们需要知道进程退出时的状态,我们需要通过进程结束时的退出码来识别。
进程正常结束时:
1.从main()函数返回
2.调用exit()函数
3.调用_exit()//系统调用
我们可以用(echo $?)查看上一次进程执行结束的退出码。
来看下面一个例子:
#include
#include
#include
int main()
{
printf("hello world\n");
exit(2);
printf("continue\n");
return 3;
}
按照我们上面讲的,当这个程序执行结束后,我们查看退出码:发现从exit(2)处将进程结束了。退出码为exit()的参数。
再来看看exit(),和_exit()的区别吧:
#include
#include
#include
int main()
{
printf("hello world");
sleep(2);
exit(2);
}
执行结果为:等待2秒之后打印"hello world",进程结束
include #include
#include
int main()
{
printf("hello world");
sleep(2);
_exit(2);
}
等待两秒之后,没有进行输出字符串,进程结束。
这里为什会先等2秒再打印,因为这里的printf()是按行刷新的,但是这里并没有'\n'就不会按行刷新缓冲区,等到进程结束再刷新缓冲区,进行输出,切记,这里不可以理解为sleep(),在printf()之前执行。
exit()和_exit()都是进程正常退出的的方法,从上面的例子可以看到,_exit(),就是将进程强行退出,而exit()在这里做了一些事情,执行用户定义的清理函数,还有这里可以看到的刷新缓冲区,还有关闭文件流等
在之前讲进程状态是讲过,如果子进程退出,而父进程对其不管不顾,就有可能造成僵尸进程,而造成内存泄露,而且父进程创建的子进程是为了让其执行任务,当然父进程需要知道子进程任务完成的如何,那么父进程就需要通过进程等待的方式来回收子进程资源,获取子进程推出状态。
pid_t wait(int *status);
我们称其为阻塞式等待,等待成功之前父进程处于阻塞状态。
返回值: 等待成功为被等待进程的pid,失败为-1.
参数:输出型参数,获取子进程的退出状态,不关心则可以设置为NULL 。
pid_t waitpid(pid_t pid,int *status,int options);
第一个参数:pid>0代表等待指定的进程。pid=-1,代表等待任意一个进程
第二个参数:输出型参数,获取子进程的推出状态,不关心则可以设置为NULL。
第三个参数://TODO下面有讲到
返回值:若等待成功该值为被等待的进程的pid,否则为-1。
这里的进程退出状态不能简单的当作int类型来看,可以当作位图来看,具体细节如下图示:
我们可以根据退出状态的低7位是否位0来判断进程是否是正常终止的
(一)wait()
如果父进程创建了多个子进程,要保证wait()调用次数和子进程数量个数相同
#include
#include
#include
#include
#include
int main()
{
pid_t id=fork();
if(id<0)
{
perror("fork");
exit(1);
}
if(id==0)
{//child1
printf("child 1 pid:%d\n",getpid());
exit(0);
}
if(id>0)
{
pid_t pid=fork();
if(pid<0)
{
perror("fork");
exit(1);
}
if(pid==0)
{//child 2
printf("child 2 pid: %d\n",getpid());
sleep(1);
exit(0);
}
if(pid>0)
{//father
printf("parent pid:%d\n",getpid());
wait(NULL);//这里父进程只等待了一次
while(1)
{
sleep(1);
}
}
}
}
上面的父进程创建了两个子进程,但是只等待了一次,并且并不确定等待是哪个子进程,这样就会出现僵尸进程
(二)waitpid()
可以根据第3个字节求出退出状态。
include
#include
#include
#include
int main()
{
pid_t pi=fork();
if(pi<0)
{
perror("fork");
}
else
{
if(pi==0)
{//child
int count=3;
while(count--)
{
printf("child:%d %d\n",getpid(),getppid());
sleep(1);
}
printf("child quit\n");
exit(13);//注意这里退出码为13
}
else
{
int status;
printf("father:%d\n",getpid());
pid_t ret=waitpid(pi,&status,0);
if(ret)
{
printf("sig:%d,exit code:%d\n",status&0x7f,(status>>8)&0xff);
}
printf("father quit\n");
}
}
_exit(2);
}
这里可以了解到进程时正常退出的,并且退出码为13
再来看一种情况,我们让进程异常退出,在进程运行中,将进程kill掉(在另外一个终端下进行)
#include #include
#include
#include
int main()
{
pid_t pi=fork();
if(pi<0)
{
perror("fork");
}
else
{
if(pi==0)
{//child
int count=100;//这里设置大一点的数
while(count--)
{
printf("child:%d %d\n",getpid(),getppid());
sleep(1);
}
printf("child quit\n");
exit(13);//注意这里退出码为13
}
else
{
int status;
printf("father:%d\n",getpid());
pid_t ret=waitpid(pi,&status,0);
if(ret)
{
printf("sig:%d,exit code:%d\n",status&0x7f,(status>>8)&0xff);
// status&0x7f拿到后7位,看是否正常退出
// (status>>8)&0xff拿到第三个字节,即退出状态
}
printf("father quit\n");
}
}
_exit(2);
}
运行之后,在另一终端下将子进程kill掉,
看到上面用的时9号信号将进程异常终止的,所以状态中的信号位置就为9,
上面是我们自己计算的终止信号和退出状态,其实系统中有两者宏供我们使用(但是我老记不住)
WIFEXITED(status)//(相当于(status&0x7f==0) ,查看进程是否正常退出) 为真 则表示正常退出则为进程退出状态,
WEXITSTATUS(status)//(相当于 (status>>8)&0xff)上面WIFEXITED为真,则提取退出码
例如下面代码
#include
#include
#include
#include
int main()
{
pid_t pi=fork();
if(pi<0)
{
perror("fork");
}
else
{
if(pi==0)
{//child
int count=3;
while(count--)
{
printf("child:%d %d\n",getpid(),getppid()); sleep(1);
}
printf("child quit\n");
exit(13);//注意这里退出码为13
}
else
{
int status;
printf("father:%d\n",getpid());
pid_t ret=waitpid(pi,&status,0);
if(ret)
{
if( WIFEXITED(status))
printf("exit code:%d\n",WEXITSTATUS(status));
else
{
printf("异常终止\n");
}
}
printf("father quit\n");
}
}
exit(0);
}
(三)非阻塞式等待
在上面两种等待的方式,父进程都是在等待的时候处于阻塞状态,当在waitpid()中的第三个参数为WNOHANG是,这时就是非阻塞式等待
#include
#include
#include
#include
int main()
{
pid_t pi=fork();
if(pi<0)
{
perror("fork");
}
else
{
if(pi==0)
{//child
int count=3;
while(count--)
{
printf("child:%d %d\n",getpid(),getppid());
sleep(3);
}
printf("child quit\n");
exit(13);
//注意这里退出码为13
}
else
{//father
int status;
printf("father:%d\n",getpid());
while(1)
{
pid_t ret=waitpid(pi,&status,WNOHANG);
if(ret>0)
{//等待到子进程
if( WIFEXITED(status))
printf("exit code:%d\n",WEXITSTATUS(status));
break;
}
else
{
if(ret==0)
{//等待的子进程没有结束,不予等待,去做别的事情
printf("child if running ,to do other thing\n");
}
else
{
//错误状态 break;
}
}
sleep(1);
}
printf("father quit\n");
}
}
exit(0);
}
看到上面这种非阻塞式等待,父进程以循环的方式来查看子进程是否结束(等待子进程结束),若此时子进程没有结束,父进程可以转去做别的事情,而不是阻塞在这里啥也不做。
当父进程有多个子进程时:
此处应用的时轮询式非阻塞式等待
include
#include
#include
#include
#include
int main()
{
pid_t id=fork();
if(id<0)
{
perror("fork");
exit(1);
}
if(id==0)
{//child1
printf("child 1 pid:%d\n",getpid());
sleep(1);
exit(0);
}
if(id>0)
{
pid_t pid=fork();
if(pid<0)
{
perror("fork");
exit(1);
}
if(pid==0)
{//child 2
printf("child 2 pid: %d\n",getpid());
sleep(1);
exit(0);
}
if(pid>0)
{//father
printf("parent pid:%d\n",getpid());
int ret=0;
while(1)
{
if((ret=waitpid(-1,NULL,WNOHANG))==0)
{//说明没有等待子进程
printf("Do other thing\n");
}
if(ret<0)
{//说明父进程已经没有进程可以等待了
break;
}
if(ret>0)
{//说明父进程等到了一个子进程
printf("Wait child pid:%d\n",ret);
}
sleep(1);
}
}
}
}
自己之前的一个问题没有解决:
Makefile 编写时用.PHONY定义一个伪目标,当我们普通的目标make时会根据依赖文件修改时间和目标生成时间来判断是否还需要执行编译语句,但是定义为伪目标的话会每次都认为是依赖文件每次都是最新的,所以每次都会重新生成伪目标,
静态链接:将静态库文件(.a)在程序的链接阶段被复制到程序中
动态链接:将动态库文件(.so)在程序运行时由系统自行的加载到内存中