首页 > 代码库 > 第3章 文件I/O
第3章 文件I/O
概述
本章主要讨论两个内容:1,不带缓冲的I/O;2,多进程共享文件
Unix环境下,I/O设备抽象成了文件,所以将这两个内容放在一起讲解。让我们带着问题去学习,效果可能更好。
一 什么是文件描述符?
从OS的角度,我们知道,I/O设备是共享设备,然而共享设备会带来进程之间的竞争,即临界资源的问题。比如,一台打印机,它是一个共享设备,在进程A进行打印的过程中,不能直接打印进程B想要打印的内容。怎么管理这个临界资源呢?Unix系统采用内核进行管理。如下图1:
图1 内核和进程
对于内核而言,它用文件描述符引用一个打开的文件,并向对应的进程返回这个文件描述符。以后对于该文件的操作,均针对这个文件描述符进行。形象一点讲,文件相当于学生自己,文件描述符就相当于学号,显然学号比姓名更加易于操作和管理。
二 什么是缓冲?
因为还没有讲标准I/O(带缓冲的I/O),所以这里就概要地讲解缓冲。
提到缓冲,往往指的是应用级缓冲,而非内核当中的缓冲,因为不管是不带缓冲的I/O,还是标准I/O,在内核当中均有I/O缓冲区。如下图:
图2 缓冲
三 一个简单的不带缓冲I/O实例
int readT(unsigned char *&T,const string name) { int fd = open(name.c_str(),O_RDWR);//打开文件 if(fd == -1)//判断打开是否成功 { fprintf(stderr,"error open text"); return -1; } int n = lseek(fd,0,SEEK_END);//得出文件长度 if(n == -1)//是否成功 { fprintf(stderr,"error seek text"); return -1; } T = new unsigned char[n];//为存储文件内容分配缓冲空间 if(T == NULL)//空间分配是否成功 { fprintf(stderr,"error molloc space"); return -1; } lseek(fd,0,SEEK_SET);//定位到文件开始位置 int cur = 0; int num = 0; while(1) { num = read(fd,T+cur,n-cur);//尽最大的努力读入文件 if(num == -1) { fprintf(stderr,"error read text 43"); return -1; } else if(num == 0) break; cur += num; } if(cur != n) { fprintf(stderr,"error in read text,cur is %d,n is %d\n",cur,n); return -1; } close(fd);//关闭打开的文件 return n; }这是我写的一个应用实例,从文件name中读入数据。其中
num = read(fd,T+cur,n-cur);//尽最大的努力读入文件这一行是非常好的一种方法。
四 怎么实现文件共享
4.1 内核打开文件数据结构
存在这种情况1:进程A正在以读方式打开文件1,同时它又想用读的方式打开文件2。形象一点:我看一会儿《apue》,又想看《Tcp/Ip》,那我就同时把两本书放在手边,想看哪个看哪个,不会干扰到,计算机怎么实现呢?如图:
图3 打开文件的内核数据结构
4.2 两个进程共享同一文件
存在这种情况2:进程A正在以读方式打开文件1,进程B也在以读方式打开文件1,而且两个进程互不干扰,怎样才能做到呢?举个形象的例子:小玉在看招聘海报,小伟也在看同一张招聘海报,显然他俩没必要争抢打架,各看各的就行了。那么操作系统内核是怎么处理这种情况的呢?也是类似的,如图:
图4 进程共享文件
明白了文件共享的内核数据结构,那么就能清楚的知道文件重定向等是怎么实现的,具体的函数细节可以就是api的学习了,运用几次就会熟悉。
五 修改文件状态标志
由四,我们可以看出,进程是通过文件表访问文件的,文件表中一个重要的项是文件状态标识,那么是不是只能在打开文件的时候设置它的值呢?显然不是,那就用到了fcntl函数了。
六 小结
通过本章的学习,就能编写正确的不带缓冲的I/O。了解了文件共享实现的核心机制。当然,里面还涉及各个api的参数等等,很多细节没有讲到,但是我们了解了它的整个的设计思想,这些细节再加以练习就能很好掌握了。