Linux学习记录——십오 进程控制(2)
文章目录
1、程序替换
在之前的父子进程中,创建子进程之前,父进程就有了一些代码,fork创建子进程后,子进程就运行父进程的一部分代码。进程替换则是为了让子进程运行不属于父进程的代码。
1、了解替换
程序替换的命令是exec。
前两个函数参数里有三个点…,三个点意味着可变参数列表。
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4
5 int main()
6 {
7 printf("begin.....
");
8 printf("begin.....
");
9 printf("begin.....
");
10 printf("进程开始, PID是: %d
", getpid());
11
12 execl("/bin/ls", "ls", "-a", "-l", NULL);
13
14 printf("end.......
");
15 printf("end.......
");
16 printf("end.......
");
17 return 0;
18 }
结果如图
在计算机内部,指令是在磁盘里的,程序运行后,它的pcb就在内存中。当遇到execl后,会把磁盘中的可执行程序替换到当前进程的数据。实际打印出来时,后面的end没有出现,这时候已经替换过了。
2、基本原理
每个进程都有自己的虚拟内存,页表,和映射好的物理内存。进行程序替换时,会把原来在物理内存中映射过去的空间的代码和数据替换为磁盘中可执行程序的代码和数据,这就是程序替换。
那么程序替换有没有创建新的进程呢?它没有创建进程。它只是把一个加载好的新的程序给替换了过来,内存中原有进程的pid并没有变。
对于磁盘中的程序来说,这个程序是直接被加载进了内存中,程序就是通过程序替换加载进内存的。exec等函数可以看做加载器。
操作系统内部会先把数据结构都创建出来,然后再去找程序,这也就是加载。
3、多进程
刚才的程序看不到打印end是因为新的代码和数据替换过去了,老代码已经没有人去执行了,所以就没有执行。所以程序替换只能全局替换,不能局部替换。
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/wait.h>
5
6 int main()
7 {
8 pid_t id = fork();
9 if(id == 0)
10 {
11 //child
12 printf("我是子进程: %d
", getpid());
13 execl("/bin/ls", "ls", "-a", "-l", NULL);
14 }
15
16 sleep(5);
17
18 //father
19 printf("我是父进程: %d
", getpid());
20 waitpid(id, NULL, 0);
21 return 0;
22 }
像上面这样,可以发现,程序替换只能影响当前进程。因为当进行程序替换时,子进程会改变物理内存的数据,系统就会写时拷贝,给子进程另开一个空间,所以它们互不干扰。这样的替换让子进程运行了新代码,所以物理内存的代码区可以发生进程替换。
如果程序替换失败,那么就继续执行老代码,并且会返回-1.
int n = execl("/bin/lssssssss", "lssssssss", "-a", "-l", NULL);
printf("Fail : %d
", n);
exit(0);
这样写确实会看到-1,不过没有意义,因为失败就会往下进行,成功就不继续了,所以不需要看返回值。
也写一下父进程获取退出码的代码
20 //father
21 int status = 0;
22 printf("我是父进程: %d
", getpid());
23 waitpid(id, &status, 0);
24 printf("child exit code: %d
", WEXITSTATUS(status));
25 return 0;
这样子进程出了什么错误,父进程就会展现出对应的退出码。
4、程序替换接口
execl函数
int execl(const char* path, const char* arg, …);
path是路径,arg就是命令执行的方式,把命令行参数一个个传过来,最后以NULL结尾。
execv函数
int execv(const char* path, char* cont argv[])
argv意味要传数组。
char* const myargv[] = {"ls", "-a", "-l", "-n", NULL};
execv("/bin/ls", myargv);
execlp函数
int execlp(const char* file, const char* arg, …)
execl后面加上p,第一个参数也改为file,这个函数的特点,当我们执行指定程序的时候,只需要传指定程序名即可,系统会在环境变量PATH中查找。
execlp("ls", "ls", "-a", "-l", "-n", NULL);
execvp函数
int execvp(const char* file, char* const argv[]);
综合上面四个可以看出规律,带p说明函数会去环境变量里找路径,带v则说明要传数组。
execle函数
int execle(const char* path, const char* arg, …, char* const envp[])
除了执行系统命令,还可以运行自己写的程序。
e代表环境变量,可以传自定义的环境变量,但是会影响原有的环境变量。
如果想把自定义变量放到环境变量里面,那么可以先extern char** environ; 然后在数组后面添加上environ。如果要两个都打印,那就putenv(“MYENV=YouCanSeeMe”);括号里保留的是写的自定义变量代码。所以环境变量能够被在全局域就可以靠这个函数来实现。
如果不这样写,也可以在文件外面
export MYENV=YouCanSeeMe
./myproc
因为这时候加入的环境变量在bash里,那么可以给到创建的子进程里,只要execle里面传了environ就行。
execvpe函数
int execvpe(const char* file, char* const argv[], char* const envp[]);
execve函数
int execve(const cjar* filename, char* const argv[], char* const envp[]);
总结
总共7个函数,只有最后一个execve是系统调用的函数,其他6个都是对它的封装。也就是无论调哪一个,内部都是调用了execve这个函数。
2、编写极简的shell(bash)
要先打印出左边的用户,然后后面可以输入命令。
1 myshell:myshell.c
2 gcc -o $@ $^ -std=c99
3 .PHONY:clean
4 clean:
5 rm -f myshell
1 #include <stdio.h>
2 #include <string.h>
3 #include <assert.h>
4 #include <unistd.h>
5 #include <sys/types.h>
6 #include <sys/wait.h>
7
8 #define MAX 1024
9
10 int main()
11 {
12 char commandstr[MAX] = {0};
13 while(1)
14 {
15 printf("[zyd@hjdkhs2houqwie iiruoeg]# ");
16 fflush(stdout);
17 char* s = fgets(commandstr, sizeof(commandstr), stdin);
18 assert(s);
19 (void)s;//保证在release方式发布的时候,因为去掉了assert,所以会出现因s没有被带来的编译警告,这里什么都不做仅充当一次使用
20 commandstr[strlen(commandstr) - 1] = '