【Linux】基础:进程控制
摘要:本文主要介绍关于Linux进程控制内容,分为创建、退出、等待与替换四个板块,希望读者可以掌握每个板块的主要概念以及使用原因和调用方法。
文章目录
- 【Linux】基础:进程控制
- 一、进程创建
- 1.1 概述
- 1.2 过程
- 1.3 写时拷贝
- 1.4 常规用法
- 1.5 调用失败
- 二、进程退出
- 2.1 进程退出场景
- 2.2 进程退出码获取
- 2.3 进程退出码概述
- 2.4 进程退出方法
- 三、进程等待
- 3.1 概述
- 3.2 wait
- 3.3 waitpid
- 返回值
- 参数
- wstatus
- status获取
- 阻塞与非阻塞(WNOHANG)
- 小节:waitpid
- 四、进程替换
- 4.1 概述
- 4.2 替换函数
- 4.2.1 替换函数概述
- 4.2.2 execl
- 4.2.3 execlp
- 4.2.4 execv
- 4.2.5 execvp
- 4.2.6 execle、execve
- 4.3 小节与关系
一、进程创建
对于进程创建,在进程概念一文中,进行了简单的介绍。主要方式由两种,可以直接运行可执行文件来创建进程,还可以通过fork()
函数来创建进程,在此主要介绍fork()
创建进程
1.1 概述
通过man指令查看fork函数,其中的作用概述为:创建一个子进程,它从已存在进程中创建一个新进程,新进程为子进程,而原进程为父进程。其中的头文件与函数定义如下:
SYNOPSIS
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
# 返回值:自进程中返回0,父进程返回子进程id,出错返回-1
1.2 过程
进程调用fork,当控制转移到内核中的fork代码后,进行如下操作:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
- fork返回,开始调度器调度
在此,需要注意fork之后的一个细节,就是fork之后是运行fork之后的代码,出现这种情况的主要原因是进程中的PCB中程序计数器PC的指向原因,如果需要运行fork之前的代码,就需要修改PC指针。示意图如下:
1.3 写时拷贝
在fork创建子进程后,如果代码和数据段式出于只读的情况下,父子进程会共享物理内存的代码段与数据段,可以是当代码或者数据发生写时,就会发生写时拷贝。所谓写时拷贝即在物理开辟新的空间,修改相应的页表映射关系。
示意图如下:
1.4 常规用法
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
- 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数
1.5 调用失败
- 系统中有太多的进程
- 实际用户的进程数超过了限制
二、进程退出
进程退出,与进程创建的反操作类似,在系统层面上,需要减少一个内存,就需要删去进程创建时创建的数据结构,比如:PCB、mm_struct、页表等,并且代码和数据申请到的空间也要释放掉。
2.1 进程退出场景
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
2.2 进程退出码获取
对于正常终止的程序可以通过echo $?
来查看进程的退出码,示例如下:
[lht@VM-12-7-centos Blog_ControlProcess]$ cat myproc.c
#include<stdio.h>
int main(){
return 2;
}
[lht@VM-12-7-centos Blog_ControlProcess]$ ./myproc
[lht@VM-12-7-centos Blog_ControlProcess]$ echo $?
2
从上面的案例,main函数的return值就是进程的退出码,echo $?
是输出最近一次进程的退出码。
2.3 进程退出码概述
可以通过进程退出码的判断来判断进程退出时的状态,退出的状态主要可以用两种方式区分。可以分为正常退出与异常退出,当正常退出时可以分为运行结果正确与运行结果错误。当正常退出时,如果结果正确将会返回0,如果结果错误将会返回其他错误码,在此对于错误码已经完成了集成,在C语言的strerror
可以通过错误码来获取错误信息。示例如下:
#include<stdio.h>
#include<string.h>
int main(){
for(int i = 0;i<135;i++){
printf("%d : %s\n",i,strerror(i));
}
return 0;
}
[lht@VM-12-7-centos Blog_ControlProcess]$ make
gcc -o myproc myproc.c -std=c99
[lht@VM-12-7-centos Blog_ControlProcess]$ ./myproc
0 : Success
1 : Operation not permitted
.......
133 : Memory page has hardware error
134 : Unknown error 134
当程序异常退出时,说明程序崩溃了,错误码就失去了意义,因此通过获取信号来表示错误信息,在后文将会对其进行具体介绍。
2.4 进程退出方法
正常终止:
- main:return返回,代表进程结束
- exit:在任意地方调用,代表进程终止,参数是退出码,将会完成收尾工作
- _exit:强制终止进程,不进行收尾工作
异常退出:信号终止
正常终止的main返回是主程序或者普通函数运行完之后,是我们比较熟悉的,在此主要介绍exit
与_exit
两个函数。
exit
NAME
exit - cause normal process termination
SYNOPSIS
#include <stdlib.h>
noreturn void exit(int status);
exit
的作用**是进程退出,可以在任意地方调用,在进程退出后,还会进行处理后续的收尾工作。**对于函数的参数为退出码,定义了进程的终止状态,父进程通过wait来获取该值,将在进程等待中具体介绍。而关于收尾的工作,将在_exit中进行介绍,代码示例如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void function(){
exit(10);
printf("I am a function,running\n");
}
int main(){
function();
printf("I am main function ,running\n");
return 0;
}
[lht@VM-12-7-centos Blog_ControlProcess]$ ./myproc
[lht@VM-12-7-centos Blog_ControlProcess]$ echo $?
10
_exit
NAME
_exit, _Exit - terminate the calling process
SYNOPSIS
#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void _Exit(int status);
_exit
的作用为退出进程,_exit
往更仔细的说是强制终止,不会进行后续收尾工作,比如对于标准输出流的刷新,示例如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
printf("I am main function ,running");
sleep(4);
return 0;
}
[lht@VM-12-7-centos Blog_ControlProcess]$ make
gcc -o myproc myproc.c -std=c99
[lht@VM-12-7-centos Blog_ControlProcess]$ ./myproc
I am main function ,running
使用_exit
[lht@VM-12-7-centos Blog_ControlProcess]$ make
gcc -o myproc myproc.c -std=c99
[lht@VM-12-7-centos Blog_ControlProcess]$ ./myproc
将会发现使用了_exit
后不会对输出缓冲区进行刷新操作。
**实际上在系统中,数据时暂时被保存在输出缓冲区中的,exit
与main
系统会进行缓冲区的刷新,但_exit
不会。**图示如下:
三、进程等待
3.1 概述
在父进程创建子进程后,是需要子进程去完成某项任务的,而父进程需要通过相关接口等待子进程结束,子进程退出,父进程如果不管不顾,就可能造成僵尸进程的问题,进而造成内存泄漏,进程一旦变成僵尸状态,kill -9 也无法将其杀死,因此是对其进行等待。不仅如此,父进程还需要获取子进程退出后的相关状态与信息。**总而言之,进程等待是父进程等待子进程退出,回收子进程资源,获取子进程推出信息。**进程等待的方法主要有wait与waitpid两个函数,以下也会对其中分点说明。
同时,根据上述说明,也可以理解父进程等待的原因,在此总结为三点:
- 通过获取子进程的退出信息,能够得知子进程的执行结果
- 可以保证时序问题,子进程先退出,父进程后退出
- 进程退出时会进入僵尸状态,会出现内存泄露问题,需要通过父进程wait,释放该子进程资源
3.2 wait
wait函数通过man指令可以查询如下内容:
NAME
wait, waitpid, waitid - wait for process to change state
SYNOPSIS
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
其作用为等待进程状态改变,实际上就是等待进程状态转换为僵尸状态。关于其参数为退出状态,将会在waitpid模块中进行解析,在此将其设为NULL。而返回值就是等待进程的pid,如果等待进程失败,会返回负数。
以下通过实验来说明wait处理进程等待的两个原因,示例如下:
实验一:进程等待可以保证时序问题,子进程先退出,父进程后退出。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main(){
pid_t id = fork();
if(id == 0){
int cnt = 5;
printf("child pid:%d\n",getpid());
while(cnt){
printf("child is running: cnt is :%d\n",cnt);
cnt--;
sleep(1);
}
exit(0);
}
pid_t ret = wait(NULL);
if(ret>0){
printf("father wait: %d,success\n",ret);
}
else{
printf("father wait failed\n");
}
}
[lht@VM-12-7-centos Blog_ControlProcess]$ make
gcc -o myproc myproc.c -std=c99
[lht@VM-12-7-centos Blog_ControlProcess]$ ./myproc
child pid:3804664
child is running: cnt is :5
child is running: cnt is :4
child is running: cnt is :3
child is running: cnt is :2
child is running: cnt is :1
father wait: 3804664,success
实验二:进程退出时会进入僵尸状态,会出现内存泄露问题,需要通过父进程wait,释放该子进程资源。
检测脚本:
while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep; sleep 5; echo "===========================================================" ; done
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main(){
pid_t id = fork();
if(id == 0){
int cnt = 5;
printf("child pid:%d\n",getpid());
while(cnt){
printf("child is running: cnt is :%d\n",cnt);
cnt--;
sleep(1);
}
}
sleep(10);
pid_t ret = wait(NULL);
if(ret>0){
printf("father wait: %d,success\n",ret);
}
else{
printf("father wait failed\n");
}
sleep(15);
return 0;
}
[lht@VM-12-7-centos Blog_ControlProcess]$ while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep; sleep 2; echo "===========================================================" ; done
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
3782511 3809766 3809766 3782511 pts/1 3809766 S+ 1002 0:00 ./myproc
3809766 3809767 3809766 3782511 pts/1 3809766 S+ 1002 0:00 ./myproc
===========================================================
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
3782511 3809766 3809766 3782511 pts/1 3809766 S+ 1002 0:00 ./myproc
3809766 3809767 3809766 3782511 pts/1 3809766 Z+ 1002 0:00 [myproc] <defunct>
===========================================================
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
3782511 3809766 3809766 3782511 pts/1 3809766 S+ 1002 0:00 ./myproc
3.3 waitpid
waitpid函数通过man指令可以查询如下内容:
NAME
wait, waitpid, waitid - wait for process to change state
SYNOPSIS
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
返回值
- 当正常返回的时候waitpid返回收集到的子进程的进程ID
- 如果设置了选项WNOHANG(非阻塞等待),而调用中waitpid发现没有已退出的子进程可收集,则返回0
- 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在
参数
pid
pid=-1 等待任一个子进程,与wait等效。
pid>0 等待其进程ID与pid相等的子进程。
wstatus
WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options
WNOHANG:表示非阻塞等待,若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
简单的使用示例如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main(){
pid_t id = fork();
if(id == 0){
int cnt = 5;
printf("child pid:%d\n",getpid());
while(cnt){
printf("child is running: cnt is :%d\n",cnt);
cnt--;
sleep(1);
}
exit(0);
}
sleep(10);
pid_t ret = waitpid(-1,NULL,0);
if(ret>0){
printf("father wait: %d,success\n",ret);
}
else{
printf("father wait failed\n");
}
sleep(15);
return 0;
}
[lht@VM-12-7-centos Blog_ControlProcess]$ ./myproc
child pid:3833296
child is running: cnt is :5
child is running: cnt is :4
child is running: cnt is :3
child is running: cnt is :2
child is running: cnt is :1
father wait: 3833296,success
在以上示例中,调用改了waitpid
函数,-1表示等待任意进程结束,NULL为传递一个空指针作为退出状态参数,0表示阻塞等待,其效果与wait
一样。
其中还会对以上函数内容细节进行详细说明
wstatus
又或者成为status
,是一个输出型参数,父进程通过这一个参数来获得子进程的退出信息,得到子进程的执行结果,其中status
是占用32个比特位,只是用其中的低16个比特位,而不使用高16位比特位。当子进程正常结束时,会在第8至15位比特位上写入退出状态,当子进程被信号杀死时,会在前7位写入终止信号,第八位为core dump
标志,示意图如下:
其中,这里的退出状态对应着开始时,在分析进程退出时提到的进程运行完毕的场景,运行完毕后无论是结果如何都会通过退出码来呈现进程运行完毕的信息。当异常终止时,前文提到,退出码变得无意义,这时我们需要了解异常终止的原因,就是收到了某种信号,因此在status
中就记录了这种信号。
status获取
获取status
的方法主要用两种,分别是使用位运算与使用宏,位运算就根据其32位中对状态的比特位分布,用右移与&完成运算,要获取status
中的退出状态,就需要右移八位,并用0xFF进行提取后八位,要提取信号,不需要移动,但需要用0x7F提取前七位。而宏这用系统提供的两个宏完成:WIFEXITED
与 WEXITSTATUS
。对其进行实验展示:
实验1:通过位运算获取正常进程退出并运行成功的子进程退出状态信息
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main(){
pid_t id = fork();
if(id == 0){
int cnt = 3;
printf("child pid:%d\n",getpid());
while(cnt){
printf("child is running: cnt is :%d\n",cnt);
cnt--;
sleep(1);
}
exit(0);
}
int status;
pid_t ret = waitpid(-1,&status,0);
if(ret>0){
printf("father wait: %d,success\n",ret);
printf("status exit code = %d\n",(status>>8)&0xFF);
printf("status exit signal = %d\n",(status)&0x7F);
}
else{
printf("father wait failed\n");
}
return 0;
}
[lht@VM-12-7-centos Blog_ControlProcess]$ ./myproc
child pid:3836941
child is running: cnt is :3
child is running: cnt is :2
child is running: cnt is :1
father wait: 3836941,success
status exit code = 0
status exit signal = 0
实验2:通过位运算获取正常进程退出并运行结果错误的子进程退出状态信息
......
int main(){
pid_t id = fork();
if(id == 0){
......
exit(10);
}
......
}
[lht@VM-12-7-centos Blog_ControlProcess]$ ./myproc
child pid:3837140
child is running: cnt is :3
child is running: cnt is :2
child is running: cnt is :1
father wait: 3837140,success
status exit code = 10
status exit signal = 0
实验3:通过位运算获取异常进程退出的子进程退出状态信息
通过kill -9 pid
指令将进程杀死
[lht@VM-12-7-centos Blog_ControlProcess]$ ./myproc
child pid:3837643
child is running: cnt is :3
child is running: cnt is :2
child is running: cnt is :1
father wait: 3837643,success
status exit code = 0
status exit signal = 9
可以发现可以提取到退出信号
实验4:WIFEXITED 与 WEXITSTATUS来判断运行是否成功与获取进程退出状态
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main(){
pid_t id = fork();
if(id == 0){
int cnt = 3;
printf("child pid:%d\n",getpid());
while(cnt){
printf("child is running: cnt is :%d\n",cnt);
cnt--;
sleep(1);
}
exit(10);
}
int status;
pid_t ret = waitpid(-1,&status,0);
if(ret>0){
if(WIFEXITED(status)){
printf("eixt code:%d\n",WEXITSTATUS(status));
}
}
else{
printf("father wait failed\n");
}
return 0;
}
[lht@VM-12-7-centos Blog_ControlProcess]$ ./myproc
child pid:3838525
child is running: cnt is :3
child is running: cnt is :2
child is running: cnt is :1
eixt code:10
阻塞与非阻塞(WNOHANG)
对于waitpid的第三个参数,可以选择阻塞等待与非阻塞等待,对于默认行为0,表示是阻塞等待,而WNOHANG
表示设置等待为非阻塞等待,等价于wait,WNOHANG
也是基于非阻塞等待的轮询方案。
对于阻塞来说,相当于当发生阻塞时,会将进程状态改变,退出运行队列进入进程的阻塞队列,当阻塞结束时,进行返回,返回的本质是进程的PCB从等待队列退出进入运行队列,被CPU调度。对于非阻塞来说,使用基于非阻塞的轮询访问方案,不会进入阻塞队列,并且询问后立马返回。
对于WNOHANG
的理解可以为which no hang
,hang是计算机的词汇,表示卡住了长时间动不了,which no hang 则表示了不要卡住,就是不要阻塞了的意思。一下通过实验来展示其应用:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main(){
pid_t id = fork();
if(id == 0){
int cnt = 3;
printf("child pid:%d\n",getpid());
while(cnt){
printf("child is running: cnt is :%d\n",cnt);
cnt--;
sleep(1);
}
exit(10);
}
int status = 0;
while(1){
pid_t ret = waitpid(id,&status,WNOHANG);
if(ret == 0){
// 子进程没有退出,但是waitpid成功,需要父进程等待
printf("child doesn`t exit,waitpid success,parent waiting\n");
}
else if(ret > 0){
// 子进程退出,waitpid成功,需要父进程获取对应退出结果
printf("child exit,waitpid success\n");
printf("father wait %d success, status code = %d ,status exit signal = %d\n",ret,(status>>8)&0xFF,(status)&0x7F);
}
else{
//等待失败
perror("waitpid");
exit(1);
}
sleep(1);
}
return 0;
}
[lht@VM-12-7-centos Blog_ControlProcess]$ ./myproc
child doesn`t exit,waitpid success,parent waiting
child pid:3840767
child is running: cnt is :3
child doesn`t exit,waitpid success,parent waiting
child is running: cnt is :2
child doesn`t exit,waitpid success,parent waiting
child is running: cnt is :1
child doesn`t exit,waitpid success,parent waiting
child exit,waitpid success
father wait 3840767 success, status code = 10 ,status exit signal = 0
waitpid: No child processes
小节:waitpid
waitpid
是一个系统调用接口,用户通过在用户层调用系统调用接口,传入输出型参数,获取status
。在操作系统内核当调用waitpid
时存在父进程等待子进程,当子进程结束,子进程进入僵尸状态,父进程通过记录用户层传入status的地址进行赋值,来获取子进程状态,最后还需要回收相应资源。在获取过程中进行了位运算,比如退出码将左移八位按位或,将退出信号直接或运算。
四、进程替换
4.1 概述
进程替换是进程不变,但是却替换当前进程的代码和数据的技术。在之前学习进程概念与进程地址空间的内容时,可以了解到关于进程创建后,会有PCB与进程地址空间和页表完成逻辑内存与物理内存的对应,而进程替换技术就是将物理内存空间指向的代码与数据进行替换。示意图如下:
进程替换的本质是把进程的代码和数据加载到特定的进程上下文中。好比在过去进行C语言程序书写时,我们写的程序都是需要加载到内存中才可以执行,而如何加载了exec*
类型接口在后文会进行详细介绍,将会使用到。最后,与进程创建相比,进程替换是没有创建新进程的,是使用了旧进程的老壳子。
而需要进行进程替换的主要原因,就是需要子进程进行一个全新的程序,需要注意的是,当子进程完成替换后,父进程是不受影响的,这是因为进程的独立性,在代码发生改变时,会进行代码区的写时拷贝。
4.2 替换函数
4.2.1 替换函数概述
exec*
函数的man指令查看如下:
NAME
execl, execlp, execle, execv, execvp, execvpe - execute a file
SYNOPSIS
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execle(const char *path, const char *arg, ...
/*, (char *) NULL, 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[]);
从整体上看,一共有六个函数,而他们的作用是相同的,都是执行一个文件。而他们的名称可以看出是有一定的规律的,同时也是符合一定的命名规则的,而不同的命名规则也对应了不同的参数形式。以下内容,将整体介绍函数功能,在对其中命名与参数进行解释,最后对其进行各种实验举例。
函数解释
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回
- 如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值
命名理解
- l(list) : 表示参数采用列表
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
函数名 | 参数格式 | 是否带路径 | 是否使用当前环境变量 |
---|---|---|---|
execl | 列表 | 不是 | 是 |
execlp | 列表 | 是 | 是 |
execle | 列表 | 不是 | 否,需要自我组装环境变量 |
execv | 数组 | 不是 | 是 |
execvp | 数组 | 是 | 是 |
execve | 数组 | 不是 | 否,需要自我组装环境变量 |
4.2.2 execl
int execl(const char *path, const char *arg, ...
参数
- path:需要执行的目标文件路径的全路径,所在路径或者文件名
- arg:表示可变参数列表,要执行的目标程序,在命令行上如何执行就在此通过参数的形式,一个个的传入,最后需要通过NULL结尾
返回值:如果替换成功将不会返回,如果替换失败将会返回-1
示例如下:
#include<stdio.h>
#include<unistd.h>
int main(){
printf("command beginning……\n");
execl("/usr/bin/ls"/*执行路径*/,"ls","-a","-l","-i"/*如何执行*/,NULL/*NULL结束*/);
printf("command end……\n");
return 0;
}
[lht@VM-12-7-centos Blog_ControlProcess]$ ./proc
command beginning……
total 60
662278 drwxrwxr-x 2 lht lht 4096 Nov 20 22:34 .
659164 drwxrwxr-x 9 lht lht 4096 Nov 19 16:21 ..
662297 -rw-rw-r-- 1 lht lht 106 Nov 20 22:30 Makefile
662266 -rwxrwxr-x 1 lht lht 17840 Nov 20 00:46 myproc
662298 -rw-rw-r-- 1 lht lht 1085 Nov 20 00:46 myproc.c
663168 -rwxrwxr-x 1 lht lht 17528 Nov 20 22:34 proc
662281 -rw-rw-r-- 1 lht lht 239 Nov 20 22:34 proc.c
4.2.3 execlp
int execlp(const char *file, const char *arg, ...
参数
- file:函数与execl极为相似,主要差别在于该参数,在此可以通过输出参数需要执行的可执行程序文件名,通过在环境变量中查找该文件,并执行
- arg:表示可变参数列表,要执行的目标程序,在命令行上如何执行就在此通过参数的形式,一个个的传入,最后需要通过NULL结尾
返回值:如果替换成功将不会返回,如果替换失败将会返回-1
示例如下:
#include<stdio.h>
#include<unistd.h>
int main(){
printf("command beginning……\n");
execlp("ls"/*在环境变量中寻找的文件名*/,"ls","-a","-l","-i"/*如何执行*/,NULL/*NULL结束*/);
printf("command end……\n");
return 0;
}
[lht@VM-12-7-centos Blog_ControlProcess]$ ./proc
command beginning……
total 60
662278 drwxrwxr-x 2 lht lht 4096 Nov 20 22:37 .
659164 drwxrwxr-x 9 lht lht 4096 Nov 19 16:21 ..
662297 -rw-rw-r-- 1 lht lht 106 Nov 20 22:30 Makefile
662266 -rwxrwxr-x 1 lht lht 17840 Nov 20 00:46 myproc
662298 -rw-rw-r-- 1 lht lht 1085 Nov 20 00:46 myproc.c
663168 -rwxrwxr-x 1 lht lht 17528 Nov 20 22:37 proc
662281 -rw-rw-r-- 1 lht lht 255 Nov 20 22:37 proc.c
4.2.4 execv
int execv(const char *path, char *const argv[]);
参数
- path:需要执行的目标文件路径的全路径,所在路径或者文件名
- argv:通过数组的形式传入需要自行的命令行参数,数组中的每个字符串为每一个参数,具体可看示例
返回值:如果替换成功将不会返回,如果替换失败将会返回-1
示例如下:
#include<stdio.h>
#include<unistd.h>
int main(){
printf("command beginning……\n");
char* const argv[] = {"ls","-a","-l","-i",NULL};
execv("/usr/bin/ls"/*执行路径*/,argv/*如何执行,通过数组传参*/);
printf("command end……\n");
return 0;
}
[lht@VM-12-7-centos Blog_ControlProcess]$ ./proc
command beginning……
total 60
662278 drwxrwxr-x 2 lht lht 4096 Nov 20 22:42 .
659164 drwxrwxr-x 9 lht lht 4096 Nov 19 16:21 ..
662297 -rw-rw-r-- 1 lht lht 106 Nov 20 22:30 Makefile
662266 -rwxrwxr-x 1 lht lht 17840 Nov 20 00:46 myproc
662298 -rw-rw-r-- 1 lht lht 1085 Nov 20 00:46 myproc.c
663168 -rwxrwxr-x 1 lht lht 17528 Nov 20 22:42 proc
662281 -rw-rw-r-- 1 lht lht 279 Nov 20 22:41 proc.c
4.2.5 execvp
int execvp(const char *file, char *const argv[]);
参数
- file:在此可以通过输出参数需要执行的可执行程序文件名,通过在环境变量中查找该文件,并完成执行
- argv:通过数组的形式传入需要自行的命令行参数,数组中的每个字符串为每一个参数,具体可看示例
返回值:如果替换成功将不会返回,如果替换失败将会返回-1
示例如下:
#include<stdio.h>
#include<unistd.h>
int main(){
printf("command beginning……\n");
char* const argv[] = {"ls","-a","-l","-i",NULL};
execvp("ls"/*执行文件名,在环境变量中查找*/,argv/*如何执行,通过数组传参*/);
printf("command end……\n");
return 0;
}
[lht@VM-12-7-centos Blog_ControlProcess]$ ./proc
command beginning……
total 60
662278 drwxrwxr-x 2 lht lht 4096 Nov 20 22:55 .
659164 drwxrwxr-x 9 lht lht 4096 Nov 19 16:21 ..
662297 -rw-rw-r-- 1 lht lht 106 Nov 20 22:30 Makefile
662266 -rwxrwxr-x 1 lht lht 17840 Nov 20 00:46 myproc
662298 -rw-rw-r-- 1 lht lht 1085 Nov 20 00:46 myproc.c
663168 -rwxrwxr-x 1 lht lht 17528 Nov 20 22:55 proc
662281 -rw-rw-r-- 1 lht lht 301 Nov 20 22:55 proc.c
4.2.6 execle、execve
int execle(const char *path, const char *arg, ...
/*, (char *) NULL, char * const envp[] */);
参数
- path:需要执行的目标文件路径的全路径,所在路径或者文件名
- arg:表示可变参数列表,要执行的目标程序,在命令行上如何执行就在此通过参数的形式,一个个的传入,最后需要通过NULL结尾
- envp:表示自己维护环境变量,在系统中有属于自身的环境变量,但可以通过数组在此进行自定义环境变量
返回:如果替换成功将不会返回,如果替换失败将会返回-1
补充:由于execle与execvpe非常相似,只是在传参上有区别,而这里主要介绍关于e
的内容,因此主要以execle为例
示例如下:
首先书写一个关于打印环境变量的程序:
#include<stdio.h>
int main(){
extern char**environ;
for(int i = 0;environ[i];i++){
printf("%s ",environ[i]);
}
printf("print environ finished!\n");
return 0;
}
打印示例如下:
为此,进行实验代码书写:
#include<stdio.h>
#include<unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char *argv[], char *env[])
{
if(fork() == 0){ //child
printf("command start……\n");
char *env[] = {
"ENV1=environ1",
"ENV2=environ2",
NULL
};// 自定义环境变量
execle("./myproc","myproc",NULL,env);
printf("command end……\n");
exit(1);
}
waitpid(-1, NULL, 0);
printf("wait child success!\n");
return 0;
}
[lht@VM-12-7-centos Blog_ControlProcess]$ ./proc
command start……
ENV1=environ1
ENV2=environ2
my exe running .... done
wait child success!
4.3 小节与关系
实际上这些接口看起来差异较大,实际上只是参数不同而已,而如此多的接口主要是满足各个不同的应用场景。而进程替换的接口函数之间是存在关系的,通过man函数仔细观察可以发现,只有execve
是系统调用,也就是其他接口并不是系统调用,而是使用了execve
这一个接口实现各自功能,各种功能图示如下:
补充:
- 代码将会放到: https://gitee.com/liu-hongtao-1/c–c–review.git ,欢迎查看!
- 欢迎各位点赞、评论、收藏与关注,大家的支持是我更新的动力,我会继续不断地分享更多的知识!