Linux下进程控制

news2024/11/24 9:21:07

文章目录

  • 创建进程
    • fork创建进程
      • fork返回值
      • 写诗拷贝
      • fork常规用法
      • fork失败的原因
    • 进程终止
      • 进程正常终止
      • 查看进程退出码
      • _exit函数
      • exit函数
      • exit 和 _exit 的区别
      • return退出
    • 进程等待
      • 进程等待的方式
        • wait方法(系统调用)
        • waitpid方法(系统调用)
      • WEXITSTATUS 和 WIFEXITED
      • 阻塞等待和非阻塞等待
    • 进程程序替换
      • 替换函数
        • execl函数
        • execlp函数
        • execle函数
        • execv函数
        • execvp函数
        • execvpe函数
      • exec系列函数
      • exec家族关系

创建进程

fork创建进程

fork函数
image.png

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

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

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

image.png

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
    printf("before fork pid is :%d\n",getpid());
    pid_t id = fork();
    if(id < 0){
        perror("fork");
    }

    printf("after fork pid id :%d\n",getpid());
    
    printf("fork return value is :%d\n",id);
    return 0;
}

上面程序运行结果

image.png
可以看出,12行和14行中的打印信息被执行了两次,而且两次的值也不一样。
image.png
因为fork之前父进程单独执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器 决定。

fork返回值

  • 子进程返回0
  • 父进程返回的是子进程的id

写诗拷贝

fork之后,父子进程共享代码,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。

image.png

image.png

fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子 进程来处理请求
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

fork失败的原因

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

进程终止

进程退出的场景

  • 代码跑完,结果正确。
  • 代码跑完,结果不正确
  • 代码没跑完,进程出异常

进程正常终止

  • main返回
  • 调用_exit
  • 使用exit

再平时写C/C++程序时,一般都i会这样写

#include <stdio.h>
int main()
{
    //...
    return 0;
}

都会在main函数最后一行写一句return 0;
return 0;就是进程的退出码,一般0表示成功,非0 表示失败。
一但程序执行失败,需要知道原因,用不同的数字来表示不同的失败原因。

查看进程退出码

使用echo $?可以查看最近一次进程的退出码

image.png
比如系统中没有lll命令,执行,然后查看进程退出码,可以看到退出码为127

image.png
在C标准库函数中,有一个strerror函数,用于将错误码(通常是由系统调用或库函数返回的错误码)转换为对应的错误消息字符串。

#include <stdio.h>
#include <string.h>
int main()
{
    for (size_t i = 0; i < 255; i++)
    {
        printf("error code %d:%s\n",i,strerror(i));
    }
    return 0;
}

Linux下错误码一共有133个。
image.png

_exit函数

image.png
参数:status 定义了进程的终止状态,父进程通过wait来获取该值
_exit在程序的任意位置都可以终止进程

#include <stdio.h>
#include <unistd.h>
void test()
{
    printf("Hello World\n");
    _exit(1);
}
int main()
{
    test();
    return 0;
}

运行结果:

image.png
main函数中的return 0;还没有执行到进程就终止了。

exit函数

exit和_exit 的区别,exit是c语言的进程终止的函数,而_exit是Linux系统调用接口的函数,c语言在实现exit函数时会封装_exit。

image.png

#include <stdio.h>
#include <stdlib.h>
void test()
{
    printf("Hello World\n");
    exit(1);
}
int main()
{
    test();
    return 0;
}

进程退出码和上面一样,都是1,main函数中的return 0;不会执行,进程就终止了。

exit 和 _exit 的区别

  • eixt会刷新缓冲区 更推荐
  • _eixt 不会刷新缓冲区。
  • exit封装了_exit;

image.png
image.png
同样的代码,分别使用eixt 和 _exit终止进程,exit会刷新缓冲区。(printf语句没有加\n),而_exit则不会刷新缓冲区。

return退出

  • return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返 回值当做 exit的参数。
  • 只有在main函数中return才能起到进程终止的作用,在其他函数中return不会终止进程,仅仅只是函数调用结束。

进程等待

进程等待的必要性

  • 子进程退出,父进程不等待,子进程可能变僵尸,从而造成内存泄漏
  • 一个进程一旦变为僵尸,谁都无能为力,kill也杀不掉,因为,无法杀掉一个已经死掉的进程。
  • 父进程创建子进程,子进程的任务完成的情况,我们需要知道。子进程的运行结果是否正确,是否正常退出(比如ls命令 是bash的子进程,ls的执行情况是需要我们知道的)
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。(父进程必须要做的)

进程等待的方式

wait方法(系统调用)

man 2 wait 认识wait

image.png
返回值:
等待成功,返回子进程的pid
等待失败,返回-1。

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

wait:它等待任意子进程退出,而不需要指定特定的子进程ID。如果没有子进程退出,wait 会阻塞当前进程直到有子进程退出。(阻塞等待简单理解就是父进程啥也不干,就只等子进程退出进行回收)

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
    pid_t id = fork();
   if(id < 0 )
   {
    perror("fork\n");
   }
   else if(id == 0){
    //子进程
    printf("i am child process.pid = %d\n",getpid());
    //子进程退出 退出码为1 
    exit(1);
   }
   else{
     //父进程
    printf("i am Parent process waiting...\n");
    sleep(3);//父进程等待三秒后回收子进程
    int status = 0;
    pid_t waitid = wait(&status);//不想获取子进程退出码可以设置为NULL
    //等待失败处理
    if(waitid == -1)
    {
        perror("wait fail\n");
        exit(-1);
    }
    printf("wait sucess, child excit code = %d,waitid = %d\n",status,waitid);
   }
    return 0;
}

运行结果:

PixPin_2024-01-04_21-04-23.gif

image.png
这里父进程获取到的子进程退出码并不是1,而是256
这是因为status占4个字节,32个比特位。前十六位不用关心,后是6位前8位表示进程正常的退出码,最后七位表示进程异常收到的信号,退出码和信号中间的一位是core dump标志位,和信号有关,这里不用深究。

image.png
exit(1) 则是进程正常退出,也没有收到异常信号,status则是

image.png
这就是256的原因。
如果想直接拿到进程的退出码,而不是status,(status>>8)& 0xff即可
上面代码31行修改为

 printf("wait sucess, child excit code = %d,waitid = %d\n",(status>>8)&0xFF,waitid);

运行结果:

image.png

waitpid方法(系统调用)

等待指定进程或者任意子进程,相比wait更灵活
man 2 waitpid 认识waitpid

image.png
参数

  • pdi:等待子进程的id,若设置为-1,则和wait等效
  • status:输出型参数,获取子进程的退出状态,不关心子进程退出状态设置为NULL
  • options:设置等待方式,阻塞等待或者非阻塞等待

返回值

  • 等待成功返回被等待进程的pid。
  • 等待方式设置为非阻塞等待(WNOHANG),而调用中waitpid发现没有已退出的子进程可收集,则返回0;

阻塞等待示例:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
    //waitpid阻塞等待
    pid_t id = fork();
    if(id < 0 ){
        perror("fork\n");
        exit(-1);
    }
    else if(id == 0){
        //子进程
        printf("i am child process. pid = %d\n",getpid());
        sleep(3);
        exit(1);
    }
    else {
        //父进程
       printf("parent process waiting ...\n");
       int status = 0 ;
       pid_t ret = waitpid(-1,&status,0);//阻塞等待任意子进程
      
       if(WIFEXITED(status) && ret == id){//等待成功
        printf("wait sucess,child return code = %d,ret = %d\n",WIFEXITED(status),ret);
       }
       else{//等待失败
        printf("wait fail\n");
       }
    }
    return 0;
}

运行结果:

image.png
等待成功,子进程退出码为1,waitpid返回值为子进程的pid

WEXITSTATUS 和 WIFEXITED

WIFEXITEDWEXITSTATUS 是在 <sys/wait.h> 头文件中定义的两个宏,用于处理子进程的退出状态信息。

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

非阻塞等待示例:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
    //waitpid非阻塞等待
    pid_t id = fork();
    if(id < 0 ){//失败处理
        perror("fork\n");
        exit(-1);
    }
    else if(id == 0){//子进程
        printf("i am child process. pid = %d\n",getpid());
        sleep(3);
        exit(1);
    }
    else {//父进程
       printf("parent process waiting ...\n");
       int status = 0;
       pid_t ret  = 0;
       do
       {
            ret = waitpid(-1,&status,WNOHANG);//非阻塞等待任意子进程
            if(0 == ret){//子进程还没退出,父进程非阻塞等待可以做其他事情
                printf("child process is running,parent do other things\n");
                sleep(1);
            }
       }while(0 == ret);
       //等待成功
       if(WIFEXITED(status) && ret == id){
       //打印子进程退出码和waitpid返回值
        printf("wait sucess,child return code = %d,ret = %d\n",WEXITSTATUS(status),ret);
       }
       //等待失败
       else{
        printf("wait fail\n");
       }
    }
    return 0;
}

运行结果:

PixPin_2024-01-08_21-21-19.gif

image.png
父进程非阻塞等待子进程时还可以做其他的事情, wiatpid返回值时子进程的id

阻塞等待和非阻塞等待

阻塞等待:

阻塞等待会导致进程无法执行其他任务,直到等待的事件发生。
waitpid函数设置为阻塞等待,只需将options参数设置为0

  • 阻塞等待的优点:

    • 实现简单,易于理解。
    • 不需要额外的代码来检查事件状态。
  • 阻塞等待的缺点:

    • 整个进程或线程被挂起,无法执行其他任务。
    • 可能导致资源浪费,因为进程被阻塞时,它可能无法充分利用系统资源

非阻塞等待

waitpid函数设置为阻塞等待,需要将options参数设置为WNOHANG。(一个宏)

  • 非阻塞等待的优点:

    • 可以充分利用系统资源,因为在等待事件的同时可以执行其他任务。
    • 更灵活,适用于需要同时处理多个任务的情况。
  • 非阻塞等待的缺点:

    • 实现较为复杂,需要额外的代码来轮询或处理事件通知。
    • 可能会增加系统负载,因为需要周期性地检查事件状态

进程程序替换

进程程序替换是指一个正在运行的进程将自己的地址空间、代码、数据和堆栈等信息替换为另一个程序的内容。

  • 用fork创建的子进程和父进程执行的是相同的程序,但有可能执行不同的代码分支。一般fork创建的子进程需要调用exec函数来执行另一个程序。
  • 当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。
  • 调用exec并不会创建新的子进程,所以调用exec前后该进程的id并未改变。
  • 程序替换是通过特定的接口,加载到磁盘上的一个程序,加载到调用进程的地址空间中

image.png

替换函数

有六种以exec开头的函数,统称exec函数:

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 execvpe(const char *file, char *const argv[], char *const envp[]);
execl函数
int execl(const char *path, const char *arg, ...);
  • 参数1:path 是要执行的程序的路径(需要指定路径)。
  • 参数2:是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。
    示例:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
    //进程程序替换
    pid_t id = fork();
    if(id < 0){
        perror("fork fail\n");
    }
    else if(id == 0){
        //子进程
        printf("i am child process, pid =  %d",getpid());
        //子进程执行ls -a 命令
        execl("/usr/bin/ls","ls","-a",NULL);
        printf("exec end\n");
        exit(1);
    }
    else{
        //父进程
        int status = 0;
        pid_t  ret = waitpid(id,&status,0);//阻塞等待指定子进程
        if(WIFEXITED(status) && ret == id)//等待成功
        {
            printf("wait sucess,child return code = %d,ret = %d, id = %d\n",WEXITSTATUS(status),ret,id);
        }
    }
    return 0;
}

运行结果:

image.png
可以发现 当进程程序替换完成后,exec后面的代码将不再执行。
一旦exec替换失败,才会只执行后面的代码
比如将上面17行要替换的进程改为一个不存在的,

execl("/usr/bin/lsl","ls","-l",NULL);

执行结果:子进程后面的代码被执行了。

image.png

execlp函数
int execlp(const char *file, const char *arg, ...);
  • 参数1:需要执行的程序名称。只需要指定程序名称,不需要指定路径
  • 参数2:是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。
    示例:子进程执行ls命令
execlp("ls","ls","-l",NULL);

image.png

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

参数1:需要执行程序的路径,
参数2:是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。
参数3:envp:是一个以 NULL 结尾的字符串数组,用于设置新程序的环境变量。

// 设置新程序的环境变量
char *envp[] = {"MY_VARIABLE=value", NULL};
// 使用 execle 函数替换当前进程的程序
execle("/bin/echo", "echo", "Hello, execle!", (char *)NULL, envp);

运行结果:

image.png

execv函数
int execv(const char *path, char *const argv[]);
  • 参数1:path 是要执行的程序的路径。
  • 参数2:argv 是一个以 NULL 结尾的指针数组,其中包含新程序的名称和参数。
char *const argv[] = {"ls","-l",NULL};
execv("/usr/bin/ls",argv);

运行结果:

image.png

execvp函数
int execvp(const char *file, char *const argv[]);

不用指定路径即可。

execvpe函数
int execvpe(const char *file, char *const argv[], char *const envp[]);

第一个参数是要执行程序的路径,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾,第三个参数是你自己设置的环境变量。

exec系列函数

  • 这些函数如果调用成功,则加载指定的程序并从启动代码开始执行,不再返回。
  • 如果调用出错,则返回-1。
  • exec函数只有成功的返回值,没有失败的返回值。

exec家族关系

image.png

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

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

相关文章

vue无法获取dom

处理过程 watch监听值变化 index.js:33 [Vue warn]: Error in callback for watcher "$store.state.modelsStorageUrl": "TypeError: Cannot set properties of undefined (setting modelScene)"watch: {"$store.state.modelsStorageUrl":{ha…

24上半年教师资格证笔试报名全流程✅

&#x1f550;考试时间安排 &#xff08;一&#xff09;报名时间&#xff1a; 2024年1月12日9:30至15日16:00 &#xff08;二&#xff09;缴费时间&#xff1a; 2024年1月17日24:00 &#xff08;三&#xff09;考试时间&#xff1a; 2024年3月9日星期六 &#xff08;四&#xf…

上网行为管理到底是什么!

之前经常听过上网行为管理系统&#xff0c;那这个系统到底是什么&#xff0c;还真不是很清楚&#xff01;今天仔细查了很多资料&#xff0c;分享给大家&#xff1a; 上网行为管理是指一种帮助互联网用户控制和管理对互联网的使用&#xff0c;包括对网页访问过滤、上网隐私保护…

vue3 useAttrs的使用场景,提取共有props

1 场景 多个类似组件都需要传参data&#xff0c;用于请求接口或者处理数据&#xff0c;想让组件干净整洁&#xff0c;把参数data提出来 2 方法 选项式 可以使用mixin混入或者extends继承&#xff08;略&#xff09; 组合式 可以使用hook 无脑式踩坑&#xff08;如下代码…

SwiftUI之深入解析Alignment Guides的超实用实战教程

一、Alignment Guide 简介 Alignment guides 是一个强大的布局工具&#xff0c;但通常未被充分利用。在很多情况下&#xff0c;它们可以帮助我们避免更复杂的选项&#xff0c;比如锚点偏好。如下所示&#xff0c;对对齐的更改也可以自动&#xff08;并且容易地&#xff09;动画…

Python书籍推荐,建议收藏

学习Python的书籍可太多了&#xff0c;从入门到放弃&#xff0c;应有尽有啊 入门书籍 根据豆瓣评分的高低&#xff0c;这里介绍了一些经典入门书籍&#xff0c;大家根据自身情况选择尝试 《Python编程&#xff1a;从入门到实践&#xff08;第二版&#xff09;》 非常经典且非…

【python】TCP测速程序

一、服务端 下面是一个简单的 Python 服务端程序的示例&#xff0c;使用标准库中的 socket 模块来建立一个 TCP 服务器。该服务器接收客户端的连接请求&#xff0c;客户端发送一定大小的数据流以测试 TCP 带宽。 实际场景中带宽测试可能需要更复杂的逻辑来确保测试的准确性。 …

Android AAudio

文章目录 基本概念启用流程基本流程HAL层对接数据流计时模型调试 基本概念 AAudio 是 Android 8.0 版本中引入的一种音频 API。 AAudio 提供了一个低延迟数据路径。在 EXCLUSIVE 模式下&#xff0c;使用该功能可将客户端应用代码直接写入与 ALSA 驱动程序共享的内存映射缓冲区…

基于springboot在线考试系统源码和论文

网络的广泛应用给生活带来了十分的便利。所以把在线考试管理与现在网络相结合&#xff0c;利用java技术建设在线考试系统&#xff0c;实现在线考试的信息化。则对于进一步提高在线考试管理发展&#xff0c;丰富在线考试管理经验能起到不少的促进作用。 在线考试系统能够通过互…

前端实现截图并下载

原理: 使用一个名为html2canvas的JavaScript库。这个库允许你将当前的HTML内容渲染到一个canvas元素上&#xff0c;然后将其转换为图像并进行下载。 你需要在项目中引入html2canvas库。你可以从官方网站&#xff08;https://html2canvas.hertzen.com/&#xff09;下载&#xf…

编译原理期末大题步骤——例题

一、预测分析方法步骤 提取左公因子&#xff0c;消除左递归判断文法是否为LL(1)文法若是&#xff0c;构造预测分析表&#xff1b;否则&#xff0c;不能进行分析。根据预测分析表对输入串进行分析 例子&#xff1a; 文法G[E]&#xff1a; …

selenium python 实现基本自动化测试的示例代码

安装selenium 打开命令控制符输入&#xff1a;pip install -U selenium 火狐浏览器安装firebug&#xff1a;www.firebug.com&#xff0c;调试所有网站语言&#xff0c;调试功能 Selenium IDE 是嵌入到Firefox 浏览器中的一个插件&#xff0c;实现简单的浏览器操 作的录制与回…

如何克隆驱动器,不同的操作系统有不同的推荐软件

你需要将Windows或macOS安装迁移到新驱动器吗?你可以使用服务备份文件,也可以创建数据的完整一对一副本。通过克隆你的驱动器,你可以创建一个精确的副本。 一些业务级别的备份服务,如IDrive和Acronis,具有内置的磁盘克隆功能,是对正常文件备份的补充。但对于一次性克隆(…

BitMap解析(一)

文章目录 前言数据结构添加与删除操作 JDK中BitSet源码解析重要成员属性初始化添加数据清除数据获取数据size和length方法集合操作&#xff1a;与、或、异或 前言 为什么称为bitmap&#xff1f; bitmap不仅仅存储介质以及数据结构不同于hashmap&#xff0c;存储的key和value也…

在python里面探索web框架

一、常识性知识 python Web框架三巨头&#xff1a;Flask&#xff08;简单易学&#xff09;、Django(复杂庞大)、FastAPI 1. Django&#xff1a;Django是一个高级的Web框架&#xff0c;它提供了强大的功能和工具&#xff0c;用于快速开发复杂的Web应用程序。 2. Flask&#xff…

海外社媒运营为什么需要选择优质IP代理?

跨境电商卖家尤其需要关注海外社媒运营&#xff0c;想要更好地运营Instagram、Facebook、TikTok 或 Twitter等&#xff0c;挖掘社媒潜力需要采取战略方法&#xff0c;而社交媒体IP代理在这一活动中发挥着至关重要的作用&#xff0c;下面为你详细介绍。 一、社交媒体代理IP及其运…

新年喝酒有讲究,怎么喝葡萄酒呢?

中国的新年有着独特又深远的意义&#xff0c;无论人在天涯海角&#xff0c;回家团圆是每个人的心愿。新年亲朋好友欢聚一堂&#xff0c;没有酒哪有气氛&#xff0c;所以喝酒是必不可少的活动项目。云仓酒庄的品牌雷盛红酒LEESON分享那么&#xff0c;新年喝啥酒&#xff0c;葡萄…

C# 一看就懂的装箱拆箱案例

文章目录 装箱&#xff08;Boxing&#xff09;拆箱&#xff08;Unboxing&#xff09;编程语言中的装箱与拆箱优缺点 在C#中&#xff0c;装箱&#xff08;Boxing&#xff09;和拆箱&#xff08;Unboxing&#xff09;是值类型与引用类型之间相互转换的过程。 装箱&#xff08;Box…

【Qt打包】Qt打包生成可安装exe文件

第三方打包 gitee 项目地址&#xff1a;https://gitee.com/hudejie/universal-software-installation-package 纯净包备份&#xff08;v0.1&#xff09;&#xff1a;https://download.csdn.net/download/weixin_45863921/88720027 1 项目介绍 作者项目介绍&#xff1a; 基于NS…

通用机V8R6集群部署_1主1备1见证_图形化_Centos7

KingbaseES 提供数据库部署工具进行数据库集群的部署。KingbaseES 提供基于图形化和命令行操作的集群部署方式&#xff0c;本文档主要用于指导不支持 GUI 的服务器上的 KingbaseES 集群部署工作。 集群简介 KingbaseES软件能够提供一主一备以及一主多备的高可用集群架构&…