🌎进程控制【下】
文章目录:
进程控制
execl接口介绍
多进程版本程序替换
其他exec接口
接口介绍
替换本地程序
总结
前言:
在Linux系统中,进程程序替换是一种重要的操作,通过进程程序替换,程序可以更新自己的代码和数据,让进程富有动态性和灵活性,话不多说,开始今天的话题。
🚀execl接口介绍
我们的程序只能执行该程序自己的代码,这是众所周知的,但是今天,我想要创建一个子进程来执行别的文件的代码是否可行呢?
在Linux下是可实现的,因为Linux给我们提供了对应的接口:
这些接口支持我们程序在运行的过程中进行程序替换,从而执行到自己想执行的程序。
int execl(const char* path, const char* arg, ...) :
path :表示带路径文件名的字符串,从而搜索到对应的文件
arg, ...:表示可变参数列表,参数不确定,可传入一个或多个
最后必须以NULL结尾。
首先第一个接口,以下面代码来理解:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>int main()
{printf("I am a process, pid: %d\n", getpid());printf("exec begin...\n");execl("/usr/bin/ls", "ls", "-a", "-l", NULL); //程序替换,可变参数printf("exec end ...\n");return 0;
}
能够清晰观察到,在begin之后,程序被替换为了ls 指令,并且选项为 -al,执行程序,发现运行成功了,但是仔细观察之后,在execl之后的printf并没有起作用。
结论1:
程序在执行完exec* 的接口之后,是不会再执行后续的代码了,因为后续代码已经被替换。
从man手册里有exec* 接口返回值的描述:
结论2:
exec* 只有失败有返回值,为-1。成功就是成功替换了,所以没返回值。
替换完成后是属于创建了新的进程还是旧的进程不变呢?我们不妨做个测试:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>int main()
{printf("I am a process, pid: %d\n", getpid());printf("exec begin...\n");sleep(5);execl("/usr/bin/top", "top", NULL); printf("exec end ...\n");return 0;
}
虽然在替换之后进程的名字变了,但是前后两次的pid并没有变化。
结论3:
进程替换并不会创建新的程序,依旧是原来进程的pid。
🚀多进程版本程序替换
通过之前的学习,我们知道进程之间相互独立,那么我们就可以创建一个子进程,让其来执行程序替换,而父进程回收结果:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("I am a process, pid: %d\n", getpid());pid_t id = fork();if(id == 0){sleep(3);printf("exec begin...\n");execl("usr/bin/ls", "ls", "-a", "-l", NULL);printf("exec end ...\n");exit(1);}pid_t rid = waitpid(id, NULL, 0);if(rid > 0){printf("wait success\n");}exit(1);
}
运行之后,就可以让子进程执行程序替换,并且父进程回收子进程的资源。
我们来思考一个问题:程序替换为什么对父进程没有影响?这是因为,进程具有独立性,在程序替换的时候发生写时拷贝。
🚀其他exec接口
✈️接口介绍
我们通过man手册查询exec*接口,发现不止一个接口,还有六个接口:
我们需要了解这七个接口的含义以及用法,但是在这里我不会全部一一列举,因为有些接口是类似的,这些类似的接口我只需要说一个就够了。
首先,这些接口中带有 ‘p’ 字符的接口都有 path 这个参数,实际上这个参数的意义是:
PATH: 并不需要告诉系统程序的具体位置,只需要告诉系统程序的名称,系统在进行替换的时候,会自动在PATH环境变量中去查找。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("I am a process, pid: %d\n", getpid());pid_t id = fork();if(id == 0){sleep(3);printf("exec begin...\n");execlp("ls", "ls", "-a", "-l", NULL);//使用带有'p'的接口printf("exec end ...\n");exit(1);}pid_t rid = waitpid(id, NULL, 0);if(rid > 0){printf("wait success\n");}exit(1);
}
使用带 ‘p’ 字符的接口,就不需要带替换程序的路径了,只需要替换程序的名字,在OS中会 依照PATH环境变量来寻找该程序。
下面就是带有 ‘v’ 字符的接口,实际上这个v 在参数里表示的是 const char* argv[],我们在main函数里面是见过的,也就是 命令行参数表。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("I am a process, pid: %d\n", getpid());pid_t id = fork();if(id == 0){char *const argv[] = {(char*)"ls",(char*)"-a",(char*)"-l"};sleep(3);printf("exec begin...\n");execv("/usr/bin/ls", argv);//带有 'v' 字符的接口printf("exec end ...\n");exit(1);}pid_t rid = waitpid(id, NULL, 0);if(rid > 0){printf("wait success\n");}exit(1);
}
最开始我们也见过带有 ‘l’ 字符的接口,它表示的是 list,也就是列表,把需要执行的命令和参数全部放在接口内。
那么带 ‘vp’ 的其实就是传 程序名,以及参数列表即可:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("I am a process, pid: %d\n", getpid());pid_t id = fork();if(id == 0){char *const argv[] = {(char*)"ls",(char*)"-a",(char*)"-l"};sleep(3);printf("exec begin...\n");execvp("ls", argv);//带 'vp' 的接口printf("exec end ...\n");exit(1);}pid_t rid = waitpid(id, NULL, 0);if(rid > 0){printf("wait success\n");}exit(1);
}
✈️替换本地程序
我们前面的程序替换全部都是使用系统提供好的程序,我们使用自己写的程序该当何如?
#include<stdio.h>
#include<stdlib.h>int main(int argc, const char* argv[])
{for(int i = 0; argv[i]; ++i){printf("argv[%d]:%s\n", i, argv[i]);}printf("I'm test process!\n");printf("I'm test process!\n");printf("I'm test process!\n");printf("I'm test process!\n");printf("I'm test process!\n");return 0;
}
此时我们使用之前学习的make语法已经行不通了,因为无论怎样,只能编译过一个,今天我们来看点别的:
.PHONY:all#由依赖关系无依赖方法
all:myprocess mytest mytest:mytest.cgcc -o $@ $^ -g -std=c99
mybin:mybin.cgcc -o $@ $^ -g -std=c99.PHONY:clean
clean:rm -f mybin mytest
在需要生成多个文件之前使用 .PHONY,加上依赖关系,但是不需要依赖方法,这样就能 根据依赖关系 从前到后依次 生成可执行文件。
那么现在我mybin.c文件的子进程要替换 mytest 程序,我们可以这么写:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("I am a process, pid: %d\n", getpid());pid_t id = fork();if(id == 0){ //child processsleep(1);printf("exec begin...\n");execl("./mytest", "./mytest", "-a", "-b", "-c", NULL);//切换自己写的程序printf("exec end ...\n");exit(1);}pid_t rid = waitpid(id, NULL, 0);if(rid > 0){printf("wait success\n");}exit(1);
}
当然,这里我是用C语言调用C语言程序,但是我们可以调用其他语言吗?答案是 可以调用 其他语言写的程序。
这是因为:不论什么语言,运行之后都是进程,只要是进程就都能在Linux下运行!
我们修改test文件,让其打印系统环境变量表:
#include<stdio.h>
#include<stdlib.h>int main(int argc, const char* argv[], const char
{for(int i = 0; env[i]; ++i){printf("env[%d]:%s\n", i, env[i]);}return 0;
}
此时再使用程序替换,让子进程执行这段代码,父进程等待子进程资源回收:
我们也可以使用系统变量 environ,来获取环境变量:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main()
{extern char **environ;for(int i = 0; environ[i]; ++i){printf("env[%d]:%s\n", i, environ[i]);}return 0;
}
我们使用 mybin 文件来执行程序的:
在mybin.c 中,我们并没有传递环境变量表给子进程,但是子进程却能默认拿到环境变量表?
实际上,子进程会默认拿到父进程环境变量表,那么mybin 也是子进程,是bash的子进程,所以mybin能拿到bash的环境变量,而mybin的子进程可以拿到父进程环境变量:
我们导入一个新环境变量在系统里以供猜想:
export VAL=youcanseeme
我们在进程地址空间那一节说过,进程地址空间内在 栈的上方 是 存储命令行参数以及环境变量的地方:
而在本文的最开始,我们也说了,进程替换替换的仅仅是进程的代码和数据,环境变量是不变的。
如果我们想单纯新增环境变量呢?我们可以使用 putenv:
此时我在程序内写入了mytest环境变量,但是当我们在系统中查询时:
此时并没有在系统中出现,但是当我们运行程序之后:
此时进程内就多了一项mytest的环境变量,而这个环境变量的导入位置是mytest 父进程传给子进程的环境变量,而mytest的父进程是bash,也就是说,在这里bash将从 0-24号环境变量传给了进程mytest,而mytest 使用了putenv新增了环境变量给子进程。
而现在我想 设置全新的环境变量给子进程,这个时候我们就需要用到带有 ‘e’ 字符的接口了
接口中还存在带 ‘e’ 字符的接口,e表示的就是 env:const char* env[], 也就需要 环境变量表。
其实这是以 覆盖 的方式来传递环境变量,也就相当于子进程设置了全新的环境变量了。
我在最前面总共列举了七个接口,一个程序替换为什么会有这么多的接口呢?但他们的功能都是进行程序替换,所以他们在功能上没有区别。
他们仅仅是在传参上有区别,其实我们 程序替换的系统调用只有一个,就是 execve 接口,剩下的六个全部都是由这个接口进行封装的。
📒✏️总结
- 进程不仅仅只能运行自己的程序,和可以运行其他程序,使用 exec* 的接口 就可以做到,被称为 程序替换。
- exec* 接口有七个,他们的 功能全部相同,仅仅是 使用参数不同。
- 子进程会 默认 继承父进程的环境变量表,并 不需要父进程显示传给子进程。
- 程序替换 不看 是什么 语言 的程序,因为 在Linux下运行起来都是进程。
- 七个接口只有 execve 是系统调用,其他6个全是由此接口进行封装。
创作不易,还望三联支持博主呀~~