前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Linux之进程控制

Linux之进程控制

作者头像
摘星
发布2023-10-15 15:34:44
发布2023-10-15 15:34:44
19900
代码可运行
举报
文章被收录于专栏:C/C++学习C/C++学习
运行总次数:0
代码可运行

前言

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

一、再识fork

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

  1. 我们已经知道了fork有两个返回值,但是为什么会出现这种现象呢? fork函数的实现在操作系统内部,当这个函数准备返回时,它的核心代码已经执行完了也就是子进程已经被创建了,并且已经在OS的运行队列中准备被调度了。 fork后,有两个执行流,父子进程是共享的,所以return会被父子进程各自调度一次,也就是调度两次。
  1. 为什么给父进程返回子进程的pid,给子进程返回0呢? 子进程的父进程只有一个,而父进程可以有很多个子进程,这是一个现实的事情,一个孩子只有一个父亲,而一个父亲可以有很多个孩子。 那么给父进程返回子进程的pid,给子进程返回0,可以很方便的区分父子进程。
  2. 同一个变量,为什么会有两个不同的值呢? 返回的本质是写入,谁先返回,就是谁先写入,因为进程具有独立性,因此会发生写时拷贝。变量的地址相同但是内容不同。
  3. fork的使用场景 (1)父进程希望子进程执行一部分父进程的代码,用if和else if将父进程和子进程的代码分隔开(现实:父亲希望子女继承家业,成为自己的帮手); (2)父进程希望子进程执行另一个程序,用程序替换实现(现实:孩子要闯出属于自己的天地)。
  4. fork失败 (1)系统中有太多的进程; (2)实际用户的进程数超过了限制。

二、退出码

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

文件test4.c

代码语言:javascript
代码运行次数:0
运行
复制
  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
代码语言:javascript
代码运行次数:0
运行
复制
  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
代码语言:javascript
代码运行次数:0
运行
复制
  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)。

  1. 设置为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

代码语言:javascript
代码运行次数:0
运行
复制
  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

代码语言:javascript
代码运行次数:0
运行
复制
  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

代码语言:javascript
代码运行次数:0
运行
复制
  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

代码语言:javascript
代码运行次数:0
运行
复制
  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

代码语言:javascript
代码运行次数:0
运行
复制
  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

代码语言:javascript
代码运行次数:0
运行
复制
  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相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。 最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-05-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、再识fork
  • 二、退出码
  • 三、进程终止
    • 1.进程终止的情况
    • 2.进程退出的方法
    • 3._exit()和exit()
  • 四、进程等待
    • 1.进程等待的场景
    • 2.进程为什么要等待
    • 3.进程等待的方式
      • wait
      • waitpid
    • 进程等待的信息存在哪里
    • 补充
  • 五、进程替换
    • 1.进程函数
    • 2.演示
      • execl函数:
      • 其他
    • 3.替换原理
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档