此处主要讲 fork()函数 和 vfork()函数
fork()函数的一些基本概念我们在前面fork()函数中讲过,如不了解可点击查看,此次我们主要对上篇没有说到的fork()函数的内容进行补充说明
fork()函数
• 子进程是拷贝父进程的PCB的,子进程的大部分数据是来源于父进程的。 eg:内存指针(数据段,代码段),页表
• 父进程创建子进程成功之后,父子进程是独立的两个进程(进程的独立性),父子进程的调度取决于操作系统内核
• 进程是抢占式执行的,父子进程谁先运行是不确定的
• 写时拷贝
fork失败的原因:
• 系统中有太多的进程
使用top命令可查看系统中的进程数
• 实际用户的进程数超过了限制
使用 ulimit -a 命令可查看最大进程限制为7261
父进程指向一个进程虚拟地址空间,父进程调用vfork()函数创建一个子进程也指向同一个进程虚拟地址空间,此时会出现压栈问题
• vfork()函数存在调用栈混乱的问题
• 解决方法:子进程先调用,子进程调用完毕之后,父进程再调用
• 从main函数的return返回
情况1:代码执行完毕,结果正确
情况2:代码执行完毕,结果不正确
• 程序崩溃
• 调用exit函数----库函数
void exit(int status); //status退出码
测试如下:
创建一个main函数写入exit函数,会发现函数运行到exit就结束,不会运行exit后面的代码
如果将代码改成如下,去掉hello后面的\n再次运行,会发现hello依旧可以运行出来
因为exit函数可以刷新缓冲区所以,有没有\n都如上代码的输出结果并没有影响
• 调用_exit函数----系统调用函数
void _exit(int status); //status退出码
测试如下:
创建一个main函数写入_exit函数,会发现函数运行到_exit就结束,不会运行_exit后面的代码
如果将代码改成如下,去掉hello后面的\n再次运行,会发现并不会有结果输出
那么为什么会出现如上的情况呢?
因为\n可以刷新缓冲区,没有\n,_exit函数并不能刷新缓冲区,所以没有结果输出
对比上面exit函数和_exit函数的运行结果会发现:
• exit函数会刷新缓冲区
• _exit函数不会刷新缓冲区
执行用户定义的清理函数: atexit函数
`int atexit(void (*function)(void));
• 参数是一个函数指针,接收一个函数地址
测试入下:
程序会按如下箭头进行运行,,所以运行结果应先打印“hello”,再打印出“我是func”
若在exit函数上面加一个死循环,则程序将不会运行到exit函数,我们猜想程序会按如下图所示箭头指向运行,所以运行结果应该只是打印出“hello”然后进行死循环
通过以上两种测试结果可以证明传入atexit函数func函数是一个回调函数
回调函数的执行相当于定闹铃,定闹铃的时候闹铃不会响,只有到定的时间闹铃才会响;atexit注册传入func这个函数的时候,并不会调用这个函数,只有当程序终止的时候才会调用这个函数
• “\n”
• fflush函数
• exit()函数
• main()函数的return
父进程进行等待,子进程先于父进程退出,由于父进程在等待子进程,所以父进程会回收子进程的退出资源,从而防止进程产生僵尸进程
pid_t wait(int *status);
返回值:
• 成功:返回子进程的pid号
• 失败:返回 -1
参数:整形指针;保存的status占4个字节
coredump标志位:
• 如果取值为1,则表示当中的进程是由coredump(核心转储文件)产生
• 如果取值为0,则表示当前进程没有coredump产生
终止信号:当前的程序是由什么信号导致的终止
正常情况:看进程退出码
异常情况:看coredump标志位和终止信号
拓展:出参
参数int* status 是应该出参
调用者准备应该int类型的变量,将地址传递给wait函数,wait函数在自己实现的内部,进行赋值,当wait函数返回之后,调用者就可以通过int变量获取进程退出的信息
首先我们创建一个僵尸进程,不使用wait函数
如上测试是一个僵尸进程,当我们加上wait函数后测试结果如下:
我们可以看到僵尸进程没有了
我们可以在子进程中加一个死循环,让子进程不退出,看父进程会不会退出,我们的猜想加上wait函数后,子进程不退出,父进程会一直等待
我们可以通过=》查看进程调用堆栈命令:pstack
• pstack [pid]
来查看父进程现在在做什么,如果查看到父进程在进行wait则符合我
们之前的猜想
如上父进程的状态称为阻塞状态
阻塞状态:当前执行流调用某一个函数,该函数一直不返回,称这个现象为阻塞
子进程在退出的时候,会给父进程发一个信号,这个信号是SIGCHLD,而wait的实现内部就是判断是否收到了SIGCHLD信号,如果收到了SIGCHLD信号就把子进程的资源回收掉
1.如何获取进程退出码:(status >> 8) & 0xff
2.如何获取终止信号:status & 0x7f
3.如何获取coredump标志位:(status >> 7) & 0x1
pid_t waitpid(pid_t pid, int *status, int options);
pid:
• -1:等待任一子进程,一旦等待到了,就返回
• 0:等待特定子进程,>0的值,就是子进程的pid号
status:出参,同wait函数
options:
• WNOHANG:非阻塞等待方式(需要搭配循环去使用,直到完成函数功能),目的没完成
• 0:阻塞等待方式
waitpid返回值
- 当没有等待到相应的子进程,waitpid返回值为0
- 等待到相应的子进程,waitpid的返回值为等待到的进程pid号(及大于0的)
测试如下:
#include
2 #include<sys/wait.h>
3 #include<unistd.h>
4 #include<stdlib.h>
//1.创建子进程, 模拟让子进程先于父进程退出
//2.父进程的逻辑当中执行进程等待
//非阻塞模式的等待方式
5 int main()
6 {
7 pid_t pid = fork();
8 if(pid < 0)
9 {
10 perror("fork");
11 return 0;
12 }
13 else if(pid == 0)
14 {
15 //child
16 printf("i am child,pid is %d,ppid is %d\n",getpid(),getppid());
17 sleep(5);
18 exit(1);
19 }
20 else
21 {
22 //father
//waitpid的返回值:
//当没有等待到相应的子进程, waitpid的返回值为0
//等待到相应的子进程, waitpid的返回值为等待到的进程PID号(大于0)
23 pid_t ret = 0;
24 do
25 {
26 ret = waitpid(-1,NULL,WNOHANG);
27 if(ret == 0)
28 {
29 printf("sub process is running...\n");
30 }
31 sleep(1);
}while(ret == 0);
33 printf("i am father,wait sub process id is %d\n",ret);
34 sleep(10);
35 printf("i am father,pid is %d,exit...",getpid());
36 }
37 return 0;
38 }