目录
一、进程创建
描述一下,fork创建子进程,操作系统都做了什么?
fork后父子进程是全部包括之前的代码都共享;
fork创建子进程(内核数据结构(OS创建) + 进程代码和数据(一般从磁盘中来,也就是C/C++程序加载之后的结果));
创建子进程,给子进程分配对应的内核结构,必须是子进程自己独有的,因为进程具有独立性,理论上,子进程也要有自己的代码和数据,可是一般而言,我们没有加载的过程,也就是说,子进程没有自己的代码和数据,所以子进程只能使用父进程的代码和数据那么:
代码:都是不可修改的,只能读取,所以父子共享是没有问题的!
数据:可能被修改的,所以必须分离!
对于数据:
创建子进程时,不需要将不会访问的,或者只会读取的数据都拷贝一份,但是为了保证独立性,还是需要一定数据的拷贝,这里只拷贝将来会被父进程或者子进程写入的数据;但是一般而言,即使是操作系统也无法提前知道那些空间可能会被写入的,而且即使是提前知道了,也不能保证立马就使用拷贝的空间。所以OS选择了写实拷贝技术,来进行父子进程数据的分离。
写实拷贝:父子进程共用代码,只有在进行数据写入时才重新内存分布;
为什么选择写实拷贝?
1.用的时候在给你分配空间(高效)
2.操作系统无法在代码执行前预知那些空间会被拷贝
1.我们的代码汇编之后,会有很多行的代码,而且每行代码都加载到内存之后,都有对应的地址;
2.因为进程随时可能被中断(可能并没有执行完),下次回来,还必须保证从之前的位置继续运行,就要求CPU必须随时记录下当前进程执行的位置,所以CPU内有对应的寄存器数据,用来记录当前进程的执行位置!
寄存器在CPU内,只有一份,寄存器内的数据,是可以有多份的 !(进程的上下文)
fork创建子进程的时候,虽然父子进程会各自调度,各种会修改EIP(存储进程代码位置的寄存器),但是已经不重要了,因为子进程已经认为自己的ETI起始值是在fork之后的代码了!!所以子进程会从fork之后的代码开始执行;
写实拷贝:
父子进程代码共享,子进程页表拷贝父进程页表,在子进程或者父进程进行写入操作时,页表发生改变,重新分配内存;
因为有写实拷贝技术的存在,所以,父子进程得以彻底分离,完成了进程独立性的技术保证;
写实拷贝是一种延时申请技术,可以提高整机的内存使用率;
二、进程终止
1.进程终止时,操作系统做了什么?
释放进程中申请的相关内核数据结构和对应数据和代码;(本质就是释放系统资源)
2.进程终止的常见方式?
- a.代码跑完,结果正确
- b.代码跑完,结果不正确
- c.代码没有跑完,程序崩溃了(信号部分,涉及一点点)
main函数返回值的含义是什么?return 0的含义是什么?为什么总是0?
0:退出码;返回上一进程,用来判断进程执行结果的,可以忽略。
命令:echo $? // 获取上一个进程的退出码
程序崩溃了,退出码无意义。一般而言退出码对应的return语句,没有被执行!
3.用代码,如果终止一个进程?
- main函数内的return语句就是终止进程的,return 退出码;
- exit(int status)在任何地方调用,都表示终止进程;(库函数)
- _exit(int status)终止进程;(系统接口直接终止进程,不进行清理函数和缓冲区的刷新)
库函数 vs 系统接口
逐渐底层:语言 -> 库函数 -> 系统接口 -> 操作系统
printf - \n 数据是存放在“缓冲区”的,那么这个”缓冲区“在哪里,谁维护的?
一定不在操作系统内部;如果是操作系统维护的,那么_exit(int statut) 就可以刷新缓冲区;"缓冲区"是C标准库维护的;
三、进程等待
- 子进程退出,父进程不管子进程,子进程就要处于僵尸状态 ---- 导致内存泄漏
- 父进程创建子进程,是让子进程办事的,那么子进程把任务完成得怎么样?父进程需要关心吗?如果需要,如何得知?如果不需要,该怎么处理?
- 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
僵尸状态的进程,即使是kill -9也无法杀死一个已经死去的进程;
如何等待?等待是什么?
wait:回收僵尸进程的问题
参数:wait(int *status)
让父进程来等待子进程退出,并回收
pid_t ret = wait(NULL);
if (ret > 0)
{
printf("等待子进程成功,ret: %d\n", ret);
}
waitpid;获取子进程退出结果的问题
参数:waitpid(pid_t pid, int *status, int option)
- pid = -1 所有子进程
- pid > 0 指定的子进程
- status 获取子进程退出结果(退出码)输出型参数(是按照bit位的方式进行划分的,只使用低16位,高8位表示退出码)
- option 默认为0 (阻塞态等待)(WNOHANG非阻塞态等待)
进程异常退出,或者崩溃,本质是操作系统将这进程杀掉了 (通过发送信号的方式);
status的低7位bit位,表示进程收到的信号!
程序异常,不仅仅是内部代码的问题,也可能是外部杀掉了你的进程;
- 父进程通过wait/waitpid可以拿到子进程的退出结果,为什么要用wait/waitpid呢?不直接使用全局变量?(不可以,进程具有独立性,写实拷贝)
- 既然进程具有独立性的,进程的退出码不也是进程的数据码?父进程又怎么拿到的呢?wait/waitpid究竟做了什么呢?
僵尸进程:至少要保留进程的PCB信息,task_struct里面保留了任何进程退出时的结果信息!可知wait/waitpid本质是读取了子进程的PCB结构体中的信息;
- wait/waitpid有这个权限吗?
当然有,它是系统调用,操作系统操作!
四、进程替换
-
1、是什么?
fork()后,父子进程各自执行父进程代码的一部分,父子代码共享,数据写时拷贝!
如果子进程不和父进程共享代码,而是想自己执行一个全新的程序,程序替换是通过特定的接口,加载磁盘上的一个权限的程序(代码和数据),加载到调用进程的地址空间中!
原理:
-
2、怎么办?
1、不创建子进程
1 #include <iostream>
2 #include <vector>
3 #include <unistd.h>
4 #include <stdio.h>
5
6 int main()
7 {
8 printf("当前进程开始的代码\n");
9
10
11 printf("当前进程结束的代码\n");
12 return 0;
13 }
~
execl进程替换函数;
1 #include <iostream>
2 #include <vector>
3 #include <unistd.h>
4 #include <stdio.h>
5
6 int main()
7 {
8 printf("当前进程开始的代码\n");
9
10 execl("/usr/bin/ls", "ls", "-l", NULL);
11
12 printf("当前进程结束的代码\n");
13 return 0;
14 }
运行结果
execl是进程替换的函数,调用之后,进程所有的代码都会被替换,之后的代码就不会执行了!!!
execl为什么调用成功,没有返回值呢?
调用成功后,将之前进程的返回值等都全部替换了;
2、创建子进程
简易的xshell代码
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/wait.h>
4 #include <stdlib.h>
5
6 int main()
7 {
8 pid_t id = fork();
9 if (id == 0)
10 {
11 //子进程
12 printf("子进程开始运行: pid:%d\n", getpid());
13 sleep(2);
14 execl("/usr/bin/ls", "ls", "-l", NULL);
15 exit(1);
16 }
17 else
18 {
19 //父进程
20 int status = 0;
21 printf("父进程开始运行: pid:%d\n", getpid());
22 pid_t id = waitpid(-1, &status, 0);
23
24 if (id > 0)
25 {
26 printf("wait success, exit code: %d\n", WEXITSTATUS(status));
27 }
28 }
29
30
int execv(const char * path, char *const argv[]); -- 用数组的方式传参数
int execlp(cosnt char* file, char const* arg, ... ); --- 会自己在环境变量中查找
int execlp("ls", "ls", "-a", "-l", "NULL");
前面的"ls"表示要执行谁;
后面的"ls"表示要如何执行;
这些函数本质就是将自己的这些参数,传给可执行程序中的main函数;
int execvp(const char* file, char *const argv[]); --- 会自己在环境变量中查找
char* const argv[] = { (char*)"ls",(char*)"-l" ,"NULL"};
int execvp("ls", argv);
int execle(const char* path, const char *arg, ... ,const char* envp[]);
1.如何执行其他我写的C、C++二进制可执行程序
2.如何执行其他语言的程序
//子进程
12 printf("子进程开始运行: pid:%d\n", getpid());
15 execlp("python", "python", "test.py", NULL);
16 exit(1)
即:exec*就是加载器的底层接口!
环境变量具有全局性
为什么环境变量会有全局性呢?
int execle(const char* path, const char *arg, ... ,const char* envp[]);
这个函数里面的const char* envp[],就是将父进程的环境变量传给子进程,如此递归下去,环境变量就具有了全局性;
int execve (const char *filename, char *const argv [], char *const envp[]);
真正的系统调用!!之前的都是系统提供的基本封装;