【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/785563.shtml

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

相关文章

Free RTOS day2

1.思维导图 2.使用PWMADC光敏电阻完成光控灯的实验 int adc_val0;//用于保存ADC采样得到的数值 float volt0;//用于保存电压值 int main(void) {MX_GPIO_Init();MX_DMA_Init();MX_TIM1_Init();MX_USART1_UART_Init();MX_ADC_Init();MX_TIM3_Init();HAL_TIM_PWM_Start(&hti…

【linux】基础IO(一)

文件只有站在系统层面才能彻底理解 简单回顾一下文件&#xff1a; 首先我们要明确一点&#xff0c;我们说的打开文件不是写下fopen就打开文件&#xff0c;而是当我们的进程运行起来&#xff0c;进程打开的文件。 我们在C语言一般都会使用过如下的代码进行向文件中写入 但是除…

GUID测试程序

全局唯一标识符&#xff08;GUID&#xff0c;Globally Unique Identifier&#xff09;是一种由算法生成的二进制长度为128位的数字标识符。GUID主要用于在拥有多个节点、多台计算机的网络或系统中。在理想情况下&#xff0c;任何计算机和计算机集群都不会生成两个相同的GUID。G…

Clickhouse-表引擎探索之MergeTree

引言 前文曾说过&#xff0c;Clickhouse是一个强大的数据库Clickhouse-一个潜力无限的大数据分析数据库系统 其中一个强大的点就在于支持各类表引擎以用于不同的业务场景。 MergeTree MergeTree系列的引擎被设计用于插入极大量的数据到一张表当中。数据可以以数据片段的形式一…

手写SpringBoot(二)之动态切换Servlet容器

系列文章目录 手写SpringBoot&#xff08;一&#xff09;之简易版SpringBoot 手写SpringBoot&#xff08;二&#xff09;之动态切换Servlet容器 手写SpringBoot&#xff08;三&#xff09;之自动配置 手写SpringBoot&#xff08;四&#xff09;之bean动态加载 手写SpringBoot&…

衍生品交易概况

场内 场外 交易台架构 报价、交易、研究、程序个股、股指Flow、Exotic线性、非线性 对冲管理 管理风险敞口 做好情景分析 尊重市场选择 及时调整策略 理解头寸 善于学习 场外衍生品交易员的一天 盘前 回顾市场、决定今天总体方向处理隔夜敞口 盘中 处理客户询价…

C语言中入门到实战————动态内存管理

目录 前言 一、为什么要有动态内存分配 二、 malloc和free 2.1 malloc 2.2 free 三、calloc和realloc 3.1 calloc 3.2 realloc 四. 常见的动态内存的错误 4.1 对NULL指针的解引用操作 4.2 对动态开辟空间的越界访问 4.3 对非动态开辟内存使用free释放 4.4 使…

【算法】01背包问题(代码+详解+练习题)

题目&#xff1a; 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。 第 i 件物品的体积是 vi&#xff0c;价值是 wi。 求解将哪些物品装入背包&#xff0c;可使这些物品的总体积不超过背包容量&#xff0c;且总价值最大。 输出最大价值。 输入格式 第一行两个整…

视频素材库有哪些网站?八大平台视频素材库创作推荐

视频创作的小达人们&#xff0c;是不是经常在想&#xff0c;视频素材库有哪些网站能提供高质量的素材呢&#xff1f;别担心&#xff0c;今天我要为你们揭秘八个超棒的视频素材网站&#xff0c;让你的视频制作更加轻松在创作的路上如鱼得水&#xff01; 蛙学网&#xff1a;海量…

深入探索Yarn:安装与使用指南

Yarn 是一个由 Facebook 开发的 JavaScript 包管理器&#xff0c;旨在提供更快、更可靠的包管理体验。它与 npm 类似&#xff0c;但在某些方面更加高效和可靠。本文将介绍如何安装 Yarn&#xff0c;并展示如何使用它来管理 JavaScript 项目的依赖。 1. 安装 Yarn Yarn 可以通…

三步提升IEDA下载速度——修改IDEA中镜像地址

找到IDEA的本地安装地址 D:\tool\IntelliJ IDEA 2022.2.4\plugins\maven\lib\maven3\conf 搜索阿里云maven仓库 复制https://developer.aliyun.com/mvn/guide中红框部分代码 这里也是一样的&#xff1a; <mirror><id>aliyunmaven</id><mirrorOf>*&…

【c++初阶】类与对象(下)

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

ESP32学习---ESP-NOW

ESP32学习---ESP-NOW 基于Arduino IDE环境获取mac地址单播通讯一对多通讯多对一通讯多对多通讯模块1代码模块2模块3 广播通讯 基于ESP-IDF框架 乐鑫编程指南中关于ESP-NOW的介绍&#xff1a;https://docs.espressif.com/projects/esp-idf/zh_CN/v5.2.1/esp32/api-reference/net…

7.1 Mysql shell 定时备份

直接上脚本----linu 定时任务执行 #!/bin/bash# 配置信息 DB_USER"your_username" # 数据库用户名 DB_PASSWORD"your_password" # 数据库密码 DB_NAME"your_database_name" # 要备份的数据库名 BACKUP_DIR"/path/to/backup/directory"…

如何在Ubuntu系统部署Z-blog博客结合cpolar实现无公网IP访问本地网站

文章目录 1. 前言2. Z-blog网站搭建2.1 XAMPP环境设置2.2 Z-blog安装2.3 Z-blog网页测试2.4 Cpolar安装和注册 3. 本地网页发布3.1. Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1. 前言 想要成为一个合格的技术宅或程序员&#xff0c;自己搭建网站制作网页是绕…

CrossOver玩游戏会损害电脑吗 CrossOver玩游戏会卡吗 Mac玩游戏 crossover24免费激活

CrossOver是一款可以在macOS上运行Windows应用程序的软件&#xff0c;它利用了Wine技术&#xff0c;无需安装虚拟机或双系统&#xff0c;可以直接在苹果系统下运行Windows游戏。那么&#xff0c;使用CrossOver玩游戏会损害电脑吗&#xff1f;CrossOver玩游戏会卡吗&#xff1f;…

LLaMA-Factory参数的解答

打开LLaMA-Factory的web页面会有一堆参数 &#xff0c;但不知道怎么选&#xff0c;选哪个&#xff0c;这个文章详细解读一下&#xff0c;每个参数到底是什么含义这是个人写的参数解读&#xff0c;我并非该领域的人如果那个大佬看到有参数不对请反馈一下&#xff0c;或者有补充的…

【Entity Framework】EF中的增删改查

【Entity Framework】EF中的增删改查 文章目录 【Entity Framework】EF中的增删改查一、概述二、DbContext数据上下文三、EntityState五个状态值四、EF添加数据4.1 EF Add方式4.2 EF 通过改变对象的状态为 Added4.3 调用方sql4.4 调用存储过程 五、EF修改数据5.1 不查询数据库&…

项目管理系统在制造业的应用,提高生产效率的秘诀与解决方案

缩短产品交货周期&#xff0c;提高产品交付率是当下很多制造业面临的难题&#xff0c;项目管理系统业务流程自动化&#xff0c;能够显著改善项目效率。接下来我们说一说项目管理系统在制造业的应用&#xff0c;项目管理系统制造业解决方案。 制造业典型的项目背景 随着企业体量…

学习【Redis原理篇】这一篇就够了

目录 1. 数据结构1-1. 动态字符串&#xff08;SDS&#xff09;1-2. intset1-3. Dict 2. 网络模型3. 通信协议4. 内存策略 1. 数据结构 1-1. 动态字符串&#xff08;SDS&#xff09; 我们都知道Redis中保存的Key是字符串&#xff0c;value往往是字符串或者字符串的集合。可见字…