3. 历程程序替换
3.1 单历程版 – 看看程序替换
makefile
- mycommand:mycommand.c
- gcc -o $@ $^ -std=c99
- .PHONY:clean
- clean:
- rm -f mycommand
复制代码 mycommand.c
- #include <stdio.h>
- #include <unistd.h>// 提供execl, getpid等函数
- #include <stdlib.h>
- int main(){
- // 打印exec调用前的进程信息
- // getpid(): 获取当前进程ID
- // getppid(): 获取父进程ID
- printf("before: I am a process, pid:%d, ppid:%d\n",getpid(),getppid());
-
- //这类方法的标准写法
- // execl函数执行新程序
- // 参数1 "/usr/bin/ls": 要执行的程序的完整路径
- // 参数2 "ls": 程序名称(argv[0])
- // 参数3 "-a": 显示所有文件(包括隐藏文件)
- // 参数4 "-l": 使用长格式显示
- // 参数5 NULL: 参数列表结束标志
- execl("/usr/bin/ls","ls", "-a", "-l", NULL);
-
- // 如果exec执行成功,这行代码永远不会被执行
- // 因为原程序的内容已被ls程序替换
- printf("after: I am a process, pid:%d, ppid:%d\n",getpid(),getppid());
- return 0;
- }
复制代码 打印效果:
- ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$ ./mycommand
- before: I am a process, pid:261495, ppid:261085
- total 36
- drwxrwxr-x 2 ydk_108 ydk_108 4096 Jan 20 22:26 .
- drwxrwxr-x 13 ydk_108 ydk_108 4096 Jan 20 22:14 ..
- -rw-rw-r-- 1 ydk_108 ydk_108 82 Jan 20 22:16 makefile
- -rwxrwxr-x 1 ydk_108 ydk_108 16832 Jan 20 22:26 mycommand
- -rw-rw-r-- 1 ydk_108 ydk_108 895 Jan 20 22:26 mycommand.c
- ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$
复制代码 这个程序被ls替换了。
一开始这个历程有自己的数据段和代码段,ls有自己的数据段和代码段,但是这里ls直接把mycommand的历程的代码段和数据段在内存中覆盖了,进而把页表中的虚拟地址也更改了。
这个就叫做历程替换,也就是历程替换的基本原理。
3.2 替换原理
用fork创建子历程后执行的是和父历程相同的程序(但有大概执行不同的代码分支),子历程往往要调用一种exec函数以执行另一个程序。当历程调用一种exec函数时,该历程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新历程,以是调用exec前后该历程的id并未改变。
3.3 替换函数
其实有六种以exec开头的函数,统称exec函数:(这六个都是库函数调用接口,他们的区别只是传参的不同)
- #include <unistd.h>
-
- int execl(const char *path, const char *arg, ...);// 第一个参数是完整路径,第二个参数通常是程序名
- int execlp(const char *file, const char *arg, ...);//execlp自己会在默认的PATH系统的环境变量里面找,所以可以不带路径
- int execle(const char *path, const char *arg, ...,char *const envp[]);// e 表示可以传递环境变量,最后一个参数是环境变量数组
- int execv(const char *path, char *const argv[]);// v 表示参数以数组形式传递,比execl更适合参数数量不确定的情况
- int execvp(const char *file, char *const argv[]);// 结合了v(数组)和p(PATH搜索)的特点
- int execvpe(const char *file, char *const argv[], char *const envp[]);// 结合了v(数组)、p(PATH搜索)和e(环境变量)的特点
复制代码 这个和上面6个不一样,这个是体系调用接口,上面6个是库函数调用接口。
int execve(const char *path, char *const argv[], char *const envp[]);
函数解释
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1
- 以是exec函数只有出错的返回值而没有成功的返回值。
要执行一个程序起首就要找到这个程序,以是这内里全部的exec函数的第一个参数都是帮我们绝对如何找到这个程序的。
找到这个程序后,要告诉体系如何执行这个程序。要不要涵盖选项?涵盖哪些?
如果exec*能够实现体系程序,那么可以实现我们自己的程序吗?
可以的。
定名理解
这些函数原型看起来很容易混,但只要把握了规律就很好记。 (l和v不会同时出现)
- l(list) : 表示参数采用列表,一个一个传参
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
3.4 多历程版 – 验证各种程序替换接口
- #include <stdio.h>
- #include <unistd.h>// 提供execl, getpid等函数
- #include <stdlib.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- int main(){
- pid_t id =fork();
- if(id == 0){
- //child
- printf("before: I am a process, pid:%d, ppid:%d\n",getpid(),getppid());
- sleep(5);
- execl("/usr/bin/ls","ls", "-a", "-l", NULL);
- printf("after: I am a process, pid:%d, ppid:%d\n",getpid(),getppid());
- exit(0);
- }
- //father
- pid_t ret = waitpid(id, NULL, 0);
- if(ret > 0){
- printf("wait success, father pid:%d, ret id:%d\n",getpid(), ret);
- }
- sleep(5);
- return 0;
- }
复制代码 运行效果:
- ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$ ./mycommand
- before: I am a process, pid:261679, ppid:261678
- total 36
- drwxrwxr-x 2 ydk_108 ydk_108 4096 Jan 20 22:54 .
- drwxrwxr-x 13 ydk_108 ydk_108 4096 Jan 20 22:14 ..
- -rw-rw-r-- 1 ydk_108 ydk_108 82 Jan 20 22:16 makefile
- -rwxrwxr-x 1 ydk_108 ydk_108 17008 Jan 20 22:54 mycommand
- -rw-rw-r-- 1 ydk_108 ydk_108 628 Jan 20 22:54 mycommand.c
- wait success, father pid:261678, ret id:261679
- ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$
复制代码 通过下令查看:
- ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$ while :; do ps ajx |head -1 && ps ajx |grep mycommand |grep -v grep; sleep 1;echo"----------";done
- PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
- echo----------: command not found
- PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
- echo----------: command not found
- PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
- 261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
- 261678 261679 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
- echo----------: command not found
- PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
- 261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
- 261678 261679 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
- echo----------: command not found
- PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
- 261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
- 261678 261679 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
- echo----------: command not found
- PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
- 261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
- 261678 261679 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
- echo----------: command not found
- PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
- 261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
- 261678 261679 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
- echo----------: command not found
- PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
- 261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
- echo----------: command not found
- PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
- 261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
- echo----------: command not found
- PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
- 261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
- echo----------: command not found
- PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
- 261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
- echo----------: command not found
- PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
- echo----------: command not found
- PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
- echo----------: command not found
- ^C
复制代码 程序替换有没有创建新的子历程?
没有,子历程的pid没有变。不创建新历程,只进行历程的程序代码和数据的替换结构。
增补知识:
- 为什么after后面的代码没有执行呢?
由于程序替换后,after的printf的代码已经是老程序的代码了,被ls覆盖掉了。
- 程序替换成功后,exit之后的代码不会执行,那么如果替换失败呢?(比方路径写错,下令不存在)
那么程序就会继承今后走。以是exit*函数只有失败有返回值,没有成功的返回值。
- 我们的CPU怎么知道新程序应该进入的入口地址呢?
Linux中形成的可执行程序是有格式的,EIF,是有可执行程序的表头的,可执行程序的入口就在表中。
- 实际上不但是可以调用ls下令,还可以调用自己写的C程序,python程序,shell脚本,Java程序。但是无论是我们的可执行程序照旧脚本,为什么能够跨语言调用呢?
由于全部的语言运行起来,本质上都是历程。
- 环境变量是什么时间给历程的呢?
环境变量实际上也是数据。当我们创建子历程的时间,环境变量就已经被子历程继承下去了。以是程序替换中,环境变量信息不会被子历程替换。
- 如果我想给子历程传递环境变量,应该怎么传递呢?
- 新增环境变量
直接给bash添加环境变量
在父历程中使用putenv()添加环境变量
- 彻底替换环境变量
使用execle的时间把环境变量参数换成自己的
3.5 自定义shell
touch.sh
- #!/usr/bin/bash
- echo "hello 1"
- echo "hello 1"
- echo "hello 1"
- echo "hello 1"
- echo "hello 1"
- ls -a -l
复制代码 运行:bash touch.sh
- ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$ bash touch.sh
- hello 1
- hello 1
- hello 1
- hello 1
- hello 1
- total 40
- drwxrwxr-x 2 ydk_108 ydk_108 4096 Jan 21 00:00 .
- drwxrwxr-x 13 ydk_108 ydk_108 4096 Jan 20 22:14 ..
- -rw-rw-r-- 1 ydk_108 ydk_108 82 Jan 20 22:16 makefile
- -rwxrwxr-x 1 ydk_108 ydk_108 17008 Jan 20 22:54 mycommand
- -rw-rw-r-- 1 ydk_108 ydk_108 628 Jan 20 22:54 mycommand.c
- -rw-rw-r-- 1 ydk_108 ydk_108 101 Jan 21 00:00 touch.sh
- ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$
复制代码 下令行交互的shell代码:
- // 包含必要的头文件
- #include <stdio.h> // 标准输入输出
- #include <stdlib.h> // 标准库函数
- #include <assert.h> // 断言
- #include <string.h> // 字符串操作
- #include <unistd.h> // UNIX标准函数
- #include <sys/types.h> // 系统数据类型
- #include <sys/wait.h> // 进程等待
- // 定义shell提示符的格式
- #define LEFT "[" // 左边界符号
- #define RIGHT "]" // 右边界符号
- #define LABLE "#" // 提示符标签
- #define DELIM " \t" // 命令分隔符(空格和制表符)
- #define LINE_SIZE 1024 // 命令行最大长度
- #define ARGC_SIZE 32 // 命令参数最大个数
- #define EXIT_CODE 44 // 子进程退出码
- // 全局变量定义
- int lastcode = 0; // 上一条命令的返回值
- int quit = 0; // 退出标志
- extern char **environ; // 环境变量数组
- char commandline[LINE_SIZE]; // 存储命令行
- char *argv[ARGC_SIZE]; // 存储解析后的命令参数
- char pwd[LINE_SIZE]; // 存储当前工作目录
- // 自定义环境变量存储空间
- char myenv[LINE_SIZE];
- // 获取当前用户名
- const char *getusername(){
- return getenv("USER");
- }
- // 获取主机名
- const char *gethostname(){
- return getenv("HOSTNAME");
- }
- // 获取当前工作目录
- void getpwd(){
- getcwd(pwd, sizeof(pwd));
- }
- // 实现shell交互界面
- void Interact(char *cline, int size){
- getpwd();
- // 打印提示符,格式为 [用户名@主机名 当前目录]#
- printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname(), pwd);
- // 读取用户输入
- char *s = fgets(cline, size, stdin);
- assert(s);
- (void)s;
-
- // 去掉输入字符串末尾的换行符
- cline[strlen(cline)-1] = '\0';
- }
- // 解析命令行字符串为参数数组
- int splistring(char cline[], char *_argv[]){
- int i = 0;
- // 获取第一个参数(命令名)
- argv[i++] = strtok(cline, DELIM);
- // 获取后续参数
- while(_argv[i++] = strtok(NULL, DELIM));
- return i-1; // 返回参数个数
- }
- // 执行外部命令
- void NormalExcute(char *_argv[]){
- pid_t id = fork();
- if(id < 0){
- perror("fork");
- return;
- }
- else if(id == 0){ // 子进程
- // 使用execvp执行命令
- execvp(_argv[0], _argv);
- exit(EXIT_CODE);
- }
- else{ // 父进程
- int status = 0;
- // 等待子进程结束
- pid_t rid = waitpid(id, &status, 0);
- if(rid == id){
- // 保存命令执行结果
- lastcode = WEXITSTATUS(status);
- }
- }
- }
- // 处理内建命令
- int buildCommand(char *_argv[], int _argc){
- // cd命令
- if(_argc == 2 && strcmp(_argv[0],"cd")==0){
- chdir(argv[1]); // 改变当前目录
- getpwd(); // 更新pwd
- sprintf(getenv("PWD"),"%s", pwd); // 更新PWD环境变量
- return 1;
- }
- // export命令:设置环境变量
- else if(_argc == 2 && strcmp(_argv[0],"export")==0){
- strcpy(myenv, _argv[1]);
- putenv(myenv);
- return 1;
- }
- // echo命令:显示文本或变量值
- else if(_argc == 2 && strcmp(_argv[0],"echo")==0){
- if(strcmp(_argv[1],"$?")==0){ // 显示上一条命令的返回值
- printf("%d\n",lastcode);
- lastcode=0;
- }
- else if(*_argv[1]=='$'){ // 显示环境变量的值
- char *val = getenv(_argv[1]+1);
- if(val) printf("%s\n",val);
- }
- else{ // 显示普通文本
- printf("%s\n",_argv[1]);
- }
- return 1;
- }
-
- // 为ls命令添加颜色支持
- if(strcmp(_argv[0],"ls")==0){
- _argv[_argc++] = "--color";
- _argv[_argc] = NULL;
- }
-
- return 0; // 不是内建命令
- }
- int main(){
- while(!quit){
- //1.
- //2.交互问题,获取命令行
- Interact(commandline, sizeof(commandline));
- //3.子串分割的问题,解析命令行
- int argc = splistring(commandline, argv);
- if(argc == 0) continue;
-
- //4.指令的判断,判断是不是内建命令
- //debug
- //for(int i=0;argv[i];i++){
- // printf("[%d]: %s\n",i,argv[i]);
- //}
- //内键命令本质上就是shell内部的一个函数。
- int n = buildCommand(argv, argc);
-
-
- //5.普通命令的执行
- // 如果不是内建命令,则作为外部命令执行
- if(!n) NormalExcute(argv);
- }
- return 0;
- }
复制代码 打印效果:
- ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$ ./myshell
- [ydk_108@(null) /home/ydk_108/108/lesson17]# ls -a
- . .. makefile mycommand mycommand.c myshell myshell.c touch.sh
- [ydk_108@(null) /home/ydk_108/108/lesson17]# pwd
- /home/ydk_108/108/lesson17
- [ydk_108@(null) /home/ydk_108/108/lesson17]#
- Segmentation fault
- ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$
复制代码 以是,当我们进行登陆的时间,体系就是要启动一个shell历程。
我们shell本身的环境变量是从哪里来的?
当用户登录的时间,shell会读取目次用户下的.bash_profile文件,内里生存了导入环境变量的方式。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |