【Linux】基础:进程控制

news2024/12/25 0:22:40

【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创建子进程后,如果代码和数据段式出于只读的情况下,父子进程会共享物理内存的代码段与数据段,可以是当代码或者数据发生写时,就会发生写时拷贝。所谓写时拷贝即在物理开辟新的空间,修改相应的页表映射关系。

示意图如下:

img

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后不会对输出缓冲区进行刷新操作。

**实际上在系统中,数据时暂时被保存在输出缓冲区中的,exitmain系统会进行缓冲区的刷新,但_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;
}

打印示例如下:

image-20221120230953613

为此,进行实验代码书写:

#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这一个接口实现各自功能,各种功能图示如下:


补充:

  1. 代码将会放到: https://gitee.com/liu-hongtao-1/c–c–review.git ,欢迎查看!
  2. 欢迎各位点赞、评论、收藏与关注,大家的支持是我更新的动力,我会继续不断地分享更多的知识!

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

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

相关文章

Nginx重定向

Rewrite简介 Rewrite是Nginx服务器提供的一个重要基本功能&#xff0c;是Web服务器产品中几乎必备的功能。主要的作用是用来实现URL的重写。 注意:Nginx服务器的Rewrite功能的实现依赖于PCRE的支持&#xff0c;因此在编译安装Nginx服务器之前&#xff0c;需要安装PCRE库。Ngin…

(续)SSM整合之springmvc笔记(SpringMVC处理ajax请求)(P154-158)

目录 SpringMVC处理ajax请求 一 准备工作 1 新建spring_mvc_ajax com.atguigu 2 .导入依赖 3 添加web模块 4 .配置web.xml 5 . springmvc.xml 6 .创建控制层 7 . index.html 8 静态 9 部暑到tomcat上 10 启动tomcat 二 . 测试SpringMVC处理ajax 1 . ind…

TCP三次握手与四次挥手详解

TCP三次握手(建立TCP连接): 建立TCP连接&#xff0c;就是指建立一个TCP连接时&#xff0c;需要客户端和服务总共发送3个包以确认连接的建立。在socket编程中&#xff0c;这一过程由客户端执行connect来触发。 在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连…

GSN前瞻预处理

在数控加工等应用中&#xff0c;要求数控系统对机床进行平滑的控制&#xff0c;以防止较大的冲击影响零件的加工质量。 运动控制器的前瞻预处理功能可以根据用户的运动路径计算出平滑的速度规划&#xff0c;减少机床的冲击&#xff0c;从而提高加工精度。 下面用一个实例来说明…

一文了解Spring MVC(上)

目录 什么是Spring MVC 什么是MVC Spring MVC 和MVC的区别 怎么学Spring MVC Spring MVC的创建和连接 RequestMapping注解 获取参数 传递基础数据类型/包装类型&#xff08;无注解&#xff09; 添加RequestParam注解&#xff08;使用在方法参数上&#xff09; 传递自…

python——GIL锁详解

文章目录一、GIL全局解释器锁二、为什么会有GIL锁&#xff1f;三、多线程无法利用多核优势&#xff1f;计算密集型和IO密集型计算密集型——采用多进程计算密集型——采用多线程IO密集型——采用多进程IO密集型——采用多线程四、总结一、GIL全局解释器锁 1、GIL锁不是python的…

C语言 数据的存储

C语言 数据的存储一、数据与进制之间的关系1. 十进制与二进制之间的转换2. 二进制与十六进制之间的转换二、整型数据存储1. 原、反、补码2. 整型数据在内存中的存储3. 为什么整型数据存在内存中存储的是补码4. 有符号和无符号的数据类型有符号和无符号的存储范围猜想5. 关于 ch…

Java项目:SSH企业人力资源管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 企业人力资源管理系统&#xff0c;分为超级管理员与普通管理员两种角色,超级管理员可以对普通管理员进行添加、删除等操作&#xff1b; 超级管理…

【k8s】1、基础概念和架构及组件

文章目录一、kubernetes概述1、什么是kubernetes&#xff1f;2、应用程序部署方式的演变3、为什么要用kubernetes&#xff1f;二、kubernetes 特性三、Kubernetes集群架构与核心组件1、master组件1.1 kube-apiserver&#xff08;中央枢纽&#xff09;1.2 kube-controller-manag…

股价下跌18%后,满帮集团(YMM)的财务业绩正在加速放缓

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 市场与竞争 根据Beroe Advantage Procuremen的一份市场研究报告&#xff0c;2020年中国道路运输服务市场的规模估计为250亿美元。 这意味着这个行业近年来的复合年增长率为6.5%。 物流参与者往往集中在中国的西南和中南部地…

.NET跨平台框架选择之一 - Avalonia UI

1. Avalonia UI简介 Avalonia UI文档教程&#xff1a;https://docs.avaloniaui.net/docs/getting-started 随着跨平台越来越流行&#xff0c;.NET支持跨平台至今也有十几年的光景了(Mono开始)。 但是目前基于.NET的跨平台&#xff0c;大多数还是在使用B/S架构的跨平台上&…

数据分析面试重点

2022年10月求职季&#xff0c;疫情的影响&#xff0c;但是也挡不住各位小伙伴&#xff0c;找工作的热情。目前&#xff0c;数据分析行业大火&#xff0c;相信很多小伙伴都想去这一行业试试水。想要成功进入数据分析行业&#xff0c;就必须得通过数据分析面试&#xff0c;面试的…

计控实验(二)——积分分离PID控制实验

太原理工大学计算机控制技术实验之积分分离PID控制实验 积分分离PID控制实验实验原理实验内容实验结果思考题实验原理 上图是一个典型的PID 闭环控制系统方框图&#xff0c;其硬件电路原理及接线图可设计如下&#xff0c;图中画“○”的线需用户在实验中自行接好&#xff0c;对…

跟艾文学编程《Python基础》PyCharm 安装

作者&#xff1a;艾文&#xff0c;计算机硕士学位&#xff0c;企业内训讲师和金牌面试官&#xff0c;公司资深算法专家&#xff0c;现就职BAT一线大厂。 邮箱&#xff1a;1121025745qq.com 博客&#xff1a;https://edu.csdn.net/lecturer/894?spm1003.2001.3001.4144 内容&am…

[附源码]java毕业设计文具销售系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

6、子查询 、having、(not) exists、表中增加一列并补充数据、删除表中对应的数据-mysql

目录子查询一、需求分析与问题解决1、实际问题2、子查询的基本使用3、子查询的分类二、单行子查询1、单行比较操作符2、 代码示例3、HAVING 中的子查询4、CASE中的子查询5、 子查询中的空值问题6、 非法使用子查询三、多行子查询1、多行比较操作符2、代码示例3、空值问题四、 相…

《Flowable流程引擎从零到壹》Flowable流程引擎介绍和实战项目初始化流程引擎实例

14天学习训练营导师课程&#xff1a; 邓澎波《Flowable流程引擎-基础篇【2022版】》 邓澎波《Flowable流程引擎-高级篇【2022版】》 学习笔记《Flowable流程引擎从零到壹》回城传送 ❤️作者主页&#xff1a;小虚竹 ❤️作者简介&#xff1a;大家好,我是小虚竹。Java领域优质创…

mysql中的这些日志,你都知道吗?

在使用mysql的过程中&#xff0c;经常会听到mysql具有数据恢复能力&#xff0c;当我们在业务开发中误删了某些数据后&#xff0c;可以将数据库恢复到误删之前的状态。同时还具有故障恢复能力&#xff0c;当数据库所在的机器突然掉电停机后&#xff0c;mysql也可以保证数据一致性…

Java8 Stream 的核心秘密

小伙伴们好呀&#xff0c;我是 4ye&#xff0c;今天来分享下 Java8 Stream 的源码 核心回顾 stream 是一次性的&#xff0c;不是数据结构&#xff0c;不存储数据&#xff0c;不改变源数据.。API 分为终端和中间操作&#xff0c;中间操作是惰性的&#xff0c;碰到终端才去执行。…

python自动化测试

测试软件&#xff1a;pycharm 解释器版本&#xff1a;3.10.7 测试浏览器&#xff1a;谷歌 或 edge 谷歌web驱动下载地址&#xff1a;chromedriver.storage.googleapis.com/index.html edgeweb驱动下载地址&#xff1a;Microsoft Edge WebDriver - Microsoft Edge Developer 第一…