《Linux C编程实战》笔记:进程操作之退出,执行,等待

news2024/11/16 13:26:52

进程退出

进程退出表示进程即将运行结束。在Linux中退出分为正常退出和异常退出。

正常退出:

  1. 在main函数中执行return
  2. 调用exit函数
  3. 调用_exit函数

异常退出:

  1. 调用abort函数
  2. 收到某个信号,这个信号是程序终止

退出方式比较

  1. exit和return的区别:exit是一个函数,有参数;而return是函数执行完后的返回。exit把控制权交给系统,而return将控制权交给调用函数。
  2. exit和 abort的区别:exit是正常终止进程,而about是异常终止。
  3. exit(int exit_code): exit中的参数exit_code为0代表进程正常终止,若为其他值表示程序执行过程中有错误发生,比如溢出、除数为0。
  4. exit()和_exit()的区别:exit在头文件stdlib.h中声明,而_exit()声明在头文件unistd.h中。两个函数均能正常终止进程,但是_exit()会执行后立即返回给内核,而exit()要先执行一些清除操作,然后将控制权交给内核。

父子进程终止的先后顺序不同会产生不同的结果。在子进程退出前父进程先退出,则系统会让 init进程接管子进程(前面有代码演示过)。当子进程先于父进程终止,而父进程又没有调用wait 函数等待子进程结束,子进程进入僵死状态,并且会一直保持下去除非系统重启。子进程处于僵死状态时,内核只保存该进程的一些必要信息以备父进程所需。此时子进程始终占用着资源,同时也减少了系统可以创建的最大进程数。如果子进程先于父进程终止,且父进程调用了wait或waitpid函数,则父进程会等待子进程结束。

执行新程序

使用fork或vfork创建子进程后,子进程通常会调用exec函数来执行另外一个程序。系统调用exec用于执行一个可执行程序以代替当前进程的执行映像。

注意:exec调用并没有生成新进程。一个进程一旦调用exec函数,它本身就“死亡”了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,惟一保留的就是进程ID。也就是说,对系统而言,还是同一个进程,不过执行的已经是另外一个程序了。

Linux下,exec函数族又6种不同的调用形式,如下:

  1. execl

    • int execl(const char *path, const char *arg, ...);
    • 这个函数接受一个以空格分隔的参数列表,最后一个参数必须为 NULL

    execl("/bin/ls", "ls", "-l", (char *)NULL);

  2. execv

    • int execv(const char *path, char *const argv[]);
    • 这个函数接受一个参数数组,最后一个元素必须为 NULL

    char *args[] = {"ls", "-l", NULL}; execv("/bin/ls", args);

  3. execle

    • int execle(const char *path, const char *arg, ..., char *const envp[]);
    • execl 相似,但可以指定新程序的环境变量。

    execle("/bin/ls", "ls", "-l", (char *)NULL, envp);

  4. execve

    • int execve(const char *path, char *const argv[], char *const envp[]);
    • execv 相似,但可以指定新程序的环境变量。

    char *args[] = {"ls", "-l", NULL}; execve("/bin/ls", args, envp);

  5. execlp

    • int execlp(const char *file, const char *arg, ...);
    • execl 类似,但会在 PATH 环境变量指定的目录中查找可执行文件。

    execlp("ls", "ls", "-l", (char *)NULL);

  6. execvp

    • int execvp(const char *file, char *const argv[]);
    • execv 类似,但会在 PATH 环境变量指定的目录中查找可执行文件。

    char *args[] = {"ls", "-l", NULL}; execvp("ls", args);

这些函数执行成功时不会返回,而是将当前进程的映像替换为新的程序。如果函数调用失败,它们会返回 -1,并设置 errno 变量以指示错误的原因。

为了更好理解exec,首先要理解环境变量这个概念。Linux引入了环境变量的概念,包括用户的主目录,终端类型、当前目录等,它们定义了用户的工作环境,所以称为环境变量。可以使用env命令查看环境变量值,用户也可以修改这些变量值以定制自己的工作环境

示例程序1

演示环境变量的应用

#include<cstdlib>
#include<malloc.h>
#include<cstring>
#include <cstdio>
#include<ctime>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<cerrno>
using namespace std;
extern char **environ;
int main(int argc,char *argv[]){
    int i;
    printf("Argument:\n");
    for(int i=0;i<argc;i++)
        printf("argv[%d] is %s\n",i,argv[i]);
    printf("Environment:\n");
    for(int i=0;environ[i]!=nullptr;i++)
        printf("%s\n",environ[i]);
    return 0;
}

还有很多环境变量,图片放不下了

命令行里输入命令env,环境变量和程序里应该是一样的

在C语言中,extern char **environ 是一个用于访问进程环境变量的外部变量声明。这个变量通常由操作系统或C库提供,用于存储当前进程的环境变量。

每个C程序在运行时都有一个环境变量表,其中包含了键值对形式的环境变量。environ 是一个指向字符串指针数组的指针,每个字符串指针指向一个环境变量的字符串。

通过访问 environ 变量,你可以迭代这个字符串指针数组,直到遇到一个空指针,表示环境变量列表的结束。每个字符串指针指向一个形如 "key=value" 的字符串,表示一个环境变量。

有一些系统和编译器提供了一个额外的参数,通常称为 envp,它是一个指向环境变量的字符串数组的指针。

int main(int argc, char *argv[],char **envp);

打印envp,也可以得到环境变量

int main(int argc,char *argv[],char **envp){
    int i;
    printf("Argument:\n");
    for(int i=0;i<argc;i++)
        printf("argv[%d] is %s\n",i,argv[i]);
    printf("Environment:\n");
    for(int i=0;envp[i]!=nullptr;i++)
        printf("%s\n",envp[i]);
    return 0;
}

这是main函数为上面形式的运行结果,确实可以打印环境变量

事实上无论是哪个exec函数,都是将可执行程序的路径、命令行参数和环境变量3个参数传递给可执行程序的main函数。

上面说到如果失败,即遇到错误的事件,exec函数会返回-1,以下是一些错误

 在 Linux 操作系统下,exec函数族可以执行二进制的可执行文件,也可以执行Shell脚本程序,但Shell脚本必须以下面所示的格式开头:第一行必须为:#! interpretername [arg]。其中 interpretername可以是Shell或其他解释器,例如,/bin/sh 或usr/bin/perl,arg是传递给解释器的参数。

示例程序2

演示exec函数的用法

processimage.cpp

这里写的是子进程到时候执行exec后执行的代码

#include<cstdio>
#include<sys/types.h>
#include<unistd.h>
int main(int argc,char *argv[],char **environ){
    int i;
    printf("I am a process image!\n");
    printf("My pid= %d,parent=%d\n",getpid(),getppid());
    printf("uid = %d,gid = %d\n",getuid(),getgid());
    for(i=0;i<argc;i++)
        printf("argv[%d]:%s\n",i,argv[i]);
}

getuidgetpid 是两个与进程和用户身份相关的系统调用函数。

  1. getuid 函数:

    • uid_t getuid(void);
    • 用于获取当前进程的用户实际用户标识(User ID)。返回值是用户的实际用户ID。
  2. getpid 函数:

    • pid_t getpid(void);
    • 用于获取当前进程的进程ID。返回值是当前进程的进程ID。

execve.cpp

#include<cstdio>
#include<sys/types.h>
#include <sys/wait.h>
#include<unistd.h>
#include<cstdlib>
int main(int argc,char *argv[],char **environ){
    pid_t pid;
    int stat_val;
    printf("Exec example\n");
    pid=fork();
    switch (pid)
    {
    case -1:
        perror("Process Creation failed\n");
        exit(1);
    case 0:
        printf("Child process is running\n");
        printf("My pid =%d,parentpid= %d\n",getpid(),getppid());
        printf("uid=%d,gid=%d\n",getuid(),getgid());
        execve("processimage",argv,environ);
        printf("process never go to here!\n");//是不会允许到这一句的
    default:
        printf("Parent process is running\n");
        break;
    }
    wait(&stat_val);
    exit(0);
}

注意顺序,先编译第一个程序,而且可执行文件名称要对应execve的参数

可以看到新程序进程的pid,ppid,uid和gid都保持了原来子进程的。调用execve之后,原有的子进程的映像被替代,所以那句打印永远不会执行

wait 函数的原型如下:

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int *status);

  • pid_t 是进程ID的数据类型,通常是整数。
  • int *status 是一个指向整数的指针,用于存储子进程的终止状态。

wait 函数用于等待任意一个子进程的退出,并获取子进程的退出状态。其返回值是已经终止的子进程的进程ID。如果调用失败,返回值为 -1。

status 参数用于存储有关子进程终止状态的信息。如果不关心子进程的退出状态,可以将 status 设置为 NULL如果 status 不是 NULL,则可以使用一些宏来检查 status 中的信息,例如:

  • WIFEXITED(status):如果子进程正常终止(不是信号终止),返回非零。
  • WEXITSTATUS(status):获取子进程的退出状态,只有在 WIFEXITED(status) 为真时才有效。

执行新程序后的进程除了保持了原来的进程ID、父进程ID、实际用户ID和实际组ID之外,进程还保持了许多原有特征,主要有。

  1. 当前工作目录。
  2. 根目录。
  3. 创建文件时使用的屏蔽字。
  4. 进程信号屏蔽字。
  5. 未决警告。
  6. 和进程相关的使用处理器的时间。
  7. 控制终端。
  8. 文件锁。

等待进程结束

之前提到的僵死状态,如果父进程调用了wait或waitpid,就不会使子进程编程僵尸进程

#include<sys/types.h>
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc,int options);

wait函数使父进程暂停执行,直到它的一个子进程结束为止。该函数的返回值是终止运行的子进程的PID。参数 statloc所指向的变量存放子进程的退出码(注意statloc本身不等于退出码),即从子进程的main 函数返回的值或子进程中exit函数的参数。如果statloc不是一个空指针,状态信息将被写入它指向的变量。

头文件sys/wait.h中定义了解读进程退出状态的宏。

注:status就是通过statloc返回来的值

  1. WIFEXITED(status)

    • 如果子进程正常终止(不是由于信号),则返回非零值。
  2. WEXITSTATUS(status)

    • 如果 WIFEXITED(status) 为真,该宏返回子进程的退出状态。这是子进程传递给 exit_exit 函数的值。
  3. WIFSIGNALED(status)

    • 如果子进程是因为信号而终止,而不是正常退出,则返回非零值。
  4. WTERMSIG(status)

    • 如果 WIFSIGNALED(status) 为真,该宏返回导致子进程终止的信号编号。
  5. WIFSTOPPED(status)

    • 如果子进程当前已经停止,则返回非零值。这通常是由于接收到一个信号,要求进程停止执行。
  6. WSTOPSIG(status)

    • 如果 WIFSTOPPED(status) 为真,该宏返回导致子进程停止的信号编号。
  7. WIFCONTINUED(status)

    • 如果子进程由于接收到 SIGCONT 信号而继续运行,则返回非零值。

waitpid也用来等待子进程的结束,但它用于等待某个特定进程结束。参数pid指明要等待的子进程的PID。 参数 statloc的含义与wait函数中的statloc相同。options参数允许用户改变waitpid的行为,若将该参数赋值为WNOHANG,则使父进程不被挂起而立即返回并执行其后的代码。

下面是 waitpid 函数中 pid 参数不同取值的意义:

  1. pid > 0:

    • 表示等待具有进程ID为 pid 的子进程。
    • 如果该子进程还没有退出,则父进程会阻塞等待,直到子进程退出为止。
    • 如果子进程已经退出,父进程会立即返回。
  2. pid == -1:

    • 表示等待任意子进程,类似于 wait 函数。
    • 父进程会等待第一个终止的子进程,无论其进程ID是多少。
  3. pid == 0:

    • 表示等待与调用 waitpid 的父进程在同一个进程组的任意子进程。
    • 这对于等待同一作业中的任意子进程很有用。
  4. pid < -1:

    • 表示等待进程组ID为 pid 的任意子进程。
    • 这对于等待特定进程组中的任意子进程很有用。

以下是一些常用的 waitpid 选项及其对应的宏:

  1. WNOHANG:

    • 启用非阻塞模式,即如果没有子进程退出,则立即返回,而不会阻塞父进程。
  2. WUNTRACED:

    • 用于获取已经停止但尚未进入终止状态的子进程的状态信息。
  3. WCONTINUED:

    • 用于获取已经继续运行的子进程的状态信息。

这些宏可以与位运算结合使用,以同时指定多个选项。

如果想让父进程周期性地检查某个特定的子进程是否已经退出,可以按如下方式调用waitpid。

waitpid(child_pid, NULL,WNOHANG);

如果子进程尚未退出,它将返回0;如果子进程已经结束,则返回child _pid。调用失败时返回-1。失败的原因包括没有该子进程、参数不合法等。

注意:wait等待第一个终止的子进程,而 waitpid则可以指定等待特定的子进程。waitpid 提供了一个wait的非阻塞版本。有时希望取得一个子进程的状态,但不想使父进程阻塞,waitpid 提供了一个这样的选项: WNOHANG,它可使调用者不阻塞。如果一个没有任何子进程的进程调用wait函数,会立即出错返回。

示例程序3

演示wait的使用和子进程退出码的获得

#include<cstdlib>
#include<malloc.h>
#include<cstring>
#include <cstdio>
#include<ctime>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<unistd.h>
#include<cerrno>
using namespace std;
int main(){
    pid_t pid;
    const char *msg;
    int k;
    int exit_code;
    printf("Study how to get exit code\n");
    pid=fork();
    switch (pid)
    {
    case 0:
        msg="Child process is running";
        k=5;
        exit_code=37;//这就是子进程的退出码
        break;
    case -1:
        perror("Process creation failed\n");
        exit(1);
    default:
        exit_code=0;
        break;
    }
    if(pid!=0){//父进程会执行这里
        int stat_val;
        pid_t child_pid;
        child_pid=wait(&stat_val);//父进程暂停等待子进程结束
        printf("Child process has exited,pid=%d\n",child_pid);
        if(WIFEXITED(stat_val))//通过这个判断子进程是否是正常结束的
            printf("Child exited with code %d\n",WEXITSTATUS(stat_val));//获得退出码
        else printf("Child exited abnormally\n");
    }
    else{//子进程会执行这里
        while (k-->0)
        {
            puts(msg);
            sleep(1);
        }
    }
    exit(exit_code);
}

运行结果:

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

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

相关文章

「Verilog学习笔记」游戏机计费程序

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 timescale 1ns/1nsmodule game_count(input rst_n, //异位复位信号&#xff0c;低电平有效input clk, //时钟信号input [9:0]money,input set,input boost,output reg[9:0…

工业缺陷检测新时代!OpenCV4六种方法助你轻松应对生产难题!

OpenCV4工业缺陷检测的六种方法 机器视觉缺陷检测好书推荐工业上常见缺陷检测方法方法一&#xff1a;方法二&#xff1a;方法三&#xff1a;方法四&#xff1a;方法五&#xff1a;方法六&#xff1a; 写在末尾&#xff1a; 主页传送门&#xff1a;&#x1f4c0; 传送 送书系列…

Module build failed: TypeError: this.getOptions is not a function

在使用webpack打包出现以上错误时&#xff0c;可能是你安装的css-loader和style-loader的版本过高。 我用的webpack版本是3.6.0 因此需要降低一下版本 在你编辑器终端输入以下命令&#xff1a; npm install css-loader3.6.0 npm install --save-dev style-loader1.00 然后接下…

八大易犯领英LinkedIn错误

领英是一个全球知名的职场社交平台&#xff0c;拥有海量的用户&#xff0c;也成为了外贸人开发客户的一个重要平台。但是如果没有很好地避好一些易犯错误&#xff0c;那很可能努力的结果是事倍功半。接下来我来讲解八大容易犯的领英错误。 1、没有完善个人信息 领英是一个职场…

基于单片机的智能小车 (论文+源码)

1. 系统设计 此次可编程智能小车系统的设计系统&#xff0c;结合STM32单片机&#xff0c;蓝牙模块&#xff0c;循迹模块&#xff0c;电机驱动模块来共同完成本次设计&#xff0c;实现小车的循迹避障功能和手机遥控功能&#xff0c;其整体框架如图2.1所示。其中&#xff0c;采用…

淘宝类目信息API接口获取淘宝商品分类信息API调用说明(含APIkey密钥)

cat_get-获得淘宝分类详情 item_cat_get-获得淘宝商品类目 公共参数 名称类型必须描述keyString是调用key&#xff08;点此获取&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_search,item_get,item_search_…

【web安全】密码爆破讲解,以及burp的爆破功能使用方法

前言 菜某总结&#xff0c;欢迎指正错误进行补充 密码暴力破解原理 暴力破解实际就是疯狂的输入密码进行尝试登录&#xff0c;针对有的人喜欢用一些个人信息当做密码&#xff0c;有的人喜欢用一些很简单的低强度密码&#xff0c;我们就可以针对性的生成一个字典&#xff0c;…

配置https环境

为什么要配置https环境 在使用 HTML5 的 API 时&#xff0c;很多 API 只能在 https 保证安全的情况下才能开启。这就要求我们在本地开发环境也能够配置 https&#xff0c;否则你需要每次部署到配有 https 的测试环境中才能看到预览效果&#xff0c;这对开发的敏捷度造成了极大…

网络安全事件频发现状

近日&#xff0c;腾讯视频、菜鸟、滴滴等App崩溃的消息登上热搜&#xff0c;引发不少网友热议。今年以来&#xff0c;已有多起App崩溃事件发生&#xff0c;甚至有企业因此业绩损失超亿元。互联网应用的系统安全和稳定性建设越来越被社会广泛关注。 12月3日晚&#xff0c;有网友…

node.js mongoose Aggregate介绍

目录 简述 Aggregate的原型方法 aggregate进行操作 简述 在 Mongoose 中&#xff0c;Aggregate 是用于执行 MongoDB 聚合操作的类。MongoDB 聚合操作是一种强大的数据处理工具&#xff0c;可以用于对集合中的文档进行变换和计算 通过Model.aggregate创建一个aggregate(Agg…

硬件产品经理:硬件产品敏捷开发

目录 简介 敏捷 CSDN学院 作者简介 简介 之所以敏捷产品开发流程会越来越普遍。 主要得益于这个方法可以让企业使用更少的资源去开发出令客户满意的新产品。 敏捷开发强调的最重要的一点就是“快”。 也就是要求通过快速迭代来获取频繁的客户反馈。 这就特别适合应对市…

Android-高效加载大图

Android 高效加载大图 前言读取位图尺寸和类型将按比例缩小的版本加载到内存中 前言 图片有各种形状和大小。在很多情况下&#xff0c;它们的大小超过了典型应用界面的要求。例如&#xff0c;系统“图库”应用会显示使用 Android 设备的相机拍摄的照片&#xff0c;这些照片的分…

制作一个简单 的maven plugin

流程 首先&#xff0c; 你需要创建一个Maven项目&#xff0c;推荐用idea 创建项目 会自动配置插件 pom.xml文件中添加以下配置&#xff1a; <project> <!-- 项目的基本信息 --> <groupId>com.example</groupId> <artifactId>my-maven-plugi…

中国500米逐年植被净初级生产力(NPP)数据集(2000-2022)

中国500米逐年植被净初级生产力&#xff08;NPP&#xff09;数据集&#xff08;2000-2022&#xff09; 净初级生产力(NPP)是指植物在单位时间单位面积上由光合作用产生的有机物质总量中扣除自养呼吸后的剩余部分&#xff0c;是生产者能用于生长、发育和繁殖的能量值&#xff0c…

UCloud + 宝塔 + PHP = 个人网站

UCloud 宝塔 PHP 个人网站 文章目录 1.概要2.UCloud使用教程&#xff08;租用云端服务器&#xff09;3.宝塔使用教程&#xff08;免费服务器运维面板&#xff09;4.总结 1.概要 今天主要是想教大家如何将在网络上白嫖到源码&#xff08;特指PHP源码!!!&#xff09;搭建运行…

Linux安全之SELinux理解

安全增强式 Linux&#xff0c;即SELinux(Security-Enhanced Linux)是一个 Linux 内核的安全模块&#xff0c;其提供了访问控制安全策略机制&#xff0c;包括了强制访问控制(Mandatory Access Control&#xff0c;MAC)。SELinux 是一组内核修改和用户空间工具&#xff0c;已经被…

【matlab】渐变填充曲线

【matlab】渐变填充曲线 clear;clc;close all; n4; x0:n*pi/499:n*pi; ysin(x); % 颜色包 for nm2:69 CMload([D:\matlab_w…

力扣 面试经典150算法题

1合并两个有序数组88. 合并两个有序数组-CSDN博客简单23

Vue的脚手架

脚手架配置 脚手架文档&#xff1a;Vue CLI npm config set registry https://registry.npm.taobao.org vue.config.js配置选项&#xff1a; 配置参考 | Vue CLI ref选项 ref和id类似&#xff0c;给标签打标识。 document.getElementById(btn); this.$ref.btn; 父子组…

【数据结构】树状数组算法总结

知识概览 树状数组有两个作用&#xff1a; 快速求前缀和 时间复杂度O(log(n))修改某一个数 时间复杂度O(log(n)) 例题展示 1. 单点修改&#xff0c;区间查询 题目链接 活动 - AcWing本活动组织刷《算法竞赛进阶指南》&#xff0c;系统学习各种编程算法。主要面向…