🌈个人主页: 秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
🔥 系列专栏: https://blog.csdn.net/qinjh_/category_12625432.html
目录
前言
💬 hello! 各位铁子们大家好哇。
今日更新了Linux的进程替换的内容
🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝
进程程序替换
代码和现象
运行后,发现使用了ls命令,而且打印end的语句也不见了。
exec*函数的作用:让进程通过exec*函数把全新的程序替换到自己对应的代码和数据,然后执行新的程序。
exec*函数执行完毕后,后续的代码不见了,因为被替换了。
替换函数
其实有六种以exec开头的函数,统称exec*函数:
- 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 execve(const char *path, char *const argv[], char *const envp[]);
替换原理
我们自己的代码编译运行后就会变成可执行程序,运行起来后就变成进程。
进程=内核数据结构+代码和数据
替换的意义是:内核数据结构不变,个别属性可能会变,用新程序的代码代替老程序的代码。
进程的替换没有创建新的进程!所以调用exec*前后该进程的id并未改变。
站在被替换进程的角度,本质就是这个程序由磁盘被加载到内存中了。(冯诺依曼体系)
函数解释
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1
- 所以exec*函数只有出错的返回值而没有成功的返回值。
如上图,没有lss命令,所以替换会失败。如果替换成功,就不会向后继续运行。所以只要继续运行了,就一定是替换失败了。
cpp
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<sys/types.h>
5 #include<sys/wait.h>
6
7 int main()
8 {
9 printf("testexec ... begin!\n");
10
11 pid_t id=fork();
12 if(id==0)
13 {
14 sleep(2);
15 //child
16 execl("/usr/bin/ls","ls","-l","-a",NULL);
17 exit(1);
18 }
19
20 //father
21 int status=0;
22 pid_t rid=waitpid(id,&status,0);
23 if(rid>0)
24 {
25 printf("father wait success,child exit code:%d\n",WEXITSTATUS(status));
26 }
27 printf("testexec ... end!\n");
28 return 0;
29 }
运行结果如上图, 进程替换成功了,父进程也等待成功。上面是用fork创建子进程,让子进程去替换,让子进程完成任务,而不改变父进程。这也是进程替换的重要意义。
命名理解
- l(list) : 表示参数采用列表
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
以execl为例,path表示要执行的程序的路径。第二个是可变参数,如果我们用ls命令,后面需要选项的话,就用逗号隔开。选项带完后,后面必须加上NULL,这是格式要求。
上面的选项参数的隔开就是l(列表)的体现。
execv的第一个参数跟上面的一样。v就是vector的意思,所以参数二就是传数组。
带有p的就是 用户可以不传执行文件的路径(但是文件名称要传)。直接告诉exec*,我想执行谁就行。有了p,系统会自动在环境变量PATH中进行查找。
注意上面的参数1表示我想执行谁,参数2表示我想怎么执行。 二者含义不一样。
用一个可执行程序替换另一个可执行程序:
当我们想要通过make一下就能生成两个可执行程序,可以通过.PHONY设置一个为目标,把想要生成的可执行文件作为依赖方法,这样就能同时生成两个了。
上面是通过一个程序替换另一个程序的例子。
有了这个例子的基础,接下来介绍execvpe
cpp
1 #include<iostream>
2 #include<unistd.h>
3 using namespace std;
4
5 int main(int argc,char *argv[],char* env[])
6 {
7 int i=0;
8 for(;argv[i];i++)
9 {
10 printf("argv[%d]:%s\n",i,argv[i]);
11 }
12
13 printf("------------------------\n");
14 for(i=0;env[i];i++)
15 {
16 printf("env[%d]:%s\n",i,env[i]);
17 }
18 printf("------------------------\n");
19
20
21 cout<<"hello C++,I am a C++ pragma!"<<getpid()<<endl;
22 cout<<"hello C++,I am a C++ pragma!"<<getpid()<<endl;
23 cout<<"hello C++,I am a C++ pragma!"<<getpid()<<endl;
24 cout<<"hello C++,I am a C++ pragma!"<<getpid()<<endl;
25 return 0;
26 }
testexec.c
cpp
7 int main()
8 {
9 printf("testexec ... begin!\n");
10
11 pid_t id=fork();
12 if(id==0)
13 {
14 char* const argv[]=
15 {
16 (char*)"mypragma",
17 NULL
18 };
19
20 char* const envp[]=
21 {
22 (char*)"HAHA=1111",
23 (char*)"HEHE=2222",
24 NULL
25 };
26 printf("child pid:%d\n",getpid());
27 sleep(2);
28 execvpe("./mypragma",argv,envp);
43 exit(1);
44 }
46 //father
47 int status=0;
48 pid_t rid=waitpid(id,&status,0);
49 if(rid>0)
50 {
51 printf("father wait success,child exit code:%d\n",WEXITSTATUS(status));
52 }
53 printf("testexec ... end!\n");
54 return 0;
55 }
编译运行testexec.c程序后,就会用mypragma程序替换。里面的execvpe,参数1是要替换的文件名,参数2表示怎么执行,参数3就是环境变量。参数2和参数3都会被传到替换文件中。所以运行结果如下图:
对于main函数的子进程,它的父进程main本身也是bash的子进程,所以可以通过环境变量的第三方指针extern char** environ 获取系统的环境变量。
所以,参数3的意义就是整体替换所有的环境变量。
事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在 man手册第3节。 之所以弄那么多接口,主要是为了支持不同的应用场景。
简易shell
cpp
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <errno.h>
5 #include <unistd.h>
6 #include <sys/types.h>
7 #include <sys/wait.h>
8
9 #define SIZE 512
10 #define ZERO '\0'
11 #define SEP " "
12 #define NUM 32
13 #define SkipPath(p) do{ p += (strlen(p)-1); while(*p != '/') p--; }while(0)
14
15
16 // 为了方便,我就直接定义了
17 char cwd[SIZE*2];
18 char *gArgv[NUM];
19 int lastcode = 0;
20
21 void Die()
22 {
23 exit(1);
24 }
25
26 const char *GetHome()
27 {
28 const char *home = getenv("HOME");
29 if(home == NULL) return "/";
30 return home;
31 }
32
33 const char *GetUserName()
34 {
35 const char *name = getenv("USER");
36 if(name == NULL) return "None";
37 return name;
38 }
39 const char *GetHostName()
40 {
41 const char *hostname = getenv("HOSTNAME");
42 if(hostname == NULL) return "None";
43 return hostname;
44 }
45 // 临时
46 const char *GetCwd()
47 {
48 const char *cwd = getenv("PWD");
49 if(cwd == NULL) return "None";
50 return cwd;
51 }
52
53 // commandline : output
54 void MakeCommandLineAndPrint()
55 {
56 char line[SIZE];
57 const char *username = GetUserName();
58 const char *hostname = GetHostName();
59 const char *cwd = GetCwd();
60
61 SkipPath(cwd);
62 snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, strlen(cwd) == 1 ? "/" : cwd+1);
63 printf("%s", line);
64 fflush(stdout);
65 }
66
67 int GetUserCommand(char command[], size_t n)
68 {
69 char *s = fgets(command, n, stdin);
70 if(s == NULL) return -1;
71 command[strlen(command)-1] = ZERO;
72 return strlen(command);
73 }
74
75
76 void SplitCommand(char command[], size_t n)
77 {
78 (void)n;
79 // "ls -a -l -n" -> "ls" "-a" "-l" "-n"
80 gArgv[0] = strtok(command, SEP);
81 int index = 1;
82 while((gArgv[index++] = strtok(NULL, SEP))); // done, 故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL,刚好让gArgv最后一个元素是NULL, 并且while判断结束
83 }
84
85 void ExecuteCommand()
86 {
87 pid_t id = fork();
88 if(id < 0) Die();
89 else if(id == 0)
90 {
91 // child
92 execvp(gArgv[0], gArgv);
93 exit(errno);
94 }
95 else
96 {
97 // fahter
98 int status = 0;
99 pid_t rid = waitpid(id, &status, 0);
100 if(rid > 0)
101 {
102 lastcode = WEXITSTATUS(status);
103 if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);
104 }
105 }
106 }
107
108 void Cd()
109 {
110 const char *path = gArgv[1];
111 if(path == NULL) path = GetHome();
112 // path 一定存在
113 chdir(path); //更改当前的工作路径
114
115 // 刷新环境变量
116 char temp[SIZE*2];//临时缓冲区
117 getcwd(temp, sizeof(temp)); //得到当前进程的绝对路径
118 snprintf(cwd, sizeof(cwd), "PWD=%s", temp);//
119 putenv(cwd); // 导入新的环境变量
120 }
121
122 int CheckBuildin()
123 {
124 int yes = 0;
125 const char *enter_cmd = gArgv[0];
126 if(strcmp(enter_cmd, "cd") == 0)
127 {
128 yes = 1;
129 Cd();
130 }
131 else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)
132 {
133 yes = 1;
134 printf("%d\n", lastcode);
135 lastcode = 0;
136 }
137 return yes;
138 }
139
140
141 int main()
142 {
143 int quit = 0;
144 while(!quit)
145 {
146 // 1. 我们需要自己输出一个命令行
147 MakeCommandLineAndPrint();
148
149 // 2. 获取用户命令字符串
150 char usercommand[SIZE];
151 int n = GetUserCommand(usercommand, sizeof(usercommand));
152 if(n <= 0) return 1;
153
154 // 3. 命令行字符串分割.
155 SplitCommand(usercommand, sizeof(usercommand));
156
157 // 4. 检测命令是否是内建命令
158 n = CheckBuildin();
159 if(n) continue;
160 // 5. 执行命令
161 ExecuteCommand();
162 }
163 return 0;
164 }