Linux:进程(三)

news2025/1/22 15:50:31

1. 进程创建补充

fork之后父子两个执行流分别执行,fork之后谁谁先执行由调度器来决定。

一般,父子代码共享。当父子不再写入时,数据也是共享的,但是当有一方要写入,就触发写时拷贝

fork调用失败的原因

1. 系统中有太多进程

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

2. 进程终止

进程终止的本质是释放系统资源,就是释放进程申请的相关内核数据结构和对应的代码和数据。

 进程退出主要有以下三个场景

1. 代码运行完毕,结果正确

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

3. 代码异常终止

 如果是1和2这种正常终止的情况可以通过 echo $ ?  来查看进程退出码

退出码

退出码(退出状态)可以告诉我们最后一次执行的命令的状态。在命令结束后,我们可以知道命令是正确完成了还是错误结束的,是操作系统用来判断进程是否完成任务的方式。程序返回退出代码0时表示执行成功,返回0以外的任何代码都被视为不成功

正常退出有以下方法

1. 从main返回 

2. 调用exit   头文件<stdlib.h>

3. 调用_exit  头文件<unistd.h>

#include<stdio.h>
 #include<stdlib.h>
 #include<unistd.h>
 int main()
 {
     printf("hello world\n");

     //_exit(101);                                                                         
    exit(100);
     return 33;
 }     

 以下结果分别是return  exit 与 _exit的结果

 

当代码异常终止时:退出码无意义

程序的进程退出码是写在task_struct内部的,我们可以通过strerror函数来获取退出码的描述,

strerror(1);
strerror(2);
....
exit与_exit函数
#include<unistd.h>
void _exit(int status);
//status定义了进程的终止状态,父进程通过wait来获取该值
//status是int,但是只有低八位(后面说明)可以被父进程使用_exit(-1),echo $? 到的返回值是255


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

 exit最后也会调用_exit,但是还要先执行一些操作

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

2. 关闭所有打开的流,所有缓存数据都被写入

3. 调用_exit

 _exit就直接退出进程,不会对缓冲区进行刷新

用以下代码来验证一下

#include<stdio.h>
 #include<stdlib.h>
 #include<unistd.h>
 int main()
 {
     printf("hello world");
    
    //先测试完_exit再将其删掉测试exit
    _exit(101);                                                                         
    exit(100);
     return 0;
 }     

 _exit结果如下

exit结果如下

 所以缓冲区一定不是操作系统内部的,在用户空间中,C语言标准库提供

三种方式

exit 是C库里的

_exit 是系统给的

但是实际上exit内部(底层封装)是用了_exit

main函数中的return表示进程完成而其他函数中的return只表示自己的函数调用完成,但是exit与_exit在哪调用都表示进程结束并返回给父进程bash子进程的退出码。

main函数中的 return x; 等价于执行exit(x); 因为在main函数运行结束后会将main函数的返回值当做exit参数来调用exit函数

 3. 进程等待

为什么要有进程等待

1.子进程退出,父进程如果不管不顾,就可能造成僵尸进程内存泄漏

2.变成僵尸进程后,就连kill -9 也对它没有办法,因为不能杀死一个死去的进程

3.我们需要知道父进程给子进程的任务如何了,例如子进程运行完成结果是否正确,是否正常退出了。

4.父进程通过等待的方式来回收子进程资源,获取子进程退出信息(可以选择的)

 wait函数与waipid函数
//头文件
#include<sys/types.h>
#include<sys/wait.h>

pid_t wait(int* status);

pid_t waitpid(pit_t pid,int* status,int options);

wait函数:

返回值:成功返回被等待进程的pid,失败返回-1。

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

waitpid函数:

返回值:

正常返回时waitpid返回收集到的子进程的进程id

如果第三个参数选择了WNOHANG,而调用waitpid发现没有已退出的子进程可收集,则返回0

如果调用中出错,则返回-1,这时errno会设置成相应的值以指示错误所在;

参数

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

status:输出型参数,不关心可设置为NULL

options:默认为0,表示阻塞等待。

设置为WNOHANG时:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待,若正常结束返回该子进程的ID

status参数

在wait和waitpid中都有一个status参数,该参数是一个输出型参数,由操作系统来填充。

如果传递NULL,表示不关心子进程退出状态信息。否则,操作系统会根据该参数,将子进程退出信息反馈给父进程。

status不能简单的当成整形来看,可以当做位图来看,如下图所示

 低七个比特位全0,一旦不是全0就是异常退出的,退出码就无意义了

 status在代码正常运行完毕的情况下是进程退出码,异常终止是保存异常时所对应的信号编号。

所以正常看高8位,异常时看低7位,第8位是coredump 标志用一下操作

(status>>8)&0xff;//获取高八位的值

status&0x7f;//获取第七位的值

这两个操作,操作系统为我们提供了两个宏来表示对应的退出码和退出信号

(1)  WIFEXITED(status):若为正常终止子进程返回的状态则为真。(查看进程是否正常退出)

(2)  WEXITSTATUS(status):若WIFEXITED为0,提取子进程退出码。(查看进程的退出码)

#include<stdio.h>
 #include<stdlib.h>
 #include<unistd.h>
 #include<sys/wait.h>
 #include<errno.h>
 #include<string.h>
 #include<sys/types.h>
 int main()
 {
     pid_t pid;
     if((pid=fork())==-1)
     {
         perror("fork");
         exit(1);
     }
     if(pid==0)
     {
         printf("子进程pid:%d\n",getpid());
         sleep(20);
         exit(10);
     }
      else
     {
         int st;
         int ret=wait(&st);//
 
         if(ret>0&&(st&0x7F)==0)//后七位为0
         {                                                                                 
             printf("子进程退出代码为:%d\n",(st>>8)&0xFF);
        }
         else if(ret>0)
             printf("终止信号:%d",st&0x7F);
     }
    return 0;
}

 让子进程正常终止

父进程成功获取退出信息。运行中在另一台终端将子进程kill掉

输出结果是9,父进程同样等待成功了

我们来使用waitpid与两个宏来看一下代码如下

 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<sys/wait.h>
  5 #include<errno.h>
  6 #include<string.h>
  7 #include<sys/types.h>
  8 int main()
  9 {
 10     pid_t pid;
 11     if((pid=fork())==-1)
 12     {
 13         perror("fork");
 14         exit(1);
 15     }
 16     if(pid==0)
 17     {
 18         printf("子进程pid:%d\n",getpid());
 19         sleep(20);
 20         exit(10);
 21     }
        else if(pid>0)
 23     {
 24         int st;
 25         int ret=waitpid(pid,&st,0);//
 26                                                                                           
 27         if(ret>0&&WIFEXITED(st))//子进程正常结束返回真
 28         {
 29             printf("子进程退出代码为:%d\n",WEXITSTATUS(st));
 30         }
 31         else if(ret>0)
 32             printf("终止信号:%d",st&0x7F);
 33     }
 34     return 0;
 35 }

结果一样 

阻塞等待

验证阻塞等待

#include<stdio.h>
 #include<stdlib.h>
 #include<unistd.h>
 #include<sys/wait.h>
 #include<errno.h>
 #include<string.h>
 #include<sys/types.h>
 int main()
 {
     for(int i=0;i<10;i++)
     {
        pid_t pid;
         pid=fork();
        if(pid<0)
         {
             printf("%s fork error\n",__FUNCTION__);//一个宏会自动替换为该函数名字
        }
         else if(pid==0)
         {
             pid_t p1=getpid();
             printf("此子进程pid:%d\n",p1);
             sleep(2);
            exit(11);
         }
        else if(pid>0)
         {
             int status=0;
            pid_t ret=waitpid(-1,&status,0);//阻塞式等待                                  
             printf("检测waitpid \n");
            if(ret==pid&&WIFEXITED(status))
             {
                printf("等待孩子成功,return pid: %d\n",WEXITSTATUS(status));
            }
            else
             {
                printf("失败\n");
                 return 1;
             }
         }
 
    }
    return 0;
}

非阻塞等待

大部分的父子进程关系中,当子进程未退出时,父进程通常处于阻塞等待的状态,在此期间父进程不能进行其他操作,我们可以通过将waitpid的第三个参数设置为WNOHANG,即在没有子进程退出时立即返回不等待,等待方可以做自己的事情(通过函数指针或者function<>;包装器)

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

 void test()
 {
    printf("hello\n");
}
int main()
{
            pid_t pid;
         pid=fork();
         if(pid<0)
          {
             printf("%s fork error\n",__FUNCTION__);//一个宏会自动替换为该函数名字
        }
          else if(pid==0)
         {
             pid_t p1=getpid();
              printf("此子进程pid:%d\n",p1);
              sleep(2);
            exit(11);
         }
            else
         {
              int status=0;
            pid_t ret=waitpid(pid,&status,WNOHANG);                                                               
            //pid_t ret=waitpid(pid,&status,0);//阻塞式等待
            test();
            test();
             test();
             printf("检测waitpid \n");
             if(ret==pid&&WIFEXITED(status))
             {
               printf("等待孩子成功,return pid: %d\n",WEXITSTATUS(status));
             }
            else
              {
                 printf("失败\n");
                 return 1;
              }
         }
        return 0;
}

阻塞等待先等子进程完毕再进行自己的操作,子进程不结束,父进程会阻塞在wait/waitpid调用处

非阻塞等待,先执行了函数调用,waitpid发现没有要退出的子进程直接返回0

另外:

如果子进程已经退出了,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。

如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。

如果不存在该子进程,则立即出错返回

 4. 进程替换

我们通过fork函数创建子进程后,父子会各自执行父进程的一部分代码,我们可以通过进程替换来让子进程执行一个全新的程序。

进程程序替换的概念

程序替换是通过特定的接口,加载磁盘上的一个全新的程序(代码和数据),加载到调用进程的地址空间之中。

用fork创建子进程后,执行的是和父进程相同的程序(但有可能通过if else执行不同的代码分支),子进程一般通过调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新进程替换,从新程序的启动例程开始执行。但是调用exec不会创建新的进程所以进程替换前后这个进程的id是不会变化的

其原理如下图所示

进程替换函数

以exec开头的函数,统一叫做exec函数:

#include <unistd.h>

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

需要注意

一旦程序替换成功了,就去执行新的代码了,原始代码的后半部分就不存在了

所以exec函数只有失败返回值,没有成功返回值

调用失败返回-1

在进程替换的过程中不是创建了新的进程只是把当前进程的代码和数据覆盖式的进行替换

不会影响父进程(进程具有独立性)->会释放旧空间(子进程首先释放其当前占用的地址空间,包括之前从父进程继承并共享的代码段、数据段、堆和栈等)->重新分配空间(根据新程序要求为子进程重新分配新的地址空间,这个地址空间是专门为新程序准备的)->加载新程序(通过加载器使得程序从新程序的入口点开始执行)

execl函数

 int execl(const char path,const char *arg,...);

path:路径+程序名(要执行谁)

arg:可变参数列表(怎么执行它)

结尾必须以NULL表明参数传递完成

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

int main()
{
    pid_t id=fork();
    if(id<0)
    {
        perror("fork() error\n");
    }
    if(id==0)
    {
        printf("child\n");
        execl("/usr/bin/ls","ls","-l","-a",NULL);

        perror("execl error\n");
        exit(-1);
    }
    else if(id>0)
    {
        int status;

        pid_t tmp=waitpid(id,&status,0);
        if(tmp<0)
        {                                                                                                                                                                                                          
            perror("waitpid error\n");
            exit(1);
        }
        if(WIFEXITED(status))
        {
            printf("exit code:%d\n",WEXITSTATUS(status));
        }
    }

    return 0;
}

将上面的语句替换为下方语句来执行一个cpp程序

        execl("./hello","./hello",NULL);

 即在当前目录下寻找hello,并执行

当然也可能调用python或者是java的程序

execlp函数

int execlp(const char *file, const char *arg, ...);
函数名字比上面多了一个p,这个p表示环境变量,无需写全路径了

file只要说明要执行的文件名即可(execlp会自动在环境变量PATH中查找指定的命令)作用:要执行谁

arg:同上可变模板参数(要怎么执行)

NULL结尾

execlp("ls", "ls", "-a", "-l", NULL);//执行ls -a -l
execlp("./hello", "./hello", NULL);//执行C++程序

execv函数

int execv(const char *path, char *const argv[]);

名字比第一个多了v:即vector数组

path:路径+程序名(要执行谁)

argv:一个命令行参数表(一个指针数组)

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };//通过指针数组
execv("/usr/bin/ls", myargv);

execle函数

 int execle(const char *path, const char *arg, ...,char *const envp[]);

path:路径+程序名

arg:可变参数列表

envp:自己设置的环境变量(传到替换程序的main参数env数组中)

char* env[] = { (char *cibst)"MYVAL=2025", NULL };//自己的环境变量
execle("./hello", "./hello", NULL, env);//执行./hello

其他的参数就是将以上的组合起来使用

 总结一下

l(list):表示参数采用列表

v(vector):参数用数组

p(path):有p自动搜索环境变量PATH

e(env):表示自己维护环境变量

函数名参数格式是否使用当前环境变量
execl列表
execlp列表
execle列表需要自己组装环境变量
execv数组
execvp数组
execve数组需要自己组装环境变量

只有execve是真正的系统调用其他的最终都调用了execve,所以execve在man手册的第二节,其他函数在man手册第三节。各个函数关系如下图所示


这篇就到这里啦(๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

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

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

相关文章

2025年1月21日刷题记录

1.leetcode1768题目 链接&#xff1a;1768. 交替合并字符串 - 力扣&#xff08;LeetCode&#xff09; 代码&#xff1a; class Solution { public:string mergeAlternately(string word1, string word2) {string word3;int a word1.size(), b word2.size();int i 0, j 0…

Mysql触发器(学习自用)

一、介绍 二、触发器语法 注意&#xff1a;拿取新的数据时用new&#xff0c;旧数据用old。

wireshark工具简介

目录 1 wireshark介绍 2 wireshark抓包流程 2.1 选择网卡 2.2 停止抓包 2.3 保存数据 3 wireshark过滤器设置 3.1 显示过滤器的设置 3.2 抓包过滤器 4 wireshark的封包列表与封包详情 4.1 封包列表 4.2 封包详情 参考文献 1 wireshark介绍 wireshark是非常流行的网络…

「2024·我的成长之路」:年终反思与展望

文章目录 1. 前言2.创作历程2.1 摆烂期2.2 转变期3. 上升期 2. 个人收获3.经验分享4. 展望未来 1. 前言 2025年1月16日&#xff0c;2024年博客之星入围公布&#xff0c;很荣幸获得了这次入围的机会。2024年对我个人是里程碑的一年&#xff0c;是意义非凡的一年&#xff0c;是充…

【RAG落地利器】向量数据库Chroma入门教程

安装部署 官方有pip安装的方式&#xff0c;为了落地使用&#xff0c;我们还是采用Docker部署的方式&#xff0c;参考链接来自官方部署: https://cookbook.chromadb.dev/running/running-chroma/#docker-compose-cloned-repo 我们在命令终端运行&#xff1a; docker run -d --…

电阻电位器可调电阻信号隔离变送器典型应用

电阻电位器可调电阻信号隔离变送器典型应用 产品描述&#xff1a; 深圳鑫永硕科技的XYS-5587系列是一进一出线性电子尺(电阻/电位计信号及位移)信号隔离变送器&#xff0c;是将输入电阻,线性电子尺,角度位移传感器信号进行采集,隔离,放大并转换成模拟量信号的小型仪表设备,并以…

[创业之路-259]:《向流程设计要效率》-1-让成功成熟业务交给流程进行复制, 把创新产品新业务新客户交给精英和牛人进行探索与创造

标题&#xff1a;成功与创新的双轨并行&#xff1a;以流程复制成熟&#xff0c;以精英驱动新知 在当今这个日新月异的商业环境中&#xff0c;企业要想持续繁荣发展&#xff0c;就必须在稳定与创新之间找到完美的平衡点。一方面&#xff0c;成熟业务的稳定运营是企业生存和发展的…

模拟飞行入坑(五) P3D 多通道视角配置 viewgroup

背景&#xff1a; P3D进行多个屏幕显示的时候&#xff0c;如果使用英伟达自带的屏幕融合成一个屏&#xff0c;或者使用P3D单独拉伸窗口&#xff0c;会使得P3D的画面被整体拉伸&#xff0c;又或者,当使用Multichannel进行多个设备联动时&#xff0c;视角同步组合需要配置&#…

Java中的错误与异常详解

Java中的错误与异常详解 Java提供了一种机制来捕获和处理程序中的异常和错误。异常和错误都继承自 Throwable 类&#xff0c;但它们有着不同的用途和处理方式。 1. Error&#xff08;错误&#xff09; Error 是程序无法处理的严重问题&#xff0c;通常由 JVM&#xff08;Java…

免费开源的三维建模软件Blender

软件介绍 Blender是一款功能强大且免费开源的三维建模、动画制作和渲染软件&#xff0c;广泛应用于影视制作、游戏开发、建筑可视化、教育及艺术创作等多个领域。 核心功能 Blender是一款全能型3D软件&#xff0c;涵盖了从建模、动画到渲染、后期合成的完整工作流程。 1、建…

ElasticSearch DSL查询之排序和分页

一、排序功能 1. 默认排序 在 Elasticsearch 中&#xff0c;默认情况下&#xff0c;查询结果是根据 相关度 评分&#xff08;score&#xff09;进行排序的。我们之前已经了解过&#xff0c;相关度评分是通过 Elasticsearch 根据查询条件与文档内容的匹配程度自动计算得出的。…

iOS 网络请求: Alamofire 结合 ObjectMapper 实现自动解析

引言 在 iOS 开发中&#xff0c;网络请求是常见且致其重要的功能之一。从获取资料到上传数据&#xff0c;出色的网络请求框架能夠大大提升开发效率。 Alamofire 是一个极具人气的 Swift 网络请求框架&#xff0c;提供了便据的 API 以完成网络请求和响应处理。它支持多种请求类…

面向对象编程——对象实例化

在python中&#xff0c;对象实例化是根据类的定义创建具体对象的过程。也就是将类当成模板&#xff0c;从而定义了对象的结构和行为&#xff0c;而实例化则是根据这个模板创建具体的对象实例。每个实例都有自己独立的状态&#xff0c;但是却共享类的结构和方法。 代码&#xff…

阿里云-银行核心系统转型之业务建模与技术建模

业务领域建模包括业务建模和技术建模&#xff0c;整体建模流程图如下&#xff1a; 业务建模包括业务流程建模和业务对象建模 业务流程建模&#xff1a;通过对业务流程现状分析&#xff0c;结合目标核心系统建设能力要求&#xff0c;参考行业建 模成果&#xff0c;形成结构化的…

Unreal Engine 5 C++ Advanced Action RPG 九章笔记

第九章 Hero Special Abilities 2-Challenges Ahead(前方的挑战) 本次章节主要解决三件问题 怒气能力特殊武器能力治疗石怒气能力 对于这个能力我们需要处理它的激活和持械状态,当没有怒气时应该取消该能力当这个能力激活时,我希望角色是进入无敌状态的,不会受到伤害怒气状…

cursor重构谷粒商城05——docker容器化技术快速入门【番外篇】

前言&#xff1a;这个系列将使用最前沿的cursor作为辅助编程工具&#xff0c;来快速开发一些基础的编程项目。目的是为了在真实项目中&#xff0c;帮助初级程序员快速进阶&#xff0c;以最快的速度&#xff0c;效率&#xff0c;快速进阶到中高阶程序员。 本项目将基于谷粒商城…

【FPGA】MIPS 12条整数指令【1】

目录 修改后的仿真结果 修改后的完整代码 实现bgtz、bltz、jalr 仿真结果&#xff08;有问题&#xff09; bltz------并未跳转&#xff0c;jCe&#xff1f; 原因是该条跳转语句判断的寄存器r7&#xff0c;在该时刻并未被赋值 代码&#xff08;InstMem修改前&#xff09; i…

洛谷题目:P2742 [USACO5.1] 圈奶牛Fencing the Cows /【模板】二维凸包 题解 (本题较难)

题目传送门&#xff1a;P2742 [USACO5.1] 圈奶牛Fencing the Cows /【模板】二维凸包 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 另&#xff1a;由于一些文章的疏忽&#xff0c;导致一些错别字&#xff0c;代码错误&#xff0c;公式错误导致大家的理解和误导&#xff0c;…

多线程之旅:线程安全问题

之前说到了多线程的创建和一些属性等等&#xff0c;接下来&#xff0c;就来讲讲多线程安全问题。 小编引入这段代码讲解下&#xff1a; public class Demo13 {public static int count0;public static void main(String[] args) throws InterruptedException {Thread t1new…

html学习笔记(3)

一、文本格式标签 效果标签&#xff08;旧版&#xff09;标签&#xff08;语义化&#xff0c;强调&#xff09;加粗<b><strong>倾斜<i><em>下划线<u><ins>删除线<s><del> 前面的标签 b 、 i 、 u 、 s 就仅仅是实现加粗、倾…