APUE编程:91---进程处理(进程终止、处理函数:exit、_exit、_Exit、atexit、on_exit)

一、进程的终止方式

  • 五种正常终止的方式:

从main返回在main函数内执行return语句
调用exit调用exit函数。此函数由ISO C定义,其操作包括调用各终止处理程序(终止处理程序再调用atexit函数时登记),然后关闭所有标准I/O流等。因为ISO C并不处理文件描述符、 多进程(父、子进程)以及作业控制,所以这一定义对UNIX系统而言是不完整的
调用_exit或_Exit
最后一个线程从其启动例程返回
从最后一个线程调用pthread_exit
  • 异常终止的3种方式:

调用abort调用abort。它产生SIGABRT信号,所以是下一种异常终止的一种特
接到一个信号当进程接收到某个信号时。信号可由进程本身(如调用abort函数)、其他进程和内核产生。例如,进程引用地址空间之外的存储单元,或者除以0,内核就会为该进程产生相应的信号
最后一个线程对取消请求做出响应
  • 不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符释放它所使用的存储器等等

进程终止状态

  • 对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于exit、_exit、_Exit,实现这一点的方法是,将其退出状态作为参数传送给函数。在异常终止情况,内核(不是进程本身)产生一个指示其异常终止原因的终止状态( termination status)
  • 在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态

子进程在父进程之前终止,父进程如何获取子进程的终止状态?

  • 内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到有关信息。这种信息至少包括进程ID、该进程的终止状态、以反该进程使用的CPU时间总量。内核可以释放终止进程所使用的所有存储器,关闭其所有打开文件

二、exit、_exit、_Exit

#include <stdlib.h>
void exit(int status);
void _Exit(int status);

#include <unistd.h>
void _exit(int status);
  • 3个函数都用于正常终止一个程序
  • _exit和_Exit立即进入内核。exit则先执行一些清理处理(例如关闭所有由内核打开的描述符),再返回内核

参数可取得值

  • 0:表示进程正常结束
  • 1~255:表示进程出错结束
  • C标准指定了两个常量也可以传递给exit作为参数使用:

    • EXIT_SUCCESS:成功退出
    • EXIT_FAILURE:出错退出
//stdio.h中

#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1

三、进程终止状态

  • exit、_exit、_Exit这三个函数都带一个整型参数,称为终止状态(或退出状态)

未定义的进程终止状态

  • 如果程序符合下面的某一条件,那么就说这个进程的终止状态是未定义的:
    • ①若调用这些函数时不带终止状态
    • ②main执行了一个无返回值的return语句
    • ③main没有声明返回类型为整型
  • 案例:执行下面没有返回值的main函数,其终止码是随机的
#include <stdio.h>
main()
{
    printf("hello, world\n");
}

  • 如果使用gcc的1999 ISO C标准,就会提示警告信息
  • 如果main的返回类型为整型,并且main执行到最后一条语句时返回(隐式返回),那么该进程的终止状态是0
  • main函数(仅限main函数)调用return和调用exit是等价的。例如,下面都返回0
exit(0); //等价于return(0);

四、return和exit区别

概念一:

  • 如果main函数返回一个整型以及用exit代替return,对某些C编译程序和UNIX  lint(1)程序而言会产生不必要的警告信息,因为这些编译程序并不了解main中的exit与return语句的作用相同

概念二:

  • 避开这种警告信息的一种方法是:在main中使用return语句而不是exit。但是这样做的结果是不能用UNIX  的grep实用程序来找出程序中所有的exit调用

概念三:

  • 另外一个解决方法是将main说明为返回void而不是int,然后仍旧调用exit。这也避开了编译程序的警告,但从程序设计角度看却 并不正确,而且会产生其他的编译编辑警告,因为main的返回类型应当是带符号的整型
  • ISO C和POSIX.1定义main返回整型

五、atexit()函数

#include <stdlib.h>
int atexit(void (*func)(void));

//参数:函数指针
//返回值:成功返回0,失败返回非0
  • 功能与特点:
    • 这个函数用于登记终止处理程序
    • atexit先登记的终止函数后执行,后登记的先执行
    • 一个函数可以被登记多次
  • 什么是终止处理程序?
    • 程序结束时,main函数结束之后还可以调用登记函数
    • ISO C规定,一个进程可以登记多至32个函数
  • atexit()与exit()的关系:
    • atexit()来登记终止处理程序
    • 然后exit()自动调用atexit()登记的这些终止处理程序
  • 根据ANSI  C和POSIX.1,exit首先调用各终止处理程序,然后关闭(通过fclose)所有的打开流。POSIX.1扩展ISO C标准,它说明,如若程序调用exec函数族中的任一函数,则将清除所有已安装的终止处理程序
  • 注意,内核使程序执行的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是显式或隐式地(调用exit)调用_exit。进程也可非自愿地由一个信号使其终止

进程的开启与终止流程图

演示案例

#include <stdio.h>
#include <stdlib.h>
static void my_exit1(void);
static void my_exit2(void);
int main(void)
{
    if (atexit(my_exit2) != 0)
        perror("can’t register my_exit2");
    if (atexit(my_exit1) != 0)
        perror("can’t register my_exit1");
    if (atexit(my_exit1) != 0)
        perror("can’t register my_exit1");
    printf("main is done\n");
    return(0);
}
static void my_exit1(void){
    printf("first exit handler\n");
}
static void my_exit2(void){
    printf("second exit handler\n");
}

六、on_exit()函数

#include <stdlib.h>
int on_exit(void (*function)(int , void *), void *arg);

//返回值:成功返回0;否则返回非零值
  • on_exit()与atexit()函数相同,都是用于登记终止处理程序
  • 特点也与atexit()函数相同

参数:

  • 参数1:进程终止处理函数(参数1为调用on_exit()函数进程的退出码,参数2为附加信息,为void*类型)
  • 参数2:传递给on_exit()参数1所绑定的函数的第2个参数

演示案例

  • 子进程绑定两个on_exit函数。父进程等待子进程结束
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

void On_exit1(int status,void *arg)
{
    printf("%s:child exit,exit code is %d\n",(char*)arg,status);
}

void On_exit2(int status,void *arg)
{
    printf("%s:child exit,exit code is %d\n",(char*)arg,status);
}

int main()
{
    pid_t pid;
    int status;
    if((pid=fork())==-1){
        perror("fork");
        exit(1);
    }else if(pid==0){
        printf("I am child,pid=%d\n",getpid());
        sleep(1);
        char buff1[]="On_exit1 function";
        char buff2[]="On_exit2 function";
        on_exit(On_exit1,buff1);
        on_exit(On_exit2,buff2);
        exit(2);
    }else{
        printf("I am father,pid=%d\n",getpid());
    }

    if(wait(&status)<=0){
        perror("wait");
        exit(2);
    }
    printf("wait success,status code :%d\n",WEXITSTATUS(status));
    exit(0);
}

七、僵死进程案例

演示案例

  • 子进程先结束,但是父进程处于sleep状态,因此子进程就变为僵死进程了
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main()
{
	pid_t pid;
	if((pid=fork())==-1)
		perror("fork");
	else if(pid==0)
	{
		printf("child_pid pid=%d\n",getpid());
		exit(0);
	}
	sleep(3);
	//子进程已经结束了,但是父进程没有对子进程进行任何处理,wait、waitpid之类的
	system("ps");
	exit(0);
}

  • 更改上面的案例,在父进程调用wait之前和调用wait之后都执行一次system("ps")。可以看到父进程之后wait处理子进程之后,子进程的僵死状态消失了
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main()
{
	pid_t pid;
	if((pid=fork())==-1)
		perror("fork");
	else if(pid==0)
	{
		printf("child_pid pid=%d\n",getpid());
		exit(0);
	}
	sleep(3);
	printf("****before wait:\n");
	system("ps");
	wait(NULL);
	printf("****after wait:\n");
	system("ps");
	exit(0);
}

八、父进程在子进程终止之前终止案例

  •  让子进程sleep(2),父进程sleep(1),使父进程先结束。子进程之后就被init进程领养了
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
    pid_t pid;
    if((pid=fork())<0){
        perror("frok");
        exit(1);
    }else if(pid==0){
        printf("I am child,myPid=%d,ppid=%d\n",getpid(),getppid());
        sleep(2);
        printf("I am child,myPid=%d,Ppid=%d\n",getpid(),getppid());
        exit(0);
    }else{
        printf("I am father,myPid=%d\n",getpid());
        sleep(1);
        exit(0);
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董哥的黑板报

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值