尝试一文带你理解 --- 进程的控制

序言

 在前两篇文章中都使用到了名为 fork 的函数,我们简单地介绍了他可以创建一个子进程。所以,在这篇文章中,除了进程的创建,还会介绍进程的退出,进程的等待,进程的替换等内容,帮助大家更好地去了解进程的控制😊。


1. 进程的创建

1.1 简述 fork 函数的使用

 我们可以使用 fork 函数创建一个子进程,该函数不需要参数,若创建成功,会返回两个返回值:

  • 在父进程中fork 返回新创建的子进程的进程 ID(PID)
  • 在子进程中fork 返回 0
  • 如果 fork 调用失败,则在父进程中返回 -1 ,并设置全局变量 errno 以指示错误原因。

举个栗子:

  1 #include <stdio.h>2 #include <unistd.h>3 4 int main(){5     int val = 1;6     pid_t pid = fork();7 8     // 创建子进程失败9     if(pid < 0){10         printf("Failed to create child process.");11         return 1;12     }13     // 子进程14     else if(pid == 0){15         val += 1;16         printf("I am child process, pid is %d, val = %d, &val = %p.\n", getpid(), val, &val);17     }18     // 父进程19     else{                                                                                                                                                                                                     20         printf("I am parent process, pid is %d, val = %d, &val = %p.\n", getpid(), val, &val);21     }22 23     return 0;24 }

注: 在上一篇文章中,我们详细介绍了该函数为何会存在两个返回值。 👉 点击查看

1.2 简述子进程创建过程

  • 复制进程fork 函数会复制当前进程的上下文来创建一个新的进程。这个复制过程包括进程的 PCB 内的部分信息、虚拟地址空间(写时复制)、页表、文件描述符、环境变量等。
  • 共享与独立:虽然子进程是父进程的复制品,但两者在操作系统中被视为独立的进程,拥有各自的进程 ID(PID)。它们各自独立地执行程序,且可以通过不同的返回值来区分是父进程还是子进程。

看到这里有些同学可能会觉得矛盾😟,进程之间怎么会独立又共享呢,这不是矛盾吗?这是因为,实质上这是进程在不同方面的特性,就比如我们从两个角度来看待:

独立

  • 数据独立:父子进程之间采用写时拷贝,一方的改变并不会影响另一方的数据。
  • 地址空间独立:每个进程都拥有自己独立的地址空间,确保了进程之间的互不干扰,提高了系统的稳定性和安全性。

共享

  • 环境变量:子进程在创建时会继承父进程的环境变量(除非显式修改)。环境变量提供了一种在进程间共享配置信息的方式。
  • 代码:代码本身在运行时就是不可被修改的,所有父子进程共享一份代码资源。

2. 进程的退出

 大家在平时编程的过程中,是不是习惯于在 main函数 的结尾处 return 一个值,这个值通常都是 0 / 1,大家有想过这是干什么的吗,有什么意义吗?

2.1 程序运行完毕

当程序运行完毕时(通俗的说就是代码跑完了),最终只有两种情况:

  • 代码确实是按照我们的计划执行的,这时返回 0
  • 代码执行的逻辑不正确,不是预期的,这时返回 非零的数

举个栗子:

  1 #include <stdio.h>2 3 int main(){4     int A, B;5     printf("请输入分子分母:");6     scanf("%d %d", &A, &B);7 8     // 正常执行9     if(B != 0){10         printf("A / B = %d.\n", A / B);11         return 0;12     }13     // 不合法输入14     else{15         printf("分母不能为0 !!!\n");                                                                                                                                                                       16         return 1;17     }18 }

2.2 程序异常终止

当我们执行程序时,程序会受到多种因素的影响导致产生异常终止程序,就来一个最熟悉的场景:

	1 #include <stdio.h>2 3 int main(){4 5     int *ptr = NULL;6     int a = *ptr;                                                                                                                                                                                           7 8     return 0;
}

这段程序就不会被执行完,当他对 NULL 进行操作时,就被检测出异常,导致程序的终止。

那这时,程序就没有任何的返回信息吗?不是的,系统会返回一个终止信号,告诉你异常的原因。

2.3 程序的退出信息

 总结一下:一个程序的执行无外乎三种情况:

  • 有异常:直接退出
  • 无异常:执行正确
  • 无异常:执行错误

那怎么来描述呢?是采用的一个 unsigned int 就够了:
在这里插入图片描述
高 8 位描述的是退出信号,低 7 位描述的是退出码,中间一位我们不涉及。

该值的取值规则是:

  • 有异常:高 8 位为异常信息,低 8 位无效置 0
  • 无异常:高 8 位无效置 0,低 7 位为退出信息

3. 进程的等待

3.1 为什么要等待?

 之前我们提过,当子进程执行结束时,父进程一定要读取子进程的退出信息,回收子进程资源,避免造成僵尸进程的产生。
 之前我们提到过,为什么非要读取子进程的退出信息才让他退出。这是因为,我们需要得到子进程的执行情况,到底是执行完啦,还是跑一半就挂啦?执行完了,是对的呢?还是错的呢?我们都需要知道。

3.2 waitpid 函数介绍

现在先介绍这个函数 pid_ t waitpid(pid_t pid, int *status, int options);

返回值

  • 当正常返回的时候 waitpid 返回收集到的子进程的进程 ID
  • 若子进程还未返回正在执行,返回 0
  • 如果调用中出错,则返回 -1 ,这时 errno 会被设置成相应的值以指示错误所在;

参数

  • pid
  • Pid = -1 ,等待任一个子进程。
  • Pid > 0.等待其进程 IDpid 相等的子进程。

status:

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

options(等待方式):
WNOHANG: 若 pid 指定的子进程没有结束,则waitpid()函数返回 0 ,不予以等待。若正常结束,则返回该子进程的 ID

3.1 阻塞式等待 options = 0

 该方式最为简单,父进程就什么也不做,只是等待子进程的执行状态。当子进程执行结束时,立马就把他回收了。

场景带入:就像你等着抄你同桌的作业,你一直问追问你同桌,你写好了吗?你写好了吗?你写好了吗?当你同桌说写好了的一刹那,你立马就将他的作业拿了过来。

所以说,进程阻塞可以理解为等待资源或者是等待某种状态发生

代码实例:

  1 #include <stdio.h>2 #include <unistd.h>3 #include <sys/wait.h>4 5 int main(){6     // 创建子进程7     pid_t pid = fork();8 9     // 创建失败10     if(pid == -1){11         printf("Failed to create child process.");12     }13     // 子进程14     else if(pid == 0){15         int cnt = 5;16         while(cnt--){17             printf("I am child process, pid is %d, cnt is %d.\n", getpid(), cnt);18             sleep(1);19         }20 21         return 0;22 23     }24     // 父进程25     else{26         int status = 0;27         // 阻塞式等待28         pid_t rid = waitpid(pid, &status, 0);29         // 等待成功并且退出信号无异常30         if(rid > 0 && WIFEXITED(status)){31             printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));32         }33         else{34             printf("waited child process failed!\n");35             return 1;                                                                                                                                                                                         36         }37     }38 39     return 0;40 }

这段代码的逻辑可以看作是:

  1. 创建子进程,判断是否创建成功
  2. 子进程每隔一秒打印内容,一共执行五秒退出
  3. 父进程阻塞等待子进程的退出信息
  4. 当子进程退出时,查看相应的退出信息
  5. 程序执行完毕,退出

3.2 非阻塞等待 options = WNOHANG

 这个方式也不难理解,父进程获取到子进程还在执行的信息时,不需要持续等待,而是去执行其他任务,当任务完成时,再次回来查看子进程的状态,直到子进程执行完成。

代码实例:

   24     // 父进程25     else{    26         int status = 0;27         pid_t rid = 0;28         // 非阻塞式等待29         while(!rid){30             rid = waitpid(pid, &status, WNOHANG);31             DoOtherThings();32         }33         // 等待成功并且退出信号无异常                                                                                                                                                                       34         if(rid > 0 && WIFEXITED(status)){                                                                                                                     35             printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));                                                                 36         }                                                                                                                                                     37         else{                                                                                                                                                 38             printf("waited child process failed!\n");                                                                                                         39             return 1;                                                                                                                                         40         }                                                                                                                                                     41     }  

主要的变化在父进程里,可以看到,当 rid == 0(子进程还在执行)时,父进程执行其他的事。


4. 进程的替换

 在上述所有举的例子中,子进程和父进程都是执行的同一个代码,只是不同的分支选择,那有没有一个办法让子进程去执行一个新的程序呢?
肯定是有的,先带着大家看一个现象:

  1 #include <stdio.h>2 #include <unistd.h>3 #include <sys/wait.h>4 5 6 int main(){7     printf("Program begin ... \n");8     execl("/usr/bin/ls", "ls", "-a", NULL);                                                                                                                                                                   9     printf("Program end ... \n");10     return 0;11 }

这段程序的输出结果是:

Program begin …
. … Main.exe makefile Test.cpp

我们可以得出以下现象:

  • 程序执行了第一个 printf
  • 程序执行了 ls -a 的指令
  • 程序没有执行最后一个 printf

这里就不让大家猜发生了哈,我们直接上结论: 执行的程序被 execl函数 内的内容替换了。程序都被替换了,自然原来程序后面的内容自然也不会执行了。

4.1 替换的原理

 当进程调用 execl 一类的函数时,该进程的用户空间代码数据完全被新程序替换,从新程序的启动例程开始执行。调用 execl 并不创建新进程 ,所以调用 execl 前后该进程的 id 并未改变。
再次强调:并没有创建新的进程,而是替换了原本进程中的数据和代码!!!

在这里插入图片描述

4.2 替换函数的介绍

 一共六种以 exec 开头的函数,统称 exec 函数:

int execl(const char *path, const char *arg, ...);

int execlp(const char *file, const char *arg, ...);

int execle(const char *path, const char *arg, ...,char *const envp[]);

int execv(const char *path, char *const argv[]);

int execvp(const char *file, char *const argv[]);

int execve(const char *path, char *const argv[], char *const envp[]);
注意:该类函数执行失败返回 -1,执行成功不返回

别看着这么复杂,只要我们掌握其中几个比较典型的那么就没啥问题,触类旁通嘛。

在这里我先总结一下,其实他们的思路都是差不多的,只是表达形式不同罢了。他们的思路都可以概括为:
在这里插入图片描述

1. execl

 该函数需要我们传递该程序的 地址,执行方式,举个栗子:

execl("/usr/bin/ls", "ls", "-a", NULL); 

在这里我们想要执行以 ls -a 的方式执行命令,首先我给出了该指令的地址,其次就是执行的方式,最后以 NULL 结尾告诉函数我的参数就这么多。你可以根据你的需要任意增删参数项,来调整你的执行方式,如:

execl("/usr/bin/ls", "ls", "-al", NULL); 

2. execlp

 该函数和上一个的唯一区别是无需写全你的指令路径,若你的指令是存在于环境变量中的话,因为它会自动在环境变量里搜索你的指令:

execlp("ls", "ls", "-a", NULL); 

3. execlv

 该函数需要我们传递地址,并且以数组的方式传递执行方式:

    7     char* const argv[] =8     {9         "ls",10         "-a",11         "-l",12         NULL13     };14 15  16     execlv("/usr/bin/ls", argv); 

4.3 总结

 我们可以根据函数末尾的字母来掌握规律:

l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量


5.总结

😃在这篇文章中主要向大家介绍了进程的控制方式,希望大家有所收获。

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

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

相关文章

深度解析Linux-C——结构体(初始化,结构体数组,结构体大小,位段操作,联合体,内存对齐,C的预处理,宏和带参宏,条件编译)

目录 结构体的三种初始化 结构体的两种引用 结构体数组 结构体大小 结构体实现位段操作 联合体 内存对齐 C的预处理 带参宏 条件编译 结构体的三种初始化 定义如下结构体 struct student {char name[100]; int age; float height; } ; 1、定义变量时初始化 s…

Android APK混淆处理方案分析

这里写目录标题 一、前言1.1 相关工具二、Apk 分析2.1 apk 解压文件2.2 apk 签名信息2.3 apk AndroidManifest.xml2.4 apk code三、Apk 处理3.1 添加垃圾文件3.2 AndroidManifest.xml 处理3.3 dex 混淆处理3.4 zipalign对齐3.5 apk 重新签名3.6 apk 安装测试四、总结一、前言 提…

LoRaWAN网络中的chirpstack

目录 一、chirpstack介绍 二、网关与chirpstack之间的通信 三、NS与AS之间的通信 1、Protobuf 2、gRPC 一、chirpstack介绍 ChirpStack 是一个开源的 LoRaWAN 网络服务器&#xff0c;可用于 设置私有或公共 LoRaWAN 网络。ChirpStack 提供了一个 Web 界面 用于管理网关、设…

ELK安装(Elasticsearch+Logstash+Kibana+Filebeat)

一、简介 1.1、软件简介 ELK其实是Elasticsearch&#xff0c;Logstash 和 Kibana三个产品的首字母缩写&#xff0c;这三款都是开源产品。 1.1.1、Elasticsearch简介 Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎。它能很方便的使大量数据具有搜索、分析…

【Linux】文件系统|CHS寻址|LBA逻辑块|文件索引|inode|Date block|inodeBitmap|blockBitmap

前言 一个进程通过文件描述符标识一个打开的文件&#xff0c;进程拿着文件描述符可以在内核中找到目标文件进行读写等操作。这是打开的文件&#xff0c;而没有被打开的文件存储在磁盘中&#xff0c;是如何管理的&#xff1f;操作系统在偌大的磁盘中如何找到想要的文件并打开的…

Redis从入门到超神-(十二)Redis监听Key的过期事件

前言 试想一个业务场景&#xff0c;订单超过30分钟未支付需要做自动关单处理,修改订单状态&#xff0c;库存回退等&#xff0c;你怎么实现&#xff1f;方案一&#xff1a;可以使用定时任务扫表&#xff0c;通过支付状态和下单时间来判断是否支付过期。但是这样的方案是非常消耗…

Modbus转BACnet/IP网关BA100-配硬件说明

在现代自动化系统中&#xff0c;不同设备和系统之间的通信至关重要&#xff0c;Modbus和BACnet/IP协议虽然各有优势&#xff0c;但它们之间的直接通信存在障碍。钡铼Modbus转BACnet/IP网关作为连接这两种协议的桥梁&#xff0c;允许不同系统之间的无缝数据交换。 一、Modbus转…

如何避免蓝屏?轻量部署,安全和业务连续性才能两不误

自19日起&#xff0c;因CrowdStrike软件更新的错误配置而导致的“微软全球蓝屏”&#xff0c;影响依然在持续。这场被称为“史上最大规模的IT故障”&#xff0c;由于所涉全球企业太多&#xff0c;专家估计“蓝屏”电脑全部恢复正常仍需时日。 尽管 CEO 乔治 库尔茨&#xff08…

C++自定义字典树结构

代码 #include <iostream> using namespace std;class TrieNode { public:char data;TrieNode* children[26];bool isTerminal;TrieNode(char ch){data ch;for (int i 0; i < 26; i){children[i] NULL;}isTerminal false;} }; class Trie { public:TrieNode* ro…

matlab仿真 模拟调制(下)

&#xff08;内容源自详解MATLAB&#xff0f;SIMULINK 通信系统建模与仿真 刘学勇编著第五章内容&#xff0c;有兴趣的读者请阅读原书&#xff09; clear all ts0.001; t0:ts:10-ts; fs1/ts; dffs/length(t); msgrandi([-3 3],100,1); msg1msg*ones(1,fs/10); msg2reshape(ms…

for循环计算1~100之间3的倍数的数字之和

你要计算1~100之间的数字先得打印出来1~100之间的数字然后在判断是不是3的倍数然后在打印出数字&#xff0c;代码如下 #include<stdio.h> int main() {int i 0;for (i 1; i < 100; i){if (i % 3 0){printf("%d ", i);}}return 0; }

Java漏洞复现(ctfshow279-297)strust 漏洞复现及原理解释

Java漏洞复现 Strust原理 JavaEE--------Struts2框架-CSDN博客 Web279 struts2漏洞 S2-001是当用户提交表单数据且验证失败时&#xff0c;服务器使用OGNL表达式解析用户先前提交的参数值&#xff0c;%{value}并重新填充相应的表单数据。 这里的%{value}简单理解就是和flask的…

7月22日学习笔记 文件共享服务nfs,SAMBA文件共享与DNS域名服务

任务背景 由于业务驱动&#xff0c;为了提⾼⽤户的访问效率&#xff0c;现需要将原有web服务器上的静态资源 ⽂件分离出来&#xff0c;单独保存到⼀台⽂件服务器上。 任务要求 1. ⼀台应⽤服务器web-server部署apache&#xff0c;静态⽹⻚资源存放在另外⼀台NFS服 务器上 …

Vue3相比于Vue2进行了哪些更新

1、响应式原理 vue2 vue2中采用 defineProperty 来劫持整个对象&#xff0c;然后进行深度遍历所有属性&#xff0c;给每个属性添加getter和setter&#xff0c;结合发布订阅模式实现响应式。 存在的问题&#xff1a; 检测不到对象属性的添加和删除数组API方法无法监听到需要对…

监控Windows文件夹下面的文件(C#和C++实现)

最近在做虚拟打印机时&#xff0c;需要实时监控打印文件的到达&#xff0c;并移动文件到另外的位置。一开始我使用了线程&#xff0c;在线程里去检测新文件的到达。实际上Windows提供了一个文件监控接口函数ReadDIrectoryChangesW。这个函数可以对所有文件操作进行监控。 ReadD…

JS+H5在线文心AI聊天(第三方接口)

源码在最后面 调用的不是文心官方接口 可以正常聊天 有打字动画 效果图 源代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-s…

目标检测自顶向下入门

最近在学习Yolo和OpenCV这些计算机视觉的相关领域&#xff0c;把深度学习啃了个大概&#xff0c;准备着手学习一下Yolov5&#xff0c;趁着这个机会入门一下目标检测这个领域&#xff0c;也算是自顶向下地学习一遍吧。 目标检测 什么是目标检测 物体识别&#xff08;Object de…

【Emacs有什么优点,用Emacs写程序真的比IDE更方便吗?】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

X-AnyLabeling标注软件使用方法

第一步 下载 官方X-AnyLabeling下载地址 github&#xff1a;X-AnyLabeling 第二步 配置环境 使用conda创建新的虚拟环境 conda create -n xanylabel python3.8进入环境 conda activate xanylabel进入X-AnyLabeling文件夹内&#xff0c;运行下面内容 依赖文件系统环境运行环…

多多OJ评测系统 在前端脚手架Vue-Cli中设置页面路由

目录 设置页面路由 我们把菜单上的路由改成读取路由文件 设置成export 导出路由 在刚刚的原始路由 index.ts中导入就行了 在这边引入我们的路由文件 我们之后点击菜单 我们的路由文件是这样的 但是没有跳转 写一下事件 接下来要同步路由到菜单项 自己定义监听函数 …