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

进程退出

进程退出表示进程即将运行结束。在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.hqwc.cn/news/288073.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件代码签名的作用

随着“互联网”时代的到来&#xff0c;人们的生活变得更加便利&#xff0c;但电信诈骗、信息泄露、恶意软件等也随之而来&#xff0c;软件安全问题也越来越受到关注。为了确保软件的身份和完整性&#xff0c;越来越多的软件开发者和企业选择使用软件代码签名证书。为什么我们会…

多线程并发实现生产者/消费者

前言 无需引入第三方消息队列组件&#xff0c;我们如何利用内置C#语法高效实现生产者/消费者对数据进行处理呢&#xff1f;在.NET Core共享框架&#xff08;Share Framework&#xff09;引入了通道&#xff08;Channel&#xff09;&#xff0c;也就是说无需额外通过NuGet包安装…

Graylog配置GraylogSidecar-传输日志文件

1.GraylogSidecar概述 Graylog Sidecar 是 Graylog 日志管理系统的一个组件&#xff0c;用于配置和管理通过 Filebeat、Winlogbeat、NXLog 或其他日志收集器发送的日志流。它的作用是管理和配置这些日志收集器&#xff0c;确保它们正确地发送日志数据到 Graylog 服务器。 我用的…

centos开机自启动实战小案例

1.编写一个我们需要做事的脚本 #!/bin/bash # 打印 "Hello" echo "Hello,Mr.Phor" # 为了更好的能看到效果 我们把这段文本放置到一个文件中 如果重启能够看到 /a.txt文件 我们实验成功 echo "hahahahahahahaha" > /a.txt #每次开机 执行…

python——异常机制及常见异常汇总

异常机制本质 异常指程序运行过程中出现的非正常现象&#xff0c;例如用户输入错误、除数为零、需 要处理的文件不存在、数组下标越界等。 所谓异常处理&#xff0c;就是指程序在出现问题时依然可以正确的执行剩余的程序&#xff0c;而 不会因为异常而终止程序执行。 python…

网络机顶盒什么牌子好?2024业内最新网络电视机顶盒排名

网络机顶盒是我们平时不可缺少的部分&#xff0c;但依然有很多消费者在挑选网络机顶盒时踩雷&#xff0c;不懂网络机顶盒什么牌子好&#xff0c;业内发布了最新的好评网络机顶盒排名&#xff0c;想知道哪些网络机顶盒值得买可以了解以下这五款口碑最好的网络机顶盒。 榜一&…

基于html5的演唱会购票系统的设计与实现论文

基于html5的演唱会购票系统的设计与实现 摘要 随着信息互联网购物的飞速发展&#xff0c;一般企业都去创建属于自己的电商平台以及购物管理系统。本文介绍了基于html5的演唱会购票系统的设计与实现的开发全过程。通过分析企业对于基于html5的演唱会购票系统的设计与实现的需求…

从数据系统的角度思考研发效能(DevOps)的提升丨IDCF

作者&#xff1a;李宏喜 IDCF研发效能&#xff08;DevOps&#xff09;工程师&#xff08;中级&#xff09;认证学员 在软件研发效能&#xff08;DevOps&#xff09;的学习过程中&#xff0c;我认识到诸位老师从软件全生命周期的不同角度&#xff0c;讲述着研发效能的提升。 敏…

springboot跨域问题,解决方法

前端访问出现CORS跨域问题 不多说&#xff0c;直接上代码~ import org.springframework.stereotype.Component;import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;Component…

鸿蒙(HarmonyOS)应用开发—— video组件实操

video 组件 harmonyOS 系统提供了基础的video。下面就直接上代码 原始video 新建项目 customVideo项目 本地视频 网络地址&#xff0c;就不用说明&#xff0c;只需要把地址换成网络地址即可 在resource 文件夹下的rawfile文件夹下 添加视频 在index.ets Video({src:$rawf…

案例075:基于微信小程序的家政服务管理系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

打开C#项目时出现“error : 找不到指定的 SDK”的错误解决方法汇总

从github上克隆项目回来&#xff0c;发现编译通过&#xff0c;我是通过一下步骤解决的&#xff1a; 1.到适用于 Visual Studio 的 .NET SDK 下载网址下载对应版本的.NET SDK&#xff0c;关闭当前VS后安装&#xff1b; 2.参考VS2022报错&#xff1a;error : 找不到指定的 SDK“…