@TOC
1. 程序替换
1.创立子进程的意图是什么?
方针:为了让子进程帮父进程履行特定的使命
- 具体做法:1. 让子进程履行父进程的一部分代码
红框中的代码实际上是父进程的代码,在没有履行fork之前代码就有了,在没有创立子进程之前,父进程的代码加载到内存了,子进程被创立出来是没有独立的代码,这个代码是父进程的代码,父进程经过if判别分流让子进程去跑了
- 2.创立一个子进程不履行父进程的代码,而是让子进程在磁盘傍边履行全新的程序,这种操作称之为进程的程序替换
2.了解程序是怎么进行替换的
程序替换函数 execl
输入 man execl
检查程序替换接口
int execl(const char *path, const char *arg, …); 括号内部的 . . . 称为 可变参数列表,能够给c函数传递任意个数的参数 第一个参数为 要履行什么指令 第二个参数 为 要怎样履行程序 最终以NULL完毕表明参数传完了
创立test.c文件并输入以下内容
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
printf("begin......\n");
printf("begin......\n");
printf("begin......\n");
printf("begin......\n");
execl("/bin/ls", "ls", "-a", "-l", NULL);
printf("end........\n");
printf("end........\n");
printf("end........\n");
printf("end........\n");
}
运转可履行程序发现,只有begin 以及履行 ls -l -a显现的指令
再次修正test.c文件内容如下
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
printf("begin......\n");
printf("begin......\n");
printf("begin......\n");
printf("begin......\n");
printf("我已经是一个进程啦,我的PID:%d\n",getpid());
execl("/bin/ls", "ls", "-a", "-l", NULL);
printf("end........\n");
printf("end........\n");
printf("end........\n");
printf("end........\n");
}
test.c 经过编译构成mytest可履行程序,./可履行程序就变成进程了,CPU调度进程 ,打印出代码中的打印语句,同时调用程序替换execl,将ls程序履行起来了
[yzq@VM-8-8-centos nn]$ file /bin/ls
/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=c8ada1f7095f6b2bb7ddc848e088c2d615c3743e, stripped
运用的 /bin/ls 指令 实际上是一个可履行程序,所以ls程序是在磁盘上的
前面履行的是自己代码的一部分,当调用execl时,将磁盘中可履行程序替换当时进程的代码和数据 后半部分就不履行自己的代码了,履行ls所对应的代码 ,这个现象就叫做程序替换
程序替换就是让一个进程去履行另一个在磁盘中的程序,让一个进程把一个新的程序运转起来
3. 程序替换的基本原理
当时的进程履行当时代码时,假如履行了函数execl等接口,就会根据你所传入的程序的途径以及你要履行的名称及选项,把磁盘傍边的一个其他的程序加载到对应的内存, 用新程序的代码替换当时进程的代码段,用当时进程的数据替换老进程的数据段
站在进程的视点 进程的程序替换有没有创立新的进程呢? 没有,只是将新的程序加载到当时进程的代码段和数据段,用CPU去调度当时进程就能够跑起来了
站在程序的视点 程序被加载了内存中,就能够称程序替换的接口(execl) 为加载器
当创立进程的时分,先有进程数据结构,仍是先加载代码和数据?
修正test.c文件为以下内容
#include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 int main()
5 {
6 execl("/bin/ls", "ls", "-a", "-l", NULL); 7 }
此刻运转可履行程序,自己就写了一个ls指令
创立子进程,让子进程调用execl,在调用execl把代码和数据加载到内存 所以当创立进程的时分,先有进程数据结构,再加载代码和数据
程序替换是整体替换,不是局部替换
修正test.c文件内容如下
#include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/wait.h>
5 int main()
6 {
7 pid_t id=fork();
8 if(id==0)
9 {
10 //child
11 printf("我是子进程:%d\n",getpid());
12 execl("/bin/ls", "ls", "-a", "-l", NULL);
13 }
sleep(5);
14 printf("我是父进程:%d\n",getpid());
15 waitpid(id,NULL,0);
16 }
检查子进程完结替换后会不会影响父进程,假如影响父进程,就不应该打印父进程的这句话了
过了5秒钟,父进程结果打印出来,阐明父进程不受子进程影响
程序替换只会影响调用进程,进程具有独立性 父子进程都有自己独立的PCB 地址空间 页表 也会自己的映射联系 虽然代码有可能是跟父进程同享,当子进程进行程序替换的时分,子进程会加载新进程的代码和数据 操作系统会产生写时复制,将代码和数据进行区别 ,使子进程构成新的映射联系,从而使子进程不会影响到父进程
execl 返回值
假如出错了,execl返回值为-1
修正test.c文件内容如下
#include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/wait.h>
5 int main()
6 {
7 pid_t id=fork();
8 if(id==0)
9 {
10 //child
11 printf("我是子进程:%d\n",getpid());
12 int n=execl("/bin/lsss", "lsss", "-a", "-l", NULL);//lsss指令不存在
13 printf("you can see me:%d\n",n);
14 exit(0);
15 }
16 sleep(5);
17 printf("我是父进程:%d\n",getpid());
18 waitpid(id,NULL,0);
19 }
20
输入的lsss指令不存在,查询报错后的execl的返回值
程序替换只需成功,就会跑去履行新程序,失利了就会持续向后运转 所以execl程序替换成功不会有返回值——>假如替换失利,必定有返回值——>假如失利了,必定返回——>只需有返回值就失利了 阐明不用对execl函数的返回值进行判别,只需持续向后运转必定失利
4. 替换函数
1. execl
int execl(const char *path, const char *arg, …); l 代表 list 链表 path:代表你想履行谁 (需求带途径) 履行一个程序最基本的原则为:找到它,加载履行它 arg:你想怎么履行它(若想履行ls指令,是只履行ls,仍是履行ls- l 、ls -l -a指令 在指令行怎么履行这个指令,就把参数一个一个的传递给execl就能够了 最终以NULL完毕
具体的完成以及返回值问题上面在演示程序替换时已经运用过啦
2. execv
int execv(const char *path, char *const argv[]); v代表vector 容器 path:代表你想履行谁 (需求带途径) 把本来需求一个一个传的参数放在argv[]数组中
修正test.c文件内容
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/wait.h>
5 int main()
6 {
7 pid_t id=fork();
8 if(id==0)
9 {
10 //child
11 printf("我是子进程:%d\n",getpid());
12 char *const myargv[]={"ls", "-l", "-a",NULL};
13 execv("/bin/ls",myargv);
14 exit(0);
15 }
16 sleep(5);
17 printf("我是父进程:%d\n",getpid());
18 waitpid(id,NULL,0);
19 }
20
履行可履行程序,依旧能够履行ls指令
3. execlp
int execlp(const char *file, const char *arg, …); 带p:代表当履行程序的时分,只需求指定程序名即可,系统会主动在PATH环境变量中查找 file: 不需求传途径,只需求把在PATH环境变量的指令传过来 最终以NULL完毕
#include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/wait.h>
5 int main()
6 {
7 pid_t id=fork();
8 if(id==0)
9 {
10 //child
11 printf("我是子进程:%d\n",getpid());
12 execlp("ls", "ls", "-l", "-a",NULL);
13 exit(0);
14 }
15 sleep(5);
16 printf("我是父进程:%d\n",getpid());
17 waitpid(id,NULL,0);
18 }
履行可履行程序,依旧能够履行ls指令
4. execvp
int execvp(const char *file, char *const argv[]); 带p:代表当履行程序的时分,只需求指定程序名即可,系统会主动在PATH环境变量中查找 v代表vector 容器
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
pid_t id=fork();
if(id==0)
{
//child
printf("我是子进程:%d\n",getpid());
char* const myargv[]={"ls", "-l", "-a",NULL};
execvp("ls", myargv);
exit(0);
}
sleep(5);
printf("我是父进程:%d\n",getpid());
waitpid(id,NULL,0);
}
5. execle
int execle(const char *path, const char *arg, …, char * const envp[]); path:代表你想履行谁 (需求带途径) envp[]:代表自定义环境变量 假如调用程序替换时,若不想让子进程运用父进程的环境列表,想自定义环境变量,就能够自己传一个环境变量
在另一个目录中创立other.cc (以cc为完毕阐明是一个c++程序),并输入以下内容
#include <iostream>
#include <unistd.h>
#include<stdlib.h>
using namespace std;
int main()
{
for(int i = 0; i < 5; i++)
{
cout<< "我是另一个程序,我的pid是:"<< getpid()<<endl;
cout << " MYENV: " << (getenv("MYENV")==NULL?"NULL":getenv("MYENV")) << endl;
sleep(1);
}
return 0;
}
修正test.c文件为以下内容
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
pid_t id=fork();
if(id==0)
{
//child
printf("我是子进程:%d\n",getpid());
W> char*const myenv[]={"MYENV=YouCanSeeMe", NULL};
execle("/home/mydir/my/mm/myother", "myother", NULL,myenv);
exit(0);
}
sleep(1);
printf("我是父进程:%d\n",getpid());
int status=0;
waitpid(id,&status,0);
return 0;
}
第一个途径为other.cc生成的可履行程序 myother 地点的 绝对途径
2. 自定义shell
编写极简版别的shell(bash) 方针:为了深入的了解shell的运转原理
输入 ps ajx |grep bash
,发现bash就是一个进程
因为shell是一个进程,所以用while死循环
缓冲区问题
正常来说,运转可履行程序会显现指令行,可是因为没有\n刷新缓冲区,也没有运用相关的刷新库函数,所以指令行会一直在缓冲区中 直到 程序完毕才显现,可是这是个死循环,所以什么都不会显现
履行可履行程序后即可显现指令行
fgets 运用呈现空格问题
fgets 规范输入 按行获取 char *fgets(char *s, int size, FILE *stream); 从特定的规范输入傍边获取对应的指令行输入,把对应的数据放在缓冲区中
履行可履行程序后,发现在指令行中输入 ls -a ,就会打印出 ls -a,可是输入时会多出一个空行
正常来说,都会运用回车来到下一行,而这个回车被fgets读取到了
将最终的回车符替换成’\0′
此刻就没有空格呈现了
完整代码
: mybash.c ? ? ?? buffers
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#define MAX 1024
#define ARGC 64
#define SEP " "
int split (char*commandstr,char*argv[])
{
assert(commandstr);
assert(argv);
argv[0]=strtok(commandstr,SEP);//在commandstr以空格作为切开符
if(argv[0]==NULL)//切开失利
{
return -1;
}
int i=1;
while(1)
{
argv[i]=strtok(NULL,SEP);//持续切开空格
if(argv[i]==NULL)
{
break;
}
i++;
}
W>}
void print(char*argv[])
{
int i=0;
for(i=0;argv[i];i++)
{
printf("%d:%s\n",i,argv[i]);
}
}
int main()
{
while(1)
{
char commandstr[MAX]={0};
char*argv[ARGC]={NULL};
printf("[yzq@mymachine currpath]# ");
fflush(stdout);
char*s= fgets(commandstr,sizeof(commandstr),stdin);
assert(s);
(void)s;//保证在release方法发布的时分,因为去掉assert了,所以s就没有被运用,而带来的编译告警, 什么都没做,可是充当一次运用
commandstr[strlen(commandstr)-1]='\0';//将最终的回车符用'\0'覆盖掉
int n= split(commandstr,argv); //从指令行中获取的字符串传入argv中
if(n!=0)
continue;//切开失利就什么都不做
// print(argv);//打印字符串
pid_t id=fork();
assert(id>=0);
(void)id;
if(id==0)
{
//child
execvp(argv[0],argv);
exit(1);
}
int status=0;
waitpid(id,&status,0);
}
}
动图演示