文章目录
- 一、进程程序替换
- (一)概念
- (二)为什么程序替换
- (三)程序替换的原理
- (四)如何进行程序替换
- 1. execl
- 2. 引入进程创建——子进程执行程序替换,会不会影响父进程呢?
- (五)大量的测试各种不同的接口
- 1.命名理解 (带v和带l的)
- 2.记忆技巧
- 3.带e和带p
- (六)具体接口说明
- 1.execv
- 2.execlp
- 3.execvp
- 4.execle
- 二、模拟实现shell
- 三、内建命令——以chdir为例
一、进程程序替换
(一)概念
子进程执行的是父进程的代码片段,如果我们想让创建出来的子进程,执行全新的程序呢?
需要用到:进程的程序替换!
(二)为什么程序替换
我们一般在服务器设计(linux编程)的时候,往往需要子进程干两件种类事情!
- 让子进程执行父进程的代码片段(服务器代码)
- 让子进程执行磁盘中一个全新的程序(shell,想让客户端执行对应的程序,通过我们的进程,执行其他人写的进程代码等等),c/c++ ->c/c++/Python/Shell/Php/Java…
(三)程序替换的原理
- 将磁盘中的程序,加载入内存结构
- 重新建立页表映射,谁执行程序替换,就重新建立谁的映射(子进程)
效果:让我们的父进程和子进程彻底分离,并让子进程执行一个全新的程序!
这个过程有没有创建新的进程呢?
没有,子进程的PCB等结构并未改变,只是改变的页表映射关系。
程序替换成功后,运行完新程序,则程序直接退出;程序替换成功后,原进程没有退出,使用原进程运行新程序
我们只能调用接口,为什么呢?
因为这个过程实际上是把数据从一个硬件搬到另一个硬件的操作,这个操作只能由OS操作系统完成
(四)如何进行程序替换
man execl 查看进行程序替换的函数:
我们如果要执行一个全新的程序,我们需要做几件事情呢?
1.程序本质就是一个磁盘上的文件,所以我们需要先找到这个程序在哪里
2.程序可能携带选项进行执行(也可以不携带),然后告诉OS,我要怎么执行这个程序?(要不要带选项)
命令行怎么写(ls -l -a), 这个参数就怎么填"ls",“-l”,“-a”,最后必须是NULL,标识参数传递完毕[如何执行程序的]
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,
..., 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. execl
makefile
myexec:myexec.c
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f myexec
myexec.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(int argc,char *argv[]) {
printf("process is running...\n");
pid_t id = fork();
assert(id != -1);
if (id == 0) {
sleep(1);
printf("我是一个进程,我的pid是:%d\n",getpid());
// 执行ls -l -a 命令
//execl("/usr/bin/ls","ls","-l","-a",NULL);
// 这里有两个ls, 重复吗?不重复,一个是告诉系统我要执行谁?一个是告诉系统,我想怎么执行
// 执行top命令
execl("/usr/bin/top","top",NULL);
printf("我执行完毕了,我的pid是:%d\n",getpid());
// 执行完以上的代码,我们发现一个问题!!
// 最后一句代码为什么没有被打印出来呢!!!
}
return 0;
}
因为进程一旦替换成功,是将当前进程的代码和数据全部替换了!!!
后面的printf是代码吗??有没有被替换呢??当然,已经早就被替换了!!该代码不存在了!!
所以这个程序替换函数,用不用判断返回值?为什么?
答:不用判断返回值,因为只要成功了,就不会有返回值execl,一旦替换成功,是将当前进程的所有代码和数据全部替换了,execl就直接执行ls命令的代码去了。。而失败的时候,必然会继续向后执行!!最多通过返回值得到什么原因导致的替换失败!
2. 引入进程创建——子进程执行程序替换,会不会影响父进程呢?
子进程执行程序替换,会不会影响父进程呢? ?
不会,因为进程具有独立性。
为什么,如何做到的? ?数据层面发生写时拷贝!当程序替换的时候,我们可以理解成为:代码和数据都发生了写时拷贝完成父子的分离!
(五)大量的测试各种不同的接口
1.命名理解 (带v和带l的)
这些函数原型看起来很容易混,但只要掌握了规律就很好记。
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
2.记忆技巧
execl结尾 l 为list,列表传参——>可变参数包,一个一个传。
execv结尾 v 为vector,数组传参——>传的是指针数组。
3.带e和带p
带e的都是可以传环境变量的(execle,execvpe)但是会覆盖系统原有的环境变量,把自己传的环境变量交给进程;
不带e是默认继承系统的环境变量;带p的都是可以自带路径的,直接传命令名称即可(execlp,execvp,execvpe)
(六)具体接口说明
1.execv
int execv(const char *path, char *const argv[]);
path 依然是程序的路径,参数 argv[] 是存着要实现指令的指针数组
char *const argv_[] = {
"ls",
"-a",
"-l",
"--color=auto",
NULL
};
2.execlp
int execlp(const char *file, const char *arg, ...); 带p的就传程序名即可
file:要执行的程序。执行指令的时候,默认的搜索路径,在哪里搜索呢?在环境变量PATH
命名带p的,可以不带路径,只说出你要执行哪一个程序即可!
execlp("ls","ls", "-a", "-1 ",NULL)
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(int argc,char *argv[]) {
printf("process is running...\n");
pid_t id = fork();
assert(id != -1);
if (id == 0) {
sleep(1);
printf("我是一个进程,我的pid是:%d\n",getpid());
//execl("/usr/bin/ls","ls","-l","-a",NULL);
// 这里有两个ls, 重复吗?不重复,一个是告诉系统我要执行谁?一个是告诉系统,我想怎么执行
// 执行top命令
char *const argv_[]={
(char*)"ls",
(char*)"-a",
(char*)"-l",
NULL
};
//execl("/usr/bin/top","top",NULL);
execlp("ls","ls","-a","-l",NULL);//这里出现了两个ls,含义一样吗?不一样!
exit(1);
//execvp("ls", argv_);
printf("我执行完毕了,我的pid是:%d\n",getpid());
// 执行完以上的代码,我们发现一个问题!!
// 最后一句代码为什么没有被打印出来呢!!!
}
int status = 0;
int ret = waitpid(id,&status,0);
if(ret == id)
{
sleep(2);
printf("父进程等待成功!\n");
}
return 0;
}
3.execvp
int execvp(const char *file, char *const argv[]);
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(int argc,char *argv[]) {
printf("process is running...\n");
pid_t id = fork();
assert(id != -1);
if (id == 0) {
sleep(1);
printf("我是一个进程,我的pid是:%d\n",getpid());
//execl("/usr/bin/ls","ls","-l","-a",NULL);
// 这里有两个ls, 重复吗?不重复,一个是告诉系统我要执行谁?一个是告诉系统,我想怎么执行
// 执行top命令
char *const argv_[]={
(char*)"ls",
(char*)"-a",
(char*)"-l",
NULL
};
//execl("/usr/bin/top","top",NULL);
//execlp("ls","ls","-a","-l",NULL);//这里出现了两个ls,含义一样吗?不一样!
exit(1);
execvp("ls", argv_);
printf("我执行完毕了,我的pid是:%d\n",getpid());
// 执行完以上的代码,我们发现一个问题!!
// 最后一句代码为什么没有被打印出来呢!!!
}
// 父进程
int status = 0;
int ret = waitpid(id,&status,0);
if(ret == id)
{
sleep(2);
printf("父进程等待成功!\n");
}
return 0;
}
4.execle
int execle(const char *path, const char *arg,
..., char * const envp[]);
这里的前几个接口都非常熟悉了,这里最后一个接口叫做环境变量。那么为什么要有这个接口呢?
说到环境变量之前我们先来看一下这个问题,我们刚刚提到过,进程替换可以让我们执行其他语言写的程序,那么我们怎么来执行呢?(我们使用execl 函数来调用)
我们现在的目标是想用我们写的myexec.c把mycmd.cpp调用起来,那么怎么来用呢?
myexec.c
我们当前使用的是绝对路径来调用我的mycmd程序!
当然我们也可以使用相对路径来调用。
相对路径调用——
makefile
.PHONY:all
all: mybin myexec
mybin:mybin.c
g++ -o $@ $^ -std=c++11
myexec:myexec.c
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f myexec mybin
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
printf("我是一个进程,我的pid是:%d\n",getpid());
pid_t id=fork();
if(id==0)
{
//child
printf("我是子进程,我的pid是:%d\n",getpid());
execl("./mycmd","mycmd",NULL);
exit(1);
}
//一定是父进程
int status=0;
int ret=waitpid(id,&status,0);
if(ret==id)
{
sleep(2);
printf("父进程等待成功!\n");
}
return 0;
}
为什么会有这么多接口?——因为要适配应用场景。
execve为什么是单独的?——实际上,只有 execve是系统调用,其他都是对系统接口的封装,最后都要调用到execve!