【Linux】开始掌握进程控制吧

在这里插入图片描述
送给大家一句话:
我并不期待人生可以一直过得很顺利,但我希望碰到人生难关的时候,自己可以是它的对手。—— 加缪

开始学习进程控制

  • 1 前言
  • 2 进程创建
    • 2.1 fork函数初识
    • 2.2 fork函数返回值
    • 2.3 写时拷贝
    • 2.4 fork常规用法
    • 2.5 fork调用失败的原因
  • 2 进程终止
    • 2.1 终止是在做什么
    • 2.2 进程终止的情况
    • 2.3 如何终止
  • 3 进程等待
    • 3.1 进程等待必要性
    • 3.2 进程等待的方法
      • wait方法
      • waitpid方法
  • 4 总结
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见!!!

1 前言

通过对进程的学习,我们对虚拟地址,页表,物理地址有了大概认识。我们平时使用的地址都是虚拟地址,通过页表可以访问物理地址(统一的视角进行控制,保证数据安全)。也认识到写时拷贝
也认识O(1)调度算法,通过两个队列(活跃队列,过期队列)完成进程的分时控制,通过优先级来放入不同位置,以时间复杂度O(1)快速寻找进程。

2 进程创建

2.1 fork函数初识

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。fork函数具有两个返回值,通过对返回值的判断(if else )可以进行父进程和子进程的不同书写。
注意:进程调用fork,当控制转移到内核中的fork代码后,内核做以下工作:

  1. 分配新的内存块和内核数据结构给子进程
  2. 将父进程部分数据结构内容拷贝至子进程(进程:内核的相关数据管理的数据结构(task_struct + mm_struct + 页表)+ 代码与数据)
  3. 添加子进程到系统进程列表当中
  4. fork返回,开始调度器调度

这里是为了保证父进程和子进程的独立性。

2.2 fork函数返回值

  • 子进程返回0
  • 父进程返回的是子进程的pid

那为什么父进程返回子进程PID ,给子进程返回0呢???
很好理解:就像现实生活中,父母有了孩子,会给他或她起一个名字,父母知道了名字,就可以很好管理孩子。父进程与子进程同理,父进程为了便于管理子进程,所以fork函数会返回对应子进程的pid。

2.3 写时拷贝

通过图解可以很好理解写时拷贝。
在这里插入图片描述
在创建子进程的时候,子进程的页表映射与父进程一致(默认继承的),一旦子进程要进行修改数据,为了保证进程的独立性(保证父进程安全运行),不得不开辟一个新空间,并修改子进程页表的映射(虚拟地址不变!)。

2.4 fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

一般使用if else 分开书写,也可以通过系统调用打开新的进程。

2.5 fork调用失败的原因

  1. 系统中有太多的进程(数据空间是有限的)
  2. 实际用户的进程数超过了限制(必须是有限的)

2 进程终止

2.1 终止是在做什么

进程终止会进行:

  1. 释放曾经的代码和数据所占据的空间
  2. 释放内核数据结构

但是task_struct会延期处理,因为终止的进程处于Z状态(僵尸进程)

2.2 进程终止的情况

我们的main函数常常会有一个返回值 0 ,那为什么要返回零呢???

  1 #include<stdio.h>  2 #include<unistd.h>  3 #include<string.h>  4   5 6 int main()7 {8    9   printf("I am parent process,pid:%d,ppid:%d\n",getpid(),getppid());10   sleep(2);11    12   return 100;                                                                                                                                                                 13 }  

来看我们返回100(为了效果明显)时
在这里插入图片描述
echo 打印的是bash 的环境变量,这个100 就是刚才进程返回到父进程(bash)的退出码(环境变量 表示最近一个进程的退出码),一般0表示正常运行,非零表示有问题。
父进程关心子进程的信息,想要知道子进程是否正常运行。不同的退出码表示不一样的失败原因,我们来获取一下:

    1 #include<stdio.h>2 #include<unistd.h>3 #include<string.h>4 5 int g_val = 10000;6 7 int main()8 {9   10   for(int errcode = 0 ;errcode <=255; errcode++)11   {12     printf("%d: %s\n",errcode,strerror(errcode));13                                                                                                                                                                             14   }                                                                                                                                                15   printf("I am parent process,pid:%d,ppid:%d\n",getpid(),getppid());                                                                               16   sleep(1);                                                                                                                                        17                                                                                                                                                    18   return 0;                                                                                                                                        19 }     

这样就可以获取所有的退出码和对应的退出信息了:
在这里插入图片描述
通过退出码就能获取对应退出信息,来告知用户为什么退出了。其底层实现也好理解,通过数字与字符串的一一对应来做到。
常见进程退出场景:

  1. 代码运行完毕,结果正确(正常结束进程)
  2. 代码运行完毕,结果不正确
  3. 代码异常终止,出现异常提前退出

就像:VS编程运行的时候,如果崩溃了 — 操作系统发现你的进程做了不应该做的事情,OS就杀死进程!!!
一旦出现异常,退出码就没有意义了!!! 为什么出异常才是最重要的!!!
那为什么会出现异常呢??? 原因是:进程出现异常的本质是进程收到来自OS发给进程的信号!(kill -9 就是一个信号)

注意:

  • 先确认是否异常
  • 不是异常就是代码正常跑完,看退出码即可。
  • 可以通过退出信号来判断出现了什么异常

2.3 如何终止

正常终止(可以通过 echo $? 查看进程退出码)

  1. 从main函数return,表示进程终止
  2. 调用exit
  3. _exit
    异常退出
    ctrl + c,信号终止

来看手册中如何描述的:
在这里插入图片描述
调用exit 函数试试:

  1 #include<stdio.h>  2 #include<unistd.h>  3 #include<string.h>  4 #include<stdlib.h>5 int g_val = 10000;  6   7 int main()  8 {  9   10   printf("I am parent process,pid:%d,ppid:%d\n",getpid(),getppid());11   sleep(1);12   exit(123);                                                                                                                                                                  13                                                                                                                                     14 }                                                                                                                                                15    

运行后是这样的效果:
在这里插入图片描述
exit比return 直接,调用一次就可以完全退出!

_exit 是一个系统调用(system call),参数与exit一致,使用与exit几乎一模一样。

**但是exit会冲刷缓冲区,而_exit 不会,**因为缓冲区在系统调用之上 ,而exit 是一个C语言库函数。图解:

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

  1. 执行用户通过 atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit
    在这里插入图片描述

3 进程等待

3.1 进程等待必要性

  • 子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

也就是说,任何进程在退出时都要被父进程进行等待,不然子进程处于僵尸进程就会造成内存泄漏!!!

3.2 进程等待的方法

  1. wait方法
  2. waitpid方法
  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回

wait方法

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

我们测试一下:

  1 #include<stdio.h>                                          2 #include<unistd.h>                                         3 #include<string.h>                                         4 #include<stdlib.h>                                         5 #include<sys/types.h>                                      6 #include<sys/wait.h>                                       7                                                            8 void Childrun()                                            9 {                                                          10   int cnt = 5;                                             11   while(cnt)                                               12   {                                                        13     printf("I am child ,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);14     sleep(1);                                              15     cnt--;                                                 16   }                                                        17 }                                                          18                                                            19 int main()                                                 20 {                                                          21                                                            22   printf("I am father ,pid:%d,ppid:%d\n",getpid(),getppid());23                                                            24   pid_t id = fork();                                       25   if(id == 0)                                              26   {                                                        27     //child                                                28     Childrun();                                            29     printf("child quit...\n");                             30     exit(0);                                               31   }                                                        32   //father                                                 33   sleep(10);                                               34   pid_t rid = wait(NULL);                                  35   if(rid > 0)                                              36   {                                                        37     printf("wait success,rid:%d\n",rid);                   38   }                                                        39   sleep(3);                                                                        40   return 0;                                                  41 }   

这个程序会在子进程运行结束前等待子进程,并且会存在一段时间的窗口期,此时子进程处于僵尸进程:
在这里插入图片描述

在这个父进程等待的过程中,父进程一直在等待子进程的退出,处于阻塞等待状态。父进程本质是等待某种软件条件就绪,那么如何理解阻塞等待子进程呢???
就是把自己列入等待队列,把状态列入不运行状态,等待子进程(类似scanf 的阻塞)。

waitpid方法

#include<sys/types.h>
#include<sys/wait.h>
pid_ t waitpid(pid_t pid, int *status, int options);

注意其中的三个参数!

返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:
Pid = -1 , 等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID

pid_t rid = waitpid(-1,NULL,0); 与刚才的wait等价!!!如果id不为-1,是一个对应的id,那么就会等待指定进程!!!,如果id错误(不存在该进程),就会发生等待错误!!!
status 是一个输出型参数,需要我们传入一个指针来获取。来测试一下(子进程退出码设置为1 )

  1 #include<stdio.h>                    2 #include<unistd.h>                   3 #include<string.h>                   4 #include<stdlib.h>                   5 #include<sys/types.h>                6 #include<sys/wait.h>                 7                                      8 void Childrun()                      9 {                                    10   int cnt = 5;                       11   while(cnt)                         12   {                                  13     printf("I am child ,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);14     sleep(1);                        15     cnt--;                           16   }                                  17 }                                    18                                      19 int main()                           20 {                                    21                                      22   printf("I am father ,pid:%d,ppid:%d\n",getpid(),getppid());23                                      24   pid_t id = fork();                 25   if(id == 0)                        26   {                                  27     //child                          28     Childrun();                      29     printf("child quit..\n");       30     exit(1); //退出码设置为1  31   }                                  32   //father                           33   sleep(10);                         34   int status = 0 ;                   35   pid_t rid = waitpid(id , &status , 0);36   if(rid > 0)                        37   {                                  38     printf("wait success,rid:%d\n",rid);39   }                                  40   printf("father quit...,status:%d\n",status);                                     41   sleep(3);                                                                  42   return 0;                                                                  43 } 

在这里插入图片描述
这就成功获取了status!
我们需要的是 退出码 和 退出信号,那么我们如何通过status获取这两个数据呢???
在这里插入图片描述
也就通过位运算就可以成功获取了。
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/2650b18879684c50981074b8e6bbdf7d.png)这样就可以了:
在这里插入图片描述
通过两个信息就可以判断进程是否正常运行,如果异常,也能知道异常原因了。

当然,如果使用位运算就有点那啥了,我们可以使用宏:

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

对于第三个参数,就可以让父进程在等待的刚才中区做其他事情。也就是进行非阻塞等待:WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID

  1. 阻塞等待就类似张三给李四打电话帮忙,李四正在忙,告诉张三等一会,然后张三这个电话就不挂了,等着李四完成工作,张三也不做其他事情。
  2. 非阻塞等待类似张三给李四打电话帮忙,李四正在忙,告诉张三等一会,然后张三说李四忙完打回来,张三就先去做其他事情。

下面写入了一段非阻塞轮询等待的代码,这样就能保证父进程在等待的过程中,可以去做其他事情!

 34   while(1)                                                                                                                                 35   {                                                                                                                                        36     int status = 0 ;                                                                                                                       37     pid_t rid = waitpid(id , &status , 0);                                                                                                 38     if(rid == 0)                                                                                                                           39     {                                                                                                                                      40       sleep(1);                                                                                                                            41       printf("child is running ,father check next time!\n");                                                                               42     }                                                                                                                                      43     else if(rid > 0)                                                                                                                        44     {                                                                                                                                      45       if(WIFEXITED(status))                                                                                                                46       {                                                                                                                                    47         printf("child quit success,child exit code:%d\n",WEXITSTATUS(status));                                                             48       }                                                                                                                                    49       else                                                                                                                                 50       {                                                                                                                                    51         printf("child quit unnormal!\n");                                                                                                  52       }                                                                                                                                    53       break;                                                                                                                               54     }                                                                                                                                      55     else                                                                                                                                   56     {                                                                                                                                      57       printf("waitpid failed!\n");                                                                                                         58       break;                                                                                                                                                                  59     }                                                            60   }

来看运行效果(父进程一直在查):
在这里插入图片描述
这样就完成了。

4 总结

  1. 等待很容易理解,等待是必须进行的,回收子进程资源,获取子进程退出信息
  2. 进程等待常用waitpid,并常用非阻塞等待

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/778600.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3月23日笔记

广播域与泛洪范围是相同的 广播&#xff1a;在同一个泛洪范围内&#xff0c;强迫交换机泛洪&#xff08;主动&#xff09; 泛洪&#xff08;被动&#xff09; ARP的工作原理&#xff1a;ARP先通过广播发送请求包&#xff0c;所有收到该广播包的设备都会将其中的源IP和源MAC相…

Oracle存数字精度问题number、binary_double、binary_float类型

--表1 score是number(10,5)类型 create table TEST1 (score number(10,5) ); --表2 score是binary_double类型 create table TEST2 (score binary_double ); --表3 score是binary_float类型 create table TEST3 (score binary_float );实验一&#xff1a;分别往三张表插入 小数…

OpenCV 形态学处理函数

四、形态学处理&#xff08;膨胀&#xff0c;腐蚀&#xff0c;开闭运算&#xff09;_getstructuringelement()函数作用-CSDN博客 数字图像处理(c opencv)&#xff1a;形态学图像处理-morphologyEx函数实现腐蚀膨胀、开闭运算、击中-击不中变换、形态学梯度、顶帽黑帽变换 - 知乎…

JAVA面试八股文之集合

JAVA集合相关 集合&#xff1f;说一说Java提供的常见集合&#xff1f;hashmap的key可以为null嘛&#xff1f;hashMap线程是否安全, 如果不安全, 如何解决&#xff1f;HashSet和TreeSet&#xff1f;ArrayList底层是如何实现的&#xff1f;ArrayList listnew ArrayList(10)中的li…

Day24:私信列表、私信详情、发送私信

测试用户&#xff1a;用户名aaa 密码aaa 查询当前用户的会话列表&#xff1b;每个会话只显示一条最新的私信&#xff1b;支持分页显示。 首先看下表结构&#xff1a; conversation_id: 用from_id和to_id拼接&#xff0c;小的放前面去&#xff08;因为两个人的对话应该在一个会…

Siemens S7-1500TCPU 运动机构系统功能简介

目录 引言&#xff1a; 1.0 术语定义 2.0 基本知识 2.1 运动系统工艺对象 2.2 坐标系与标架 3.0 运动机构系统类型 3.1 直角坐标型 3.2 轮腿型 3.3 平面关节型 3.4 关节型 3.5 并联型 3.6 圆柱坐标型 3.7 三轴型 4.0 运动系统的运动 4.1 运动类型 4.1.1 线性运动…

新手体验OceanBase社区版V4.2:离线部署单节点集群

本文源自OceanBase用户的分享 先简单总结如下&#xff1a; 1.本文适合初学者体验OceanBase社区版 v4.2.2 2.仅需准备一台配置为2C/8G的Linux虚拟机 3.通过离线方式安装&#xff0c;以便更直观地了解安装过程 一、Linux系统准备 在宿主机(即你的windows PC电脑)上安装vbox软…

【JavaEE初阶系列】——多线程案例四——线程池

目录 &#x1f6a9;什么是线程池 &#x1f388;从池子中取效率大于新创建线程效率(why) &#x1f6a9;标准库中的线程池 &#x1f388;为什么调用方法而不是直接创建对象 &#x1f388;工厂类里的方法 &#x1f4dd;newCachedThreadPool() &#x1f4dd;newFixedThread…

【微服务】Nacos(配置中心)

文章目录 1.AP和CP1.基本介绍2.说明 2.Nacos配置中心实例1.架构图2.在Nacos Server加入配置1.配置列表&#xff0c;加号2.加入配置3.点击发布&#xff0c;然后返回4.还可以编辑 3. 创建 Nacos 配置客户端模块获取配置中心信息1.创建子模块 e-commerce-nacos-config-client50002…

Matlab之求直角坐标系下两直线的交点坐标

目的&#xff1a;在直角坐标系下&#xff0c;求两个直线的交点坐标 一、函数的参数说明 输入参数&#xff1a; PointA&#xff1a;直线A上的点坐标&#xff1b; AngleA&#xff1a;直线A的倾斜角&#xff0c;单位度&#xff1b; PointB&#xff1a;直线B上的点坐标&#xf…

LeetCode - 股票平滑下跌阶段的数目(分组循环)

2110. 股票平滑下跌阶段的数目 当数组中的数字满足这个prices[i] 1 prices[i - 1]条件之后&#xff0c;就是平滑下降的阶段&#xff0c;也就是将数组中连续的数字进行一个分组。每次计算一个分组即可。 class Solution { public:long long getDescentPeriods(vector<int&…

如何写好一篇文档?

&#x1f304; 前言 什么是好的文档&#xff1f;在我看来&#xff0c;不减分地表达清楚作者的意图&#xff0c;即是一个不错的文档&#xff0c; 从作者角度上讲&#xff0c;能够让读者快速、清晰理解作者要表达的内容。 从读者角度上讲&#xff0c;读者能够快速、清晰地了解到…

分布式部署LNMP+WordPress

需要四台虚拟机&#xff0c;实际上&#xff0c;我们只需要操作三台 一个数据库&#xff0c;一个nginx&#xff0c;一个php&#xff0c;还需要准备一个软件包wordpress-4.7.3-zh_C 首先配置nginx的服务环境 [rootnginx ~]# vi /usr/local/nginx/conf/nginx.conf 修改文件中的loc…

蓝桥杯23年第十四届省赛真题-三国游戏|贪心,sort函数排序

题目链接&#xff1a; 1.三国游戏 - 蓝桥云课 (lanqiao.cn) 蓝桥杯2023年第十四届省赛真题-三国游戏 - C语言网 (dotcpp.com) 虽然这道题不难&#xff0c;很容易想到&#xff0c;但是这个视频的思路理得很清楚&#xff1a; [蓝桥杯]真题讲解&#xff1a;三国游戏&#xff0…

2. Java基本语法

文章目录 2. Java基本语法2.1 关键字保留字2.1.1 关键字2.1.2 保留字2.1.3 标识符2.1.4 Java中的名称命名规范 2.2 变量2.2.1 分类2.2.2 整型变量2.2.3 浮点型2.2.4 字符型 char2.2.5 Unicode编码2.2.6 UTF-82.2.7 boolean类型 2.3 基本数据类型转换2.3.1 自动类型转换2.2.2 强…

数字图像处理——直方图的均衡化

1.方法简介&#xff1a; 直方图均衡化通常用来增加许多图像的全局对比度&#xff0c;尤其是当图像的有用数据的对比度相当接近的时候。通过这种方法&#xff0c;亮度可以更好地在直方图上分布。这样就可以用于增强局部的对比度而不影响整体的对比度&#xff0c;直方图均衡化通…

电源66319D控制方法

实现自动化控制&#xff0c;电源为基础的模块&#xff0c;下面为大家讲解电源66319D的控制逻辑。 新建底层控制逻辑 在文件basis_contorl.py中写入仪器控制底层代码&#xff0c;代码如下&#xff1a; import tkinter.messagebox import pyvisaclass InstrumentControl(object…

探析Zoho Projects项目管理系统功能优势

世上万物&#xff0c;都是有难有易&#xff0c;有简单&#xff0c;也有复杂&#xff0c;项目也不例外。面对复杂的项目&#xff0c;应该如何有效管理呢&#xff1f;答案无疑是项目管理系统&#xff0c;项目管理系统有哪些强大的功能&#xff1f;Zoho Projects项目管理系统的功能…

要提升视频面试效率,需要留意以下几点

在当今远程工作的世界里&#xff0c;视频面试对于成功招聘至关重要。在这个过程中&#xff0c;过度沟通&#xff0c;要有耐心&#xff0c;提供电子产品&#xff0c;并表现出同理心。 随着世界转向远程工作&#xff0c;视频面试已经开始成为维持成功招聘策略的重要组成部分。多…

政安晨:专栏目录【TensorFlow与Keras实战演绎机器学习】

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras实战演绎机器学习 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 本篇是作者政安晨的专栏《TensorFlow与Keras…