让正在运行的程序创建出来一个子进程,它从已存在的进程中创建一个新进程,新进程为子进程,而原进程为父进程。
返回值:pid_t
创建失败:返回-1;
创建成功:
大于0:返回给父进程
等于0:返回给子进程
创建子进程,子进程拷贝父进程的PCB
1):分配新的内存块和内核数据结构(task_struct)给子进程
2):将父进程部分数据结构内容拷贝至子进程
3):添加子进程到系统进程列表当中,添加到双向链表中
4):fork返回,开始调度器(操作系统开始调度)调度
内核空间:Linux操作系统和驱动程序运行在内核空间。即系统调用函数都是在内核空间运行的,因为是操作系统提供的函数。
用户空间:应用程序都是运行在用户空间的。即自己写的代码都是运行在用户空间的。
但是,当程序员的代码调用了系统调用函数,则会切换到内核空间执行。执行完毕之后,再返回到用户空间继续执行用户代码。
在父进程创建出子进程后,子进程的PCB是拷贝父进程的,页表也是拷贝父进程的,在刚开始,同一变量的虚拟地址和物理地址的映射关系是一样的,操作系统并没有给子进程的变量在物理内存中分配空间,子进程的变量还是原来父进程中物理地址中的内容。
当数据进行改变时:以写时拷贝的方式进行拷贝一份,此时父子进程通过各自的页表,指向不同的物理地址。
当数据不发生改变:父子进程共享同一个数据。
守护进程:
父进程创建子进程,让子进程执行真正的业务(进程程序替换),父进程负责守护子进程。
当子进程在执行业务时出现问题,父进程负责重新启动子进程,让子进程继续提供服务。
进程终止可分为三种
代码运行结束,结果正确;
代码运行结束,结果不正确;
代码异常终止;
正常终止:
从main函数的return返回 只有main函数的return才能结束程序
调用exit函数(库函数)#include
作用:谁调用终止谁
其中status为程序退出的退出码( 可以用echo $?查看进程退出码)
调用_Exit函数(系统调用函数)#include
作用:谁调用终止谁
异常终止: 异常终止即程序崩溃
可能导致其的原因有:ctrl+c,内存访问越界,访问空指针
exit函数比_exit函数多执行两个步骤
1.执行用户自定义的清理函数
2.冲刷缓冲区,关闭流等
3.调用进程(_Exit);(exit函数的实现也调用了_Exit函数)
#include
#include
void atexit_callback(void)
{
printf("i am atexit_callback\n");
}
int main()
{
//atexit函数是注册函数,并没有在注册时候使用,所有此时不会调用
atexit(atexit_callback);
printf("end\n");
exit(0);
return 0;
}
可以看到,函数的调用是在exit之后执行的
而如果将exit改为_Exit,则不会调用函数
缓冲区:缓冲区是C标准库定义的,不属于操作系统内核。建立缓冲区可以减少IO次数,当触发冲刷缓冲区的条件后,缓冲区的内容才会进行IO操作(比如打印到屏幕上,写入文件内)
刷新缓冲区的方法
1.exit
2.main函数的return
3.fflush
4.\n
注意:_exit不会刷新缓冲区
僵尸进程:子进程先于父进程退出,父进程没有回收子进程的退出状态,则子进程会变成僵尸进程
为了防止僵尸进程的发生,父进程可以进行进程等待,回收子进程的退出状态信息,防止子进程变成僵尸进程
(僵尸进程的最合理解决方案:进程等待)
函数原型:
#include
#include
pid_t wait(int*status);//等待任一子进程
作用:等待子进程退出(回收退出的子进程的退出状态)
返回值:
成功:返回被等待进程的pid;
失败:返回-1;
参数:输出型参数,获取子进程退出状态,不关心则设置为NULL;
int * status:
wait函数的调用者,想要获取子进程的退出状态信息,但wait的返回值以及有含义了,所以wait获取到的子进程的退出状态信息是通过参数进行传递的。
只用到低位的两个字节
子进程正常退出,如果传递status,会获取到子进程的退出状态
子进程异常退出,如果传递status,会获取到coredump标志位+退出信号
正常退出的时候,退出码会被设置,coredump标志位为0,不会有退出信号
异常退出的时候,退出码不会被设置,coredump标志位为1,退出信号被设置
wait返回值:
大于0并且退出信号没有被设置(==0):正常退出
大于0并且退出信号被设置(有值):异常退出
先设置下核心转储文件的大小为unlimited,也就是不限制
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
1.正常返回时waitpid返回收集到的子进程的进程ID ;
2.若设置WNOHANG(非阻塞),而调用中waitpid发现没有已退出的子进程可收集,则返回0;
3.若调用出错,返回-1,这时errno会被设置成相应的值以指示错误所在;
pid_t pid:
-1:等待任一子进程与wait等效
大于0:等待其进程与pid相等的子进程。
int * status:
子进程退出信息(同wait)
int options:
WNOHANG:设置waitpid为非阻塞状态
若pid指定的子进程没有结束,则waitpid的函数返回0,不予以等待。若正常结束,返回该子进程pid。
设置为非阻塞状态后,子进程并没有被父进程等待到
解决方法:waitpid函数调用要搭配循环使用
为什么要进行进程程序替换:
父进程创建出来的子进程和父进程拥有相同的代码段,所有,子进程看到的代码和父进程是一样的
当我们想要子进程执行不同的程序时,就需要子进程调用进程程序替换的接口,从而让子进程执行不同的代码
#include
`
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 charfile,char *const argv[]);
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回-1
所以exec函数只有出错的返回值而没有成功的返回值。
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
int execl(const char *path, const char *arg, …);
参数:
pash:带路径的可执行程序
arg:传递给可执行程序的命令行参数;第一个参数为可执行程序本身,如果需要传递多个参数,则用“,”进行间隔,末尾以NULL结尾。
返回值:调用成功则加载新的程序从启动代码开始执行,不再返回,调用失败返回-1;
没有打印出end,即程序替换成功了
int execlp(const char *file, const char *arg, …);
file:可执行程序,可以不用带路径,也可以带路径。
execlp会自动搜索PATH这个环境变量去看能不能找到;找到:正常替换;没找到:报错返回,替换失败返回-1;
arg:传递给可执行程序的命令行参数;第一个参数为可执行程序本身,如果需要传递多个参数,则用“,”进行间隔,末尾以NULL结尾。
int execle(const char *path, const char *arg, …,char *const envp[]);
path:带路径的可执行程序(需要路径)
arg:传递给可执行程序的命令行参数;第一个参数为可执行程序本身,如果需要传递多个参数,则用“,”进行间隔,末尾以NULL结尾。
envp :在调用的时候,需要自己组织环境变量传递给函数
返回值:成功则替换;失败返回-1;
int execv(const char *path, char *const argv[]);
path:带路径的可执行程序
argv:传递给可执行程序的命令行参数,以指针数组的方式进行传递
第一个参数为可执行程序本身,多个参数都放在数组当中,末尾以NULL结尾
返回值:成功则替换;失败返回-1;
int execvp(const char*file,char *const argv[])
file:可执行程序,可以不用带路径,也可以带路径。
argv:传递给可执行程序的命令行参数,以指针数组的方式进行传递
第一个参数为可执行程序本身,多个参数都放在数组当中,末尾以NULL结尾
返回值:成功则替换;失败返回-1;
int execve(const char *path, char *const argv[],,char *const envp[]);
path:带路径的可执行程序
argv:传递给可执行程序的命令行参数,以指针数组的方式进行传递
第一个参数为可执行程序本身,多个参数都放在数组当中,末尾以NULL结尾
返回值:成功则替换;失败返回-1;
envp :在调用的时候,需要自己组织环境变量传递给函数
返回值:成功则替换;失败返回-1;