Linux--进程控制

1. main函数的返回值

1.1 代码运行正常

  • 疑问
  1. 为什么main函数要有返回值?返回值给了谁?
  2. 为什么main函数总是返回0?有什么作用吗?

返回值给到OS,目的是运行程序,形成进程;而运行结果如何还要通过返回值判定:main函数的return值是进程的退出码,0表示代码运行完毕且结果正确。

退出码又是什么?又有什么作用?可以用其他数字代替0吗?

首先要理解,进程结束无非三种结果:

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止(进程崩溃)

前两种情况,可以通过退出码来知道进程处于什么状态(0表示成功,非0表示不成功)。

为什么是这样的安排呢?

这是因为正确的结果只有一个,而0也是独一无二;但是错误的结果会有很多种,非0的结果也有很多种,当进程结果不正确,我们会想知道它为什么不正确,那么就可以通过对应的退出码来知道是什么样的原因,每一个错误码都对应着一种错误方式。

每种退出码都有对应的字符串含义,帮助用户确定任务失败的原因。

下列程序可以打印出0-134所对应的错误原因:

int main()
{
   for(int i = 0;i < 134;i++)
   {
      printf("%d:%s\n",i,strerror(i));
   }
   return 0;
}

1.2 代码异常终止

代码异常终止,可以理解为程序崩溃。

此时的退出码已经没有任何代表性了,没有任何的意义。

2. 进程退出

main函数return代表进程退出,那么非main函数呢?

非main函数return叫做函数返回,main函数返回才叫做程序终止。

2.1 进程终止

进程终止的函数叫做exit(),如果在函数的某一行使用了exit(),那么后续代码将不被执行,并且输出用户指定的退出码。

格式为:

void exit(int status);//status为退出码

在这里插入图片描述

两个参数分别表示零和非零,也就是退出码的成功与失败

  • 输出缓冲区
int main()
{
    printf("hello world!");//不添加\n,不会造成行缓冲刷新
    sleep(4);
    exit(EXIT_SUCCESS);//退出码为0
    //return 0;
}

上述代码休眠4秒后打印"hello world!",然后程序终止。

这里的"hello world!"一开始会存放在输出缓冲区,并不会打印,然后等待程序返回或者终止时被刷新。也就是说,return和exit都会造成输出缓冲区的刷新。

return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。

除此之外,另一个进程终止函数_exit()也能终止进程,但不同的是它并不会刷新输出缓冲区。但是这和内存泄漏不是一码事,因为不刷新缓冲区不代表它不会释放该空间内存

2.3 进程退出,OS做了什么

在系统层面:进程退出意味着少了一个进程,那么相对应的:PCB、mm_struct、页表的清理和各种映射关系的处理,代码申请的空间释放,这些工作都由OS来完成。

2.4 Linux下查看退出码

echo $?

该语句用于查看最近一次进程的退出码;echo是打印或者输出,$?代表显示最近一次进程的退出码。

除了函数运行是进程,命令行参数同样也是进程:

ls -a -l -i
echo $?

同样可以用echo $?来查看进程退出码。

3. 进程等待wait

3.1 什么是进程等待?为什么要进程等待?

进程等待就是父进程fork之后,要通过wait/waitpid等待子进程退出。

子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏,所以需要父进程wait,然后释放子进程的资源。进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。 而如果父进程通过进程等待的方式拿到了子进程对应的信息,那么接下来子进程将不会进入Z状态,该状态会被系统默认释放掉。

再者,父进程要得到子进程的反馈,就必须要保证它要在子进程之后终止。

最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

3.2 进程等待的方法

每种方法都只能等待一个子进程。

  • wait方法
pid_t wait(int* status);

返回值:成功返回被等待进程pid,失败返回-1。
参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

通过测试这个函数可以知道,父进程是可以等待子进程的,达到回收僵尸进程的效果。

  • waitpid方法

这个方法和wait的区别就是它可以指定等待哪一个子进程

pid_ t waitpid(pid_t pid, int* status, int options);

返回值:当正常返回的时候waitpid返回收集到子进程的进程ID;

options有个参数叫做WNOHANG,如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;这是options的功能,可以先不了解。

  • 参数1:

pid:

Pid=-1,等待任一个子进程,与wait等效。Pid>0,等待其进程ID与pid相等的子进程

  • 参数2:

status(退出结果而不是退出码):先给定一块空间,然后操作系统会把退出结果保存到这里。

既然父进程需要得到子进程的相关运行信息,那么怎么样才能得到?

父进程需要通过status拿到子进程的退出码。

status有32个比特位;而status只使用低16个比特位。这16个比特位在经过wait之后:当子进程正常终止,就在次低八位有了对应的退出码;而当子进程异常终止,就在低七位有了终止信息,由于异常终止退出码无意义所以次低七位不使用。低七位的最高一位是标志位不讨论。

要理解其内容,要先理解进程正常退出与异常终止:正常退出代表着会收到退出码(用次低八位表示),而异常终止会收到信号,有这种信号的时候,退出码就不重要了,也即是说如果信号不为0则退出码是什么都无所谓了;这个信号收到与否用低七位来表示

在这里插入图片描述

这个图的意思是:无论什么时候,都只使用低16位,也就是上图中的16位。

首先看的是程序是否收到终止信号:此时的辨别标志放在了上图异常终止那部分的低七位;

当上述判断得出程序正常终止,此时的低八位不用看,看的是次低八位,也就是看的是上图正常终止的次低八位,里面存放的是退出码。

所以这个过程包括:

  1. 判断子进程是否正常终止(非0就是非正常终止,每个数字都对应了一种错误原因):

判断该进程是否正常终止或返回,通过标志位。

方法:通过status&0x7F来实现。(要拿到低七位。和0111 1111 相与)

  1. 若正常终止,获得其退出码

方法:通过(status>>8)&0xFF来实现。(把次低八位移过去低八位,再和全1相与,可以筛选出此时低八位的二进制)

  • 宏方法

当然也可以不使用上述的位操作,系统有默认的方法,这也是我们最常用的方法

WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(本质是查看信号)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(本质是查看33进程的退出码)

所以说,并不是进程等待成功就代表子进程运行成功,而只能意味着子进程退出了。

附上测试代码和结果:

在这里插入图片描述

强行杀死子进程,收到九号信号,那么此时的退出码是什么已经不重要了:
在这里插入图片描述

  • 参数3 options:

先默认设置为0。WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

4. 阻塞与非阻塞

  • 概念

阻塞等待:可以理解为父进程在等待子进程的时候什么事也不做,直到子进程终止。
非阻塞等待:可以理解为父进程在等待的过程中进行了多次的对子进程的检测。(基于阻塞的轮询方案)

  • 本质

阻塞等待的本质:其实是进程的PCB被放入了等待队列,并将进程的状态改为S状态。
返回的本质:当父进程识别到子进程已结束,父进程的PCB从等待队列拿到R队列,从而被CPU调度。

对于父进程非阻塞来说,有三种结果:

  1. waitpid成功,并且子进程也运行成功;
  2. waitpid成功,但是子进程并没有运行完;
  3. waitpid失败。

设置为非阻塞的方法,就是把第三个参数options设置为WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

如没有出现结果,父进程会反复进行等待,但是这个反复等待不是阻塞状态,因为父进程在这期间还会做其他的事,这就是基于阻塞的轮询方案。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

久菜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值