Linux(十)僵死进程及文件操作
一、僵死进程(僵尸进程)
(一)僵死进程产生的原因(条件)
其实,僵死进程的定义也就是它的产生原因(条件),让我们一起了解一下吧!
当子进程先于父进程结束,父进程没有获取子进程的退出码,此时子进程变成僵死进程。(这句话非常重要!!!)
我们来进一步了解一下:
1、退出码:(举个例子)exit(0)中的0就是退出码,表示正常退出。
2、用我们自己的话解释僵死进程就是:不是就绪、阻塞、运行(进程的三个基本状态)的进程就是僵死进程。
3、在有父、子进程的情况下,正常删除子进程的步骤:(1)先释放子进程的实体(2)将子进程的退出码赋值为0(3)父进程获取子进程的退出码(4)删除子进程的PCB(也就是进程控制块)
简而言之,就是子进程先结束,并且父进程没有获取它的退出码;
那么僵死进程产生的原因或者条件就是:子进程先于父进程结束,并且父进程没有获取子进程的退出码;
(二)僵死进程的产生过程
让我们通过图片更加清楚的了解产生过程吧
上面的图片从左到右、从上到下分别为初始、1、2、3步,产生僵死进程则就出现在第三步
(三)观察僵死进程
让我们通过代码一起来观察一下僵死进程,用到的代码我放在下面啦!
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
int main(){
char *s=NULL;
int n=0;//控制父子进程执行的次数;
pid_t id=fork();
assert(id !=- 1);
if(id == 0){
s="child";
n=3;
}//子进程
else{
s="parent";
n=7;
}//父进程
//父子进程
int i=0;
for(;i<n;i++){
printf("s=%s\n",s);
sleep(1);//是因为两个进程同时打印太快了,一下就出来感受不到,sleep后就感觉到同时
}
exit(0);
}
在上面这个代码中,子进程执行3次,父进程执行7次,就能观察到僵死进程
在上述我提供的截图可以看待,[test]的后面出现了<defunct>这就是指僵死进程。
(四)处理僵死进程的意义
也就是僵死进程的坏处:白白消耗内存
在《Linux内核设计与实现》27页有这样的描述
(五)处理僵死进程的两种方法
通过定义我们了解到僵死进程的产生原因,那么我们从产生原因入手就可以得到处理僵死进程的两种方法:(1)父进程先结束(2)父进程调用wait()方法获取子进程的退出码
1、父进程先结束
在上述代码中,我们将子进程改为10,父进程改为3,就会发现没有僵死进程,这是因为父进程已经结束,子进程变成了孤儿进程(这个名词非常生动,意思就是没有父进程的子进程就是孤儿),孤儿进程会被收养
我们将代码中父子进程部分修改一下,打印出PID和PPID
for(;i<n;i++){
printf("s=%s\n,pid=%d,ppid=%d\n",s,getpid(),getppid());
sleep(1);//是因为两个进程同时打印太快了,一下就出来感受不到,sleep后就感觉到同时
}
我们就会发现下图中找到的结果
2、父进程调用wait()方法获取子进程的退出码
在第二种方法中,我们看到了一个新的函数wait(),让我们使用帮助手册查看一下使用方法(man 2 wait,不能是man wait,因为此时帮助手册上的wait(1)是指命令,而不是库函数)
在这里,我们将代码的父进程部分进行修改,可以让我们看到退出码
修改后运行,会发现我在图片中标注的内容
我们通常也会这样使用wait(),下图这样也是可以的
让我们一起总结一下,两种处理方法的本质其实是一样的,都调用了wait获取子进程退出码,方法一是父进程先结束后子进程被Init(现在随着内核的发展不一定是1)收养,Init之后调用wait获取子进程退出码;方法二是父进程直接调用wait()但是两种方法又有区别,就是父进程调用wait会阻塞,等子进程执行完之后,父进程才会执行。
若想父进程调用wait不阻塞,那么我们需要结合信号一起,后面小编会在学习信号时向大家介绍。
(补充:退出码)
小编在这里还想带大家了解一下退出码
我们将上面调用wait的代码中的exit(0)改为exit(3);
然后执行
就会发现退出码变成了768,这其实也就是3的意思。
二、文件操作
(一)Linux操作文件的底层系统调用
在C语言中,我们曾学习过几个库函数,分别是fopen,fread,fwrite,fclose;
那么,在系统调用中则是open,read,write,close;系统调用的方法是实现在内核中的(陷入内核,切换到内核)
(二)open、write、read、close详细介绍
1、open的介绍
首先,让我们使用man 2 open打开帮助手册
open的重载,两个参数用于打开一个已经存在的文件;三个参数的用于新建一个文件,并设置访问权限;
pathname:文件的路径和名称;
flags:文件的打开方式;
mode:文件的权限,如"0600”;
open的返回值为int,称为文件描述符;
文件描述符:文件打开以后,内核给文件的一个编号;(>0的整数)
open的返回值是int,称之为“文件描述符”;每打开一个文件,我们就会得到一个文件描述符,这个文件描述符是一个整型,通过文件描述符就可以对文件进行读写这样的操作.open失败返回-1,成功返回一个大于等于0的值;0,1,2是默认打开的;
flags的打开标志,如:(下面都是常用的)
O_WRONLY:只写打开;
O_RDONLY:只读打开;
O_RDWR:读写方式打开;
O_CREAT:文件不存在则创建;
O_APPEND:文件末尾追加;
O_TRUNC:清空文件,重新写入;
(下面三个函数都需要查看帮助手册,这里我就不展示了,希望大家可以在练习时自己学会查看帮助手册)
2、write的介绍
fd:对应打开的文件描述符
buf:写入的文件内容;
count:要写入多少个字节;
返回值:ssize_t:实际写入了多少个字节;
3、read的介绍
fd:对应打开的文件描述符;
buf:把文件内容读取到一块空间buf中;
count:期望要读取的字节数;
返回值:ssize_t:实际读取了多少个字节;
4、close的介绍
关闭文件描述符;
练习:
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include<fcntl.h>
int main(){
//write(1,"hello",5);
//int fd = open("file.txt",O _WRONLY|O_CREAT,0600);//不写路径就是当前路径
int fd = open("file.txt",O_RDONLY);//不写路径就是当前路径
assert(fd!=-1);//这一句特别重要,因为有可能出现路径或文件名写错
printf("fd=%d",fd);
write(fd,"hello",5);
char buff[128]={0};
int n= read(fd,buff,127);
printf("n=%d,buff=%s\n",n,buff);
close(fd);
exit(0);
}
大家可以根据上述代码进行练习,也可以尝试使用读写复制文件。
【小编有话说】今天的内容就要结束啦,今天也是干活满满的一篇文章呢!大家一定要和小编一起练习呀,自己动手才是最重要的呢!!!
最后还是老三样,点赞收藏和关注~
喜欢小编的文章就不要忘记这三样,毕竟点赞收藏关注,找到小编不迷路~