程序替换
main 函数回顾说明
main(int argc,char* argv[],char* env[])
argc:程序运行参数的个数
argv:程序运行参数,字符指针数组,最后一个参数之后,以NULL结尾
env:程序的环境变量,字符指针数组,最后一个参数之后,以NULL结尾
程序替换
execve 指定运行参数argv
int execve(char* path,char* argv[],char* env[]);
功能: 将 path 这个路径名所指定的程序加载到内存中,然后让当前进程调度管理这个程序的运行
argv:用于设定这个程序的运行参数
env:用于设定这个程序的环境遍历
返回值:替换成功运行新程序 没有返回值;失败返回-1
使用示例:
// execve.c 程序
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
printf("这是我程序运行的起始位置~\n");
char* argv[]={"-a","-d","-c","xzz",NULL}; //argv 字符函数必须以 NULL 结尾
char* env[]={"MYVAL=999","CLASS=98",NULL};
//进行程序替换,argc 程序替换运行当前程序
execve("./argc",argv,env);
//execve("/bin/ls",argv,env); //指定系统调用接口路径,指定运行参数和环境变量
return 0;
}
其中所调用的 argc 程序:
//argc.c 程序
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(int argc,char* argv[],char* env[])
{
int i=0;
for(;argv[i]!=NULL;++i) //将运行参数打印
printf("argv[%d]=[%s]\n",i,argv[i]);
printf("~~~~~~~~~~~~~~~~~~\n");
i=0;
for(;env[i]!=NULL;++i) //打印环境变量
printf("env[%d]=[%s]\n",i,env[i]);
return 0;
}
运行结果:
execvp 不需要指定环境变量
int execvp(char* file,char* argc[],cahr* env);
第一个参数不用指定路径,默认会在 PATH环境变量指定的路径下找
环境变量默认使用当前已有的,不用手动设置环境变量
使用示例:
int main()
{
printf("这是我程序运行的起始位置~\n");
//char* argv[]={"-a","-d","-c","xzz",NULL}; //argv 字符函数必须以 NULL 结尾
//char* env[]={"MYVAL=999","CLASS=98",NULL};
// execve("./argc",argv,env);
char *argv[]={"ls","-l",NULL};
char* env[]={NULL};
//使用运行参数 argv 中 ls -l 替换当前程序
execvp("ls",argv);
//env 默认采用已有的环境变量,且不需要指定路径
//execvp("./argc",argv);
// 等价于 execve("ls",argc,env-main中第三个参数)
printf("这是我程序运行的终止~~~\n");
return 0;
}
execv
int execv(char* path,char* argv[]);
不用设置环境变量,但是需要路径
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
printf("这是我程序运行的起始位置~\n");
char *argv[]={"ls","-l",NULL};
execv("ls",argv); //不指定路径时会发生错误
perror("execv error\n");
printf("这是我程序运行的终止~~~\n");
return 0;
}
当没有指定 ls 的位置时,会发生替换失败:
当指定路径之后:
execv("/bin/ls",argv);
execv(“bin/ls”,argv); 需要指定路径
execvp(“ls”,argv); 不需要指定路径
execve(“bin/ls”,argv,env); 手动设置环境变量,并且需要指定路径
execl 、execlp 、execle
上述三种替换每次都需要定义一个参数字符指针数组,比较麻烦,因此常会这三种替换函数:
int execl(char* path,…/char arg*/);
int execlp(char* file,…);
int execle(char* path,…,char* env[]);
使用示例:
execlp("ls","ls","-l",NULL) //可以不指定环境变量
execle("bin/ls","ls","-l",NULL,env) //需要指定环境变量以及系统调用接口的路径
程序替换的作用
shell 是一个命令行解释器,捕捉用户的输入信息,了解用户想要干什么,然后执行对应的shell指令--------系统内核与用户之间沟通的桥梁
模拟实现 shell :
(1)父进程捕捉用户输入 [ ls -l -a ];
(2)解析输入,得到命令名称,各个参数 [ls] [-l] [-a];
(3)创建子进程;
(4)对子进程进行程序替换,将子进程要进行调度的程序换成要执行的 shell 指令程序;
(5)父进程等待子进程退出(等待指令执行完毕----避免僵尸进程),然后捕捉用户的下一个输入。
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<unistd.h>
5 #include<sys/wait.h>
6
7 int main()
8 {
9 while(1){
10 printf("【user@host~ 】$ ");
11 fflush(stdout); //刷新缓冲区
12
13 char cmd[32]={0};
14 fgets(cmd,1023,stdin) ; //获取字符
15
16 cmd[strlen(cmd)-1]='\0'; //将键盘输入的字符后加上 \0 表示结束
17
18 printf("[%s]\n",cmd); //打印一下输入的字符信息
19
20 int argc=0;
21 char* argv[32]={NULL}; //用于保存分割信息
W> 22 char* ptr=cmd; //保存获取到的字符信息
23 argv[argc++]=strtok(cmd," "); //以空格对字符串进行分割处理
24
25
26 while((argv[argc]=strtok(NULL," "))!=NULL){ //strtok 第一个参数 NULL ,表示默认以上一次终止位置作为新分割的起始位置进行分割
27
28 argc++;
29 }
30
31 //打印分割后的字符信息
32 int i=0;
33 for(;argv[i]!=NULL;++i){
34 printf("[%s]\n",argv[i]);
35 }
36 //如果第一个字符信息为 cd ,则需要更改工作路径
37 if(strcmp(argv[0],"cd")==0){
38 chdir(argc[1]); //更改当前工作路径
39 continue;
40 }
41
42
43 //创建子进程
44 pid_t cpid=fork();
45 if(cpid<0){
46 perror("fork error!");
47 continue;
48 }
49 else if(cpid==0){
50 //子进程实现程序替换
51 execvp(argv[0],argv);
52 perror("execvp perror");
53 exit(-1); //只有程序替换失败才会执行这行代码
54 }
55 wait(NULL); //每一个子进程运行完毕后,才能开始捕捉下一个输入进行操作
56 }
57
58 return 0;
59 }
60
运行结果:
进入模拟 shell 界面:
模拟实现 ls:
模拟实现 cd(返回上一层目录):
小点点
wc -l :统计个数(行数统计)
du :查看磁盘空间
pstree :查看目录的树形结构
Q1:不算main函数进程,一一共创建了多少个进程?
Q2:请问打印了多少个 * ?
int main()
{
for(int i=0;i<2;++i){
fork();
printf("*\n");
}
return 0;
}
答案:6个
存在 \n 说明会刷新缓冲区,因此创建子进程时不会复制父进程内容(*)
Q3:请问打印了多少个 * ?
int main()
{
for(int i=0;i<2;++i){
fork();
printf("*");
}
return 0;
}
答案:8个
不存在 \n 说明不会刷新缓冲区,因此 * 不会打印,暂时存储在缓冲区里,因此创建子进程会同时复制父进程缓冲区中内容(*)
ps:欢迎评论交流~~