一、僵死进程
1.1僵死进程产生的原因
子进程先于父进程结束, 而父进程没有获取子进程退出码,释放子进程占用的资源,此时子进程将成为一个僵死进程。
在第一个框这里时父进程子进程都没有结束,显示其pid 父进程是2349,子进程是2350
在第二个框这里时父进程没有结束,子进程结束,因此显示父进程的pid,但是因为父进程没有获取子进程的退出码,子进程就处于僵死状态<defunct>
第三个框这里父进程结束,父进程成功获取子进程的退出码,子进程结束。
1.2 PCB消失的条件
获取到退出码,子进程要父进程获取子进程的退出码才能完全结束。
1.3危害
占用资源空间。如果进程不调用wait/waitpid,那么保留的那段信息就不会被释放,其进程号就会一 直被占用,但是系统能使用的进程号是有限的,如果产生大量的僵死进程,将因为没有可用的进程号而 导致系统不能产生新的进程。
1.4 如何处理
1.父进程调用wait()方法获取子进程的退出码
wait(&val):执行该指令,会将子进程的退出码填到val中。
结果:
这个时候我们不难发现:先打印的都是子进程,父进程一点都没有打印,当子进程结束才开始父进程,等val退出码打印后才开始。
原因:wait,即先阻塞父进程,直至子进程运行结束,父进程获取到子进程的退出码后,父进程才继续运行。
wait(&val):返回值pid_t,调用函数后会将退出码通过指针赋值到val上。
WIFEXITED(val):由于退出码为1字节,val为4字节,通过该函数可以将其转化为1字节,返回值为bool类型,判断进程是否正常结束。
WEXITSTATUS(val):获取进程退出状态。
2.父进程先结束----孤儿进程
结果
在父进程结束后出现了提示符,但是此时子进程还没有结束,所以还在执行
第一个框,父进程的id为2621,他的父进程是2135,子进程的id为2622,他的父进程为2621
父进程结束后,会随机为子进程分配一个父进程,该父进程的id为1536,收养了这个孤儿进程
注意:老版本是 init进程,其进程号为1,新版本是随机分配一个系统进程
init进程一定会对子进程执行wait()指令,获取子进程的退出码,子进程的pcb被操作系统删除,至此子进程彻底结束。
1.5 练习
1.5.1 练习一
结果
原因
板块一:此时i=0,执行fork语句生成板块2,打印第一个A,此时i++变成1,然后继续循环,执行fork语句,生成板块3(板块三的i是从1开始的),打印第二个A,i++变成2,结束板块一
板块三:因为是子进程,从fork语句后执行,打印第一个A,i++变成2,结束板块三
板块二:也是子进程,从fork语句后执行,打印第一个A,i++变成1,继续循环,执行fork语句生成板块四(板块四的i是从1开始的),打印第二个A,i++变成2,结束板块二。
板块四:也是子进程,打印第一个A,i++变成 2,结束板块2.
总计:6个A
1.5.2 练习二
结果
原因
注意一点:fork()执行完后,子进程从fork语句后执行
第一个板块:执行fork语句生成板2,此时将打印的A放在缓冲区,为什么呢?可以看下linux day06讲printf这块,此时i++变成1,继续循环,执行fork语句生成板块3,(注意板块三内容有原先父进程的缓冲区:A和i=1),然后执行printf,此时板块1的缓冲区中有AA两个,i++等于2,结束板块1.
第三个板块:因为复制了板块1的,i从1开始,直接执行printf语句,缓冲区有AA两个,i++变成2,j结束板块3.
第二个板块:因为是板块1的子进程,从printf处执行,缓冲区有一个A,i++等于1,继续for循环,执行fork语句形成板块4,然后缓冲区:AA,i++->2,结束板块2
第四个板块:因为复制了板块2,从printf处执行,缓冲区:AA,i++ ->2结束板块4
所以总计8个A
注意:练习一和练习二最大的区别在于缓存区的刷新
1.5.3 练习三
结果
原因
首先执行fork()||fork()语句,复制了一份,因为父进程的id>0,根据或语句特点直接结束,打印第一个A。然后子进程中0||fork(),或语句如果第一个为1 就直接跳过了,但是为0 就要继续执行后面语句,因此复制了一份代码,由于第二个fork()返回了当前父进程的id,直接结束语句,打印第二个A。最后子进程的pid为0,所以返回值为0,0||0为假,打印最后一个A结束整个程序,所以打印三个A
二、操作文件的系统调用
在C语言中有fopen,fclose等文件操作
头文件:#include<fcntl.h>
2.1 open()
文件已存在:int open(const char *pathname, int flags);
文件之前不存在,需要创建:int open(const char *pathname, int flags, mode_t mode);
pathname :将要打开的文件路径和名称flags : 打开标志,如 O_WRONLY 只写打开 O_RDONLY 只读打开O_RDWR 读写方式打开 O_CREAT 文件不存在则创建O_APPEND 文件末尾追加 O_TRUNC 清空文件,重新写入mode : 权限 如:“0600” 0是八进制,6是4 r+2 w返回值: 为文件描述符
2.2 read()
从文件中提取数据
ssize_t read( int fd, void * buf, size_t count);fd 对应打开的文件描述符buf 存放数据的空间count 计划一次从文件中读多少字节数据返回值 :为实际读到的字节数
2.3 write()
从文件中写入数据
ssize_t write( int fd, const void * buf, size_t count);参数介绍 :fd 对应打开的文件描述符buf 存放待写入的数据count 计划一次向文件中写多少数据
2.4 close()
关闭文件
int close( int fd);参数介绍 :fd 要关闭的文件描述符
2.5 举例
2.5.1 写文件
fd:文件描述符,通过编号id可以找到文件,0 标准输入,1标准输出,2标准错误输出
所以fd值为3
2.5.2 读文件
2.6 父进程先打开一个文件,fork 后子进程是否可以共享使用?
2.6.1 文件表
当不使用file.txt,把三关闭了,如果打开另外一个文件,则继续复用三。(不使用就关闭,这样就不会一直占用,使表不变大)
2.6.2 先open后fork
2.6.1.2 结果:
父进程打开的文件,子进程也可以访问,并且共享文件偏移量,如果想要关闭文件,需要父子进程都关闭文件。
2.6.1.3原因
父子进程共用一个结构体,引用计数为2,所以文件偏移量+1,那么子进程就输出d,再+1,父进程输出,所以结果出现四个偏移。
2.6.3 先fork后open
2.6.2.2 结果
2.6.2.3 原因
因为父和子进程各有一个结构体,打开一个文件,其偏移量并不互相影响,所以都是从a开始。
三、系统调用与库函数的区别
库函数的实现在函数库中,属于用户空间,系统调用的实现在内核中,属于内核空间。