Linux的进程详解(进程创建函数fork和vfork的区别,资源回收函数wait,进程的状态(孤儿进程,僵尸进程),加载进程函数popen)

news2024/11/15 23:32:31

 目录

什么是进程

 Linux下操作进程的相关命令

进程的状态(生老病死)

创建进程系统api介绍:

fork()

父进程和子进程的区别

vfork()

进程的状态补充:

孤儿进程

僵尸进程

回收进程资源api介绍:

wait()

waitpid()

exit()

popen


什么是进程

        一个程序是由源代码在编译后产生的,格式为ELF的,存储于硬盘的文件,在Linux中,程序文件的格式都是ELF,这些文件在被执行的瞬间,就被载入内存,所谓的载入内存,就是将数据段、代码段这些运行时必要的资源拷贝到内存,另外系统会再分配相应的栈、堆等内存空间给这个进程,使之成为一个动态的实体。而这个动态的实体,程序中的代码和数据,被加载到内存中运行的过程,就叫进程,程序是静态的,进程是动态的

 Linux下操作进程的相关命令

ps 查看当前终端运行的进程

ps -e 查看当前系统的所有进程

./ 程序名 运行当前目录下的程序 

top 查看当前Linux系统的进程运行情况

pstree 查看进程树

killall 杀死所有进程 后面加名字即为杀死所有叫这个名字的进程

kill 杀死一个进程 后面跟进程的pid即为杀死指定的pid进程

killall/kill -STOP 进程名/进程PID #暂停进程

killall/kill -CONT 进程名/进程PID #继续运行继承

进程的状态(生老病死)

文字是从某本书摘抄的,我觉得写的非常好(当然图也是)

创建进程系统api介绍:
fork()
 #include <sys/types.h>
 #include <unistd.h>

 pid_t fork(void);

创建一个子进程

成功时返回两个返回值

返回子进程pid,返回子进程pid时,执行父进程

返回值0,返回值为0时,执行子进程

创建失败时返回一个值为-1

返回值-1时不创建子进程

pid_t pid;  //pid_t跟int含义相同

if(pid == 0)
{
    //子进程的代码  
}
if(pid > 0)
{
    //父进程的代码
}
父进程和子进程的区别

父子进程的以下属性在创建之初完全一样:
A) 实际UID和GID,以及有效UID和GID。
B) 所有环境变量。
C) 进程组ID和会话ID。
D) 当前工作路径。
E) 打开的文件。
F) 信号响应函数。
G) 整个内存空间,包括栈、堆、数据段、代码段、标准IO的缓冲区等等。
而以下属性,父子进程是不一样的:
A) 进程号PID。PID是身份证号码,哪怕亲如父子,也要区分开。
B) 记录锁。父进程对某文件加了把锁,子进程不会继承这把锁。
C) 挂起的信号。这是所谓“悬而未决”的信号,等待着进程的响应,子进程不会继承这些信号。


子进程只会执行fork()语句下面的程序段,不会执行fork上的程序段

引用:”的确子进程包含有和父进程一样的代码和数据(虽然一样但的确是自己的一份)。 但别忘了,子进程复制的不仅是父进程的代码和数据,还包括状态,这个状态就包含有PC指针寄存器的值。 也就是说子进程创建完成后,他和父进程一样,PC指针都指向下一条语句, 因此子进程是从自身创建完成后的地方继续运行 ,而父进程运行过得代码将不再运行。“

参考文献:为什么fork创建子进程后,父进程中运行过的代码在子进程中不再运行了_fokk一个子进程后,父进程变量的值在子进程中仍然存在对吗-CSDN博客

分析下面的demo,判断printf打印了几次

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

int main()
{
    fork();
    fork();
    fork();
    printf("15678\n"); 
}

printf共打印8次,可以用一个阻塞函数阻塞程序运行,然后在Linux终端输入pstree查看原因


vfork()

创建一个子进程并阻塞父进程

fork()和vfork()的区别

1.资源复制方式:

fork:创建子进程时,子进程会复制父进程的数据段、堆栈段和代码段。虽然现代Linux通过写时复制(Copy-on-Write, COW)技术优化了这一过程,但本质上,父子进程拥有独立的地址空间。

vfork:子进程与父进程共享数据段。vfork创建的子进程基本上是一个轻量级进程,它不会复制父进程的地址空间,而是直接共享。

2.执行顺序:

fork:父子进程的执行顺序是不确定的。操作系统可能会先调度父进程或子进程运行。

vfork:保证子进程先运行。在子进程调用exec或exit之前,父进程不会运行。这确保了在子进程执行完毕或替换为新程序之前,父进程不会继续执行。

1.vfrok 保证子进程先执行,父进程后执行 

2.fork 父子进程的内存独立,vfrok 父子进程共享数据段 (常量,静态数据,全局变量)。

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

// 全局变量,存储数据段
int value = 0;

int main()
{
    // 1.创建一个子进程
    pid_t pid = vfork(); // 共享数据段
    //pid_t pid = fork(); // 父子进程的内存完全独立
    if (pid == 0)       // 子进程
    {
        while (1)
        {
            printf("%d 我是子进程pid是%d  我的爸爸是%d\n", value, getpid(), getppid());
            value++;
            sleep(1);
            if (value == 10)
            {
                printf("子进程结束\n");
                exit(0); // 子进程结束
            }
        }
    }

    //sleep(12);

    printf("父进程运行\n"); // 父进程被阻塞了,直到子进程结束
    if (pid > 0)            // 父进程
    {
        while (1)
        {
            printf("%d 我是父亲进程pid是%d  我的儿子是%d\n", value, getpid(), pid);
            value--;
            sleep(1);
        }
    }
}


可以看到在执行过程中父进程中的value的值受子进程的影响

进程的状态补充:
孤儿进程

如果一个子进程的父进程比子进程先死亡,那么这个子进程就会变成孤儿进程,由系统进程(init进程)进行管理

要防止孤儿进程的产生,父进程必须比子进程后死亡

僵尸进程

子进程比父进程先死亡,并且父进程未回收子进程的资源。那么子进程就会变成僵尸进程。
创建僵尸进程

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{

    for (int i = 0; i < 10; i++)
    {
        int pid = fork(); // fork 后父子进程谁先执行是不确定的,由操作系统调度决定
        if (pid == 0)
        {

            printf("我是子进程:%d ,我的父亲是:%d ,子进程死亡\n", getpid(), getppid());
            return 0; // 结束子进程
        }
    }

    printf("父亲继续运行,等待任意键结束\n");
    getchar(); // 挂起态

    printf("父进程结束\n");
    return 0;
}

使用ps -e查看僵尸进程

回收进程资源api介绍:
wait()

回收进程资源

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

pid_t wait(int *wstatus);

成功时返回回收资源的进程的pid号

失败时返回-1

wait一次回收一次任一资源(回收的资源是随机的)

可以通过wait获取进程的退出状态

有在wait(int *status)里放一个整形指针,如果 status 不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内,如果不关心终止状态,则可将该参数指定为空指针,如wait(NULL)

死亡原因由以下宏定义获取


因为wait执行一次只回收一次资源,所以想把所有子进程资源都回收需要不断循环
 

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

int main()
{
    // 1.创建一个子进程
    pid_t pid = fork();

    if (pid == 0) // 子进程
    {
        int i = 0;
        while (1)
        {
            printf("子进程执行中 %d\n", i++);
            if (i == 20)
            {
                printf("子进程结束\n");
                // 退出子进程
                return 5;
            }
            sleep(1);
        }
    }

    while (1) 
    {
        ret = wait(NULL);
        if (ret == -1) {
            if (errno == EINTR) {    // 返回值为-1的时候有两种情况,一种是没有子进程了,还有一种是被中断了
                continue;                  //如果是被中断了就continue继续执行
            }
            break;
        }
    }

    printf("当前子进程资源已全部回收"\n);
    printf("父进程为 %d\n", getpid());
}


通过wait获取退出状态

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

int main()
{
    // 1.创建一个子进程
    pid_t pid = fork();

    if (pid == 0) // 子进程
    {
        int i = 0;
        while (1)
        {
            printf("子进程执行中 %d\n", i++);
            if (i == 20)
            {
                printf("子进程结束\n");
                // 退出子进程,设置子进程退出状态
                return 5;
            }
            sleep(1);
        }
    }

    if (pid > 0) // 父进程
    {
        int status;
        printf("等待子进程结束\n");
        wait(&status);
        // 获取子进程的退出状态 (死亡原因)
        if (WIFEXITED(status)) // 判断子进程是否正常退出
        {
            printf("子进程正常退出\n");
            printf("退出状态码: %d\n", WEXITSTATUS(status)); // 获取子进程的return 值
        }

        // 获取是否被信号杀死
        if (WIFSIGNALED(status))
        {
            printf("子进程被信号杀死\n");
            printf("杀死子进程的信号: %d\n", WTERMSIG(status)); // 获取杀死子进程的信号
        }
    }
}

waitpid()

pid_t waitpid(pid_t pid, int *wstatus, int options); 等待指定的子进程结束

pid:等待的进程pid  
wstatus:进程的退出状态 
options:等待属性设置阻塞与非阻塞  ,默认为 0 阻塞 

exit()

void _exit(int status);
void exit(int status);

status 子进程的退出值
没有返回值

如果子进程正常退出,则 status 一般为 0。
如果子进程异常退出,则 status 一般为非 0

exit()退出时,会自动冲洗(fush)标准IO总残留的数据到内核,如果进程注册了“退出处理函数”还会自动执行这些函数。
而_exit()会直接退出。

popen

加载一个进程,并创建一个通信管道 (文件流指针)

FILE *popen(const char *command, const char *type);
command:需要加载的程序   
type:加载的权限   
"r"  :读取  
 "w" :写入 
 "e" :可执行
 返回值: 成功  文件流指针    
              失败   NULL                     
                             
int pclose(FILE *stream);  //关闭文件流指针

通过popen输入Linux命令行命令

#include <stdio.h>
int main()
{
    // 1.加载 ls -l 命令(程序)
    FILE *pf = popen("ls -l", "r");
    if (pf == NULL){
        printf("popen error\n");
        return -1;
    }
    else{
        printf("加载成功\n");
    }
    // 读取 ls -l  程序加载后的输出结果
    while (1)
    {
        char buf[1024] = {0};
        char *ret = fgets(buf, 1024, pf);
        if (ret == NULL)
        {
            break;
        }
        printf("%s\n", buf);
    }
    // 关闭程序
    pclose(pf);
}

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

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

相关文章

编译运行 llama.cpp (vulkan, Intel GPU SYCL)

llama.cpp 是一个运行 AI (神经网络) 语言大模型的推理程序, 支持多种 后端 (backend), 也就是不同的具体的运行方式, 比如 CPU 运行, GPU 运行等. 但是编译运行 llama.cpp 并不是那么容易的, 特别是对于 SYCL 后端 (用于 Intel GPU), 坑那是一大堆. 只有特定版本的 llama.cpp…

【代码随想录训练营第42期 Day38打卡 - 动态规划Part6 - LeetCode 322. 零钱兑换 279.完全平方数 139.单词拆分

目录 一、做题心得 二、题目与题解 题目一&#xff1a;322. 零钱兑换 题目链接 题解&#xff1a;动态规划--完全背包 题目二&#xff1a; 279.完全平方数 题目链接 题解&#xff1a;动态规划--完全背包 题目三&#xff1a;139.单词拆分 题目链接 题解&#xff1a;动…

blender骨骼绑定(让物体动起来)

园哥摸索了两天了&#xff0c;骨骼做好就是不能带动物体&#xff0c;点击时候要选中那个骨骼点圆圈&#xff0c;点中间骨骼没用。终于动起来了。虽然有点奇怪。 点击图二那个点&#xff0c;貌似我的骨骼生长反了。做游戏是真麻烦。本来想搞个简单的2d游戏&#xff0c;结果那个瓦…

一起学Java(4)-[起步篇]教你掌握本协作项目中的Gralde相关配置文件(上)

将思绪拉回java-all-in-one项目&#xff0c;如果你fork并下载了代码&#xff0c;你会看到在项目中除了HelloWorldMain代码外&#xff0c;还存在很多文件。如果你并不了解他们的作用并有足够的好奇心&#xff0c;那你应该想要知道他们的作用。带着好奇&#xff0c;今天我也来研究…

网络抓包测试

利用fgets遇到\n停止的特性&#xff0c;给流数据直接加间隔&#xff0c;fgets读的时候会把soket缓冲区里面的数据全部放到fgets的缓冲区内&#xff0c;再读的时候就不能从套接字fd描述符读而是从fgets的fq里面读 行为1. 读取行为&#xff1a;•fgets 读取字符直到遇到换行符 \n…

下载ncurses操作步骤

https://invisible-island.net/ncurses/announce.htmlncurses-6.5.官网下载链接 选择下载版本

信刻离线文件单向导入系统

信刻针对不同数据单向导入的需求&#xff0c;按需推出的离线文件单向导入系统采用软硬件相结合的技术&#xff0c;支持信息导入申请、身份认证、光盘读取、病毒查杀、光盘复刻、光盘数据信息导入、审查审批、用户管理、日志管理、三权管理、数据加密、数据检查、校验、安全审计…

pd虚拟机 Parallels Desktop 19 for Mac安装教程【支持Intel和M芯片】

pd虚拟机 Parallels Desktop 19 for Mac安装教程【支持Intel和M芯片】 一、准备工作 二、开始安装 安装包内有三个软件 Parallels Desktop是一款广受好评的Mac虚拟机软件&#xff0c;本文来讲述一下Parallels Desktop是如何下载、安装、激活与换机的。 Parallels Desktop 下…

外排序之文件归并排序实现

外排序介绍 外排序是指能够处理极大量数据的排序算法。通常来说&#xff0c;外排序处理的数据不能一次装入内存&#xff0c;只能放在读写较慢的外存储器(通常是硬盘)上。外排序通常采用的是⼀种“排序-归并”的策略。在排序阶段&#xff0c;先读入能放在内存中的数据量&#x…

【Kafka源码走读】消息生产者与服务端的连接过程

说明&#xff1a;以下描述的源码都是基于最新版&#xff0c;老版本可能会有所不同。 一. 查找源码入口 kafka-console-producer.sh是消息生产者的脚本&#xff0c;我们从这里入手&#xff0c;可以看到源码的入口&#xff1a; if [ "x$KAFKA_HEAP_OPTS" "x&qu…

Vue处理表格长字段显示问题

背景 有些单元个中会有比较长的内容&#xff0c;如果使用默认格式&#xff0c;会导致单元格的高度比较怪异&#xff0c;需要将超长的内容进行省略。 当前效果&#xff1a; 优化效果&#xff1a; 优化代码&#xff1a; 在内容多的单元格增加下面代码 <el-table-columnprop…

SAP成本核算-事前控制(标准成本核算)

一、BOM清单 1、BOM清单抬头 BOM用途:决定成本核算控制的依据 物料清单状态:决定成本核算控制的依据 基本数量:用于计算标准的用量 有效期:决定生产工单开单的日期范围,以及成本核算的日期范围 物料清单状态默认值后台配置:事务代码OS21 2、BOM清单行项目 有效期:决…

Java框架Shiro、漏洞工具利用、复现以及流量特征分析

Shiro流量分析 前言 博客主页&#xff1a; 靶场&#xff1a;Vulfocus 漏洞威胁分析平台 Shiro&#xff08;Apache Shiro&#xff09;是一个强大且灵活的开源安全框架&#xff0c;专为Java应用程序提供安全性解决方案。它由Apache基金会开发和维护&#xff0c;广泛应用于企业级…

Anolis os系统进入单用户模式重置密码

Anolis os系统进入单用户模式重置密码 1、重启计算机。 2、在启动时&#xff0c;当GRUB菜单出现时&#xff0c;按下任意键停止自动倒计时。 3、选择要启动的内核版本&#xff0c;然后按e键编辑启动参数。 4、找到以linux或linux16开头的行&#xff0c;通常这行包含了启动内核…

keepalived与lvs

1 lvs Linux服务器集群系统(一) -- LVS项目介绍 LVS&#xff08;Linux Virtual Server&#xff09;即Linux虚拟服务器,是一个基于Linux操作系统的虚拟服务器技术&#xff0c;用于实现负载均衡和高可用性。章文嵩&#xff0c;是中国国内最早出现的自由软件项目之一。 2 lvs发展…

Circuitjs 快捷键完全列表

对于常见组件, 反复通过菜单去选择也是比较繁琐的, 系统考虑到这一点, 也为那些常用组件添加了快捷键. 通过 菜单--选项--快捷键... 可以查看所有快捷键, 分配新的快捷键或调整现有的快捷键. 点开菜单时, 位于菜单右侧的那些字母即是对应的快捷键, 如下图所示: 注: 旧版本有, …

Debug-022-el-upload照片上传-整体实现回顾

前情概要&#xff1a; 最近业务里通过el-upload实现一个上传图片的功能&#xff0c;有一些小小的心得分享给各位。一方面是review一下&#xff0c;毕竟实现了很多细小的功能&#xff0c;还有这么多属性、方法&#xff08;钩子&#xff09;和碰到的问题&#xff0c;感觉小有成就…

Swing中如何实现快捷键绑定和修改

在许许多多市面上常见的桌面软件中, 可以使用快捷键操作&#xff0c; 比如像微信一样,使用AltA 可以打开截图窗口&#xff0c;如果不习惯于AltA按键时&#xff0c;还可以打开设置去修改。 如果在swing中也想实现一个快捷键绑定和修改的操作&#xff0c;那么应该如何操作&#…

《计算机操作系统》(第4版)第8章 磁盘存储器的管理 复习笔记

第8章 磁盘存储器的管理 一 、外存的组织方式 1. 连续组织方式(连续分配方式) (1)概述 ①定义 连续组织方式要求为每一个文件分配一组相邻接的盘块。磁盘空间的联系组织方式如图8-1所示。 ②记录方法 在目录项的“文件物理地址”字段中记录该文件第一个记录所在的盘块号和文件…

【Docker深入浅出】Docker引擎架构介绍

文章目录 一. docker引擎介绍1. Docker daemon&#xff1a;实现Docker API&#xff0c;通过API管理容器2. containerd&#xff1a;负责容器的生命周期3. runc&#xff1a;用于创建和启动容器 二. 启动容器的过程1. 启动过程2. docker daemon的维护不会影响到运行中的容器3. shi…