进程终止 等待 替换

news2024/11/13 9:23:08

文章目录

  • 一.进程的终止
    • 进程终止实在做什么?
    • 进程终止的3种情况
      • 自定义退出码
    • 如何终止进程?
  • 二.进程等待
    • 为什么要进行进程等待?
    • 进程如何等待?
      • wait
      • waitpid
    • 阻塞等待 && 非阻塞等待
  • 三.进程的程序替换
    • 先看代码 && 现象
      • execl
      • 原理
      • 多进程替换
    • 使用所有的替换方法,并认识函数参数的含义
      • execv
      • execvp
      • execlp
    • 用自己的程序替换
      • execvpe

一.进程的终止

进程的创建:内核的相关管理数据结构(task_struct + mm_struct + 页表) + 代码和数据
那么进程创建的时候,先有数据结构还是代码和数据?
例子:高考完被学校录取,学校就已经有你的档案(虚拟地址空间),报道的时候你才真正的到学校(物理内存)
所以创建进程先构建数据结构,后写入代码和数据
在这里插入图片描述

进程终止实在做什么?

释放曾经的代码和数据所占的空间
释放内核数据结构

进程终止的3种情况

问题:main函数的为什么要有返回值呢?

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("I am process,pid:%d,ppid:%d\n",getpid(),getppid());
    return 0;
}
#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("I am process,pid:%d,ppid:%d\n",getpid(),getppid());
    return 100;
}

两段代码能正常运行
在这里插入图片描述
区别就在与echo $?的值不一样
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述运行上面的第二段代码,为什么第一个 $?是100,第二个 $?是0呢?
因为 echo也是进程
在这里插入图片描述
退出码:0表示成功 !0表示失败
在这里插入图片描述
查看错误码的函数strerror(报错信息) (注意:错误码不是退出码)
在这里插入图片描述
查看错误码编号的代码:

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    for(int errcode = 0; errcode <= 255; errcode++)
    {
        printf("%d:%s\n",errcode,strerror(errcode));
    }
    printf("I am process,pid:%d,ppid:%d\n",getpid(),getppid());
    return 100;
}

在这里插入图片描述
随便写个命令,发现报错码是1,正好与上面的报错信息相对
在这里插入图片描述
问题:父进程bash为什么要得到子进程的退出码呢?
答:要知道子进程的退出情况(成功、失败、失败的原因是什么),为用户负责

自定义退出码

引子:

#include <stdio.h>

int Div(int x,int y)
{
    if( 0 == y )
    {
        return -1;
    }
    else
        return x / y;
}

int main()
{
    int result = Div(10,0);
    printf("result:%d\n",result);
    return 0;
}

运行之后,输出是-1。
不知道是因为除0的原因导致结果为-1还是相除本身结果是-1
在这里插入图片描述
说明光靠打印结果(数字),是看不清涵义。
所以自定义一下:

#include <stdio.h>

//自定义枚举常量
enum
{
    Success = 0,
    Div_Zero,
    Mod_Zero,
};

int exit_code = Success;

int Div(int x,int y)
{
    if( 0 == y )
    {
        exit_code = Div_Zero;
        return -1;
    }
    else
        return x / y;
}

int main()
{
    int result = Div(10,0);
    printf("result:%d\n",result);
    return exit_code;
}

这样就知道是因为什么退出了,退出码为1,说明除零了
在这里插入图片描述
我们不适合面对数字,更适合直观的语言,所以再写一个接口

#include <stdio.h>

//自定义枚举常量
enum
{
    Success = 0,
    Div_Zero,
    Mod_Zero,
};

int exit_code = Success;

const char* CodeToErrString(int code)
{
    switch(code)
    {
        case Success:
            return "Success";
        case Div_Zero:
            return "div zero!";
        case Mod_Zero:
            return "mod zero!";
        default:
            return "unknow error!";
    }
}

int Div(int x,int y)
{
    if( 0 == y )
    {
        exit_code = Div_Zero;
        return -1;
    }
    else
        return x / y;
}

int main()
{
    int result = Div(10,100);
    printf("result:%d[%s]\n",result,CodeToErrString(exit_code));
    result = Div(10,0);
    printf("result:%d[%s]\n",result,CodeToErrString(exit_code));
    return exit_code;
}

效果就很明显
在这里插入图片描述
综上所述:进程终止的2种情况
a.代码跑完,代码正确
b.代码跑完,代码不正确
由进程的退出码决定
第三种情况:程序能跑完吗?(生活中的事情一定要全做完吗?)
c.代码执行时,出现了异常,提前退出了
最经典的就是野指针问题。
最明显的例子:VS 编译运行的时候,崩溃了 ------> 操作系统发现发现你的进程做了不该做的事情,OS杀掉了进程
故事:某学生考试作弊,考了90分,你还在乎他为什么考了10分嘛。
一旦出现异常,退出码就不重要了
为什么会出现了异常?原因是?
进程出现了异常,本质是因为进程收到了OS发给进程的信号!
这里证明OS发送给进程信号:

#include <stdio.h>
#include <unistd.h>

int main()
{
    int* p = NULL;
    while(1)
    {
        printf("I am a  process,%d\n",getpid());
        sleep(1);
        *p = 100;
    }
    return 0;
}

在这里插入图片描述
这个进程有没有野指针不重要,重要的是因为野指针了,触发了操作系统给该进程发信号
查看进程的信号:

kill -l

发的是11号信号,SIGSEGV全称为segmentation violation,就是段错误
在这里插入图片描述
把野指针删了运行:

#include <stdio.h>
#include <unistd.h>

int main()
{
    while(1)
    {
        printf("I am a  process,%d\n",getpid());
        sleep(1);
    }
    return 0;
}

没有野指针,收到了信号,操作系统判断就是野指针。
在这里插入图片描述
综上所述:进程出现了异常时,我们可以看进程退出的时候,退出信号是多少,就可以判断我的进程为什么异常了
衡量一个进程退出,我们只需要两个数字:退出码 && 退出信号

退出码退出信号
00完全成功
!00代码运行正常,结果不对
0!0进程出异常了,退出码对不对无所谓
!0!0进程出异常了,退出码对不对无所谓

这两个数字一定要让父进程知道
但子进程如何传给父进程信息呢?
子进程PCB有sig_code(退出数字)和exit_code(退出信号)。
在这里插入图片描述
源码:
在这里插入图片描述

如何终止进程?

a. main函数return,表示进程终止
b. 代码调用exit函数
在这里插入图片描述

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    while(1)
    {
        printf("I am a  process,%d\n",getpid());
        sleep(1);
        exit(123);
    }
    return 0;
}

运行之后退出码为123
在这里插入图片描述
如果把exit放入到其他函数当中,退出码依旧是exit中的值

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int ADD()
{
    exit(13);
}

int main()
{
    while(1)
    {
        printf("I am a  process,%d\n",getpid());
        sleep(1);
        ADD();
    }
    return 0;
}

在这里插入图片描述
c. _exit --> system call
在这里插入图片描述

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    while(1)
    {
        printf("I am a  process,%d\n",getpid());
        sleep(1);
        _exit(29);
    }
    return 0;
}

退出码和代码一样是29
在这里插入图片描述
把他放入函数调用内部

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int Function()
{
    exit(29);
}

int main()
{
    while(1)
    {
        printf("I am a  process,%d\n",getpid());
        sleep(1);
        Function();
    }
    return 0;
}

退出码依旧是29
在这里插入图片描述
目前功能和exit差不多
区别:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    printf("hello world");//注意这里没有\n
    sleep(2);
    exit(6);
}

printf已经执行了,存在在缓冲区,exit让我们看到了hello world,说明exit冲刷了缓冲区
在这里插入图片描述
同样的代码_exit()

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    printf("hello world");
    sleep(2);
    _exit(6);
}

hello world并没有刷出来
在这里插入图片描述
说明区别在于exit会在进程结束的时候,冲刷缓冲区,_exit不会
注意这里说的缓冲区不是操作系统内核的缓冲区
在这里插入图片描述
在这里插入图片描述

二.进程等待

任何一个子进程,在退出的情况下,一般必须要被父进程等待。
如果子进程在退出的时候,父进程不管不顾。子进程会变成为Z(僵尸状态),PCB依旧会存在,引发内存泄漏

为什么要进行进程等待?

1.父进程通过等待,解决子进程退出的僵尸问题,回收系统资源。(一定要考虑)
2.获取子进程的退出信息,知道子进程是什么原因退出的。(可选的功能)

进程如何等待?

有两个函数wait/waitpid

wait

在这里插入图片描述
演示:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

void ChildRun()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process,pid:%d,ppid:%d\n",getpid(),getppid());
        sleep(1);
        cnt--;
    }
}

int main()
{
    printf("I am father,pid:%d.ppid:%d\n",getpid(),getppid());

    pid_t id = fork();
    if(id == 0)
    {
        //child
        ChildRun();
        printf("child quit ...\n");
        exit(0);
    }
    //father
    sleep(10);//让子进程已经退了,父进程还在sleep,就能看见子进程的僵尸状态
    pid_t rid = wait(NULL);//看见回收僵尸进程
    if(rid > 0)
    {
        printf("wait success,rid:%d\n",rid);
    }
    sleep(3);
    printf("father quit ...\n");
    return 0;
}

查看进程方便:

while :; do ps ajx | head -1 && ps ajx | grep myprocess | grep -v grep ; sleep 1; done

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所以等待,可以解决子进程的僵尸问题的。
如果把父进程的sleep(10)去掉,父进程会一直等待子进程直到子进程退出。
在子进程没退的期间,父进程一直在阻塞等待。
子进程本身就是软件,父进程本质就是在等待某种软件的条件就绪,如何理解父进程堵塞等待子进程呢?
在这里插入图片描述

waitpid

在这里插入图片描述
第一个参数pid
pid=-1:等待任意一个子进程,与wait等效
pid>0 :等待其进程pid与所写的pid相等的子进程
下面这两个的作用一模一样,表示等待任何一个子进程退出,哪个退了就返回哪个子进程的pid,就不再演示了

wait(NULL);
waitpid(-1,NULL,0);

把-1改成pid,表示等待指定的子进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

void ChildRun()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process,pid:%d,ppid:%d\n",getpid(),getppid());
        sleep(1);
        cnt--;
    }
}

int main()
{
    printf("I am father,pid:%d.ppid:%d\n",getpid(),getppid());

    pid_t id = fork();
    if(id == 0)
    {
        //child
        ChildRun();
        printf("child quit ...\n");
        exit(0);
    }
    //father
    sleep(10);//让子进程已经退了,父进程还在sleep,就能看见子进程的僵尸状态
    //pid_t rid = wait(NULL);//看见回收僵尸进程
    pid_t rid = waitpid(id,NULL,0);
    if(rid > 0)
    {
        printf("wait success,rid:%d\n",rid);
    }
    sleep(3);
    printf("father quit ...\n");
    return 0;
}

waipid也能体现出回收僵尸进程
在这里插入图片描述
是有可能等待失败的,故意填一个错误的id,失败的话会返回-1

pid_t rid = waitpid(id+1,NULL,0):

在当前的系统当中,只要id不填错,基本不会等待失败
创建子进程不就是未来让子进程帮我们完成任务吗!
完成的怎样我怎么知道?
就是第二个参数 status–> 输出型参数(需要定义一块空间,把空间的地址传进来,未来在操作系统等待的时候,可以把数据通过指针让用户看到)
在这里插入图片描述
典型的输出型参数就是

int a;
scanf("%d",&a);

status 表示的子进程退出信息

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

void ChildRun()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process,pid:%d,ppid:%d\n",getpid(),getppid());
        sleep(1);
        cnt--;
    }
}

int main()
{
    printf("I am father,pid:%d.ppid:%d\n",getpid(),getppid());

    pid_t id = fork();
    if(id == 0)
    {
        //child
        ChildRun();
        printf("child quit ...\n");
        exit(1);
    }
    //father
    sleep(7);
    int status = 0;
    pid_t rid = waitpid(id,&status,0);
    if(rid > 0)
    {
        printf("wait success,rid:%d\n",rid);
    }
    else
    {
        printf("wait failed!\n");
    }
    sleep(3);
    printf("father quit,status:%d\n",status);
    return 0;
}

发现退出信息status == 256
在这里插入图片描述
退出信息:退出码 && 退出信号
问题:整俩全局变量分别表示退出码和退出信号不好吗?
答:父进程看不到子进程的数据,如果子进程修改了全局变量就会发生写实拷贝。
一个数是如何拿到两个数字?
不能把他当成一个整数
status是int类型,有32个比特位,只考虑低16位
在这里插入图片描述
用位操作符就能看到子进程的退出信息:

printf("father quit,status:%d,child quit code:%d,child quit signal:%d\n",status,(status>>8)&0xFF,status & 0x7F);

运行没问题,256 == 2^8 --> 1 0000 0000
在这里插入图片描述
为了方便测试,把exit(1)改成exit(123)
在这里插入图片描述
杀掉子进程
在这里插入图片描述
如果不想使用位操作符,推荐两个宏,这两个宏的本质就是进行位操作

WIFEXITED(status)//W:wait IF:if EXITED:退出

若为正常终止子进程的状态,则真(查看进程是否正常退出,查signal位)

WEXITSTATUS(status):

若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
应用:WIFEXITED(status) WEXITATUS(status)

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

void ChildRun()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process,pid:%d,ppid:%d\n",getpid(),getppid());
        sleep(1);
        cnt--;
    }
}

int main()
{
    printf("I am father,pid:%d.ppid:%d\n",getpid(),getppid());

    pid_t id = fork();
    if(id == 0)
    {
        //child
        ChildRun();
        printf("child quit ...\n");
        exit(1);
    }
    //father
    sleep(7);
    int status = 0;
    pid_t rid = waitpid(id,&status,0);
    if(rid > 0)
    {
        if(WIFEXITED(status))
        {
            printf("child success,child exit code:%d\n",WEXITATUS(status));
        }
        else
        {
            printf("child quit unnormal!\n");
        }
        printf("wait success,rid:%d\n",rid);
    }
    else
    {
        printf("wait failed!\n");
    }
    sleep(3);
    return 0;
}

正常退出时:退出码为1
在这里插入图片描述
给子进程一个野指针,异常
在这里插入图片描述

阻塞等待 && 非阻塞等待

如果子进程没有退出
而父进程在进行执行waitpid等待,阻塞等待。
在这里插入图片描述
进程阻塞的时候,父进程其他事情什么都没干。
于是就有了非阻塞等待,也就是第三个参数optinos
讲个小故事理解非阻塞等待:
在这里插入图片描述

在这里插入图片描述

张三在等待李四这种过程当中,可以做其他的事情,就称为非阻塞等待。
张三同学每隔几分钟就给李四打电话,拨电话的过程就叫做函数调用。
说话的过程就叫做函数传参。
李四跟张三说我还没好,叫函数返回值。
每一次函数调用的本质就是再检测李四的状态。
在这里插入图片描述
李四那头没说好,张三就一直等待。叫做阻塞等待
打电话类似函数调用,不就绪就不返回。
waitpid检测状态的变化(也就是打电话)

pid_t waitpid(pid_t pid,int* status,int options);//options == 0 则为阻塞等待
                                                 //options == WNOHANG 则为非阻塞等待

WNOHANG 本质就是一个宏。(若云服务器卡住了,一般把这种情况叫做HANG住了)
阻塞等待:
pid_t > 0 : 等待成功了,子进程退出了,并且父进程回收成功
pid_t < 0 : 等待失败了
非阻塞等待:
pid_t == 0 : 检测时成功的,只不过子进程还没退出。需要下一次进行重复等待
非阻塞等待的时候 + 循环 = 非阻塞轮询(允许父进程做一些其他的事情(比如张三在下面打王者))
应用非阻塞轮询代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

void ChildRun()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);
        sleep(2);  //为了观察现象
        cnt--;
    }
}

int main()
{
    printf("I am father,pid:%d.ppid:%d\n",getpid(),getppid());

    pid_t id = fork();
    if(id == 0)
    {
        //child
        ChildRun();
        printf("child quit ...\n");
        exit(66);
    }
    //father
    while(1)
    {
        int status = 0;
        pid_t rid = waitpid(id,&status,WNOHANG);
        if(rid == 0)  //非阻塞轮询
        {
            sleep(1);  //为了观察现象
            printf("child is running,father check next time!\n");
            //DootherThing();  干一些其他事情
        }
        else if(rid > 0)  //运行成功
        {
            if(WIFEXITED(status))  //正常退出
            {
                printf("child quit sucess,child exit code : %d\n",WEXITSTATUS(status));
            }
            else  //错误退出
            {
                printf("child quit unnormal!\n");
            }
            break;  //成功了直接break
        }
        else  //等待失败
        {
            printf("waitpid failrd!\n");
            break;  //失败了直接break
        }
    }
    return 0;
}

在这里插入图片描述
这里举例父进程可以做的DootherThing:下载资源等
task.h

#pragma once

#include <stdio.h>

void PrintLog();
void Download();
void MysqlDataSync();

task.c

#include "task.h"

void PrintLog()
{
    printf("begin PrintLog...\n");
}

void Download()
{
    printf("begin Download...\n");
}

void MysqlDataSync()
{
    printf("begin MySQLDataSync...\n");
}

myprocess.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "task.h"

typedef void(*func_t)();

#define N 3  //三个任务
func_t tasks[N] = {NULL};

void LoadTask()  //要下载的任务
{
    tasks[0] = PrintLog;
    tasks[1] = Download;
    tasks[2] = MysqlDataSync;
}

void HandlerTask()  //处理任务
{
    int i = 0;
    for(i = 0;i < N; i++ )
    {
        tasks[i]();  //回调方式
    }
}

void DootherThing()
{
    HandlerTask();
}

void ChildRun()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);
        sleep(2);
        cnt--;
    }
}

int main()
{
    printf("I am father,pid:%d.ppid:%d\n",getpid(),getppid());

    pid_t id = fork();
    if(id == 0)
    {
        //child
        ChildRun();
        printf("child quit ...\n");
        exit(66);
    }
    LoadTask();
    //father
    while(1)
    {
        int status = 0;
        pid_t rid = waitpid(id,&status,WNOHANG);
        if(rid == 0)  //非阻塞轮询
        {
            sleep(1);
            printf("child is running,father check next time!\n");
            DootherThing();
        }
        else if(rid > 0)  //运行成功
        {
            if(WIFEXITED(status))  //正常退出
            {
                printf("child quit sucess,child exit code : %d\n",WEXITSTATUS(status));
            }
            else  //错误退出
            {
                printf("child quit unnormal!\n");
            }
            break;  //成功了直接break
        }
        else  //等待失败
        {
            printf("waitpid failrd!\n");
            break;  //失败了直接break
        }
    }
    return 0;
}

在这里插入图片描述

三.进程的程序替换

关于程序替换的函数一共有7个
在这里插入图片描述
在这里插入图片描述

先看代码 && 现象

execl

在这里插入图片描述
先看现象:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("testtexec ... begin!\n");
    execl("/usr/bin/ls","ls","-l","-a",NULL);  //可变参数不用,写NULL
    printf("testtexec ... end!\n");
    return 0;
}

发现输出的是ls命令,ls本身就是C语言写的,用exec*函数就是执行起来新的程序(进程)
在这里插入图片描述

原理

进程 = 内核数据结构 + 代码和数据
用ls代码和数据覆盖testexec的物理内存中的数据和代码
在这里插入图片描述
进程替换的本质:用新进程的代码和数据覆盖老进程的代码和数据。
问题:在替换的时候有没有创建新的进程?
答:没有,执行新程序是拿老程序的壳子
在这里插入图片描述
回到代码:为什么前面的printf输出了,后面的printf没有输出?
在这里插入图片描述
在这里插入图片描述
execl函数的返回值可以不关心了
只要替换成功,就不会向后继续运行
只要继续运行了,一定就是替换失败了
让其失败一次:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("testtexec ... begin!\n");
    execl("/usr/bin/lslslslss","ls","-l","-a",NULL);
    printf("testtexec ... end!\n");
    return 0;
}

在这里插入图片描述

多进程替换

想要进程替换,还不想影响程序本身,可以fork创建子进程,让子进程去替换,父进程wait等待即可
先看现象:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	printf("testtexec ... begin!\n");

    pid_t id = fork();
    if(id == 0)
    {
    	sleep(2);
        //child
        execl("/usr/bin/ls","ls","-l","-a",NULL);
        exit(1);  //如果替换失败,则退出
    }

    //  father
    int status = 0;
    pid_t rid = waitpid(id ,&status,0);
    if(rid > 0)
    {
        printf("father wait success,child exit coed:%d\n",WEXITSTATUS(status));
    }
    printf("testtexec ... end!\n");
    return 0;
}

子进程执行ls,父进程等待成功,退出码为0
在这里插入图片描述
创建子进程,让子进程完成任务:
1.让子进程执行父进程代码的一部分
2.让子进程执行一个全新的程序
平常创建子进程进行修改的时候,只有数据被更改
替换是连着数据和代码一起更改,发生写实拷贝
在这里插入图片描述

使用所有的替换方法,并认识函数参数的含义

在这里插入图片描述

execv

把命令一起打包传参
在这里插入图片描述
代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    printf("testtexec ... begin!\n");

    pid_t id = fork();
    if(id == 0)
    {
        sleep(2);
        char* const argv[] = 
        {
            (char*)"ls",  //编译器检查严格,强转类型避免Warning
            (char*)"-l",
            (char*)"-a",
            (char*)"--color",
            NULL
        };
        //child
        //execl("/usr/bin/ls","ls","-l","-a",NULL);
        execv("/usr/bin/ls",argv);
        exit(1);  //如果替换失败,则退出
    }

    //  father
    int status = 0;
    pid_t rid = waitpid(id ,&status,0);
    if(rid > 0)
    {
        printf("father wait success,child exit coed:%d\n",WEXITSTATUS(status));
    }
    printf("testtexec ... end!\n");
    return 0;
}

在这里插入图片描述

execvp

p表示:用户可以不传要执行的文件路径(但要传文件名),直接告诉exec*,我要执行谁即可
系统会自动在环境变量PATH中进行查找
在这里插入图片描述

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    printf("testtexec ... begin!\n");

    pid_t id = fork();
    if(id == 0)
    {
        sleep(2);
        char* const argv[] = 
        {
            (char*)"ls",
            (char*)"-l",
            (char*)"-a",
            (char*)"--color",
            NULL
        };
        //child
        //execl("/usr/bin/ls","ls","-l","-a",NULL);
        //execv("/usr/bin/ls",argv);
        execvp("ls",argv);
        exit(1);  //如果替换失败,则退出
    }

    //  father
    int status = 0;
    pid_t rid = waitpid(id ,&status,0);
    if(rid > 0)
    {
        printf("father wait success,child exit coed:%d\n",WEXITSTATUS(status));
    }
    printf("testtexec ... end!\n");
    return 0;
}

execlp

在这里插入图片描述

execlp("ls","ls","-l","-a",NULL);
execlp("top","top",NULL);

用自己的程序替换

上面的程序替换,我们替换的都是系统命令,可不可以替换我们自己写的程序?
先用C语言替换C++
mypragma.cc

#include <iostream>

using namespace std;

int main()
{
    cout << "hello C++,I am a C++ pragma!" << endl;
    cout << "hello C++,I am a C++ pragma!" << endl;
    cout << "hello C++,I am a C++ pragma!" << endl;
    return 0;
}

编译后形成的可执行程序为mypragma
在这里插入图片描述
用testexec替换myprogma程序

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    printf("testtexec ... begin!\n");

    pid_t id = fork();
    if(id == 0)
    {
    	sleep(2);
        execl("./mypragma","mypragma",NULL);  //已经找到,所以可以直接写mypragma(./mypragma也行)
        exit(1);  //如果替换失败,则退出
    }

    //  father
    int status = 0;
    pid_t rid = waitpid(id ,&status,0);
    if(rid > 0)
    {
        printf("father wait success,child exit coed:%d\n",WEXITSTATUS(status));
    }
    printf("testtexec ... end!\n");
    return 0;
}

在这里插入图片描述
并且改一下代码,验证进程并没有被替换
mypragma.cc

#include <iostream>
#include <unistd.h>

using namespace std;

int main()
{
    cout << "hello C++,I am a C++ pragma!" << getpid() << endl;
    cout << "hello C++,I am a C++ pragma!" << getpid() << endl;
    cout << "hello C++,I am a C++ pragma!" << getpid() << endl;
    return 0;
}

testexec.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    printf("testtexec ... begin!\n");

    pid_t id = fork();
    if(id == 0)
    {
    	printf("child pid:%d\n",getpid());
        execl("./mypragma","mypragma",NULL);  //已经找到,所以可以直接写mypragma(./mypragma也行)
        exit(1);  //如果替换失败,则退出
    }

    //  father
    int status = 0;
    pid_t rid = waitpid(id ,&status,0);
    if(rid > 0)
    {
        printf("father wait success,child exit coed:%d\n",WEXITSTATUS(status));
    }
    printf("testtexec ... end!\n");
    return 0;
}

发现进程的pid一样
在这里插入图片描述
不仅仅是C语言替换C++。Python,JAVA,Shell脚本都可以
任何脚本文件都有解释器,解释器都是由C/C++写的,解释器相当于可执行程序。在Linux下跑都会变成进程,只要是进程就可以被替换
比如Shell与Python:

execl("/usr/bin/bash","bash","test.sh",NULL);
execl("/usr/bin/python3","python3","test.py",NULL);

execvpe

e:environment:环境变量;envp不传参时是NULL
在这里插入图片描述
argv 是不是很像main函数的参数 ; envp 是环境变量表
mypragma.cc

#include <iostream>
#include <unistd.h>

using namespace std;

int main(int argc,char* argv[],char* env[])
{
    int i = 0;
    for(;argv[i];i++)  //打印命令行参数
    {
        printf("argv[%d] : %s\n",i,argv[i]);
    }

    printf("-------------------------------------\n");
    for(int i = 0;env[i];i++)  //打印环境变量
    {
        printf("env[%d] : %s\n",i,env[i]);
    }
    printf("-------------------------------------\n");

    cout << "hello C++,I am a C++ pragma!" << getpid() << endl;
    cout << "hello C++,I am a C++ pragma!" << getpid() << endl;
    cout << "hello C++,I am a C++ pragma!" << getpid() << endl;
    return 0;
}

testexec.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    printf("testtexec ... begin!\n");

    pid_t id = fork();
    if(id == 0)
    {
        char* const argv[] = 
        {
            (char*)"mypragma",
            NULL
        };
        char* const envp[] = 
        {
            (char*)"HAHA=111111",
            (char*)"hehe=222222",
            NULL
        };
        printf("child pid:%d\n",getpid());
        execvpe("./mypragma",argv,envp);
        exit(1);  //如果替换失败,则退出
    }

    //  father
    int status = 0;
    pid_t rid = waitpid(id ,&status,0);
    if(rid > 0)
    {
        printf("father wait success,child exit coed:%d\n",WEXITSTATUS(status));
    }
    printf("testtexec ... end!\n");
    return 0;
}

环境变量也传给子进程mypragma.cc了
在这里插入图片描述
当然也可以直接传选项:

char* const argv[] = 
        {
            (char*)"mypragma",
            (char*)"-a",
            (char*)"-b",
            NULL
        };

选项也就有了
在这里插入图片描述

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

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

相关文章

Windows电脑还在纠结用什么便签软件?

在快节奏的生活中&#xff0c;我们常常需要记录下一些重要的事情或者临时的想法&#xff0c;而便签软件就是我们的好帮手。但是&#xff0c;面对市面上众多的便签软件&#xff0c;Windows电脑用户可能会感到困惑&#xff0c;不知道该如何选择。在这里&#xff0c;小编为你提供一…

Great Wall长城工作站安装银河麒麟V10(SP1)-ARM版桌面操作系统

长城工作站安装银河麒麟V10(SP1)桌面操作系统 1. 硬件信息 [1]. Great Wall 长城台式微型计算机 产品型号&#xff1a;世恒TD120A2 型号代码&#xff1a;世恒TD120A2-019 电源&#xff1a;220V~3A 50Hz [2]. 芯片型号 架构&#xff1a; aarch64 CPU 运行模式&#xff1a…

【Qt窗口】—— 浮动窗口

目录 1.1 浮动窗口的创建 1.2 设置停靠的位置 1.3 示例小结 在Qt中&#xff0c;浮动窗口也称之为铆接部件&#xff0c;俗称为子窗口&#xff0c;浮动窗口是通过QDockWidget类来实现浮动的功能。浮动窗口⼀般是位于核⼼部件的周围&#xff0c;可以有多个。 1.1 浮动窗口的…

LeetCode 热题100-39 对称二叉树

对称二叉树 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false提示&#xff1a; 树中…

【python报错已解决】AttributeError: module ‘PIL.Image‘ has no attribute ‘ANTIALIAS‘

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引言 当我们使用某些Python库&#xff0c;如Pillow&#xff08;PIL的一个分支&#xff09;&#xff0c;进行图像处理时&#x…

设备共享租赁小程序系统开发制作方案

设备共享租赁小程序系统让用户方便地租赁或出租各类设备&#xff0c;包括但不限于工具、电子产品、运动器材等&#xff0c;以满足临时使用需求&#xff0c;同时为设备所有者创造额外收益。 目标用户 个人用户&#xff1a;需要临时使用工具、车辆等设备的个人。 企业用户&#…

STM32(F103ZET6)第十九课:FreeRtos的移植和使用

目录 需求一、FreeRtos简介二、移植FreeRtos1.复制代码2.内存空间分配和内核相关接口3.FreeRtosConfig4.添加到工程中三、任务块操作1.任务四种状态2.创建任务过程 需求 1.将FreeRtos&#xff08;嵌入式实时操作系统&#xff09;移植到STM32中。 2.在该系统中实现任务的创建、…

git学习教程--分支操作+远程仓库相关过程详述

目录 1.分支 1.1查看已有分支 1.2新的分支的创建 1.3改变指针的指向 1.4合并分支 2.删除分支 3.合并冲突 3.1一个简单操作 3.2手动解决冲突 4.git分支管理策略 4.1fast-forward模式 4.2no-ff模式 4.3总结 5.bug修复建议 6.强制删除 7.分布式版本控制系统 7.1远…

【递归回溯之floodfill算法专题练习】

1. 图像渲染 class Solution {int dx[4] {0, 0, -1, 1};int dy[4] {1, -1, 0, 0};int m, n;int oldcolor; public:vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {oldcolor image[sr][sc]; // 保存原…

Java常用API(BigDecimal)

用于小数的精确计算 用来表示很大的小数 构造方法获取BigDecimal对象 public BigDecimal(double val) public BigDecimal(string val) 静态方法获取BigDecimal对象 public static BigDecimal value0f(double val) 1.通过传递double类型的小数来创建对象 这种方式有可能…

Linux|软件开发的基础概念|软件的源码本地编译和交叉编译概念

前言&#xff1a; 本文主要讲述软件的源码本地编译和交叉编译的基本概念&#xff0c;首先&#xff0c;是介绍什么是本地编译&#xff0c;什么是交叉编译&#xff0c;其次&#xff0c;本地编译和交叉编译到底是有什么用处&#xff0c;最后是交叉编译和本地编译的具体应用场景 …

边听边打?不再是难题,4款音频转文字神器推荐

无论是会议记录、课堂笔记还是采访录音&#xff0c;能快速准确地转录成文本&#xff0c;那可是大大提高了工作效率。市面上有几款工具在这方面做得不错&#xff0c;比如365在线转文字、布谷鸟配音、腾讯云语音识别和Speechnotes。今天就来个大比拼&#xff0c;看看它们各自的表…

人机交互的频率、时长、周期

人机交互的频率是指用户与系统互动的频繁程度&#xff1b;时长是每次互动的持续时间&#xff1b;周期是指在特定时间段内进行互动的规律或间隔。人机交互的频率、时长和周期通常与以下因素有关&#xff1a; &#xff08;1&#xff09;任务复杂性&#xff1a;复杂任务需要更多的…

docker部署clickhouse

1. 创建相关配置目录 mkdir -P /data/clickhouse/data mkdir -P /data/clickhouse/conf mkdir -P /data/clickhouse/log 2. 拉取镜像 # 下载最新版本clickhouse docker pull clickhouse/clickhouse-server # 下载指定版本clickhouse docker pull clickhouse/clickhouse…

电商数据分析:如何抓住关键指标提高销售额

在电商运营中&#xff0c;数据分析是不可或缺的一环。通过精准的数据分析&#xff0c;商家可以更好地了解市场动态、优化运营策略&#xff0c;从而提升销售业绩。然而&#xff0c;很多运营者在面对海量数据时常常无从下手。那么&#xff0c;电商运营到底该如何进行数据分析&…

Chapter 05 计算属性

欢迎大家订阅【Vue2Vue3】入门到实践 专栏&#xff0c;开启你的 Vue 学习之旅&#xff01; 文章目录 前言一、基础语法二、计算属性vs方法三、完整写法 前言 Vue.js 提供了丰富的功能&#xff0c;帮助开发者高效地构建用户界面。本篇文章详细讲解了其计算属性的基本语法、应用…

笔记整理—uboot启动过程(7)malloc初始化与内存环境变量

上一章说到了env环境变量并对前两章有关init_sequence部分做了总结&#xff0c;这一章将要对uboot部分的malloc初始化以及内存环境变量进行相关的说明。 mem_malloc_init是用于初始化uboot堆管理器的。自己维护了一段内存&#xff0c;就可用进行malloc和free的操作了。那么这个…

Mac/Linux系统matplotlib中文支持问题

背景 matplotlib是python中最常用的数据可视化分析工具&#xff0c;Mac和Linux系统无中文字体&#xff0c;不支持中文显示&#xff08;希望后续可以改进&#xff09;&#xff0c;需要进行字体的下载和设置才能解决。笔者经过实践&#xff0c;发现Mac系统和Linux系统解决方案略…

数据结构算法基础-单链表的新建(头插法、尾插法)

1.头插法 2.尾插法 3.代码及运行结果 设输入的值为&#xff1a;3 4 5 6 7&#xff08;到9999终止读值&#xff09; #include <stdio.h> #include <stdlib.h> typedef int ElemType;typedef struct LNode{ElemType data;struct LNode *next; }LNode,*LinkList;LinkL…

02 vue3之ref全局桶

ref 接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property&#xff0c;指向该内部值。 <template><div class"">Ref:{{ name.a }}</div><button click"change()">change</button> </te…