目录
1.fork函数
1.1如何理解fork函数有两个返回值问题
fork()函数是系统给用户的接口函数,那么fork()函数的实现在OS里,里边的大致内容是
pid_t fork(){
1.创建子进程PCB
2.赋值,继承父进程的一部分内容
3.创建并设置页表等
4.将子进程放入调度队列
5.return pid;
}
在5 .return pid;即返回之前,此时其实子进程已经创建出来并调度了 ,所以 fork()之后父子进程共享代码不完全对在return 之前已经有两个执行流了
父子进程都要return ,所以父进程返回的是创建的子进程的pid,子进程返回给自己的是0
所以return要被调度两次,父子各自执行一次return,所以严谨的说,在fork内部已经有两个执行流生成了
1.2如何理解fork给父进程返回自己的pid,自己返回0
对于孩子和父亲,
一个父亲,有多个孩子,但没有一个孩子多个父亲这种,
所以对于父进程,肯定要记住孩子的信息等即(pid)从而方便管理
1.3如何理解同一个id值,有两个不同的值
pid_t id=fork();
在运行的时候,父子进程是共享内存的,即同时用一个物理空间,但是如果有写入,就会对于同一个值,在拷贝一份,让写入的哪个其实对应这个拷贝的
返回的本质其实就是写入,有写入,那么久会发生写时拷贝,所以会发生同一个id有两个不同的值,地址也相同,这个地址是虚拟地址
2.进程终止
2.1进程退出码
在我们写mian函数的时候,都要最后写一个return 0;
这个return 0; 是什么呢?
这个0是进程退出时候,对应的退出码,标志执行结果是否正确
可以判断退出码的结果来判断程序是否执行正确
查看退出码的办法:
echo $?
$? 保存最近一次可执行程序的退出码
注意细节,echo也是一个程序,其实会执行一次,所以后边都变为0了
退出码的意义:用0表示成功,非0表示失败,不同的退出码标记不同的错误
2.2进程退出
2.2.1进程常见退出办法
1.代码跑完,执行结果正确
2.代码跑完,执行结果错误
3.代码异常终止
所以分为正常终止和异常终止两个情况
1.正常退出
①通过main函数return 返回码
②调用exit(返回码)直接退出
③_exit(返回码)退出
2.异常退出,ctrl+c,信号终止(除零或者野指针等异常信号)
2.2.3exit()函数和_exit()函数
在写代码时候,exit()函数可以直接终止当前进程,并且exit(num)返回里边的num即退出码,exit()是库函数
_exit()函数也可以终止并且返回退出码,但是不同的是,这个是系统调用
exit()是一个库函数,而_exit是一个系统调用
库函数是在系统调用的基础上来进行封装的
exit()在终止的时候,会刷新缓冲区,而_exit()函数终止则不会刷新缓冲区,所以可以引出,----》缓冲区是不在OS层的,而是在用户层的
3.进程等待
之前讲过僵尸进程的危害,如果僵尸进程不解决,那么则会造成内存泄漏
此时可以用进程等待的方式让父进程等待子进程,但是子进程要返回是否正常退出的信息,以及进程运行的结果对还是不对
父进程通过进程等待的方式进行对子进程的资源回收,以防止僵尸进程的产生
3.1进程等待的方法
3.1.1wait()函数
#include <sys/types>
#include <sys/wait.h>
pid_t wait(int*status);
- 返回值:
成功返回被等待进程pid,失败返回-1。
- 参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
wait()函数:
父进程一旦调用了wait()函数,就会立即阻塞自己
wait()函数自动分析当前进程的某个子进程是否退出,
如果退出,那么wait()就会收集这个子进程的相关信息,把他彻底销毁释放
如果没有找到这样的一个子进程那么就会一直阻塞
注:wait()一般与fork()配用,如果没有用fork()调用wait(),那么wait()函数返回-1
这个参数status用来收集子进程死掉时候的状态信息,一般都不会关心她,所以一般这个值都设为NULL
eg: pid_t ret= wait(NULL);
3.2 waitpid()方法
#include<sys/types.h>//需要的头文件
#include<sys/wait.h>
pid_ t waitpid(pid_t pid, int *status, int options);
-
返回值: 当正常返回的时候waitpid返回收集到的子进程的进程ID;
-
参数:
①pid:
pid=-1时候,代表返回结果是-1,代表错误,等待的是错误的pid的子进程。
Pid>0.等待其进程ID与pid相等的子进程。
②options:
通常是0,代表阻塞时等待
3.2.1参数status
status参数,该参数是一个输出型参数,由操作系统填充
有两种情况
①是否正常退出,以及退出码
②异常终止的信号
如下图
前七位代表的是终止信号,次八位代表的是退出状态
①前七位如果是0,那么代表进程正常退出,那么次八位则就是子进程(进程)的退出码
②前七位如果是非0,那么代表异常终止,前7位对应的数字其实就是对应了异常的编号,次八位就没意义了,一般是0
异常情况下的信号一般就是代表了下图序号,可以用kill -l 查看,例如发生除0操作等异常
代码:正常情况下如图
4.进程的程序替换
创建子进程的目的?
a.让子进程执行父进程的部分代码
b.执行一个全新的程序
所以,如果要想执行一个全新的进程,就需要引入进程的程序替换的概念
4.1替换函数
#include<unistd.h> //头文件
int execl(const char *path,const char *arg,....);
int execlp(const char *file,const char *agr,...);
int execle(const char *path,const char *arg,...,char *consst 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[]);
①这里的path是指需要执行的程序的路径,char* arg是执行的命令以及命令行参数,.....(省略号其实也有含义),是指可以传多个参数,不管传多少个,最后要加一个NULL结尾
eg:
execl("/usr/bin/ls","ls","-l",NULL);//第一个是路径下的程序位置,后边依次就是要执行的操作
这里就是执行了ls程序
int main(){
printf("process runing....\n");
sleep(4);
execl("/usr/bin/ls","ls","-l","--color=auto",NULL);
exit(10);
printf("process exit....\n");//在这里就没有执行了,因为代码在进程替换的时候被覆盖了,所以就无法执行了
return 0;
}
这里运行的时候会注意到运行结果没有第6行的 printf("process exit....\n");
这是因为,在运行到execl的时候,发生了进程替换,从这里开始进程的代码和数据都变成了路径里的程序,所以不能直接这样,需要用创建一个子进程来进行替换,这样不会影响父进程
所以应该让子进程来进行
4.2替换原理
在运行父进程的时候,创建完子进程但是没有调用execl()的时候,这时候,按正常父子的进程运行方式一样,两个都指向同一个物理地址,公用代码段和数据段,
当子进程遇到进程替换时,因为要让路径下的程序的代码和数据段进行覆盖,所以此时发生写时拷贝,此时OS将创建一个新的区域,然后让全新程序的代码还有数据段放入创建的新的区域的内存,然后页表重新映射改位置
在此过程中没有创建新的进程,只不过只有子进程页表映射的位置变了,并且创建了一块新的内存空间,进行运行全新的程序,跟原来的代码就无关了
4.3函数解释
- 如果函数如果调用成功,则加载新的程序从新程序起始开始执行,不再返回
- 如果调用出错则返回-1,eg:没有找到路径或者命令行参数传错了原因
- 只有出错的返回值,没有成功的返回值
4.4 命名理解
对于所有的这些函数,必须都带l
或者v
传以list或者vector的形式传命令函参数,所以有execl系列和execv系列
①execl:execl,execlp,execle
②exev:execv,execvp,execve
这里的execve()比较特殊,因为这一个是系统调用函数,其他的都是基于这个进行封装
l (list) : 表示参数采用列表
v (vector) : 表示参数采用数组
p (path) : 表示自动搜索环境变量中找有的程序名
e (env) : 表示自己维护环境变量,自己去手动传(可以自定义)
4.4.1 execle()函数
execle(const char* path, const char *argv , ..... , char* const envp[]);
问题:在一个程序被执行的时候,execve()函数 先被执行 还是 main()函数 先被执行?
答案:execve先被执行
一个程序被加载到内存中,怎么被加载到内存中?
①const char* path :在Linux中,调用函数execle()来加载到内存中,所以./可执行程序
就是一个加载器,传的第一个参数就是一个路径
②const char *argv:后边跟的是命令行参数,最后边的环境变量一般都由系统填充,例如ls -l这两个都是命令行参数
③ char* const envp[]:这个就是环境变量,其实在每个程序进行编译后,环境变量会保存在虚拟进程地址空间的最上边,这些部分保存的是命令行参数,环境变量等信息,所以子进程能拿到默认的环境变量,通过进程地址空间
所以对于main()函数,也要被执行,也要被传参,所以传参是execle传给main()函数的
int main(int argc , const char* argv[] , char* const env[]){
//int argc是由`const char *argv`的长度来进行传参,代表传了几个命令行参数
//const char* argv[]是又execle()中的argv来传参,传的就是命令行参数列表
//env[]由execle()中的env[]传来的
}
标签:控制,const,函数,pid,char,exit,进程
From: https://www.cnblogs.com/kzx1019/p/18143701