【Linux】详解进程控制 ( 再谈进程退出 | 程序替换exec*类型函数 )

接续上篇博客 “详解进程控制 ( fork函数 | 写时拷贝 | 进程退出 | 进程等待 )”

再谈进程退出

  1. 进程退出会变成僵尸状态,将自己的推出结果写入task_struct
  2. wait与waitpid是一个系统调用,操作系统有资格也有能力去读取子进程的task_struct
  3. wait与waitpid就是从退出子进程的task_struct中获取的

感性认识阻塞与非阻塞:

假设张三喊李四去吃饭,张三给李四打电话,但是李四说他还有事大概需要等30分钟,这时候张三:
1.张三不挂电话,一直等到李四说我好了然后去吃饭 ----- 阻塞
2.张三把电话挂了,然后去做自己的事情边做边等,过个几分钟给李四打电话问他好了没,过个几分钟又打电话……-----每一次打电话都是一次非阻塞等待(多次非阻塞等待\轮询
打电话对应的是系统调用wait\waitpid, 张三(父进程)、李四(子进程)

轮询访问父进程代码:

//父进程
int status = 0;
while(1)
{
    pid_t ret = waitpid(id, &status, WNOHANG); //WNOHANG: 非阻塞-> 子进程没有退出, 父进程检测时候,立即返回
    if(ret == 0)
    {
        // waitpid调用成功 && 子进程没退出
        printf("wait done, but child is running...., parent running other things\n");
    }
    else if(ret > 0)
    {
        // 1.waitpid调用成功 && 子进程退出了
        printf("wait success, exit code: %d, sig: %d\n", (status>>8)&0xFF, status & 0x7F);
        break;
    }
    else
    {
        // waitpid调用失败,比如我的waitpid的函数参数id写错
        printf("waitpid call failed\n");
        break;
    }
    sleep(1);
}

运行结果:
在这里插入图片描述

非阻塞状态有什么好处?不会占用父进程的所有精力,可以在轮询期间干干别的事情。

进程程序替换

创建子进程目的:

  • 想让子进程执行父进程代码的一部分(执行父进程对应的磁盘代码中的一部分)
  • 想让子进程执行一个全新的程序(让子进程想办法,加载磁盘上指定的程序,执行新程序的代码和数据)

引入

int execl(const char *path, const char *arg, ...);
//将指定的程序加载到内存中,让指定进程进行执行
//path是用来找到这个程序,如何执行就是后面的arg传入的选项

比如:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 int main()
  4 {
  5     printf("process is running...\n");
  6 
  7     execl("/usr/bin/ls","ls",NULL);
  8 
  9     printf("process is done...\n");
 10     return 0;
 11 }

在这里插入图片描述
运行结果:
在这里插入图片描述
我将增加几个执行选项,并加上颜色显示:

execl("/usr/bin/ls","ls","--color=auto","-a","-l",NULL); 

在这里插入图片描述

上述过程就是程序替换,我们只要能找到这个程序,然后添加它的运行选项就可以执行。

程序替换原理

我们看之前的代码打印出来的结果:
在这里插入图片描述
程序执行图:
在这里插入图片描述
为什么后一个printf没有执行?
 printf也是代码,且在execl函数之后,execl函数执行完毕的时候,代码已经全部被复制,开始执行新的程序的代码了,所以printf就无法执行。
 execl是一个函数,而一个函数的调用就有可能成功有可能失败,调用失败就是没有替换成功,也就是没替换,所以会继续执行原代码:
在这里插入图片描述
 exec*没有成功返回值,因为成功了就与接下来的原代码无关,判断没有意义。execl只要返回了,一定是错误了。
在这里插入图片描述
在这里插入图片描述

父进程创建子进程,子进程的替换会影响父进程吗?

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <assert.h>
  5 #include <sys/types.h>
  6 #include <sys/wait.h>
  7 
  8 int main()
  9 {
 10     printf("process is running...\n");
 11     pid_t id  = fork();
 12     assert(id != -1);
 13     if(id==0)
 14     {
 15         sleep(1);
 16         execl("/usr/bin/ls","ls","-a",NULL);                                                                                                                                                                  
 17         exit(1);                                    
 18     }                                               
 19     int status=0;                                   
 20     pid_t ret = waitpid(id,&status,0);              
 21     if(ret>0)                                       
 22     {                                               
 23         printf("wait sucess : exit code:%d, signal:%d\n",(status>>8)&0xFF,status & 0x7F);
 24     }
 25		return 0;
 26 }

在这里插入图片描述

在这里插入图片描述
 进程具有独立性,子进程成功调用exec*会发生写时拷贝,在物理内存里面重新加载代码与数据,然后更改子进程的页表映射,此过程不会影响父进程。

虚拟地址空间+页表 : 保证了进程独立性,一旦有执行流想替换代码或数据,就会发生写时拷贝

有哪些替换函数

使用man指令查看execl函数
在这里插入图片描述
 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。

这些函数开头都是exec,而后面跟的字符可以代表不同含义:

  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量

execl:

int execl(const char *path, const char *arg, ...);

 l:list:将参数一个一个的传入exec*

替换自己写的可执行程序:

execl("./myexe","myexe",NULL);

在这里插入图片描述

程序替换,可以使用程序替换,调用任何后端语言对应的可执行–程序

execlp:

int execlp(const char *file, const char *arg, ...);
//带p无需写全路径,可以直接使用环境变量PATH
execlp("ls", "ls", "-a", "-l", "--color=auto", NULL);

 这里有两个ls, 重复吗?不重复,一个是告诉系统我要执行谁?一个是告诉系统,我想怎么执行
 p:path:如何找到程序的功能,带p字符的函数,不用告诉我程序的路径,你只要告诉我是谁,我会自动在环境变量PATH,进行可执行程序的查找!

execv:

int execv(const char *path, char *const argv[]);
char *const argv_[] = {
            "ls",
            "-a",
            "-l",
            "--color=auto",
            NULL
        };
execv("/usr/bin/ls", argv_);

 v:vector:可以将所有的执行参数,放入数组中,统一传递,而不用进行使用可变参数方案

execvp:

int execvp(const char *file, char *const argv[]);
//将 v 与 p 的特性结合起来
char *const argv_[] = {
            "ls",
            "-a",
            "-l",
            "--color=auto",
            NULL
        };
execvp("ls", argv_);
int main(int argc, char *argv[])
// ./exec ls -a -l -> "./exec" "ls" "-a" "-l"
execvp(argv[1], &argv[1]);

运行结果:
在这里插入图片描述

execle:

int execle(const char *path, const char *arg, ...,char *const envp[]);
//需要自己组装环境变量
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execle("ps", "ps", "-ef", NULL, envp)

 自定义环境变量举个例子:
在这里插入图片描述
 为了即使用全局环境变量又添加自己的环境变量,我们使用putenv来增加以一个环境变量:
在这里插入图片描述

在这里插入图片描述
先加载execle后执行main:
在这里插入图片描述

execve:

我们使用man手册打开它,可以知道它在2号手册里,代表他是系统调用
在这里插入图片描述

事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节
程序替换中execve为系统调用,其它都是封装,为了让我们有更多的选择性


如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

侠客cheems

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值