进程的独立性
进程:内核相关管理数据结构(pcb+地址空间+页表)+代码+数据
当fork函数创建一个子进程时,如何保证进程具有独立性?
父进程的数据以及代码来自磁盘,而子进程的代码是共享父进程的,至于数据,如果子进程不修改数据的话,同样也是和父进程共享的,而如果子进程要修改数据,就会发生写时拷贝,保证数据的独立性,子进程的退出也不会对父进程做干扰.父进程退出也不会对子进程做干扰.
frok函数的返回值
子进程给父进程返回自己的pid,父进程给子进程返回0,这样是为了对子进程进行标识,进而进行管理.这个在之前的文章中有讲过
进程的终止
进程终止做了什么
1.释放曾经的代码和数据所占的空间。
2.释放内核的数据结构(pcb+地址空间+页表)
当时当子进程退出后,父进程还没有获取到子进程的状态时,子进程的代码和数据已经被释放,而内核中的数据结构还没有被释放。因为子进程的内核数据结构pcb中保存了子进程的退出码和退出信号,需要等待父进程获取.
进程终止的3种情况
1.代码跑完,结果正确
2.代码跑完,结果错误
3.代码执行时,出现了异常,提前退出了
下面看一段程序,这段程序代码会跑完,结果也正确
这段程序中的return 0表示什么呀?
其实这个进程相当于bash的子进程,而main函数return的是该进程的退出码,该子进程会将该退出码返回给bash,因为bash要知道子进程退出的情况,给用户提供失败的原因是什么,对用户负责.
这个退出码 可以通过指令
echo $?
0就是这个程序的退出码,一般success表示成功
strerror
针对上面的错误码的话,对计算机而言,他知道退出码0,1,2,3对应着什么,我们可以通过strerror函数查看对应的退出码对应的是什么错误
退出码:0成功
!0失败
不同的非0值一方面表示失败,另一方面表示失败的原因,而对于计算机可以将退出码和错误做对应,如果我们想知道退出码对应的错误,就可以通过strerror函数来确定,对应的退出码都有错误描述string
退出码
如果我们将退出码设置为1的话
为什么第一次获取到的退出码是1,然后使用pwd进程后,在获取退出码的时候就变成0了呢?
其实父进程bash获取到的,是最近一个子进程退出的退出码,也就是说最后的0的退出码是pwd进程执行成功给bash的退出码,需要告诉父进程,子进程把任务完成的怎么样了
退出码可以使用默认,也可以自己定义
枚举类型:0表示成功,1表示除0,2表示模0,exit_code为退出码,刚开始让他=0表示成功.
然后Div函数里面如果分母为0了,就说明除0了,就把他的退出码修改成1;
打印退出码和退出码对应的信息,这里已经打印,就不用通过bash查看退出码了
退出信号
针对进程终止的前两种情况。我们可以通过系统或者自定义的退出码来确定。而针对于第三种,程序出现异常,在vs中,编译运行的时候,就会崩溃,原因是操作系统发现你做了不该做的事情,操作系统杀死了进程,一旦出现异常,退出码就没有任何意义了
下面展示代码演示代码出现异常
当对空指针解引用并赋值100时,会出现段错误,出现野指针问题.
而段错误在信号类型属于11号信号
进程出现异常本质是因为进程收到了os发给进程的信号
所以进程的终止,我们只需要两个信息,一个是退出码,一个是退出信号,
先确定是否异常,如果不是异常,就是代码跑完了,看退出码就可以了
退出码 | 退出信号 | 程序 |
0 | 0 | 程序跑完,结果正确 |
!0 | 0 | 程序跑完,结果不正确 |
!0 | !0 | 异常 |
0 | !0 | 异常 |
进程如何终止
1.main函数中的return 表示进程终止(非main函数,return表示函数结束)
2.crrl+c (异常终止)
3.exit函数,_exit函数(正常终止)
exit函数
#include<stdlib.h> void exit(int status); • 1 • 2
exit函数中的参数status是退出码,exit函数不管在程序的任何一个位置,只要到了exit的话,就表示进程要终止了,而return只有在main函数里面的表示进程要结束了,其他调用函数里面return只能说明函数结束.
_exit函数也是退出进程,基本上和exit的功能一样
#include <unistd.h> void _exit(int status); • 1 • 2
exit函数和_exit函数的区别和联系
区别 | 联系 |
exit是库函数,而_exit是系统调用,exit会冲刷缓冲区,_exit不会冲刷缓冲器 | 两者都会结束进程 |
这里的缓冲区不是内核缓冲区,因为操作系统的内核缓冲区在系统调用底层,如果是内核缓冲区的话,_exit也会冲刷缓冲区,作为库函数exit会调用_exit
验证exit会冲刷缓冲区,而_exit不会:
helloworld会立刻打印出来是因为exit会冲刷缓冲区
helloworld没有打印出来,是因为helloworld此时在缓冲区
进程等待
概念
任何子进程,在退出的情况下,一般必须要被父进程进行等待,进程在退出的时候,如果父进程不管不顾,退出进程,子进程会出现僵尸状态,导致内存泄露.
为什么要进程等待
1.父进程通过等待,解决子进程退出的僵尸问题,回收系统资源(必须)
2.获取子进程的退出信息,知道子进程是因为什么原因退出的(选择)
怎么实现进程等待(通过wait/waitpid)
wait方法
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
代码演示
#include<stdio.h> #include<stdlib.h> #include <unistd.h> #include<sys/types.h> #include<sys/wait.h> void childrun()//5秒子进程退出 { int cnt=5; while(cnt) { printf("i am child process,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt); sleep(1); cnt--; } } int main() { printf("i am father, pid:%d,ppid:%d\n",getpid(),getppid()); size_t id=fork(); if(id==0) {childrun(); printf("child quit ...\n");//提示子进程退出 exit(0);//子进程退出 } sleep(10);//等待10秒是为了让查看状态看到子进程退出后处于的僵尸状态 int rd=wait(NULL);//rd就是等待的子进程的pid if(rd>0) { printf("wait success,rd:%d\n",rd); } }
rd是子进程的pid,然后子进程的资源被回收,就不会引起内存泄漏.
如果子进程没有退出,父进程其实一直在进行阻塞等待,子进程本身就是软件,父进程本质上在等待某种软件条件就绪,父进程阻塞等待子进程,就会将父进程的pcb连接到子进程的运行队列里面去,直到子进程退出,父进程会将获取子进程pcb里退出号,以及退出信号,然后父进程会回收子进程pcb资源.
waitpid方法
#include<sys/types.h>
#include<sys/wait.h>
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程id;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid=-1,等待任一个子进程。与wait等效。
pid>0.等待其进程id与pid相等的子进程。
options设置为0
如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
如果不存在该子进程,则立即出错返回。
options设置为WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
获取子进程status
wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):
(status>>8)&0xFF //取次低8位退出号
status&0x7F //取低7位退出状态
#include<stdio.h> #include<stdlib.h> #include <unistd.h> #include<sys/types.h> #include<sys/wait.h> void childrun()//5秒子进程退出 { int cnt=5; while(cnt) { printf("i am child process,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt); sleep(1); cnt--; } } int main() { printf("i am father, pid:%d,ppid:%d\n",getpid(),getppid()); size_t id=fork(); if(id==0) {childrun(); printf("child quit ...\n");//提示子进程退出 exit(0);//子进程退出 } sleep(10);//等待10秒是为了让查看状态看到子进程退出后处于的僵尸状态 int rd=waitpid(-1,NULL,0);//rd就是等待的子进程的pid if(rd>0) { printf("wait success,rd:%d\n",rd); } }
情况1正常退出
#include<stdio.h> #include<stdlib.h> #include <unistd.h> #include<sys/types.h> #include<sys/wait.h> void childrun()//5秒子进程退出 { int cnt=5; while(cnt) { printf("i am child process,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt); sleep(1); cnt--; } } int main() { printf("i am father, pid:%d,ppid:%d\n",getpid(),getppid()); size_t id=fork(); if(id==0) {childrun(); printf("child quit ...\n");//提示子进程退出 exit(0);//子进程退出 } sleep(10);//等待10秒是为了让查看状态看到子进程退出后处于的僵尸状态 int status=0; int rd=waitpid(id,&status,0);//rd就是等待的子进程的pid if(rd>0) { printf("wait success,rd:%d exit_code:%d exit_singal:%d\n",rd(status>>8)&0xFF,status&0x7f); } }
情况2 9号信号验证
将上述代码子进程改成死循环
情况3 11号信号验证
将子进程中的循环改成cnt,然后将之前的段错误的代码加到子进程中去