Linux fork、进程的退出和等待详解

news2024/9/20 1:13:46

初识fork函数

它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#include <unistd.h>
pid_t fork(void);


返回值:子进程中返回0,父进程返回子进程id,出错返回-1

最简单的fork使用示例

#include<stdio.h>
#include<unistd.h>
int main()
{
    pid_t pid=fork();
    if(pid<0)
    {
        printf("创建出错\n");
    }
    else if(pid==0)
    {
        printf("子进程创建成功\n");
    }
    else{
         printf("父进程\n");
    }
    printf("父进程\n");
    return 0;
}

父进程的判断可以不写,只写子进程和出错的判断就ok

代码详解

如果想要子进程运行完自己的部分后不执行原程序部分则需要加入退出部分

创建进程fork

#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret=fork();
if(ret<0)
{
    perror("fork");
    return 1;
}
else if(ret==0)
{
    printf("child_id : %d! , ret %d\n " ,getpid(),ret);
}
else //father ret>0  
    printf("father_id : %d! , ret %d\n " ,getpid(),ret);

sleep(1);
return 0;
    }





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

int main() {
    pid_t pid;

    // 创建子进程
    pid = fork();

    if (pid < 0) {
        // fork失败
        fprintf(stderr, "fork failed");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("This is the child process. PID: %d\n", getpid());
    } else {
        // 父进程
        printf("This is the parent process. Child PID: %d\n", pid);
    }

    // 父子进程都会执行以下代码
    printf("PID: %d - End of the program\n", getpid());

    return 0;
}

fork进程创建基本功能

  • fork() 调用在父进程中调用一次,但返回两次:一次在父进程中返回,一次在子进程中返回。
  • 在父进程中,fork() 返回子进程的进程ID。一般都是>0
  • 在子进程中,fork() 返回0。
  • 如果 fork() 调用失败,它返回一个负值。

fork工作原理的理解

fork的优点

  • 利用子进程计算一些数据
  • 让子进程和父进程分开执行不同代码块

写实拷贝

  • 通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本

fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

进程退出与等待

进程退出场景

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

进程常见退出方法

正常终止(可以通过 echo $? 查看进程退出码):

  • 从main返回
  •  调用exit
  • _exit

异常退出:

  • ctrl + c,信号终止

_exit函数

#include <unistd.h>
void _exit(int status);


参数:status 定义了进程的终止状态,父进程通过wait来获取该值

说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。

-1 的补码表示是:1111 1111转成10进制位255

注意事项:

  • _exit() 调用会立即终止进程,不会执行任何清理操作,例如不会刷新标准I/O缓冲区,不会调用任何终止处理程序
  • _exit() 会关闭进程打开的所有文件描述符(通过open等函数打开文件返回后的值)。
  • _exit() 调用后,进程所占用的资源(如内存、文件描述符等)将被操作系统释放。
  • _exit() 中的参数只需要修改低8位即可

exit函数

#include <unistd.h>
void exit(int status);

参数:status 定义了进程的终止状态,父进程通过wait来获取该值

exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作:

1. 执行用户通过 atexit或on_exit定义的清理函数。
2. 关闭所有打开的流,所有的缓存数据均被写入
3. 调用_exit

由此可知,C语言用的是exit()进而能在退出前显示缓冲区内容

而系统调用_exit()则不会显示缓冲区内容

代码结果反应退出函数区别

代码

int main()
{
printf("hello");
exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
hello[root@localhost linux]#
int main()
{
printf("hello");
_exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
[root@localhost linux]#

区别

exit() 函数:

  • exit() 是C标准库函数,定义在 <stdlib.h> 头文件中。
  • 当调用 exit()时,它会刷新所有标准I/O流(例如 stdout),关闭所有标准库中打开的文件描述符,调用通过 atexit() 注册的所有退出处理函数,然后终止程序。
  • 因此,当你在调用 printf("hello") 后调用 exit(0),printf 的输出会被刷新到标准输出,你会在终端看到 “hello”。(exit()读取缓存文件)

_exit() 系统调用:

  • _exit() 是一个系统调用,定义在 <unistd.h> 头文件中。
  • 当调用 _exit() 时,它会立即终止程序,而不会刷新标准I/O流,也不会调用任何退出处理函数。
  • 因此,当你在调用 printf("hello") 后调用 _exit(0)printf 的输出缓冲区不会被刷新,“hello” 不会被打印到终端。(_exit()不读取缓存文件)

不推荐使用_exit(),使用exit()和return满足日常需求

return退出

return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。

注意:

exit() 函数和 return 语句通常不能同时使用,因为它们都会导致程序终止。

wait方法

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);


返回值:
        成功返回被等待进程pid,失败返回-1。
参数:
        输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
        当正常返回的时候waitpid返回收集到的子进程的进程ID;
        如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
        如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;



参数:
       1. pid:

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

       2.status:

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

       3.options(阻塞方式):

  • WNOHANG:如果子进程尚未终止,waitpid() 函数不会阻塞父进程,而是立即返回 -1,并设置 errno 为 ECHILD。这通常用于非阻塞等待即父进程可以在等待子进程的同时继续执行其他任务。
  • WUNTRACED:如果子进程是暂停状态(例如,因为它正在等待输入或正在后台运行),waitpid() 函数会返回子进程的进程ID,并设置 status 变量。
  • WNOHANG 和 WUNTRACED 结合使用(WNOHANG|WUNTRACED):如果指定的子进程尚未终止,waitpid() 函数不会阻塞调用进程,而是立即返回 -1,并设置 errno 为 ECHILD。如果指定的子进程是暂停状态,waitpid() 函数会返回子进程的进程ID,并将子进程的退出状态和信号信息存储在 status 变量中。(组合使用意味多种情况多种选项,不单单只适合一种情况)
  • WCONTINUED:如果子进程是暂停状态,并且被信号唤醒(例如,因为它等待的输入已到达),waitpid() 函数会返回子进程的进程ID,并设置 status 变量。

当第三个参数设置为 0 时,它等同于 WNOHANG 和 WUNTRACED 的组合。这意味着 waitpid() 函数不会阻塞父进程,并且如果子进程是暂停状态,它会返回子进程的进程ID,并设置 status 变量。如果子进程尚未终止,waitpid() 函数不会阻塞父进程,而是立即返回 -1,并设置 errno 为 ECHILD


等待函数注意事项

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

代码结果反应等待函数

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

int main() {

    pid_t pid = fork();
    if (pid < 0) {
        perror("fork failed");
        return 1;
    } else if (pid == 0) {
        // 子进程代码
        printf("子进程创建成功,倒数3s\n");
        int n = 3;
        while (n--) {
            printf("%d ", n);
        }
        printf("\n");
        exit(2);  // 子进程正常退出
    } else {
        // 父进程代码
        int status;
        pid_t ret = waitpid(pid, &status, 0);  // 等待子进程结束
        if (ret == -1) {
            perror("waitpid failed");
            return 1;
        }

        if (WIFEXITED(status))// 子进程正常退出
        {
            printf("子进程的退出状态: %d\n", WEXITSTATUS(status));
        }
        else if (WIFSIGNALED(status))// 子进程因信号终止 
        { 
            printf("子进程被信号终止,信号: %d\n", WTERMSIG(status));
        }
    }

    return 0;
}

结果

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

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

相关文章

初阶数据结构的实现2 双向链表

1.双向链表 1.1 概念与结构 1.2实现双向链表 1.2.1定义程序目标 #define _CRT_SECURE_NO_WARNINGS 1 #pragma once #include<stdio.h> #include<assert.h> #include<stdlib.h> #include<stdbool.h> typedef int LTDateType; //定义双向链表结构 typ…

list(链表)容器的规则及list的高级排序案例

1.list的基本概念&#xff1a; 功能&#xff1a;将数据进行链式存储 list&#xff08;链表&#xff09;是一种物理存储单元上非连续的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接实现的 链表是由一系列节点组成&#xff0c;节点的组成包含存储数据元素的…

秋招提前批:抢占求职先机的绝佳机遇(25届提前批名单公布)

秋招&#xff0c;对于即将毕业的大学生来说&#xff0c;是一场至关重要的求职盛宴。每年的秋季&#xff0c;各大企业纷纷抛出橄榄枝&#xff0c;为应届毕业生提供了众多宝贵的就业机会。然而&#xff0c;在求职的漫漫征途中&#xff0c;秋招是一场关键的战役。而秋招提前批&…

二百四十五、海豚调度器——用DolphinScheduler调度执行复杂的HiveSQL(HQL包含多种海豚无法正确识别的符号)

一、目的 在Hive中完成复杂JSON&#xff0c;既有对象还有数组而且数组中包含数组的解析后&#xff0c;原本以为没啥问题了&#xff0c;结果在DolphinScheduler中调度又出现了大问题&#xff0c;搞了一天、试了很多种方法、死了无数脑细胞&#xff0c;才解决了这个问题&#xf…

fastjson1.2.24 反序列化漏洞复现

fastjson简介 Fastjson 是一个 Java 库&#xff0c;可以将 Java 对象转换为 JSON 格式&#xff0c;当然它也可以将 JSON 字符串转换为 Java 对象。 Fastjson 可以操作任何 Java 对象&#xff0c;即使是一些预先存在的没有源码的对象。 这里json与java对象之间的转换&#xff0…

【GaussDB关键技术原理|高可用】DCF双集群容灾

GaussDB关键技术原理&#xff1a;高性能篇&#xff0c;从GaussDB数据库性能优化系统概述、查询处理综述、高性能关键技术等方面为大家进行了解读&#xff0c;并对高斯数据库性能优化做了总结。本篇将分享GaussDB高可用方面的相关知识&#xff0c;详细介绍GaussDB的DCF与双集群容…

双目相机立体匹配算法概述

这里写目录标题 双目相机立体匹配算法概述1.算法分类2.传统算法2.1 局部算法2.2 全局算法2.3 半全局算法 3.深度学习算法3.1 基于CNN的方法3.2 基于GAN的方法3.3 基于transformer的方法 4.总结5.参考文献 双目相机立体匹配算法概述 双目立体匹配是计算机视觉中的一个重要研究方…

数据挖掘与分析部分实验内容

一、机器学习算法的应用 1. 朴素贝叶斯分类器 相关代码 import pandas as pd from sklearn.model_selection import train_test_split from sklearn.naive_bayes import GaussianNB, MultinomialNB from sklearn.metrics import accuracy_score # 将数据加载到DataFrame中&a…

Stable Diffusion 使用详解(3)---- ControlNet

背景 炼丹师在AI绘画的过程中&#xff0c;由于Stable Diffusion的原理是水滴式的扩散作图原理&#xff0c;其实在前面也有提到&#xff0c;他的发挥是‘不稳定’的&#xff0c;因为你没有办法做到精确控制&#xff0c;只能说是大致符合你的预期。你不能总依赖抽卡固定随机数种…

SpringCloud--负载均衡

目录 前言 一.负载均衡的引入 1.1问题引入 1.2代码修改实现 二.负载均衡介绍 2.1实现负载均衡 2.2负载均衡策略 2.3LoadBalancer 原理 学习专栏&#xff1a;http://t.csdnimg.cn/tntwg 前言 在前面的Eureka当中&#xff0c;我们虽然实现了从注册中心中获取url&#xf…

spark 广播变量broadcast

broadcast使用如下图&#xff0c;可以看到创建broadcast是val barr1 sc.broadcast(arr1)&#xff0c;使用broadcast是barr1.value 创建broadcast是使用的broadcastManager。 BroadcastManager cachedValues变量是map结构&#xff0c;key是broadcastId&#xff0c;强引用&a…

初识C++|模板初阶

&#x1f36c; mooridy-CSDN博客 &#x1f9c1;C专栏&#xff08;更新中&#xff01;&#xff09; 目录 &#x1f349;1. 泛型编程 &#x1f349;2. 函数模板 &#x1f95d;2.1 函数模板概念 &#x1f95d;2.2 函数模板格式 &#x1f95d;2.3 函数模板的原理 &#x1f95…

java题目之数字加密以及如何解密

public class Main6 {public static void main(String[] args) {// 某系统的数字密码&#xff08;大于0&#xff09;&#xff0c;比如1983&#xff0c;采用加密方式进行传输//定义了一个静态数组int []arr{1,9,8,3};//1.加密//先给每位数加上5for (int i 0; i <arr.length …

1997. 孤独的素数(结果有一点问题,希望能一起求解)

问题描述 在一个 &#x1d45b; 行 &#x1d45a;列的矩阵王国中&#xff0c;生活着一些整数&#xff0c;其中一些是素数&#xff0c;一些不是素数。如果一个素数的上下左右、左上、右上、左下、右下相邻的数中都没有素数&#xff0c;我们就认为这是一个孤独的素数。 比如&am…

Spring Boot + Spring Cloud 入门

运行配置 java -jar spring-boot-config-0.0.1-SNAPSHOT.jar --spring.profiles.activetest --my1.age32 --debugtrue "D:\Program Files\Redis\redis-server.exe" D:\Program Files\Redis\redis.windows.conf "D:\Program Files\Redis\redis-cli.exe" &q…

【零基础必看的前端教程】——JavaScript(四)类型转换

欢迎大家打开前端的新篇章——JavaScript&#xff0c;JavaScript与HTML、CSS合称为前端三大件&#xff0c;JavaScript是前端的重中之重&#xff0c;小洪将继续以零基础视角&#xff0c;带你循序渐进学习前端知识&#xff0c;一看就懂&#xff0c;小白也能转行做前端&#xff01…

基于WebGoat平台的SQL注入攻击

目录 引言 一、安装好JAVA 二、下载并运行WebGoat 三、注册并登录WebGoat 四、模拟攻击 1. 第九题 2. 第十题 3. 第十一题 4. 第十二题 5. 第十三题 五、思考体会 1. 举例说明SQL 注入攻击发生的原因。 2. 从信息的CIA 三要素&#xff08;机密性、完整性、可用性&…

推荐一款基于Spring Boot 框架开发的分布式文件管理系统,功能齐全,非常便捷(带私活源码)

前言 在数字化时代&#xff0c;文件管理是企业和个人用户的基本需求。然而&#xff0c;现有的文件管理系统往往存在一些痛点&#xff0c;如存储空间有限、文件共享困难、缺乏在线编辑功能、移动端适配性差等。这些问题限制了用户在不同设备和场景下的文件处理能力。 为了解决…

spring-retry详解

spring-retry详解 1.引入依赖2.Retryable基础使用3.Recover使用4.Retryable参数详解5.需要注意 重试机制对于大部分场景来说都是必要的&#xff0c;比如同步调用三方接口&#xff0c;三方接口、信息拉取等网络原因突然不通&#xff0c;有了重试就可以多一些容错机制&#xff0c…

压缩包方式windows安装mysql

压缩包方式windows安装mysql 本文介绍通过压缩包方式在Windows本地安装mysql。 一、 具体步驟 步骤1&#xff0c;下载mysql压缩程序 这里好像上传不了压缩文件&#xff0c;先这样吧&#xff0c;后期补充。 步骤2&#xff0c;解压并手写添加 my.ini 文件 my.ini 文件内容如下…