Linux下进程控制

文章目录

  • 创建进程
    • 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

powerdesigner导出sql将name放到comment注释上

1. 批量设置 2. 脚本 Option Explicit ValidationMode True InteractiveMode im_Batch Dim mdl the current modelget the current active model Set mdl ActiveModel If (mdl Is Nothing) ThenMsgBox"There is no current Model " ElseIf Not mdl.IsKindOf(PdPD…

互斥、自旋、读写锁的应用场景

互斥、自旋、读写锁的应用场景 锁&#x1f512;1、互斥锁、自旋锁2、读写锁&#xff1a;读写的优先级3、乐观锁和悲观锁总结&#xff1a; 锁&#x1f512; ​ 多线程访问共享资源的生活&#xff0c;避免不了资源竞争而导致错乱的问题&#xff0c;所以我们通常为了解决这一问题…

string的模拟实现

string的模拟实现 msvc和g下的string内存比较成员变量构造函数与析构函数拷贝构造函数赋值拷贝c_str、size和capacity函数以及重载[]、clear、expand_capacity迭代器与遍历reservepush_back、append、insert字符串比较运算符erase<<流提取 >>流插入resizefindsubst…

SpringSecurity完整认证流程(包含自定义页面和自定义登录逻辑)

认证基本流程图&#xff1a; 1. 用户发起表单登录请求后&#xff0c;首先进入UsernamePasswordAuthenticationFilter ​ 在 UsernamePasswordAuthenticationFilter 中根据用户输入的用户名、密码构建了 UsernamePasswordAuthenticationToken&#xff0c;并将其交给 Authentic…

减少客户流失并留住长期客户:实用策略与实践分享

一、什么是客户流失&#xff1f; 客户流失是指客户停止付费并离开您的产品/服务。流失率是指在特定时间内离开产品/服务的用户除以用户总数的百分比。 比如&#xff1a; 假设某媒体平台是一个基于订阅的流媒体平台&#xff0c;为用户提供各种电影、电视剧、纪录片等。到二月…

6个提升Python编程能力的PyCharm插件

大家好&#xff0c;PyCharm作为一款强大的集成开发环境&#xff0c;本身已经提供了许多功能&#xff0c;但一些插件将进一步扩展和增强PyCharm的能力。通过使用这些插件&#xff0c;大家能够更快速地编写代码、提高代码质量、进行调试和优化&#xff0c;并将开发体验提升到一个…

【排序算法】二、希尔排序(C/C++)

「前言」文章内容是排序算法之希尔排序的讲解。&#xff08;所有文章已经分类好&#xff0c;放心食用&#xff09; 「归属专栏」排序算法 「主页链接」个人主页 「笔者」枫叶先生(fy) 目录 希尔排序1.1 原理1.2 代码实现&#xff08;C/C&#xff09;1.3 特性总结 希尔排序 1.1…

React-组件使用与组件通信

生命周期补充(不常用): 案例&#xff1a; import React, { Component } from reactexport default class App extends Component {state {num : 100,list: []}//获取到的是更新前的props 和 state getSnapshotBeforeUpdate(prevProps,prevState){ //必须搭配componentDidUpd…

红队打靶练习:DERPNSTINK: 1

目录 信息收集 1、arp 2、netdiscover 3、nmap 4、nikto 5、whatweb 目录探测 1、gobuster 2、dirsearch WEB get flag1 robots.txt /php/phpmyadmin /temporary /weblog wordpress wpscan扫描 漏洞发现 提权 系统信息收集 mysql登录 john get flag2 s…

Orchestrator源码解读2-故障失败发现

目录 目录 前言 核心流程函数调用路径 GetReplicationAnalysis 故障类型和对应的处理函数 拓扑结构警告类型 与MHA相比 前言 Orchestrator另外一个重要的功能是监控集群&#xff0c;发现故障。根据从复制拓扑本身获得的信息&#xff0c;它可以识别各种故障场景。Orchest…

Vue2.v-指令

v-if 在双引号中写判断条件。 <div v-if"score>90">A</div> <div v-else-if"score>80">B</div> <div v-else>C</div>v-on: :冒号后面跟着事件。 为了简化&#xff0c;可以直接用代替v-on:。 事件名“内联语…

【前端素材】同城服务分类手机APP页面的设计实现

一、需求分析 一个同城服务分类手机页面是一个用于提供同城服务分类信息的移动设备页面。它通常具有以下功能&#xff1a; 地理定位&#xff1a;同城服务分类手机页面可以利用用户的地理定位功能&#xff0c;获取用户当前所在的城市或地区信息&#xff0c;以便提供与用户所在地…

一、Mybatis 简介

本章概要 简介持久层框架对比快速入门&#xff08;基于Mybatis3方式&#xff09; 1.1 简介 https://mybatis.org/mybatis-3/zh/index.html MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投G…

12、DolphinScheduler

1、DolphinScheduler简介 1.1、 DolphinScheduler概述 Apache DolphinScheduler是一个分布式、易扩展的可视化DAG工作流任务调度平台。致力于解决数据处理流程中错综复杂的依赖关系&#xff0c;使调度系统在数据处理流程中开箱即用。 1.2、 DolphinScheduler核心架构 Dolph…

UV贴图和展开初学者指南

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 介绍 这正是本文的主题——UV贴图——登上舞台的时候。大多数 3D 建…

基于YOLOv8 + BotSORT实现球员和足球检测与跟踪 (步骤 + 源码)

导 读 本文主要介绍基于YOLOv8和BotSORT实现球员和足球检测与跟踪 &#xff0c;并给出步骤和代码。 背景介绍 本文旨在了解 YOLO 架构并在自定义数据集上对其进行训练&#xff0c;然后微调模型以获得更好的结果&#xff0c;并运行推理以了解最有效的方法。 什么是YOLO&#x…

练习-指针笔试题

目录 前言一、一维整型数组1.1 题目一1.2 题目二 二、二维整型数组2.1 题目一2.2 题目二2.3 题目三 三、结构体3.1 题目一&#xff08;32位机器运行&#xff09; 四、字符数组4.1 题目一4.2 题目二 总结 前言 本篇文章记录关于C语言指针笔试题的介绍。 一、一维整型数组 1.1 …

探索人工智能:深度学习、人工智能安全和人工智能

深度学习是人工智能的一种重要技术&#xff0c;它模拟了人类大脑神经网络的工作原理&#xff0c;通过建立多层次的神经元网络来实现对数据的分析和处理。这种技术的引入使得人工智能的发展进入到了一个新的阶段。 现如今&#xff0c;深度学习在各个领域都有着广泛的应用。例如…

简单的MOV转MP4方法

1.下载腾讯的QQ影音播放器, 此播放器为绿色视频播放器, 除了播放下载好的视频外没有臃肿无用功能 官网 QQ影音 百度网盘链接&#xff1a;https://pan.baidu.com/s/1G0kSC-844FtRfqGnIoMALA 提取码&#xff1a;dh4w 2.用QQ影音打开MOV文件 3.右下角打开影音工具箱 , 选择截取…

开启Android学习之旅-6-实战答题App

不经过实战&#xff0c;看再多理论&#xff0c;都是只放在笔记里&#xff0c;活学活用才是硬道理。同时开发应用需要循序渐进&#xff0c;一口气规划300个功能&#xff0c;400张表&#xff0c;会严重打击自己的自信。这里根据所学的&#xff0c;开发一个答题App。 题库需求分析…