信号与进程(1):进程创建与进程等待

进程创建

参考博客

fork与vfork的区别

fork()与vfork()函数

  1. fork :子进程拷贝父进程的数据段、代码段等资源
    vfork :子进程与父进程
    共享数据段、代码段等资源
  2. fork :父子进程的执行次序不确定
    vfork :保证子进程先运行,在调用exec或exit之前与父进程数据是共享的,在它调用exec
    或exit之后父进程才可能被调度运行
  3. vfork :保证子进程先运行,在调用exec 或exit之后父进程才可能被调度运行。如果在
    调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁

fork()函数

#include <unistd.h>/* Clone the calling process, creating an exact copy.Return -1 for errors, 0 to the new process,and the process ID of the new process to the old process.  */
extern __pid_t fork (void) __THROWNL;

例1:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <iostream>
using namespace std;int main()
{pid_t pid;pid = fork();cout << "pid:" << pid << endl;if (pid < 0)printf("error in fork!\n");else if (pid == 0)printf("I am the child process,ID is %d\n", getpid());elseprintf("I am the parent process,ID is %d\n", getpid());return 0;
}

运行输出:

prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ ./test 
pid:6448
I am the parent process,ID is 6447
prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ pid:0
I am the child process,ID is 6448
  • fork()函数用于从已存在的进程中创建一个新的进程,新的进程称为子进程,而原进程称为父进程
  • fork()的返回值有两个,子进程返回0,父进程返回子进程的进程号,进程号都是非零的正整数,所以父进程返回的值一定大于零
  • pid=fork();语句之前只有父进程在运行,而在pid=fork();之后,父进程和新创建的子进程都在运行
  • 如果pid==0,那么肯定是子进程,若pid≠0 (事实上肯定大于0),那么是父进程在运行
  • fork()函数子进程是拷贝父进程的代码,那么下面的代码会被执行两遍
cout << "pid:" << pid << endl;
if (pid < 0)printf("error in fork!\n");
else if (pid == 0)printf("I am the child process,ID is %d\n", getpid());
elseprintf("I am the parent process,ID is %d\n", getpid());

例2:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <iostream>
using namespace std;int main()
{pid_t pid;int count = 0;pid = fork();cout << "pid:" << pid << endl;if (pid < 0){printf("error in fork!\n");}else if (pid == 0){printf("I am the child process,ID is %d\n", getpid());++count;cout << "child process count:" << count << endl;}else{printf("I am the parent process,ID is %d\n", getpid());++count;cout << "parent process count:" << count << endl;}return 0;
}

运行输出:

prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ ./test 
pid:6700
I am the parent process,ID is 6699
parent process count:1
prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ pid:0
I am the child process,ID is 6700
child process count:1

注意++count;运行了两次,但两次输出都是1

因为fork()函数子进程拷贝父进程的数据段代码段等资源,++count;被父子进程各执行一次,但是子进程执行自己数据段里面的(这个数据段是从父进程那copy 过来的一模一样)count+1,同样父进程执行自己的数据段里面的count+1,他们互不影响

💡 fork()函数子进程**拷贝**父进程的数据段代码段等资源,相当于有独立的内存空间

vfork()函数

#include <unistd.h>
#include <sys/types.h>/* Clone the calling process, but without copying the whole address space.The calling process is suspended until the new process exits or isreplaced by a call to `execve'.  Return -1 for errors, 0 to the new process,and the process ID of the new process to the old process.  */
extern __pid_t vfork (void) __THROW;

将fork()函数例2代码中的fork()改为vfork(),编译运行,输出如下:

prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ ./test 
pid:0
I am the child process,ID is 6839
child process count:1
pid:6839
I am the parent process,ID is 6838
parent process count:1874747691

观察到父进程中的count完全是错误的值

因为vfork创建的子进程会优先运行,且与父进程共享地址空间,当执行完exit后或者调用exec后,父进程才可运行,而代码中未添加相应语句,导致子进程结束后count内存地址被销毁,父进程对count的操作相当于操作了一块非法的内存空间

更改如下:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <iostream>
using namespace std;int main()
{pid_t pid;int count = 0;pid = vfork();cout << "pid:" << pid << endl;if (pid < 0){printf("error in fork!\n");}else if (pid == 0){printf("I am the child process,ID is %d\n", getpid());++count;cout << "child process count:" << count << endl;exit(0);}else{printf("I am the parent process,ID is %d\n", getpid());++count;cout << "parent process count:" << count << endl;}return 0;
}

运行输出:

prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ ./test 
pid:0
I am the child process,ID is 6928
child process count:1
pid:6928
I am the parent process,ID is 6927
parent process count:2

子进程调用execexit之前与父进程数据是共享的,所以子进程退出后把父进程的数据段count改成1 ,子进程退出后,父进程又执行,最终就将count变成了2

进程等待

参考博客

Linux wait() 和 waitpid()函数介绍

【Linux】进程控制(wait 和 waitpid)的理解和使用

当有多个进程同时运行时,进程间需要协作工作,可能用到进程等待的操作,进程间的等待包括父子进程间的等待进程组内成员间的等待

一般我们在父进程fork出一个子进程,希望子进程完成某些功能,也就是帮助父进程完成某些任务的,所以父进程就需要知道子进程完成的状态如何,是成功还是失败,所以就需要父进程通过wait 或者waitpid函数等在子进程退出

为什么等待

  • 父进程等待子进程退出,是因为父进程需要子进程退出的信息,和完成功能的状态如何
  • 可以保证时序问题:子进程先退出,父进程再退出
  • 可以预防子进程成为僵尸进程,防止内存泄漏的问题;而这我们需要父进程wait等待子进程退出之后,释放它的僵尸资源,也就子进程的PCB
  • 并且我们需要知道,一旦进程成为僵尸状态,即使你使用 kill -9 也杀不死这个僵尸进程,只能通过父进程等待wait回收它

wait()函数

wait函数的作用是父进程调用,等待(阻塞式)子进程退出,回收子进程的资源

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

示例:

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{if (fork() == 0){// child processprintf("i am a child pid=%d\n", getpid());exit(0); //让子进程退出}// parent process执行,这里不会执行子进程了,因为子进程被我退出了sleep(2); //休息2s,为的是观察监控消息,是否子进程成为僵尸进程printf("wait函数开始执行\n");pid_t ret = wait(NULL);if (ret == -1){perror("wait error\n");}// wait返回成功printf("wait返回的是子进程的ret=%d执行结束,注意观察监控窗口是否>僵尸进程被回收\n", ret);sleep(2); //不让父进程那么快退出,观察窗口僵尸进程是否被回收return 0;
}

运行输出:

prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ ./wait
i am a child pid=10795
wait函数开始执行
wait返回的是子进程的ret=10795执行结束,注意观察监控窗口是否>僵尸进程被回收

waitpid()函数

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

参数为0:也就是阻塞版本的等待,也就是说该waitpid在子进程没有退出情况下就不会返回,就和wait的使用一模一样,因为wait的使用就是阻塞版本的等待方式

参数为WNOHANG::这是一个宏,表示调用wait为非阻塞的版本,非阻塞也就以为执行带waitpid函数会立即返回
而设置这个参数:返回情况有以下几种:

  1. 若pid指定的子进程没有结束,则waitpid()函数返回0,父进程不予以等待
  2. 若正常结束,则返回该子进程的ID
  3. 若等待失败,即返回-1,这时errno会被设置成相应的值以指示错误所在

示例:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <iostream>
using namespace std;int main()
{pid_t pid = fork();if (pid == 0){// childint count = 10;while (count){printf("i am a child my pid  = %d\n", getpid());cout << "count value:" << count << endl << endl;count--;sleep(1);}exit(11); //退出子进程,我们在父进程调用waitpid来获得子进程的退出码信息}// parent processint status; //该变量是父进程的变量,为的是在父进程获得子进程的退出状态的信息while (1){   //这个循环就是继续轮回检测的非阻塞版本的设计,假如子进程没退出,我们一直死循环检测知道直到它退出pid_t ret = waitpid(pid, &status, WNOHANG); // WNOHANG:表示父进程非阻塞方式等待子进程退出if (ret == 0){// ret == 0 表示waitpid等待成功,但是子进程还没有退出,waitpid返回0回到父进程的代码执行//做父进程的事情;printf("我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事\n");}else if (ret > 0){// waitpid 等待成功,子进程退出,父进程就可以获取子进程的信息printf("waitpid 返回的stauts 的退出码信息:%d,终止信号的信息:%d\n", (status >> 8) & 0xFF, status & 0x7F);break;}else{printf("waitpid is failed\n");break;}sleep(1); //让父进程每隔一秒去检测}return 0;
}

运行输出:

prejudice@prejudice-VirtualBox:~/Cplus_learning/bin$ ./waitpid 
我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
i am a child my pid  = 10209
count value:10我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
i am a child my pid  = 10209
count value:9i am a child my pid  = 10209
我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
count value:8我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
i am a child my pid  = 10209
count value:7i am a child my pid  = 10209
我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
count value:6我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
i am a child my pid  = 10209
count value:5i am a child my pid  = 10209
我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
count value:4我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
i am a child my pid  = 10209
count value:3i am a child my pid  = 10209
我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
count value:2我waitpid返回0,等待子进程成功,但是子进程没有退出,我可做父进程要做的事
i am a child my pid  = 10209
count value:1waitpid 返回的stauts 的退出码信息:11,终止信号的信息:0

💡 不可以简简单单的认为 stauts就是一个整形int,我们要把它为一个位图

在这里插入图片描述

进程终止信息被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

css实现上下左右对勾选中状态角标

&#x1f365;左上角 &#x1f365;右上角 &#x1f365;左下角 &#x1f365;右下角: &#x1f365;左上角: .blueBackground {position: relative;border: 1px solid #91c7f3;background: #F0F8FF !important;&:after {content: "";position: absolute;top:…

今天发现个有意思的问题:java基础篇章网络编程的报错问题,顺便看一下各个GPT的实力

问题&#xff1a; 一个java socket网络编程的引发的异常&#xff0c;具体代码Client.java、Server.java&#xff0c;如下 Client.java package Test2;import java.io.*; import java.net.Socket;public class Client {public static void main(String[] args) throws IOExce…

✌粤嵌—2024/5/6—盛最多水的容器

代码实现&#xff1a; 方法一&#xff1a;暴力解法 #define min(a, b) ((a) > (b) ? (b) : (a)) #define max(a, b) ((a) > (b) ? (a) : (b))int maxArea(int *height, int heightSize) {int ans 0;for (int i 0; i < heightSize; i) {for (int j i; j < heig…

武汉星起航:跨境电商行业领航者,一站式孵化服务引领全球趋势

在全球化日益深入的今天&#xff0c;跨境电商作为连接各国市场的桥梁&#xff0c;其重要性日益凸显。在这一潮流中&#xff0c;武汉星起航电子商务有限公司以其前瞻性的战略眼光和丰富的运营经验&#xff0c;迅速崛起为跨境电商行业的领军者。公司不仅自营亚马逊跨境电商业务&a…

一、Redis五种常用数据类型

Redis优势&#xff1a; 1、性能高—基于内存实现数据的存储 2、丰富的数据类型 5种常用&#xff0c;3种高级 3、原子—redis的所有单个操作都是原子性&#xff0c;即要么成功&#xff0c;要么失败。其多个操作也支持采用事务的方式实现原子性。 Redis特点&#xff1a; 1、支持…

核心代码分析

核心代码分析 下面的代码主要有两个作用 判断是否为扫描器或者密码爆破工具&#xff0c;进行交互握手&#xff0c;效果是扫描器直接爆3306弱口令。如果是直接连接&#xff0c;去读取设定好的文件&#xff0c;并写入本地保存。 这些函数用于从MySQL数据库中获取指定文件的内容…

3D+仿真分析:高效实现海底石油管道系统设计

Bentley AutoPIPE 助力节省寻找更佳管道路线所需的时间和成本 评估管道完整性以提高石油产量 从墨西哥湾浅水区开采石油通常需要铺设新的海底管道&#xff0c;从而在高压和高温条件下高效、安全地输送原油。 Grupo EspecializadoenObras Marinas (GEOMSA) 是墨西哥湾管道—土体…

iOS ------ JSONModel源码

一&#xff0c;JSONModel的基本使用 1&#xff0c;基本使用方法 - (instancetype)initWithDictionary:(NSDictionary *)dict error:(NSError **)err; - (instancetype)initWithData:(NSData *)data error:(NSError **)error; - (instancetype)initWithString:(NSString *)str…

239 基于matlab的EKF(扩展卡尔曼滤波)_UKF(无迹卡尔曼滤波)_PF(粒子滤波)三种算法的估计结果比较

基于matlab的EKF(扩展卡尔曼滤波)_UKF(无迹卡尔曼滤波)_PF&#xff08;粒子滤波&#xff09;三种算法的估计结果比较&#xff0c;输出估计误差&#xff0c;并单独对粒子滤波进行估计及其置信区间可视化。程序已调通&#xff0c;可直接运行。 239 EKF(扩展卡尔曼滤波) - 小红书 …

Linux中gcc/g++的使用

文章目录 前言gcc/g 前言 gcc和g即为编译器。其中gcc为c语言的编译器&#xff0c;只能编译c语言&#xff1b;g为c的编译器&#xff0c;既能编译c语言&#xff0c;又能编译c。 在前面的文章中&#xff0c;我们提到代码转换成可执行程序需要经过 预处理&#xff08;进行宏替换)…

Transformer全流程细致讲解

文章目录 1. Transformer 架构概述2. 编码器&#xff08;Encoder&#xff09;2.1 输入嵌入层&#xff08;Input Embedding Layer&#xff09;2.1.1 一个简单的示例 2.2 位置编码&#xff08;Positional Encoding&#xff09;2.2.1 Transformer中采用的位置编码方式2.2.2 公式中…

创新指南|组织健康仍然是企业创新长期绩效的关键

麦肯锡关于组织健康的最新调查结果表明&#xff0c;它仍然是当今全球市场中价值创造的最佳预测者和竞争优势的可持续来源。在本文中&#xff0c;我们将探讨最新的 OHI 结果&#xff0c;并重点介绍该指数揭示的有关领导力、数据和技术以及人才管理的一些更引人注目的见解。我们还…