首页 > 系统相关 >linux进程控制

linux进程控制

时间:2024-11-10 16:44:51浏览次数:3  
标签:控制 int 退出 exit linux 进程 include 我们

目录

一、进程退出

1.创建一批进程

2.进程退出场景

二、进程等待

1.等待一个进程

2.等待一批进程

3.wait等待的进程一个都不退

4.waitpid

5.非阻塞轮询

​编辑

6.waitpid的原理

三、进程替换 

1.单进程的程序替换

2. 多进程的程序替换

3.execlp

​编辑

4.execle

5.execv

6.execvp

7.execvpe       

8.execve

9.程序替换原理


一、进程退出

1.创建一批进程

在谈进程退出之前,我们先来谈谈如何创建多个进程。

我们可以直接用循环创建一批进程。

 我们可以看到所有子进程的父进程都是同一个,并且过了一会由于父进程没有回收导致子进程全部变僵尸了,在下面的进程等待的时候会解决进程僵尸的问题。

exit可以直接让进程退出,参数是一个整数,这个参数表示退出时候的返回值。

2.进程退出场景

我们先来看进程退出都有哪些场景。

第一种情况:代码运行完毕,结果正确

如果代码运行完毕结果正确,你会关心他为啥正确吗?现实生活中你考试考了第一名,你的父亲会问你,你咋考这么好?是不会问的。结果正确还不好吗?根本不用关心。

第二种情况:代码运行完毕,结果不正确

如果代码运行完毕了,结果不正确,你要不要关心他为啥不正确?如果你考试考了倒数第一,你的父亲会关心你为啥考的这么不好吗?会问你原因吗?大概率是会问的,那么在进程中,谁会关心进程的运行情况呢?一般而言是父进程。

那父进程怎么知道子进程运行结果到底是对还是不对?可以让子进程 return 不同的数字表示不同的退出原因,这个数字就叫做退出码,如果返回值是0,表示进程正确执行完毕。如果是其他表示代码执行不正确。

所以,我们为啥写main函数的时候返回值都是0呢,本质上表示:进程运行完成时结果是否正确,如果不是,可以用不同的数字表示不同的出错原因!

linux中我们可以用 echo $? 来查看最近一次进程退出时候的退出码。我们写的代码,默认父进程都是bash,也就是xshell这个进程。?是里一个环境变量,echo $?就是把这个环境变量的值打印出来给我们看。

但是这些退出码是啥意思我咋知道呢? 我们可以用语言提供给我们函数strerror查看对应数字的错误码描述。

这里其实是有134个退出信息的,但太长了就不截了,大家可以自己打印出来看看,直接写一个循环,然后调用strerror就可以。这些都是系统默认给我们提供的错误码。

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

int main()
{
   
    //show();
    for(int i = 0; i < 200; i++)
    {
        printf("%d : %s\n", i, strerror(i));
    }


    return 0;
}

当我们用ls查找一个不存在的文件,会给我们显出错信息,这个出错信息不觉得很熟悉吗?不就是上面2号错误码描述吗?ls命令运行之后也是一个进程,也有返回值,当他查一个不存在的文件时,就会返回对应的错误码,再给我们转成错误信息,就让我们看到了下面的 NO such......我们直接 echo $?查看上一个进程的退出码发现,果然是2。

这就是退出码的作用,可以让我们知道代码执行结果是否正确。

我们可不可以不用系统提供的错误码,我们自己定义行不,当然可以,我们直接可以用指针数组就可以定制化我们的错误码。

上面我们了解了父进程是如何知道子进程是否出错的,是通过错误码,如果错误码为0表示正常退出,如果非0,就表示出错了,可以用strerror查看错误码的信息,strerror一般是和配合errno使用的,这样我们不仅可以知道错误码,也可以知道错误信息。

C语言提供的errno,它里面保存的是最近一次执行的错误码,我们知道C语言是给我们提供了很多库函数的,这些库函数是不一定调用成功的,如果调用失败,错误信息就会写到errno里面,如果有很多个函数调用出错,会覆盖式的写入到errno里面,errno只会保存最后一个函数调用出错的信息!

下面我们申请一个4G的内存空间。显然是不被允许的,此时可以使用errno获取malloc函数失败的错误码,然后用strerror获取错误信息。

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


int main()
{
    int ret = 0;
    int* p = (int*)malloc(1000 * 1000 * 1000 * 4);
    if(p == NULL)
    {
        printf("%s\n",strerror(errno));
        ret = errno;
    }
    
   
    return ret;
}

 第三种情况:代码异常终止

如果代码异常了,退出码还有意义吗?

如果代码异常了,代码本身可能就没跑完,如果代码都没跑完,程序提前退了,根本没执行return最后一条语句,所以退出码没有意义。

那有没有可能最后执行return了,然后可能操作系统资源不足了把我Kill了,有可能吗?有可能。

但关键问题在于,你怎么知道return了呢?姑且认为异常的时候,你的确return了,但你敢用这个退出码吗?异常可能在任何地方发生!

还是你去考试了,考试期间作弊被抓了,这就是你考试没考完出现异常了,然后你爸问你考的怎么样,你说我考了90,你爸问你怎么考的90,你非说我中间作弊了,但是不影响,我是在考试结束1分钟的时候作弊的,此时已经没有意义了,你爸不会相信你了,作弊还分什么时候作弊,我怎么知道你是一开始考试就作弊,还是结束时作弊,我不确定,反正作弊事实你承认了,我就不相信你了。

同理,当你退出的时候出异常,退出码无意义,我们不关心退出码

你考试完毕了,要发奖学金了,是先看你考了多少分,还是看你有没有作弊?毫无疑问,先看你有没有作弊,凡是作弊的一律淘汰。

所以当一个进程退出的时候先看退出码还是先看是否出现异常,当然先看是否出现异常!没出异常在看退出码。

但是你要不要关心我为什么异常?以及我发生了什么异常?当然要了,那我们怎么关心?我咋知道进程有没有出异常?

进程出现异常,本质是收到了信号!

 比如说除0错误,寄存器就会发生溢出错误。或者野指针,野指针是页表没有建立映射或者对应的映射是只读的权限。其实所有的异常本质都是硬件错误,后面信号会谈。

我们可以用kill -l查看所有的信号。

浮点数错误是下列哪一个信号呢?是8号信号FPE,野指针是段错误,是11号信号SEGV。当出现异常,系统会以信号的方式发送给我们进程。

但是我们怎么证明呢?我们直接代码验证。

代码中浮点数错误

代码中野指针错误

代码没问题,我们给该进程发送8号信号

 代码没问题,我们给该进程发送11号信号

因此对于进程退出,我们先看有没有收到信号, 如果没有收到信号,在看错误码,如果错误码不是0,就看错误信息,这就是进程退出的逻辑。


exit和_exit

eixt

exit作用就是引起一个进程进行终止,这个地方的参数是什么意思,是当前进程的退出码。

return 也可以达到一样的效果,在main函数里return和exit作用的等价的。

 下面main函数里面return0,调用show,里面exit退出码是12。最后打印进程退出码是12.

return和exit的区别在于:exit在任何地方被调用,都表示进程退出。return只表示当前函数返回。

 _exit

_exit和exit有何区别呢? 

它们2个只有一个区别,exit会刷新缓冲区的内容,_exit不会刷新缓冲区的内容

exit:

_exit: 

 它们两都可以打出hellow world, 并且退出码也都是0。

但是当我们把hello world的\n去掉就不一样了。

exit:将hello world成功打印了出来,这里我们把\n去掉了,就不会刷新缓冲区,但我们调用exit,它会帮我们把缓冲区的数据刷出来。

_exit:并没有把 hello world 打印出来,表明_exit并不会刷新缓冲区,因此hello world还在缓冲区中,所有就没有显示。_exit它是纯粹的系统调用,而exit是封装了_exit。这个缓冲区是在用户区的,exit封装了_exit的同时,并还会把数据从缓冲区刷出来,这就是它们的区别。

 exit和return的区别

(1)在main函数中调用return和exit都表示进程退出。

(2)在其它函数调用return,表示该函数返回。在其它函数调用exit,表示进程结束。

二、进程等待

前面有一个问题一直没有解决,那就是僵尸进程。如果子进程退出,父进程放任不管,会造成内存泄漏,因此父进程需要对退出的子进程做回收,那么如何回收呢?需要用进程等待,就是调系统接口。

1.进程等待是什么?

通过系统调用wait/waitpid,来进行对子进程进行状态检测与回收的功能。

2.为什么要进程等待?

僵尸进程无法被杀死,需要通过进程等待来杀死它,进而解决内存泄漏问题---必须解决的。我们需要通过进程等待,获得子进程的退出情况---知道我布置给子进程的任务,它完成的怎么样了---要么关心,也可能不关心---可选的 。

3.要怎么做呢?

父进程通过调用wait/waitpid进行僵尸进程的回收。

我们先来看wait这个接口。 

wait:等待任意一个子进程

参数:它就一输出型参数,我们给它传递一个整形的指针变量,wait会把子进程的退出信息保存在这个指针变量里,从而让我们外部获取到,如果我们不关心子进程的退出信息,直接传递NULL。

返回值:如果返回值 < 0,表示等待失败, 成功返回子进程pid。

下面这段代码中,让子进程运行5秒后然后退出,退出后变为僵尸,父进程运行10秒,10秒结束后调用wait等待子进程,我们可以看到子进程会从僵尸状态变为直接退出。

1.等待一个进程

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

int main()
{
   
    pid_t id = fork();
    if(id == 0)
    {
        // 子进程
        int cnt = 5;
        while(cnt--)
        {
            printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
            sleep(1);
        }
        exit(0); // 执行5次后退出
    }
    else
    {
         
    // 父进程
        int cnt = 10;
        while(cnt--)
        {
            printf("I am father, pid: %d, ppid: %d\n", getpid(), getppid());
            sleep(1);
        }

        pid_t ret = wait(NULL);
        if(ret == id)
        {
            printf("process wait success !\n");
        }

    }

    sleep(5);
    return 0;
}

2.等待一批进程

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


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

const int N = 5;

int main()
{
   
    for(int i = 0; i < N; i++)
    {
        pid_t id = fork();
        if(id == 0)
        {
            RunChild();
            exit(0); // 子进程直接退出,不会执行后续代码。
        }
        printf("create child process: %d success\n", id); // 这句话只有父进程会打印
   
    }
    sleep(10); //等待
    for(int i = 0; i < N; i++)
    {
    
        pid_t ret = wait(NULL);
        if(ret > 0)
        {
            printf(" wait %d success !\n", ret);
        }
    }
  


    return 0;
}

3.wait等待的进程一个都不退

wait当任意一个子进程退出的时候,wait会回收子进程,但如果子进程都不退呢?

如果子进程不退出,父进程就死等,等你退,这种你不退我就一直死等你退叫做阻塞等待,所以一个进程等待是否只会等待硬件资源呢?软件资源也需要等待的。

因此,一个完整的进程代码,应该有fork创建,也要有wait退出。但接下来的问题是,创建一个子进程要获取子进程的退出情况呀,我创建子进程我要让他帮我办事的,事办的怎么样?我怎么知道啊。就要用到等待的参数了,我们直接看下一个等待的系统调用,下一个系统调用搞明白了wait这个系统调用就是轻轻松松。

下面我们来看看waitpid这个系统调用。

4.waitpid

wait和waitpid这两个调用是啥关系呢?wait是waitpid的子集,wait可以做的事情waitpid都可以做。

 waitpid的第一个参数:第一个参数是要等待哪一个进程,如果传-1,表示等待任意进程。

 waitpid的第二个参数:和wait的参数是一样的。如果不关心退出结果直接填NULL。

 waitpid的第三个参数:表示等待的方式,可以传递0,传递0就表示默认阻塞等待。

我们要通过第二个参数来获取子进程的退出信息,这个status,是被当做好几部分使用的,前7个比特位表示是否收到信号,可以表示退出是否出现异常!8-15的比特位表示退出码。我们使用位运算,将这两部分提取出来,如何提取呢?退出信号:status&0x7F退出码:(status>>8)&0xFF  

这两部分相互组合就可以将进程所有的退出情况表示出来,如果前7个比特位结果为0,就表示没出异常。但结果对还是不对呢,看后面的8个比特位即可。如果前7个比特位结果不为0,表示异常了,退出码都不用看了。

下面我们来使用一下。

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

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


int main()
{
   
 
    pid_t id = fork();
    if(id == 0)
    {
        RunChild(); // 子进程运行5秒后直接退出
        exit(11); // 子进程直接退出,不会执行后续代码。
    }

 
    
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if(ret > 0)
        {
            printf(" wait %d success ! exitSignal: %d, exitCode: %d\n", ret, status & 0x7F, (status >> 8) & 0xFF);
        }
        
 

    return 0;
}

退出信号是0,表示没有出异常,退出码果然是我们写的11。

接下来我们直接在子进程代码中搞一个 / 0错误。可以看到,退出信号就是8,是除零错误的信号。

下面搞一个野指针,可以看到退出信号是11号信号。

 我们可以通过status获取子进程的退出结果。但是,每次获取都要按位与,不容易记忆还很难理解,有没有啥更简单的方法呢?让我们获取退出码和信号呢?

WIFEXITED(status)这个宏,W就是等待的意思,IF就是如果,EXIT就是退出,ED可以理解为结束的意思。如果进程没有异常,返回的结果就是真。

但是这个宏的一个不好的地方就是只能表示有没有出异常,如果出异常了结果就是假,但我们无法知道异常信号是啥,如果要关心异常的时候信号是什么还是得用上面的位运算获取。

好处就是方便记忆,而且写出来的代码逻辑性更强。

WEXITSTATUS(status)这个宏,可以帮我们提取退出码。

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

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


int main()
{
   
 
    pid_t id = fork();
    if(id == 0)
    {
        RunChild(); // 子进程运行5秒后直接退出
        exit(11); // 子进程直接退出,不会执行后续代码。
    }


    int status = 0;
    pid_t ret = waitpid(id, &status, 0);
    if(ret == id) // 等待成功
    {
        if(WIFEXITED(status))
        {
            // 表示进程没有出异常
            printf("进程是正常跑完的,退出码:%d\n", WEXITSTATUS(status));
        }
        else // 子进程出异常
        {
            printf("进程出异常了\n");
        }
    }
    else // 等待失败
    {
        printf("wait fail !\n");
    }
   

    return 0;
}

此时,我们就可以写出完整的进程代码了。 

5.非阻塞轮询

使用waitpid的时候,默认是阻塞方式等待,子进程不退出,父进程就一直等待。但是对于父进程来说,子进程不退出,父进程就一直等待,父进程等待的时候啥都没做,如果父进程在等待的时间做一些事情是不是会更好呢?然后做完之后再去看子进程是否退出,如果没退出继续做自己的事情。

所以非阻塞轮询本质就是,当子进程状态不就绪(不是退出的状态),我就去做自己的事情,而不是傻等,然后做完再去看子进程的状态是否是退出状态,然后重复这个过程,直到子进程状态就绪。

那怎么设置非阻塞轮询呢?

waitpid的第三个参数,options就是用来设置阻塞方式的,如果是0,就是阻塞式等待,如果我们要

想设置成非阻塞轮询,可以传WNOHANG,W就是等待,NO就是不,HANG就是夯住了的意思。整体就是等待的时候不要夯住,就是非阻塞。

那接下来waitpid的返回值是不是就需要加一个呢? > 0表示等待成功, < 0等待失败,那非阻塞是失败吗?不是,当返回值==0的时候,就表示子进程还没有退出,父进程在等待。既然是轮询那我们就需要写一个死循环,如果等待成功或者等待失败就跳出循环。

void RunChild()
{

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

int main()
{

    pid_t id = fork();
    if (id == 0)
    {
        RunChild(); // 子进程运行5秒后直接退出
        exit(11);   // 子进程直接退出,不会执行后续代码。
    }

    int status = 0;

    while (1) // 轮询
    {
        pid_t ret = waitpid(id, &status, WNOHANG); // 非阻塞

        if (ret > 0)
        {
            if (WIFEXITED(status))
            {
                // 表示进程没有出异常
                printf("进程是正常跑完的,退出码:%d\n", WEXITSTATUS(status));
            }
            else
            {
                printf("进程出异常了\n");
            }
            // 表明出结果了,直接退出
            break;
        }
        else if (ret == 0)
        {

            printf("你好了没?子进程还没退出,我在等等...\n");
            sleep(1);
        }
        else
        {
            printf("wait fail !\n");
            // 表明出结果了,直接退出
            break;
        }
    }

    return 0;
}

但是让父进程光打印有啥意思呢?下面加点东西。

我们可以定义一个返回值为void,无参的函数指针,在一个函数指针数组(任务数组),然后写一些初始化、添加任务、执行任务的方法,让我们的父进程在等待的时候去执行。

#define TASK_NUM 5

void Task1()
{
    printf("这是一个打印日志的任务..., pid: %d\n", getpid());
}

void Task2()
{
    printf("查看野区资源是否刷新的任务..., pid: %d\n", getpid());
}

void Task3()
{
    printf("查看基地是否被摧毁的任务..., pid: %d\n", getpid());
}

typedef void (*task_t)(); // 定义一个函数指针

task_t tasks[TASK_NUM];  // 函数指针数组

int addTask(task_t t)  // 给函数指针数组里面添加任务
{
    int pos = 0;
    for(; pos < TASK_NUM; pos++)
    {
        if(!tasks[pos]) break;
    }
    if(pos == TASK_NUM) return -1;
    tasks[pos] = t;
    return 0; //添加成功
}
void initTask()  // 初始化任务数组
{
    for(int i = 0; i < TASK_NUM; i++)
    {
        tasks[i] = NULL;
    }
    addTask(Task1);
    addTask(Task2);
    addTask(Task3);

}

void executeTast()
{
    for(int i = 0; i < TASK_NUM; i++)
    {
        if(!tasks[i])
            continue;
        tasks[i]();
        
    }
}

void RunChild()
{

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

int main()
{

    pid_t id = fork();
    if (id == 0)
    {
        RunChild(); // 子进程运行5秒后直接退出
        exit(11);   // 子进程直接退出,不会执行后续代码。
    }

    int status = 0;

    initTask(); //初始化任务

    while (1) // 轮询
    {
        pid_t ret = waitpid(id, &status, WNOHANG); // 非阻塞

        if (ret > 0)
        {
            if (WIFEXITED(status))
            {
                // 表示进程没有出异常
                printf("进程是正常跑完的,退出码:%d\n", WEXITSTATUS(status));
            }
            else
            {
                printf("进程出异常了\n");
            }
            // 表明出结果了,直接退出W
            break;
        }
        else if (ret == 0)
        {

            // printf("你好了没?子进程还没退出,我在等等...\n");
            executeTast();
            sleep(1);
        }
        else
        {
            printf("wait fail !\n");
            // 表明出结果了,直接退出
            break;
        }
    }

    return 0;
}

我们可以看到,父进程在等待的时候去完成了任务,这里父进程的主要工作还是等待子进程,等待的时候去做其他工作只是顺便的,因此父进程去完成的工作最好是轻量化的,必要太重,不然可能子进程已经退出了,但父进程手里的任务还没有执行完,因此子进程等了很久才被回收,不过影响也不是很大,总是要回收的,但是尽量还是让父进程执行的工作别太重。

6.waitpid的原理

当CPU调度父进程的代码的时候,waitpid这个函数是在父进程的代码和数据里的,因此这个代码要被CPU执行,子进程也有自己的PCB和代码数据,当子进程退出的时候,代码和数据可以被直接释放,但是子进程的PCB先不能被释放,子进程退出的时候退出信息会保存在自己的PCB里面,然后父进程是通过waitpid读取子进程的退出信息的,waitpid先看子进程是否是z状态,如果是z状态直接读取退出信号和退出信息,这样就可以让我们上层拿到。

为啥不能让我们直接拿到子进程的退出信息呢?还要通过系统调用,因为父进程的代码是我们写的,父进程直接去拿子进程的退出信息,不就相当于我们用户去拿吗?操作系统允许你去拿我管理的信息吗?操作系统是不允许的,就好比你是一个编程能力很强的人,然后隔壁学校的校长想借调一下你帮我们学校参加一个比赛,然后隔壁校长不经过你们校长的同意直接跑到你宿舍就把你借走了,这行吗?不行,我们必须通过管理者拿到被管理者的数据,不能通过用户访问底层数据,因为操作系统不相信任何人。

三、进程替换 

由于子进程是把父进程的PCB、地址空间、页表拷贝了一份,因此子进程和父进程执行的代码基本是大同小异的,那么如果我们想让子进程执行和父进程完全不同的代码,此时就可以使用程序替换。

1.单进程的程序替换

在我们的代码中,有两个printf,也就是会在显示器中打印两句话,可是我们可以看到,运行的结果中,只打印了execl之前的那一句,而且后面居然把 ls -a -l这条命令的运行结果也打印了出来,这说明下面的printf完全被替换走了。

程序替换就是把别的地方的程序代码,替换到我们的代码当中。

(1) execl的使用

第一个参数:是可执行程序的路径。我们要执行一个程序,首先第一步要先找到它在哪里放着吧?

第二个参数:是可变参数列表,我们可以给它传递任意多个字符串,最后以NULL作为结尾。我们找到它在哪里放着,那我们如何执行它呢?第二个参数就决定了如何执行该程序。

上面的代码,我们要执行ls指令,ls指令在哪放着呢?就在/usr/bin/ls这个路径下,我怎么知道的呢?因为环境变量呀,PATH这个环境变量就记录了ls指令默认要查找的路径。然后知道在这个路径下,接下来怎么执行呢?ls这条指令后面可以带各种选项的,你要怎么执行,你就怎么传就行,最后以NULL结尾即可。

2. 多进程的程序替换

下面的结果如我们所料,子进程的代码被替换掉了,exec之后的代码全都不会被执行了。

如果替换失败了呢?比如说路径写错了,或者执行的方法不存在,那就只能往后走。

上面我们似乎没谈返回值,exec系列的函数如果成功了,没有返回值,你返回给谁呢?成功之后,后续的代码全都被替换了,你怎么拿到返回值呢?因此,没有成功返回值。只有失败返回值。

除了execl,还有6个程序替换的函数。下面我们一一来介绍一下。 

3.execlp

与execl相比多加了个p,p是PATH的意思,执行的程序默认会去PATH环境变量下面去找,因此我们在传递第一个参数路径的时候,不需要加/usr/bin/,只需要传ls即可,虽然回去PATH路径里面去找,但是你要执行ls还是其它命令呢?你要告诉我呀,因此要传你要执行的那个命令,也就是ls。后面的参数如同execl一样。

我们既然可以把命令当做可执行程序替换,那我们可不可以把我们自己写的代码替换呢?当然可以。

C调用C++程序

在我们当前路径下有两个可执行程序一个proc,一个otherExe,otherExe的代码很简单就是打印了一句话。接下里我们要用proc这个可执行程序去调otherExe这个可执行程序。

第一个参数代表你要执行那个可执行程序,第二个参数是你要怎么执行。 

C语言都可以去调C++的可执行程序,那可以调其它语言的可执行程序吗?是没问题的,java、python都可以去调。

C调用python程序

无论是我们的可执行程序,还是脚本,为啥能跨语言调用呢??因为所有的语言运行起来,本质都是进程!!只要是进程都能被调用。几乎所有语言都有类似的execl接口。execl就相当于一个加载器。

4.execle

我们可以把e理解为env,环境变量列表,我们可以替换的可执行程序传递环境变量。

库里面给我们提供了了一个环境变量列表的二级指针environ,这个二级指针就指向环境变量列表,使用这个二级指针之前需要声明一下,因此我们可以直接把这个二级指针传递过去。

我们不仅可以把环境变量传过去,参数列表也是可以的。打印出来的环境变量是很多的,这里就不全部截出来了。

如果我们自己定义一个环境变量列表,传进去会发生什么?直接就会把我们传递的环境变量。

5.execv

v是vector,把怎么执行放到一个数组里面传递数组即可。我们可以让对应的程序获取到命令行参数。这里我们没有传环境变量,但我们对应的程序可以打印出来环境变量。为啥呢?因为环境变量也是数据,创建子进程的时候,环境变量已经被子进程继承下去了。我们写的程序是bash的子进程。地址空间里是有专门的环境变量和命令行参数空间的,子进程会继承父进程的地址空间页表,环境变量当然也能继承下去,因此直接就可以找到。

程序替换的时候,环境变量信息是不会被替换的。

如果我们要增加一个环境变量呢?

我们可以直接在命令行上xxx=xxx,给bash新增环境变量,这样的话,就可以让子进程获得。

但是我们如果就想在我们当前的进程新增环境变量呢?我们可以用putenv这个函数。

可以看到otherExe直接就把我们新增的环境变量打印出来了。

但是我们在bash中打印的时候是看不到的,也就是说bash是没有这个环境变量的。就类似于继承一样,子进程是可以既用于父进程的环境变量,也可以有自己新增的环境变量 。

下面这两个大家一看就能懂。

6.execvp

7.execvpe       

8.execve

操作系统提供的程序替换的接口就这一个,上面的6个全是库函数,上面的6个全都是封装了这个系统调用。我相信大家一看这个参数列表就知道咋用了。

9.程序替换原理

当一个进程没被执行时,代码和数据都在磁盘里放着,当程序被CPU调度之后,代码和数据会加载到物理内存中,当执行到程序替换的时候,比如说要替换的是ls,ls也是C/C++写的程序,也有代码和数据,此时就把ls的代码和数据替换到我们进程的代码和数据对应的物理内存中,如果比替换前的物理内存小,该释放的内存释放,如果替换之后空间变大了,就扩容。就是让新程序的数据替换我们老数据的程序。

标签:控制,int,退出,exit,linux,进程,include,我们
From: https://blog.csdn.net/2301_76227083/article/details/143579017

相关文章

  • 【Linux】常用命令(2.6万字汇总)
    文章目录Linux常用命令汇总1.基础知识1.1.Linux系统命令行的含义1.2.命令的组成2.基础知识2.1.关闭系统2.2.关闭重启2.3.帮助命令(help)2.4.命令说明书(man)2.5.切换用户(su)2.6.历史指令3.目录操作3.1.切换目录(cd)3.2.查看目录(ls)3.3.创建目录(mkdir)3.4.删除目录......
  • Linux 操作系统下 edquota 命令介绍和使用案例
    Linux操作系统下edquota命令介绍和使用案例edquota命令是Linux系统中用于管理用户或组的磁盘配额的工具。通过该命令,系统管理员可以设置和编辑用户或组在文件系统中使用的磁盘空间限制edquota命令简介功能:edquota允许管理员为指定用户或组设置磁盘配额,限制他们可以......
  • 轻松理解操作系统 - Linux的数据块是如何储存数据的?
    python入门C++入门Linux由于其开源、比较稳定等特点统治了服务端领域。也因此,学习Linux系统相关知识在后端开发等岗位中变得越来越重要,甚至可以说是必不可少的。因为它的广泛应用,所以在程序员的日常工作和面试中,它都是经常出现的。它的开源特性也让它适合于让对于计算......
  • Repadmin 是一个由 Microsoft 提供的命令行工具,用于诊断和管理 Active Directory 域控
    Repadmin|MicrosoftLearnRepadmin是一个由Microsoft提供的命令行工具,用于诊断和管理ActiveDirectory域控制器间的复制问题。它最初是在Windows2000Server时期随ActiveDirectory服务一起推出的,并随着WindowsServer版本的更新不断增强和改进。其主要功能是帮助......
  • Linux常用命令
    Linux文件与路径特殊路径/         如果出现在最前方表示为根目录,如果出现在路径中表示路径分割符     如:     /home/gl     第一个/表示根目录     第二个/表示路径分割符~     表示家......
  • 在vue项目中如何实现权限控制,菜单权限,按钮权限,接口权限,路由权限,操作权限,数据权限如何
    在实际项目开发中,权限管理是一个关键功能,用于控制不同用户对系统资源的访问。权限是对特定资源的访问许可,权限控制的目的是确保用户只能访问到被分配的资源。例如,网站管理员可以对网站数据进行增删改查,而普通用户只能浏览。权限管理的分类根据功能的不同,权限控制可以分为......
  • 用 Python 控制你的鼠标和键盘
    嗨,大家好!今天咱们来聊聊怎么用Python操控你的鼠标和键盘,轻松“接管”你的电脑。通过pynput这个库,咱们可以实现对键盘和鼠标的全面掌控,听起来是不是有点酷?而且,比起其他库如pygame或pyglet,pynput在操作上更为简单,适合像咱们这种想快速搞定任务的技术人群。好了,废话不多......
  • 渗透测试-Linux基础(2)
    声明学习视频来自B站UP主泷羽sec,如涉及侵权马上删除文章。笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负。这里写目录标题Linux目录基本命令Linux编写病毒Linux资源耗尽病毒Linux目录/(根目录)这是......
  • 通过C++跨平台的预编译宏来区分不同的操作系统:Win32/Win64/Unix/Linux/MacOS
    因为C++具有跨平台的特性,所以有些需求一套代码就多端使用,比如我最近在学习的OpenGLES。但是,不同平台还是具有一定差异性,所以我们首先得判断出是什么平台?比如iOS系统和Android系统。那么如何判断呢?我们接着往下看!要检查C或C代码中主机的操作系统,我们需要检查编......
  • 储能辅助火电机组二次调频控制策略及容量优化配置研究(Matlab代码和Simulink仿真)
         ......