Linux之进程控制


前言

本文介绍了进程终止、进程等待、进程替换等进程控制的相关概念。


一、再识fork

我们在之前的内容中已经了解过系统调用fork,今天我们再来深入了解一下fork。

  1. 我们已经知道了fork有两个返回值,但是为什么会出现这种现象呢?
    fork函数的实现在操作系统内部,当这个函数准备返回时,它的核心代码已经执行完了也就是子进程已经被创建了,并且已经在OS的运行队列中准备被调度了。
    fork后,有两个执行流,父子进程是共享的,所以return会被父子进程各自调度一次,也就是调度两次。
    在这里插入图片描述

  2. 为什么给父进程返回子进程的pid,给子进程返回0呢?
    子进程的父进程只有一个,而父进程可以有很多个子进程,这是一个现实的事情,一个孩子只有一个父亲,而一个父亲可以有很多个孩子。
    那么给父进程返回子进程的pid,给子进程返回0,可以很方便的区分父子进程。

  3. 同一个变量,为什么会有两个不同的值呢?
    返回的本质是写入,谁先返回,就是谁先写入,因为进程具有独立性,因此会发生写时拷贝。变量的地址相同但是内容不同。

  4. fork的使用场景
    (1)父进程希望子进程执行一部分父进程的代码,用if和else if将父进程和子进程的代码分隔开(现实:父亲希望子女继承家业,成为自己的帮手);
    (2)父进程希望子进程执行另一个程序,用程序替换实现(现实:孩子要闯出属于自己的天地)。

  5. fork失败
    (1)系统中有太多的进程;
    (2)实际用户的进程数超过了限制。

二、退出码

退出码是用来标识一个进程任务执行结果的情况。因为成功只有一种情况,而失败的情况很多,因此,一般情况下0表示执行成功,非0表示执行失败。非0的数字不同,所表示的错误不同。系统对于退出码一般都有着相应的文字藐视,当然我们也可以自定义,也可以直接使用系统给定的映射关系。(例如,strerror这个函数)
在这里插入图片描述
在这里插入图片描述

文件test4.c

  1 #include<stdio.h>
  2 #include<string.h>
  3 int main()
  4 {
  5         for(int i = 0;i < 200; ++i)
  6         {
  7                 printf("%d : %s\n",i,strerror(i));
  8         }
  9         return 0;
 10 }

运行:
在这里插入图片描述
在这里插入图片描述
可以看到strerror函数有134种系统映射好的文字描述的错误码。

三、进程终止

1.进程终止的情况

  • 代码运行完,并且结果正确;
  • 代码运行完,结果不正确;
  • 代码未运行完,进程异常结果无意义。

2.进程退出的方法

  • 从主函数(main函数)return返回;
  • 程序的任意位置调用exit(code);
  • 系统调用_exit(code);

3._exit()和exit()

_exit()和exit()不同:

  • _exit是系统调用;exit是库函数,是对系统调用的上层封装。
  • _exit终止进程,不会刷新缓冲区;exit终止进程,会刷新缓冲区。
    在这里插入图片描述
  • 调用exit:
    文件test4.c
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 int main()
  4 {
  5         printf("hello world!");
  6         sleep(1);
  7         exit(1);
  8         return 0;
  9 }

在这里插入图片描述

  • 调用_exit()
    文件test4.c
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 int main()
  5 {
  6         printf("hello world!");
  7         sleep(1);
  8         _exit(1);
  9         return 0;
 10 }

在这里插入图片描述
同样的代码,调用exit和调用_exit终止进程后,得到的结果却不同。exit会主动刷新缓冲区,而_exit不会主动刷新缓冲区,这是因为缓冲区所在的位置。

四、进程等待

1.进程等待的场景

之前讲进程状态的时候,我们讲到一种特殊的转态:僵尸状态:子进程退出,父进程没有等待子进程(没有回收子进程的资源),会造成子进程处于僵尸状态,造成内存泄漏,同时这种状态的进程无法被杀死(甚至是kill -9都不能杀死这个进程,因为他已经死了)。为了避免这种情况,为了让父进程知道子进程的任务完成的怎么样,我们需要让父进程通过进程等待的方式,回收子进程退出后的资源。

2.进程为什么要等待

1.父进程通过进程等待的方式回收子进程的资源;
2.父进程要获取子进程的退出信息

3.进程等待的方式

wait

在这里插入图片描述
**参数:**输出型参数,一般设置为status,用于获取进程的退出状态,如果不关心进程的退出状态可以设置为NULL;
**返回值:**wait成功返回等待的子进程pid,等待失败返回-1。

waitpid

在这里插入图片描述
参数

  1. pid,设置为-1时,表示等待任意一个进程,和wait一样;也可以指定等待的进程pid,就只能等待指定的进程。
  2. status,输出型参数,由操作系统填充进程退出的信息,如果传递NULL说明不关心该信息。
    status不能当作一个简单的整数对待,而应该被当作一个位图看待:
    在这里插入图片描述
    它的次低8位表示它的退出状态(退出码)((status >> 8) & 0xff);
    低7位表示终止信号(&0x7f)。
  3. 设置为0时表示,阻塞式等待进程;设置为WNOHANG时,表示非阻塞式等待进程。

返回值:如果等待成功,返回所等待的进程pid;如果等待失败,则返回0。

进程等待的信息存在哪里

子进程和父进程都有各自对应PCB,当子进程退出且父进程未回收子进程资源时,子进程的退出信息以及子进程的资源信息都保存在子进程的PCB中。waitpid系统调用,以操作系统的身份执行代码,找到子进程,将输出型参数status参数传到子进程内部,把子进程的退出信息设置进status中,再将status取出,父进程就获得了子进程的退出信息。

补充

对于获取进程的退出结果,我们可以不采用对输出型参数进行位操作,因为Linux提供了对应操作的宏。

WIFEXITED(status):若子进程为正常终止,返回的状态,则为真(查看进程是否正常退出)
WEXITSTATUS(status):若WIFEXITED为非0,则用它提取子进程的退出码(查看进程的退出码)

五、进程替换

进程替换,就是在原进程执行到一定时间是,将原进程替换成磁盘中指定的一个新的进程,然后去执行新的进程(原进程剩余部分不会执行)。

1.进程函数

在这里插入图片描述
注意:对于execl系列函数的参数要以NULL作为结尾(参数是可以变参数)
exec系列函数的返回值,如果替换成功,则没有返回值,而替换失败则返回-1。

2.演示

execl函数:

文件mytest.c

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 int main()
  4 {
  5         printf("process is running...\n");
  6         execl("/usr/bin/ls","ls","--color=auto","-a","-l",NULL);
  7         printf("process running done!");
  8         return 0;
  9 }

在这里插入图片描述

其他

其他函数的用法:
l(list):表示参数采用列表;
v(vector):表示参数采用数组(将所有执行的参数传入数组中进行统一传递,不用使用可变参数);
p(path):自动搜索环境变量(PATH);
e(env):表示自己维护环境变量。

execlp

文件mytest.c

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 int main()
  4 {
  5         printf("process is running...\n");
  6         execlp("ls","ls","--color=auto","-a","-l",NULL);
  7         printf("process running done!");
  8         return 0;
  9 }

在这里插入图片描述

execv

文件mytest.c

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 int main()
  4 {
  5         printf("process is running...\n");
  6         char* const argv[] = {
  7                 "ls",
  8                 "--color=auto",
  9                 "-a",
 10                 "-l",
 11                 NULL
 12         };
 13         execv("/usr/bin/ls",argv);
 14         printf("process running done!");
 15         return 0;
 16 }

在这里插入图片描述

execvp

文件mytest.c

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 int main()
  4 {
  5         printf("process is running...\n");
  6         char* const argv[] = {
  7                 "ls",
  8                 "--color=auto",
  9                 "-a",
 10                 "-l",
 11                 NULL
 12         };
 13         execvp("ls",argv);
 14         printf("process running done!");
 15         return 0;
 16 }

在这里插入图片描述

execle

文件myprocess.c

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 int main()
  4 {
  5         printf("PATH:%s\n",getenv("PATH"));
  6         printf("PWD:%s\n",getenv("PWD"));
  7         printf("MYENV:%s\n",getenv("MYENV"));
  8         printf("我是一个进程\n");
  9         printf("我是一个进程\n");
 10         printf("我是一个进程\n");
 11         printf("我是一个进程\n");
 12         return 0;
 13 }

文件mytest.c

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 int main()
  4 {
  5         printf("process is running...\n");
  6         char* const envi[] = {
  7                 (char*)"MYENV=123454",
  8                 NULL
  9         };
 10         execle("./myprocess","myprocess",NULL,envi);
 11         printf("process running done!");
 12         return 0;
 13 }

在这里插入图片描述

execve

在这里插入图片描述

只有它是系统调用,其他exec系列的函数都是基于它做的封装,目的是为了有更多的应用场景,有更多的选择

3.替换原理

为什么调用替换函数后,原程序的剩余部分不再执行?

用fork创建子进程后,执行的可能是父进程相同的进程(也可能执行与父进程不同道德代码分支),子进程往往会调用exec系列的函数以执行另一个程序。
当进程调用exec系列函数时,该进程的用户空间的代码和朱家具完全被新程序替换,从新程序的启动例程开始执行。
要注意,调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

简单来说,程序替换的本质时将指定的程序的代码和数据加载到指定的位置,覆盖原本的代码和数据(即,进程替换时并没有创建新的进程)。
因此,原进程后续的代码会被新进程的代码和数据所覆盖,因此就不会执行剩余代码了。
这也是为什么只有当程序替换失败才会有返回值,因为如果替换成功就不会执行接下来的代码,返回值就无意义。


总结

以上就是今天要讲的内容,本文介绍了进程终止、进程等待、进程替换等的相关概念。本文作者目前也是正在学习Linux相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

codeJinger

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

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

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

打赏作者

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

抵扣说明:

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

余额充值