Linux进程的fork、exit、wait等函数;区分父子进程;GDB调试多进程

news2024/12/23 15:32:10

Linux系统中进程可以创建子进程。

1. fork函数:创建新进程

#include<sys/types.h>
#include<unistd.h>

pid_t fork(void);
/*
功能:
    一个进程创建新进程。原进程为父进程,新进程为子进程。
返回值:
    成功:
        子进程中返回0,父进程中返回子进程的pid。
    失败:-1
    失败原因:
        a) 进程总数达到系统上限,此时errno被设置为EAGAIN
        b) 系统内存不足,此时errno被设置为ENOMEM
*/

fork示例:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>


int test01() {
    fork(); // fork成功的话,给子进程返回0,给父进程返回子进程的pid
    printf("Hello world\n");
}

运行结果:

pc指针:指向当前运行指令的下一条指令。

fork时,父进程的pc指针也会复制,因此子进程会从fork后的指令开始执行。


2. exit和_exit函数:结束进程

#include<stdlib.h>

void exit(int status);  // 标准库函数

/*******************************************/

#include<unistd.h>

void _exit(int status);  // 系统调用
/*
功能:
    调用结束次函数的进程。
参数:
    status:返回给父进程的参数(低8位有效),
            此参数根据需求填写;
            例如,写123,则正常退出时会传递状态码123;
            若被信号终止,则传递的退出码就是信号的编号,而不再是123.
*/

exit和_exit的区别:

exit会刷新缓冲区、关闭文件描述符:

  exit和_exit区别示例:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>


int main(int argc, const char* argv[]) {
    printf("Hello.");

    _exit(0); // 什么也不输出。
    //exit(0); // 输出Hello. exit会刷新缓冲区
    //return 0; // 输出Hello. return 0也会刷新缓冲区
}
/* 
注:若printf中有\n,则_exit(0)也会输出Hello. 
因为\n在标准输出中具有刷新缓冲区的作用.
*/

return也可结束进程,return和exit的区别是:

若一个进程先后调用funcA,funcB。

若funcA中使用exit结束,则该进程立即结束,不会再调用funcB;

若funcA中使用return结束,则表示该funcA函数结束,仍会继续调用funcB.


3. wait和waitpid

进程退出时,内核释放该进程大部分资源,包括打开的文件、占用的内存等。但仍保留了该进程的PCB信息,因此需要父进程通过wait和waitpid函数来进一步回收,否则这些进程会变成僵尸进程,消耗系统资源。

(1)wait函数:

  阻塞等待子进程退出,回收子进程资源。

#include<sys/types.h>
#include<sys/wait.h>

pid_t wait(int* status);
/*
功能:
    等待任意一个子进程结束,回收该子进程资源,并传出子进程退出的状态到status。
参数:
    status:存储进程退出时的状态信息。
返回值:
    成功:被回收的子进程号
    失败:-1
*/

设置退出时的状态信息status使用方式:

(1)若WIFEXITED(status)非0:
        表示进程正常退出,可使用WEXITSTATUS(status)获取进程退出状态码(即exit的参数)。
(2)若WIFSIGNALED(status)非0:
        表示进程异常终止(被信号杀死),可使用WTERMSIG(status)获取终止信号的编号。
(3)若WIFSTOPPED(status)非0:
        表示进程被暂停,可使用WSTOPSIG(status)获取暂停信号的编号。
(4)若WIFCONTINUED(status)非0:
        表示进程暂停后被继续运行。

调用wait函数的进程会被阻塞,直到有一个子进程退出或收到一个不能被忽视的信号。

若调用wait的进程无子进程,wait函数会立即返回;若子进程早就结束,则wait函数也会立即返回,并且回收该早就结束的子进程。

若参数status的值不为NULL,wai函数会把子进程退出时的状态(int型数值)存入status,指出子进程是否为正常结束。该退出的状态信息。

wait和获取子进程退出状态信息示例:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>

int main(int argc, const char* argv[]) {
    pid_t pid = -1;
    int ret = -1;
    int status = 0;

    pid = fork();
    if (-1 == pid) {
        perror("fork");
        return 1;
    }

    if (0 == pid) { // 子进程
        printf("子进程%d运行...\n", getpid());
        sleep(10);
        exit(10); // 子进程终止。指定状态码为10.
    }

    // 父进程
    printf("父进程执行。等待子进程退出,回收其资源...\n");

    // 父进程阻塞,等待子进程退出
    ret = wait(&status);
    if (-1 == ret) {
        perror("wait");
        return 1;
    }
    printf("父进程回收子进程%d的资源...\n", ret);

    // 获取子进程退出的状态
    if (WIFEXITED(status)) {  // 子进程正常退出
        printf("子进程正常退出,状态码:%d\n", WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
        printf("子进程被信号%d杀死了...\n", WTERMSIG(status));
    } else if (WIFSTOPPED(status)) {
        printf("子进程被信号%d暂停...\n", WSTOPSIG(status));
    }
    return 0;
}

 正常退出,显示指定的状态码10,运行结果:

 在另一个终端使用kill -9杀死该进程,运行结果:

在另一个终端直接使用kill杀死该进程,运行结果:

在另一个终端直接使用kill -19暂停该进程,运行结果:

 (2)waitpid函数:

 阻塞等待子进程退出,回收子进程资源;也可设置非阻塞,无子进程退出则立即返回。

#include<sys/types.h>
#include<sys/wait.h>

pid_t waitpid(pid_t pid, int* status, int options);
/*
功能:
    等待子进程结束,回收该子进程资源,并传出子进程退出的状态到status。
参数:
    pid:
        > 0:等待进程号为pid的子进程退出;
        = 0:等待同一个进程组中的任何子进程退出;若子进程已加入其他进程组,则不会等待;
        = -1:等待任意一个子进程退出,此时和等价于wait函数;
        < -1:等待进程组号为pid绝对值的进程组中的任何子进程退出。

    status:存储进程退出时的状态信息。

    options:
        0:阻塞父进程,等待子进程退出。此时同wait函数。
        WNOHANG:若无任何子进程退出,则立即返回。
        WUNTRACED:若子进程暂停,则立即返回。(少用)
返回值:
    a) 有子进程退出时,waitpid返回已收集到的退出子进程的进程号;
    b) 若options设为WNOHANG,调用waitpid时无子进程退出,则返回0;
    c) 若调用中出错,返回-1,同时设置errno. 
        如当pid对应的进程不存在,或pid对应的进程不是调用waitpid进程的子进程,就会出错,此时errno        
        被设为ECHILD。
*/    

 waitpid使用示例:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>

int main(int argc, const char* argv[]) {
    pid_t pid = -1;
    int ret = -1;
    int status = 0;

    pid = fork();
    if (-1 == pid) {
        perror("fork");
        return 1;
    }

    if (0 == pid) { // 子进程
        printf("子进程%d运行...\n", getpid());
        sleep(10);
        exit(10); // 子进程终止
    }

    // 父进程
    printf("父进程执行。等待子进程退出,回收其资源...\n");

    // 父进程阻塞,等待子进程退出
    //ret = waitpid(-1, &status, 0); // 此时等价于wait
    ret = waitpid(-1, &status, WNOHANG); // 设置非阻塞
    if (-1 == ret) {
        perror("wait");
        return 1;
    }

    if (0 == ret) {
        printf("暂无子进程退出,waitpid直接返回.\n");
    } else {
        printf("父进程回收子进程%d的资源...\n", ret);
    }

    return 0;
}

 运行结果:

 26行添加sleep(11)后,运行结果:


4. 区分父子进程

通过fork的返回值区分:

若fork成功,则在子进程中返回0,父进程中返回子进程的pid。

示例:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/wait.h>

int main(int argc, const char* argv[]) {
    pid_t pid = -1;
    int status = 0;
    int ret = -1;

    // 创建子进程。若创建成功,则在子进程中返回0,父进程中返回子进程的pid
    pid = fork();
    if (0 < pid) {
        perror("fork");
        return 1;
    }
    
    if (0 == pid) {  // 子进程
        printf("这是子进程。进程号 = %d, 父进程号 = %d\n", getpid(), getppid());
        exit(0);  // 退出子进程,或者return。
    } else {  // 父进程
        printf("这是父进程。进程号 = %d, 子进程号 = %d\n", getpid(), pid);
    }
    
    ret = wait(&status); // 父进程等待回收子进程资源
    if(-1 == ret) {
        perror("wait");
        return 1;
    }
    
    return 0;
}

 运行结果:


5. 父子进程关系

(1)写时拷贝(copy-on-write)。读时共享、写时拷贝。父子进程未修改某变量时,无需拷贝;当父子进程之一修改该变量时,子进程就拷贝一份。

(2)在fork之前、之后open对文件描述信息的影响:

  • 在fork前open,父子进程共享一个文件描述信息,包括引用计数、文件偏移等等。子进程复制了父进程的文件表项指针,指向的是同一个文件表项。
  • 在fork后open,父子进程各自有自己的文件描述信息,互不影响。一个文件被打开了两次,即引用计数值为2,每个进程都有自己的一份,文件偏移也互不影响。

简单来说,就是先open再fork,文件描述信息是共享的;先fork再open,文件描述信息是独立的。

文件描述信息是内核为每个进程维护的一个文件描述符表,fork时文件描述信息不会被子进程复制,而是被共享(内核空间被所有进程共享)。因此fork之前open、read、write等操作改变了文件偏移,fork之后子进程会从改变了的文件偏移位置继续操作。


6. 父子进程堆区内存开辟释放问题

父子进程都要释放各自堆区的内存。

如下程序父子进程未释放堆区内存,有内存泄漏问题:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>

int main(int argc, const char* argv[]) {
    pid_t pid = -1;
    int ret = -1;
    int status = 0;
    int* p = malloc(sizeof(int)); 
    (*p) = 1;

    pid = fork();
    if (-1 == pid) {
        perror("fork");
        return 1;
    }

    if (pid > 0) {
        // 父进程
        (*p) = 3;
        printf("父进程(*p) = %d\n", (*p));
    } else {
        // 子进程
        sleep(1);
        printf("子进程(*p) = %d\n", (*p));
        exit(0);
    }

    ret = wait(&status); // 父进程回收子进程资源
    if(-1 == ret) {
        perror("wait");
        return 1;
    }
    
    return 0;
}

运行结果:

但存在内存泄漏问题。

valgrind查看内存泄漏情况:

有内存泄漏。 

 修改:在父子进程退出前释放各自的堆区内存

free(p);
p = NULL;

再次查看内存泄漏情况:无内存泄漏。


补充: GDB调试多进程

GDB调试

有如下的多进程程序,需要使用GDB分别调试父子进程:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>

int main(int argc, const char* argv[]) {
    pid_t pid = -1;
    int status = 0;
    int ret = -1;

    pid = fork();
    if (-1 == pid) {
        perror("fork");
        return 1;
    }

    if (pid > 0) {
        // 父进程
        printf("父进程说太强了");
        printf("父进程笑尿了");
        printf("父进程哈哈哈哈哈");
    } else {
        // 子进程
        printf("子进程说太强了");
        printf("子进程笑尿了");
        printf("子进程哈哈哈哈哈");
        exit(0);
    }

    
    ret = wait(&status); // 父进程等待回收子进程资源
    if(-1 == ret) {
        perror("wait");
        return 1;
    }

    return 0;
}

GDB调试默认跟踪父进程,如下:

如何跟踪子进程呢?

在fork函数调用之前设置跟踪子进程:

set follow-fork-mode child

然后就会跟踪子进程,如下:

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

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

相关文章

SuperMap iObjects Docker打包全攻略

SuperMap iObjects Docker打包全攻略 文章目录 SuperMap iObjects Docker打包全攻略说明开始打包iObjects容器启动容器参考 说明 此教程编写时使用的iObjects版本为 10.2.1 &#xff0c;理论高版本同样支持&#xff0c;具体自测。基础镜像为 Docker 官方 ubuntu:16.04完整版。…

C++ 类和对象(中)构造函数 和 析构函数

上篇链接&#xff1a;C 类和对象&#xff08;上&#xff09;_chihiro1122的博客-CSDN博客 类的6个默认成员函数 我们在C当中&#xff0c;在写一些函数的时候&#xff0c;比如在栈的例子&#xff1a; 如上述例子&#xff0c;用C 返回这个栈是否为空&#xff0c;直接返回的话&am…

基于OpenCV-python的图像增强和滤波

目录 彩色空间 直方图均衡化 图像滤波 梯度 一、彩色空间 OpenCV 的颜色空间主要有 BGR、HSV、Lab等&#xff0c;cvtColor 函数可以让图像在不同颜色空间转换。例如通过将花的图像转换到 HSV 颜色空间&#xff0c;在HSV空间内过滤出只含有花瓣颜色的像素&#xff0c;从而提…

公共资源包发布流程详解

文章目录 公有包发布并使用npm安装git仓库协议创建及使用 npm 私有包创建及使用 group npm 私有包私有仓账密存放位置 当公司各个系统都需要使用特定的业务模块时&#xff0c;这时候将代码抽离&#xff0c;发布到 npm 上&#xff0c;供下载安装使用&#xff0c;是个比较好的方案…

SQL Server基础 第七章 连接查询(内连接、表别名、左外连接、右外连接)

前言 连接查询是关系数据库中最主要的查询&#xff0c;主要包括内连接、外连接和交叉连接等。通过连接运算符可以实现多个表查询。前面章节的查询均是基于单表进行&#xff0c;但有时需要获取的信息存储于多张表中&#xff0c;此时就必须使用本章所介绍的多表连接查询技术来获取…

关于debug一晚上的一些思考,做开发到底要养成什么习惯?

总结&#xff1a;日志一定要写&#xff0c;日志一定要写&#xff0c;日志一定要写&#xff01; 今天晚上是我学开发过程中很不一样的一晚&#xff0c;今晚学到了很多。 虽然我也只是一个开发的初学小白&#xff0c;今天的debug分享是我的一个小方法和一个小记录&#xff0c;如…

第四章——数学知识2

欧拉函数 欧拉函数表示的是1-n中与n互质数的个数。 如1-6中&#xff1a;1&#xff0c;5都和6互质&#xff0c;因此互质数为2 欧拉函数分解质因数后表示为&#xff1a; 互质数个数可表示为 int main() {int n;cin >> n;while(n--){int a;cin >> a;//分解质因数int r…

TypeScript自学笔记

目录 1.什么是Ts? 1.1 设计公司&#xff1a;微软 1.2 TS概述 1.3 TS是静态类型 JS是动态类型 1.4 TS是强类型语言 JS是弱类型语言 2.TypeScript编译器 2.1 安装 2.2 TS自动编译和编译选项设置 3.TS的数据类型 3.1 基础数据类型number、string、boolean 3.2 Arrays&a…

大数据架构(二)大数据发展史

1.传统数仓发展史 传统数据仓库的发展史这里不展开架构细讲&#xff0c;只需快速过一遍即可。了解这个历史发展过程即可。 1.1 传统数仓历史 1.1.1 5个时代 传统数仓发展史可以称为5个时代的经典论证战。按照两位数据仓库大师 Ralph kilmball、Bill Innmon 在数据仓库建设理念上…

吃透Redis面试八股文

Redis连环40问&#xff0c;绝对够全&#xff01; Redis是什么&#xff1f; Redis&#xff08;Remote Dictionary Server&#xff09;是一个使用 C 语言编写的&#xff0c;高性能非关系型的键值对数据库。与传统数据库不同的是&#xff0c;Redis 的数据是存在内存中的&#xf…

Python Review 01

1、Anaconda Installation 使用Anaconda Navigator进行python环境管理&#xff0c;使用conda进行依赖管理。 2、Use of Jupyter 将代码写入一个个cell&#xff0c;代码文件由一个个cell组成&#xff0c;书写代码时就像一行一行在写笔记&#xff0c;这就是notebook的代码编辑环…

无宿主机权限情况下,获取pod的日志文件

如果没有宿主机权限,是无法访问宿主机及里边的文件的,但是如果想获取某些文件,如日志等如何操作呢? 整体思路:通过抓包工具,抓取websocket的的信息,然后把信息处理拼接后导出即可。 1、启动抓包工具 我这里使用的是charles抓包工具 2、打开对应pod的命令行窗口 3、抓…

基于灰度图像和小波图的双模态卷积神经网络在心血管疾病分类中的应用

目录 一、研究对象和ECG记录预处理 二、机器学习和LSTM 三、将一维ECG记录转换为二维图像 四、双模态CNN模型 五、性能评估 参考文献 一、研究对象和ECG记录预处理 本研究采用Chapman大学和Shaoxing人民医院&#xff08;浙江大学医学院绍兴医院&#xff09;收集的12导联…

领导力专题︱如何培养与提升领导力

本文内容结构 一、领导力的核心技能 1、完美领导者&#xff1f; 2、认识你的组织需要什么 3、不同层面领导力共有的特征和技能 4、你的个人行为准则 5、领导风格 6、创造个人影响力 7、完善自己的领导网络 二、领导力与领导者 1、领导力与组织环境 2、领导者还是管理…

2023/4/23总结

项目&#xff1a; 做出了个人信息界面&#xff0c;通过点击头像的方式&#xff1a; 然后就是点击头像可以选择文件&#xff08;后面考虑是存储该文件到自己的文件夹还是只是加载该文件比较好&#xff09;只是能选择文件&#xff0c;写了指定文件后缀名的代码但是好像没什么用…

如何将Edge插件迁移至Google?

问题描述&#xff1a; 因为无法访问谷歌&#xff0c;无法从谷歌插件市场下载插件 第一步&#xff1a;在电脑上找到插件地址 高亮部分&#xff1a;自己电脑上的用户名【不同用户可能会有所不同】 C:\Users\star-dream\AppData\Local\Microsoft\Edge\User Data\Default\Extensi…

rust的现状和未来发展

rust现状: Stack Overflow 的开发者调研显示只有 7% 的开发者在使用 Rust&#xff0c;对比 JavaScript、Python 等语言&#xff0c;使用 Rust 的开发者占比并不高&#xff1b;但从 2016 年开始&#xff0c;Rust 每年都是开发者最爱的编程语言。 根据 JetBrains 2021 年的调研报…

SSH远程访问及控制

文章目录 1.SSH远程管理1.1 SSH的概述1.2 OpenSSH服务器1.3 sshd_ config常用选项设置1.4 SSH端口、配置文件 2.配置OpenSSH服务端2.1 更改端口号2.2 用户登录控制 3.登录验证方式3.1 密码验证3.2 密钥对验证3.3 配置密钥对验证 5.TCP Wrappers访问控制5.1 TCPWrappers机制的基…

第37讲:Python if-elif-else流程控制语句核心概念以及案例演示

文章目录 1.流程控制的概念2.Python中代码块的相关注意事项3.if流程控制语句的语法格式4.if流程控制的简单使用4.1.单分支的if流程控制语句4.2.加else语句的if流程控制4.3.多分支的if流程控制4.4.多分支if代码优化 5.对象的布尔值6.if-else条件表达式6.1.if-else条件表达式语法…

String的那些事儿

String作为我们最常用的Java类之一&#xff0c;在日常开发过程中充当着重要角色&#xff1f;那么大家真的了解String吗&#xff1f;让我们一起看看下面的问题&#xff1a; String内存结构&#xff1f;对象存储在堆上还是栈上&#xff1f;一个String有多长&#xff1f;占内存多…