[Linux] 进程创建、退出和等待

news2024/10/7 0:36:02

标题:[Linux] 进程创建、退出和等待

个人主页@水墨不写bug

(图片来源于AI)

目录

一、进程创建fork()

1) fork的返回值:

2)写时拷贝

​编辑3)fork常规用法

4)fork调用失败的原因

二、什么是进程退出

1)exit()函数于系统调用接口_exit()有什么区别?

三、僵尸进程和进程等待

1)父进程处理僵尸进程就需要进程等待

2)阻塞等待 

3)非阻塞等待:

 4)输出型参数status:

 总结:


正文开始:

一、进程创建fork()

         fork()是Linux操作系统提供的系统调用,作用是从一个已经存在的进程中创建一个新进程。新进程为子进程,原进程为父进程。

//头文件
#include <unistd.h>

//函数原型
pid_t fork(void);

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

 

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  •         分配新的内存块和内核数据结构给子进程
  •         将父进程部分数据结构内容拷贝至子进程
  •         添加子进程到系统进程列表当中
  •         fork返回,开始调度器调度

        具体创建过程的内存图示见《Linux:初识进程地址空间》 ,子进程创建后这里仅仅给出一张图:

         最重要的是:当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。如下面的例子:

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

int main()
{
    printf("fork之前 我的pid:%d\n",getpid());
    pid_t id = fork();
    printf("fork之后 我的pid:%d, fork的返回值:%d\n",getpid(),id);
    return 0;
}

        运行之后,结果如图:

         分析:

        在fork之前,只有一个进程,pid=48247;

        fork之后,父进程(pid=48247)创建出子进程(pid=48248):从父子进程的pid临近和fork给子进程的返回值为0,给父进程的返回值为子进程的pid可以看出来。

         fork之后,父子进程都执行到fork函数这一行,于是,fork之前的代码不会被执行,之后的代码会被父子进程各自执行一次

1) fork的返回值:

        给子进程返回0;

        给父进程返回子进程的pid;

        如果出错了,则子进程没有被创建出来,那么给仅有的一个进程(父进程)返回-1;

2)写时拷贝

        写时拷贝(copy-on-write)是一种内存管理技术,用于优化内存使用和提高性能。它是一种延迟拷贝的策略,在创建副本之前,共享资源将不会被复制。

        写时拷贝的原理是,当一个对象需要被拷贝时,不会立即进行拷贝操作。而是让新对象与原对象共享同一份资源。当其中一个对象修改了该资源时,才会将资源复制到新的对象上。这样可以减少内存的使用,避免了不必要的复制操作。

3)fork常规用法

        一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。

        一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。(进程替换函数)

4)fork调用失败的原因

        系统中有太多的进程;

        实际用户的进程数超过了限制;


二、什么是进程退出

         我们在当初学习C语言的时候,经常会malloc空间,但是动态内存开辟是会有失败的时候,malloc的最保险的用法:

    int size = 10;

    int* arr = (int*)malloc(size*sizeof(int));
    if(arr == NULL)
    {
        perror("malloc fail:");
        exit(-1);
    }

         如果malloc失败,通常是非常严重的错误,这个时候进程已经没有必要继续运行下去了,这时候选择打印错误信息,如果调用exit函数退出。

        上述的进程退出只是进程退出的一种场景,其实进程想要退出,无非只有三种场景

1)代码运行完毕;

2)结果正确代码运行完毕,结果不正确;

3)代码异常终止

         当一个进程退出,我们在Linux终端可以使用:

echo $?

         命令查看最近一次进程退出的退出码。

        如果最近的一个进程正常退出,返回值是0;

 一个进程退出有什么方式?其实无非就是:

        1)从main函数返回;

        2)调用: exit();

        3)调用系统调用接口:   _exit();

对于其他的退出方式,一般就是我们主动退出:ctrl+C;

或者遇到空指针,或者被断言断死,其实这些退出方式有一个统称:被系统的信号杀死。

1)exit()函数于系统调用接口_exit()有什么区别?

         这两个函数的最本质功能是一样的,都是让进程退出,但是exit() 函数是对系统调用_exit()函数的封装。

        _exit函数是系统调用,exit是C语言的库函数。

进程调用_exit()会直接退出,但是调用exit会在退出之前做一些善后的处理:

        1. 执行用户通过 atexit或on_exit定义的清理函数。

        2. 关闭所有打开的流,所有的缓存数据均被写入(刷新缓冲区)

        3. 调用_exit

三、僵尸进程和进程等待

        在Linux系统中,僵尸进程(Zombie Process)是指已经结束执行的进程,但是其父进程还没有对其进行完全的清理操作,导致进程的资源没有被释放。

        僵尸进程的产生是因为操作系统在进程结束时不会立即清理相关的资源,而是会将其转变为僵尸进程,等待父进程来处理。父进程需要通过调用wait()或waitpid()系统调用来获取子进程的退出状态,并对其进行清理,释放相应的资源。如果父进程没有处理僵尸进程,那么僵尸进程会一直存在于系统中,占用系统资源。

        僵尸进程通常不会对系统造成直接的危害,因为它们已经停止运行。然而,如果有大量的僵尸进程存在,会浪费系统的进程表资源,并可能导致进程表耗尽的问题。此外,僵尸进程也可能是父进程出现问题或错误的一个指示,比如父进程无法正确处理子进程的退出状态。

1)父进程处理僵尸进程就需要进程等待

        父进程需要调用wait()/waitpid()等方法等待子进程退出。

2)阻塞等待 

        接口wait():等待任意一个子进程退出,但是在等待的过程中父进程什么事都干不了,这样的等待称为“阻塞等待”。

//头文件
#include<sys/types.h>
#include<sys/wait.h>

pid_t wait(int*status);
//返回值:
//成功返回被等待进程pid,失败返回-1。

//参数:
//输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

如果想要让父进程在等待的过程中做一些事情,就需要另一个接口:waitpid()

//头文件
#include<sys/types.h>
#include<sys/wait.h>

pid_ t waitpid(pid_t pid, int *status, int options);

//返回值:
//当正常返回的时候waitpid返回收集到的子进程的进程pid;

//如果设置了选项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

3)非阻塞等待:

        waitpid()接口如果设置了MNOHANG,那么进程走到这个接口之后,会查看一次子进程是否已经退出,如果已经退出,我们就可根据此时的返回值设置循环的出口;

        如果没有退出,则进程会执行waitpid() 之后的代码逻辑,直到下一次循环再次到达waitpid()这个接口处,再次查看,继续循环。

非阻塞等待实例:


void oper()
{
    sleep(1);
    printf("等待不妨碍做其他事\n");
}

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        printf("我是子进程,5秒后退出\n");
        sleep(5);
        exit(0);
    }
    else
    {
        int status = 0;
        printf("我是父进程,等待子进程退出\n");
        while(1)
        {
            pid_t rid = waitpid(id,&status,WNOHANG);
            if(rid > 0)
            {
                printf("父进程等到子进程退出\n");
                break;
            }
            oper();
        }
    }

    return 0;
}

运行结果:

 4)输出型参数status:

        什么是输出型参数:输出型参数就是我们穿一个参数,他的值是由操作系统填充的,因此我们在传参的时候需要传地址

        如果传递NULL,表示不关心子进程的退出状态信息。

        如果传递了status的地址,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

        status不能简单的当作整形来看待,需要当作位图来看待,具体细节如下图(status是32位整形,高16位并不携带信息,只有status低16比特位携带信息):

 对于低16位:

        如果正常退出(没有被信号杀掉):那么低8位为0,次低8位表示退出码,这里的退出码,就是函数return的返回值,也是exit()括号内传的值。退出信号的意义是由我们自己赋予的。

        如果异常退出(进程被信号杀掉):退出码虽然可以手动获得,但是已经没有意义了,因为已经发生重大错误,导致进程都没有执行完就退出了 。这时低7位表示进程的退出信号,也就是操作系统发送给进程的异常信号。第8位暂时不考虑。

        低7位正好是2的七次方:64个标识,正好对应kill命令给进程发送的终止信号的个数:

 

 总结:

        正常退出,次低8位 退出码;

        异常退出,低7位 退出信号;

其实,为了便于记忆,有两个专门对status处理的

        

WIFEXITED:

        如果子进程正常退出,返回真;

WEXITSTATUS:

        返回status的次低8位退出码。

实例:


int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        int* p = NULL;
        *p = 10;
        exit(0);
    }
    else
    {
        int status = 0;
        wait(&status);
        printf("%d\n",status);
        if(WIFEXITED(status))
        {
            printf("程序正常退出:%d\n",WEXITSTATUS(status));
        }
        else
        {
            printf("程序挂了:%d\n",(status&0x7f));
        }
    }

    return 0;
}

运行结果:


完~

未经作者同意禁止转载 

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

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

相关文章

目标侦测划分数据集代码--->voc

代码如下&#xff1a; import glob import os import random import shutil # 划分比例 p3/4#训练集 xmlpathE:\\shujuji\\MASK\\Annotations\\* imgpathE:\\shujuji\\MASK\\JPEGImages\\* xmlpathsglob.glob(xmlpath) imgpathsglob.glob(imgpath) my_list[i for i in range(l…

MATLAB图像去雾系统

应用背景 现在工业发展迅速&#xff0c;产生的废气很严重&#xff0c;导致雾霾厉害&#xff0c;现在虽然有硬件来拍摄&#xff0c;可以清晰化视野&#xff0c;但是硬件成本昂贵&#xff0c;急需寻求一种算法来帮助雾霾的清晰处理。显得经济。 采用算法原理 本文采用全局直方…

走进异常类的世界,自定义业务异常类实现指南

接下来这篇文章&#xff0c;小编将带领大家走进异常类的世界&#xff0c;探索异常类的奥秘。 引言 学习Java异常类&#xff0c;需掌握其基础概念&#xff0c;如try-catch语句、throw与throws关键字。通过实例理解异常层次结构&#xff0c;区分已检查与未检查异常。实践编写自定…

springboot文件上传(阿里云oss)

本地存储 使用uuid是为了避免文件名的重复&#xff0c;防止覆盖 RestController public class FIleUploadController {PostMapping("/upload")public Result<String> upload(MultipartFile file) throws IOException {//把文件的内容存储到本地磁盘上String …

基于SpringBoot+Vue的在线投票系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

C++的STL标准模版库容器--list类

前言 list(双向链表)类属于STL标准模版库容器模块&#xff0c;它的迭代器是双向迭代器&#xff0c;也比较好理解&#xff0c;它申请空间不是一次申请一块空间而是每一个节点各自独立的空间&#xff0c;它不再能够支持随机访问和[]&#xff0c;如果想要和string类容器或者vecto…

集合源码1

一、List接口分析 1、list接口的特点 ①List集合的所有元素是由一种线性方式进行存储的。 ②它是一个元素存储有序的集合。即元素的存入顺序和取出顺序有保证。 ③他是一个带有索引的集合&#xff0c;通过索引就可以精确的操作集合中的元素 ④集合中可以有重复的元素&#xff0…

二分查找算法——寻找旋转排序数组中的最小值点名

1.题目解析 题目来源&#xff1a;LCR173.点名——力扣 原名&#xff1a;剑指offer——0~n-1中消失的数字 测试用例 题目来源&#xff1a;153.寻找旋转排序数组中的最小值——力扣 测试用例 2.算法原理 点名 如果要寻找消失的数字&#xff0c;可以判断对应下标的数字是否和下标对…

视觉定位Revisit Anything

Revisit Anything: Visual Place Recognition via Image Segment Retrieval 项目地址 摘要&#xff1a; 准确识别重游地点对于嵌入代理的定位和导航至关重要。这要求视觉表现清晰&#xff0c;尽管摄像机视点和场景外观有很大变化。现有的视觉地点识别管道对“整个”图像进行编码…

制作离线版Koczkatamas工具包

一、下载源码 从https://github.com/koczkatamas/koczkatamas.github.io下载koczkatamas.github.io-master.zip 二、解压 $ unzip koczkatamas.github.io-master.zip三、运行index.html 可以看到输入一个字符后&#xff0c;下面的各种编码都没有显示&#xff0c;则表示运行…

【玩转 JS 函数式编程_008】3.1.2 JavaScript 函数式编程筑基之:箭头函数——一种更流行的写法

文章目录 3.1.2 箭头函数——更流行的方式 Arrow functions - the modern way1. 返回值 Returning values2. this 值的处理 Handling the this value3. arguments 的处理 Working with arguments4. 单参数还是多参数&#xff1f; One argument or many? 写在前面 故天将降大任…

儿童需要学习C++多久才能参加信息学奥赛的CSP-J比赛?

信息学奥赛&#xff08;NOI&#xff09;是国内编程竞赛领域的顶尖赛事&#xff0c;而对于初学者来说&#xff0c;参加NOI的第一步通常是通过CSP-J&#xff08;全国青少年信息学奥林匹克联赛初赛&#xff09;&#xff0c;这也是面向青少年程序员的入门级竞赛。作为信息学奥赛的基…

vue3使用three.js加载.obj模型示例

vue3使用three.js加载.obj模型示例 效果&#xff1a; 代码&#xff1a; 需要先安装three.js npm install three<template><div ref"threeContainer" class"three-container"></div> </template><script> import * as TH…

男单新老对决:林诗栋VS马龙,巅峰之战

听闻了那场激动人心的新老对决&#xff0c;不禁让人热血沸腾。在这场乒乓球的巅峰之战中&#xff0c;林诗栋与马龙的对决无疑是一场视觉与技术的盛宴。 3:3的决胜局&#xff0c;两位选手的每一次挥拍都充满了策略与智慧&#xff0c;他们的每一次得分都让人心跳加速。 林诗栋&am…

10.6学习

1.Hystrix / Sentinel ●服务雪崩场景 自己即是服务消费者&#xff0c;同时也是服务提供者&#xff0c;同步调用等待结果导致资源耗尽 ●解决方案 服务方&#xff1a;扩容、限流&#xff0c;排查代码问题&#xff0c;增加硬件监控 消费方&#xff1a;使用Hystrix资源隔离&a…

JavaSE——面向对象10:抽象类、接口

目录 一、抽象类 (一)抽象类的引出 (二)抽象类基本介绍 (三)注意事项和使用细节 (四)抽象类的最佳实践——模板设计模式 二、接口 (一)接口快速入门 (二)基本介绍 (三)注意事项与使用细节 (四)接口VS继承 (五)接口的多态性 1.多态参数 2.多态数组 3.接口存在多态…

CoreGen项目实战——代码提交信息生成

数据与相关代码见文末 1.概述 源代码与自然语言之间的语义鸿沟是生成高质量代码提交信息的一个重大挑战。代码提交信息对于开发者来说非常重要,因为它们简明扼要地描述了代码更改的高层次意图,帮助开发人员无需深入了解具体实现即可掌握软件的演变过程。手动编写高质量的提交…

Vite多环境配置与打包:

环境变量必须以VITE开头 1.VITE_BASE_API&#xff1a; 在开发环境中设置为 /dev-api&#xff0c;这是一个本地 mock 地址&#xff0c;通常用于模拟后端接口。 2.VITE_ENABLE_ERUDA&#xff1a; 设置为 "true"&#xff0c;表示启用调试工具&#xff0c;通常是为了…

Elasticsearch学习笔记(六)使用集群令牌将新加点加入集群

随着业务的增长&#xff0c;陆续会有新的节点需要加入集群。当我们在集群中的某个节点上使用命令生成令牌时会出现报错信息。 # 生成令牌 /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s node出现报错信息&#xff1a; Unable to create enrollment…

VMware WorkStation Pro 15.5(低版本安装) 教学用

VMware WorkStation Pro 15.5(低版本安装) 教学用 文章目录 VMware WorkStation Pro 15.5(低版本安装) 教学用前言安装使用 前言 VMware Workstation Pro 15.5 是一款功能强大的桌面虚拟化软件&#xff0c;适用于在单台物理电脑上运行多个操作系统。它被广泛应用于软件开发、测…