【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函数实现腐蚀膨胀、开闭运算、击中-击不中变换、形态学梯度、顶帽黑帽变换 - 知乎…

QT(3/25)

完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示“登录成功”&#xff0c;提供一个OK按钮&#xff0c;用户点击OK后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面。 如果账号和密码不匹配&#…

代码随想录学习Day 20

669.修剪二叉搜索树 题目链接 讲解链接 思路&#xff1a;采用递归方法&#xff0c;若root.val > high&#xff0c;判断左子树是否为空&#xff0c;若不空&#xff0c;递归遍历左子树&#xff0c;若空就返回null&#xff1b;若root.val < low&#xff0c;则判断右子树是…

C#——系统学习(类与对象)

类&#xff08;Class&#xff09; 定义与作用&#xff1a; 类是C#中的一种用户自定义类型&#xff0c;它是面向对象编程的核心元素之一。类是一种蓝图或者模板&#xff0c;它描述了一类具有相同特性和行为的事物。类通常包含以下部分&#xff1a; 字段&#xff08;Fields&…

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 线性运动…

ssh 启动 docker 中 app, docker logs 无日志

ssh 启动 app, 标准输出被重定向 ssh 客户端&#xff0c;而不是 docker 容器的标准输出。只需要在启动时把app 标准输出重定向到 docker标准输出。 测试如下&#xff1a; 1.启动 docker docker run -it -p 60022:22 --name test test:v4 bash -c "service ssh restart;…

C# —— 系统学习(控制结构)

下面时所有控制结构的实例与解析 条件分支结构 - if-else int score 85; if (score > 90) {Console.WriteLine("优秀"); else if (score > 80) {Console.WriteLine("良好"); } else {Console.WriteLine("合格"); } 这段代码使用的是if-…

新手体验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 强…