目录

一、前言

二、进程终止

1.从main函数返回

2.使用库函数exit

3.使用系统调用_exit

4.进程异常终止

5.perror和errno

三、进程等待

1.进程等待的方法

2.waitpid的参数status

3.waitpid的参数option


一、前言

本文介绍了进程退出时的三种场景以及常见的退出方法,对比库函数exit和系统调用_exit的区别与联系,了解C语言中perror和errno的使用,随后介绍了进程等待这一重要的概念,详细讲解了waitpid的使用和它的重要参数status和option


二、进程终止

进程有三个退出场景,分别是:

  • 代码执行完,结果正确
  • 代码执行完,结果不正确
  • 代码异常终止

前两个是正常退出,有三种常见的正常退出方法:从main函数返回,调用exit终止进程,调用_exit终止进程。而最后一个代码异常退出往往是信号导致的,例如我们熟悉的CRTL+C就是一个终止进程信号,接下来就围绕这些内容讲解。


1.从main函数返回

我们经常在写C、C++程序时会在int main后写上return 0,但只能写返回0吗,当然不是!

返回值还能有1,2,3,4,5......等等,它们分别有什么区别呢?不妨用strerror函数来验证,该函数用来将错误码转换为错误信息的字符串:

果然,退出码0就对应着成功,而其他1-133都是有对应错误信息的,而134及以后都是未知错误。

使用指令:echo $? 查看最近一个退出进程的退出码


2.使用库函数exit

注意:库函数要用man 3 exit打开

exit的参数即为退出码,和main函数中return 退出码一个意思,但也存在一些区别:

  • return只有在main函数中退出才是退出进程,而在函数中就是退出函数
  • exit无论在哪都可以直接退出程序并返回退出码

3.使用系统调用_exit

注意:系统调用要用man 2 _exit打开

那么系统调用和库函数相比有什么区别呢?我们可以使用这个代码去测试下:

代码一:
	printf("hello world");
	sleep(1); 
	exit(10);
代码二:
	printf("hello world");
	sleep(1); 
	_exit(10);

结果是库函数exit能将hello world显示出来,而系统调用_exit()却不行,这是为什么呢?

我们知道printf打印的数据如果不使用\n换行的话,数据就会放在缓冲区里,暂时不会显示出来,然而使用exit退出进程后,缓冲区的数据就被打印出来了,那么这就意味着exit能帮我们刷新缓冲区的数据,而_exit不会。


为什么C语言的库函数能刷新缓冲区而系统调用不行呢?难道缓冲区本来就不在操作系统中,而是由C标准维护的吗?是这样的,后续学习文件系统时会详细说明。

实际上,exit最后也会调用_exit,但在调用_exit之前,还做了其他工作:


4.进程异常终止

当我们使用CTRL+C杀掉进程时,这是异常终止,如果有野指针或数组越界,或者除0这些操作都会导致程序崩溃,这也是进程异常结束。

进程异常结束后再使用echo $?就没有意义了,就像考试作弊被抓到后考试成绩没有意义一样。


5.perror和errno

errno是C语言中一个全局变量,它存储着最近一个错误码,例如使用fopen函数打开文件失败后,错误码errno就会被系统自动赋值。

而perror则是搭配和errno一起使用的,它会打印出errno错误码对应的错误信息,而参数则是用户自己定义在错误信息前显示的内容,例如:


三、进程等待

控制一个进程包括创建进程、终止进程,最后回收进程资源。为了回收一个进程的资源,创建这个进程的父进程就必须等待这个子进程结束后,处理它的代码和数据。

1.进程等待的方法

我们通过系统调用wait来等待子进程死亡

接下来就用一个程序来实验一下: 

可以发现当子进程进入循环后,父进程没有立即打印等待成功,而是在此阻塞等待子进程,待子进程运行结束退出后,父进程才打印出等待成功的信息。 


2.waitpid的参数status

我们将上面的程序稍微修改一下,来测试status的作用:

可以发现waitpid的返回值就是等待的子进程的pid,而status这个值又代表什么呢?

status要当作一个位图来看待:

  • 灰色部分:status是一个int类型,占32个bit位,其中后16位是无效的,不填入任何内容
  • 黄色部分:第8-15位,用于表示waitpid得到的子进程退出码
  • 绿色部分:第7位,core dump标志位
  • 蓝色部分:第0-6位,表示waitpid得到的子进程的退出信号

我们要从status中提取出退出码和退出信号,就要对status进行位操作:

status和01111111(0x7F)按位与&,就能得到退出信号

status向右移动8位再和11111111(0xFF)按位与&,就能得到退出码

 

Linux还给用户提供了两个宏函数

WIFEXITED:检测进程是否正常退出,返回布尔值

WEXITSTATUS:提取子进程的退出码


3.waitpid的参数option

option默认为0表示父进程会阻塞等待子进程的死亡,而父进程在这期间就什么也干不了,只能等待,如果使用宏WNOHANG就能让父进程在等待的过程也能自己去干事情了。

WNOHANG就是wait no hang 非阻塞的意思,若父进程执行到waitpid时子进程没有退出,那么函数就返回0,父进程向下继续执行其他代码,若执行到waitpid时子进程退出了,那么函数就会返回退出子进程的pid

通常使用循环来搭配waitpid(pid,&status,WNOHANG)

int main()
{
    pid_t id = fork();
    if(id<0)
    {
        perror("fork");
        exit(1);
    }

    if(id==0)//子进程代码
    {
        int count = 5;
        while(count)
        {
            printf("[%d]我是子进程,我的pid是: %d\n",count,getpid());
            sleep(1);
            count--;
        }
        exit(10);//子进程执行完代码后退出
    }

    //父进程代码
    while(1)//循环访问子进程退出情况
    {
        int wait = waitpid(id,NULL,WNOHANG);
        if(wait>0)//子进程退出成功
        {
            printf("子进程退出成功,子进程pid: %d\n",wait);
            break;
        }
        else if(wait==0)//子进程还没退出,父进程干自己的事情
        {
            //此处简单模拟父进程干的事情
            printf("我是父进程,我现在要干一些别的事情\n");
        }
        else //等待子进程退出失败
        {
            perror("waitpid");
            exit(1);
        }
        sleep(1);
    }
    return 0;
}
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐