第五章 文件I/O、高级I/O、标准I/O、mmap映射

               第五章   文件I/O、高级I/O、标准I/O、mmap映射

 

     本章简单介绍UNIX的文件I/O、高级I/O、标准I/O库,并指出它们的不足之处,和APO的实现思想。APO的I/O替代和超越了:文件I/O、高级I/O、标准I/O、mmap映射、sfio。


一、进程表项、打开文件表、文件描述符、内存v节点


     多个进程可同时读一个文件,为了使得每个进程都能够按自己的步调读文件,每个进程必须有自己的文件位置指针,这样才不会受到其他进程的影响。所以,我们需要一个进程打开文件表;每张表对应一个文件描述符(文件号fd),表中的项有文件位置、内存v节点指针、文件状态标志等变量。一个进程可以最多打开64K个文件,最少会打开3个标准文件(标准输入、标准输出、标准错误)。也就意味着每个进程的打开文件表是大小不定的,可以动态变化的。UNIX是将所有的进程表项(每个进程的所有打开文件表的指针和文件描述符标志(close_on_exec)、成为一个进程表项),放在统一的一个进程表中。UNIX这种方式不够清晰、管理麻烦、实现代码啰嗦。


     APO不使用统一的一个进程表,而是每个进程单独管理其进程打开文件表;表中的每一项对应一个文件描述符(文件号fd),表中的项内容有文件位置、流容器信息、内存v节点指针、文件状态标志等变量。APO的进程打开文件表与UINX等还是有较大区别的;UNIX的打开文件表在APO中是打开文件表中的一项,APO没用进程表项、进程表,也不需要打开文件表指针。主要的区别还在于APO的文件I/O、标准I/O方法库是合一的,即是只需一套方法;也不需要使用read、write等的方法;APO就是要应用编程简单、和再简单,用户可以直接用文件描述符fd操作流容器。

     

1、打开文件表


     APO的打开文件表是可以动态变化的,最大64K个文件描述符fd。一个文件表项的大小是1E = 32B,每个进程的打开文件表项数是不一样的;所以,为了节省本地内存空间,我们需要使用位图来管理进程在内存中打开的文件数。进程的代码中要打开一个文件时,系统总是从小的文件号fd,开始分配;打开一个进程后,进程的全局变量中,一开始就已经分配了256个的打开文件表项、对应文件号fd的0-255,其中0、1、2号已经分配给3个标准文件。打开文件表项是256个一组,意思是进程的文件号不够用时,系统会再申请内存分配一组(256项)、可以用一阵子了;再不够、就再来一组等;所以,可以说打开文件表是动态生成的,并非是一开始就安排了64K项(需要64KE的空间)。没有使用的打开文件表项是0,没有使用的打开文件表项小组的指针为0。因为打开文件表项小组(256项)是动态分配内存的,所以、需要有它们的内存首指针变量。对于文件号的分配和管理是通过操作位图变量来实现的,所以、进程需要以下的全局变量:

BU1W  [256] FTEP;// 256个进程打开文件表项小组的首指针数组。

BU256 [256] fd_WT;// 256个256位的位图变量数组;对应64K个文件描述符。

BU1E  [256] FTE;// 进程最初的256个打开文件表项,0、1、2已经分配。


   进程打开时FTEP[0] = FTEP.0.W就指向了FTE进程最初的256个打开文件表项小组。FTEP.1-255.W的指针项都是0,fd_WT.0.0-2 = 000、最初的256位图变量的前3位已经分配。FTE.0-2.E就是3个标准文件的打开文件表项。


2、打开文件表项


    UNIX的只是有当前文件位置、内存v节点指针、文件状态标志,对文件操作时还需缓冲区及大小、或FILE指针等,需要2套I/O库、文件I/O和标准I/O;对于应用编程员来说、就显得啰嗦、麻烦,现在通常是用标准I/O。而APO的表项也包含了流容器的描述;打开一个文件总是和流容器关联的。内存缓冲区、就是指一段内存空间;与位容器的意思是一样的,我更喜欢用后者。很多系统的磁盘I/O都使用自己的缓冲区;而这样一来,磁盘文件内容是先到磁盘缓冲区、之后,内核再拷贝缓冲区到用户进程空间的缓冲区,或回写、也是类似的过程。这很不合理、浪费时间、空间。应该使用多大的流容器才合适;磁盘设备驱动是不清楚的,只是按照固定的容器大小来安排;只有编写应用程序的编程员才清楚。所以,磁盘设备驱动使用的位容器大小应该是由应用程序来定才合理。


APO的打开文件表项:

BU1E FTE{ // 1E的打开文件表项。file table entry 文件表项(FTE)

   BU32 vnode_p;  // v节点指针。

   BU32 fstream_p;  // 文件流容器本地内存空间指针。

   BU32  fstream_len;// 流容器对象的大小。单位E

   BU48  f_pos;  // 文件在磁盘中的当前位置。

   BU16  sflags; // 文件状态标志。

// sflags.15  FD_CLOEXEC;1、close_on_exec;调用相关exec时,关闭文件

// sflags.14  STREAM_LOCK;  1、锁住流容器,0、解锁。

// sflags.13  STREAM_ALLOW; 1、使用流容器,0、禁止使用。

// sflags.12  STREAM_T;  1、带头部的流容器,0、无头部。

// sflags.11  STREAM_MD; 流容器格式:1、全缓冲,0、行缓冲。

// sflags.10  STREAM_S; 1、流容器是用户定义,0、它方定义。

// sflags.9-8 STREAM_MOD;流容器模式:0行,1扇区、2页,3数据块

// sflags.7   O_ASYNC; 异步I/O。

// sflags.6   O_RSYNC; 同步读、写。

// sflags.5   O_DSYNC; 等待写完成(仅数据)。

// sflags.4   O_SYNC;  等待写完成(数据和属性)。

// sflags.3   O_NONBLCOK; 非阻塞模式。

// sflags.2   O_APPEND; 1、对文件的写访问只能是加在文件尾。

// sflags.1   QNOTE_FL; 1、软连接标志, 0、非软连接。

// sflags.0   O_EOF;   1、报告文件结束,0、 否。

   BU16 STREAM_NUM;   // 16位流容器内的记录数。

   BU16 STREAM_AVA_LEN;// 流容器的尾部无效长度(单位E)。

   BU16 fd_count;  // 软连接的引用计数。

   BU16 allow_err; // 低8位错误代号、高8位权限、许可标志。

//allow_err.15  FERR ; 1、操作文件出错指示,0、否。

//allow_err.14  root;  1、是根用户,0、否。

//allow_err.13  owner; 1、是用户拥有者,0、否。

//allow_err.12  grp;   1、是组用户,0、否。

//allow_err.11  Y_OK; 1、许可进程在目录中删除、或新建一个文件,0、否。

//allow_err.10  RD_OK; 1、许可进程读文件内容,0、否。

//allow_err.9   WR_OK; 1、许可进程写文件内容,0、否。

//allow_err.8   X_OK;  1、许可进程执行该文件,0、否。

   BU32 tmpfname; // 临时文件名字指针

}


3、内存v节点


    创建内存v节点的最初目的是兼容各种文件系统,各种操作系统对v节点的定义大同小异。v节点包含文件属性、对文件操作方法的引用、i节点、目录项(文件在磁盘中的位置、当前文件长度等)等;当然,网络v节点是不一样的。每一个打开的文件都有一个vnode结构。vnode 是内核中表示文件的数据结构,不论文件属于什么类型的文件系统,内核都通过与文件相连的vnode来访问文件。同系统打开文件表不同,vnode不是以文件打开为基础的,而是以文件为基础的,即一个文件只有一个vnode结构。因此,同一个文件可以被若干个进程打开,但每一个进程在系统打开文件表中用自己的打开文件表项与这个vnode相连。vnode中包含着与文件有关的所有维护信息,其中包括vnode信息和文件的inode、目录项信息;记录着文件的属主、文件大小、文件驻存的设备等文件属性信息。


4、文件描述符


    系统对于所有打开的文件都是使用文件描述符fd来引用的,当打开一个现有文件或创建一个新文件时,系统返回给进程一个文件描述符fd。每一个进程总是预先有三个打开的描述字0、1和2,因为所有用户进程都是shell进程的子进程,它们继承了由shell打开的这三个描述字;标准输入输出重定向便利用了这个特点。UNIX允许进程最多打开63个文件,APO则是允许最多打开64K个文件;LINUX则更多到达1M个。其实,允许打开的越多、为之用于管理的内存就越大;64K足够,如果真的需要更多,我们可以用一个文件号对应打开一个子目录、而子目录下的文件数2G也行。


二、文件I/O

     标准I/O函数提供了丰富的输入输出方法,但有时程序员并不需要标准I/O函数提供的数据转换和缓冲处理,而希望使用自己的方法。如,当要用很大的缓冲来读二进制文件,或需要对特定设备(如终端)进行控制操作,或需要传递文件描述字给子进程(子进程可用继承的文件描述字创建自己的流,但不能直接继承流)时,便需要使用UNIX的输入输出系统调用。这些系统调用习惯上称为低级I/O函数(文件I/O)。低级I/O函数对文件描述字进行操作,其中有一些是实现标准I/O函数的初等函数;另外一些则执行低级控制操作,没有相对应的标准I/O函数。还是需要使用2套I/O库吧,够麻烦的;而APO的文件I/O类只需3种方法open(),close(),flush()。


1、open函数(打开文件)

相关函数read,write,fcntl,close,link,stat,umask,unlink,fopen

表头文件:

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

定义函数:

int open( const char * pathname, intflags);

int open( const char * pathname,intflags, mode_t mode);

函数说明:参数pathname指向欲打开的文件路径字符串。下列是参数flags 所能使用的旗标:

O_RDONLY 以只读方式打开文件

O_WRONLY 以只写方式打开文件

O_RDWR 以可读写方式打开文件。

上述三种旗标互斥,不可同时使用,但可与下列的旗标OR(|)运算符组合。

O_CREAT 若欲打开的文件不存在则自动建立该文件。

O_EXCL 如果O_CREAT也被设置,此指令会去检查文件是否存在。文件若不存在则建立该文件,否则将导致打开文件错误。此外,若O_CREAT与O_EXCL同时设置,并且欲打开的文件为符号连接,则会打开文件失败。

O_NOCTTY 如果打开的文件为终端机时,则不将该终端机当成进程控制终端机。

O_TRUNC 若文件存在并以可写方式打开时,此旗标会令文件长度清0,而原来存于该文件的资料也会消失。

O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。

O_NONBLOCK 以不可阻断的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中。

O_NDELAY 同O_NONBLOCK。

O_SYNC 以同步的方式打开文件。

O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接,则会令打开文件失败。

O_DIRECTORY 如果参数pathname所指的文件并非为一目录,则会令打开文件失败。此为Linux2.2以后特有的旗标,以避免一些系统安全问题。参数mode 则有下列数种组合,只有在建立新文件时才会生效,此外真正建文件时的权限会受到umask值所影响,因此该文件权限应该为(mode-umaks).

S_IRWXU00700 权限,代表该文件所有者具有可读、可写及可执行的权限。

S_IRUSR 或S_IREAD,00400权限,代表该文件所有者具有可读取的权限。

S_IWUSR 或S_IWRITE,00200 权限,代表该文件所有者具有可写入的权限。

S_IXUSR 或S_IEXEC,00100 权限,代表该文件所有者具有可执行的权限。

S_IRWXG 00070权限,代表该文件用户组具有可读、可写及可执行的权限。

S_IRGRP 00040权限,代表该文件用户组具有可读的权限。

S_IWGRP 00020权限,代表该文件用户组具有可写入的权限。

S_IXGRP 00010权限,代表该文件用户组具有可执行的权限。

S_IRWXO 00007权限,代表其他用户具有可读、可写及可执行的权限。

S_IROTH 00004权限,代表其他用户具有可读的权限

S_IWOTH 00002权限,代表其他用户具有可写入的权限。

S_IXOTH 00001权限,代表其他用户具有可执行的权限。

返回值:若所有欲核查的权限都通过了检查则返回0值,表示成功,只要有一个权限被禁止则返回-1。

错误代码:

EEXIST 参数pathname 所指的文件已存在,却使用了O_CREAT和O_EXCL 旗标

EACCESS 参数pathname 所指的文件不符合所要求测试的权限。

EROFS 欲测试写入权限的文件存在于只读文件系统内。

EFAULT 参数pathname 指针超出可存取内存空间。

EINVAL 参数mode不正确。

ENAMETOOLONG 参数pathname太长。

ENOTDIR 参数pathname 不是目录。

ENOMEM 核心内存不足。

ELOOP 参数pathname有过多符号连接问题。

EIO I/O存取错误。

附加说明使用access()作用户认证方面的判断要特别小心,例如在access()后再作open()空文件可能会造成系统安全上的问题。

 

open()返回的描述字一定是当前最小未使用的描述字。这个特征特别有用,例如,如果一个程序关闭了它的标准输出,然后再调用open()创建或打开另一个文件,则标准输出使用的文件描述字1将成为这个新打开文件的描述字,从而使得标准输出可以重新定向至不同的文件或设备。

    open()方法的实现是很麻烦的,APO的也是、需要另文说明;不过,APO的fopen()系统方法除了打开文件、创建新文件外;还包含运行可执行文件,创建一个新进程、建立唯一的临时文件、创建一个TCP连接等。fopen()方法可以是非阻塞的、立即执行方式,也可以将消息发给日志文件服务进程来执行的异步方式。


2、close()函数(关闭文件)

相关函数:open,fcntl,shutdown,unlink,fclose

表头文件:#include<unistd.h>

定义函数:int close(int fd);

函数说明:当使用完文件后若已不再需要则可使用close()关闭该文件,Close()会让数据写回磁盘,并释放该文件所占用的资源。参数fd为先前由open()或creat()所返回的文件描述词。

返回值:若文件顺利关闭则返回0,发生错误时返回-1。

错误代码:EBADF 参数fd 非有效的文件描述词或该文件已关闭。

附加说明:虽然在进程结束时,系统会自动关闭已打开的文件,但仍建议自行关闭文件,并确实检查返回值。

 

关闭文件描述字意味下述一系列动作:释放描述字fd,此描述字可被后继调用的open()再次使用。释放进程在此文件上占有的任何文件锁。当与管道或FIFO相连的所有文件描述字均被关闭时,废弃任何还在管道或FIFO中的数据。当进程终止时,UNIX内核会自动地关闭该进程打开的所有文件,因此许多应用利用这一特征而不明显地调用close()关闭文件。

APO的close()除上面所说外,还包含结束一个进程、释放一个TCP连接等。


3、lseek函数(移动文件的读写位置)

相关函数:dup,open,fseek

表头文件:

#include<sys/types.h>

#include<unistd.h>

定义函数:off_t lseek(int fildes,off_t offset ,int whence);

函数说明:每一个已打开的文件都有一个读写位置,当打开文件时通常其读写位置是指向文件开头,若以附加的方式打开文件(如O_APPEND),则读写位置会指向文件尾。当read()或write()时,读写位置会随之增加,lseek()便是用来控制该文件的读写位置。参数fildes 为已打开的文件描述词,参数offset 为根据参数whence来移动读写位置的位移数。参数whence 为下列其中一种:

SEEK_SET 参数offset即为新的读写位置。

SEEK_CUR 以目前的读写位置往后增加offset个位移量。

SEEK_END 将读写位置指向文件尾后再增加offset个位移量。

当whence 值为SEEK_CUR 或SEEK_END 时,参数offet允许负值的出现。

下列是特别的使用方式:

1) 欲将读写位置移到文件开头时:lseek(int fildes,0,SEEK_SET);

2) 欲将读写位置移到文件尾时:lseek(int fildes,0,SEEK_END);

3) 想要取得目前文件位置时:lseek(int fildes,0,SEEK_CUR);

返回值:当调用成功时则返回目前的读写位置,也就是距离文件开头多少个字节。若有错误则返回-1,errno会存放错误代码。

附加说明:Linux系统不允许lseek()对tty 装置作用,此项动作会令lseek()返回ESPIPE。

 

APO不需要一个函数来设置,只是一条4ns赋值指令,如:

fd.FTE.f_pos = ?; // 设置文件的读写位置,单位E。


4、read、write函数。

read(由已打开的文件读取数据)

相关函数:readdir,write,fcntl,close,lseek,readlink,fread

表头文件:#include<unistd.h>

定义函数:ssize_t read(int fd,void * buf ,size_t count);

函数说明:read()会把参数fd 所指的文件传送count 个字节到buf 指针所指的内存中。若参数count为0,则read()不会有作用并返回0。返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。

附加说明:如果顺利read()会返回实际读到的字节数,最好能将返回值与参数count 作比较,若返回的字节数比要求读取的字节数少,则有可能读到了文件尾、从管道(pipe)或终端机读取,或者是read()被信号中断了读取动作。当有错误发生时则返回-1,错误代码存入errno中,而文件读写位置则无法预期。

错误代码:

EINTR 此调用被信号所中断。

EAGAIN 当使用不可阻断I/O 时(O_NONBLOCK),若无数据可读取则返回此值。

EBADF 参数fd 非有效的文件描述词,或该文件已关闭

write(将数据写入已打开的文件内)

相关函数:open,read,fcntl,close,lseek,sync,fsync,fwrite

表头文件:#include<unistd.h>

定义函数:ssize_t write (int fd,const void * buf,size_t count);

函数说明:write()会把参数buf所指的内存写入count个字节到参数fd 所指的文件内。当然,文件读写位置也会随之移动。

返回值:如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中。

错误代码:

EINTR 此调用被信号所中断。

EAGAIN 当使用不可阻断I/O 时(O_NONBLOCK),若无数据可读取则返回此值。

EADF 参数fd 非有效的文件描述词,或该文件已关闭

APO没有这2个函数,文件内容的读写是通过rflush、或wflush方法,由系统读文件内容到流容器(缓冲区),或从流容器(缓冲区)写到文件中去。而对流容器(缓冲区)的读写不过是一些ns级赋值指令。


5、dup(复制文件描述词)、dup2(复制文件描述词)、fcntl(文件描述词操作)

相关函数:open,close,fcntl,dup2

表头文件:#include<unistd.h>

定义函数:int dup (int oldfd);

函数说明:dup()用来复制参数oldfd 所指的文件描述词,并将它返回。此新的文件描述词和参数oldfd 指的是同一个文件,共享所有的锁定、读写位置和各项权限或旗标。例如,当利用lseek()对某个文件描述词作用时,另一个文件描述词的读写位置也会随着改变。不过,文件描述词之间并不共享close-on-exec 旗标。

返回值:当复制成功时,则返回最小及尚未使用的文件描述词。若有错误则返回-1,errno 会存放错误代码。

错误代码:EBADF 参数fd 非有效的文件描述词,或该文件已关闭。


dup2(复制文件描述词)

相关函数:open,close,fcntl,dup

表头文件:#include<unistd.h>

定义函数:int dup2(int odlfd,int newfd);

函数说明:dup2()用来复制参数oldfd 所指的文件描述词,并将它拷贝至参数newfd 后,一块返回。若参数newfd 为一已打开的文件描述词,则newfd 所指的文件会先被关闭。dup2()所复制的文件描述词,与原来的文件描述词共享各种文件状态,详情可参考dup()。

返回值:当复制成功时,则返回最小及尚未使用的文件描述词。若有错误则返回-1,errno 会存放错误代码。

附加说明:dup2()相当于调用fcntl(oldfd,F_DUPFD,newfd);请参考fcntl()。

错误代码:EBADF 参数fd 非有效的文件描述词,或该文件已关闭。


fcntl(文件描述词操作)

相关函数:open,flock

表头文件:

#include<unistd.h>

#include<fcntl.h>

定义函数:

int fcntl(int fd , int cmd);

int fcntl(int fd,int cmd,long arg);

int fcntl(int fd,int cmd,struct flock *lock);

函数说明:fcntl()用来操作文件描述词的一些特性。参数fd 代表欲设置的文件描述词,参数cmd 代表欲操作的指令,有以下几种情况:

F_DUPFD 用来查找大于或等于参数arg的最小且仍未使用的文件描述词,并且复制参数fd 的文件描述词。执行成功则返回新复制的文件描述词。请参考dup2()。

F_GETFD 取得close-on-exec 旗标。若此旗标的FD_CLOEXEC位为0,代表在调用exec()相关函数时文件将不会关闭。

F_SETFD 设置close-on-exec 旗标。该旗标以参数arg 的FD_CLOEXEC 位决定。

F_GETFL 取得文件描述词状态旗标,此旗标为open()的参数flags。

F_SETFL 设置文件描述词状态旗标,参数arg为新旗标,但只允许O_APPEND、O_NONBLOCK 和O_ASYNC 位的改变,其他位的改变将不受影响。

F_GETLK 取得文件锁定的状态

F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK 或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。

F_SETLKW F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。参数lock指针为flock 结构指针,定义如下:

struct flcok

{

short int l_type; /* 锁定的状态*/

short int l_whence;/*决定l_start位置*/

off_t l_start; /*锁定区域的开头位置*/

off_t l_len; /*锁定区域的大小*/

pid_t l_pid; /*锁定动作的进程*/

};

l_type 有三种状态:

F_RDLCK 建立一个供读取用的锁定

F_WRLCK 建立一个供写入用的锁定

F_UNLCK 删除之前建立的锁定

l_whence 也有三种方式:

SEEK_SET 以文件开头为锁定的起始位置。

SEEK_CUR 以目前文件读写位置为锁定的起始位置

SEEK_END 以文件结尾为锁定的起始位置。

返回值:成功则返回0,若有错误则返回-1,错误原因存于errno.


flock(锁定文件或解除锁定)

相关函数:open,fcntl

表头文件:#include<sys/file.h>

定义函数:int flock(int fd,int operation);

函数说明:flock()会依参数operation 所指定的方式对参数fd 所指的文件做各种锁定或解除锁定的动作。此函数只能锁定整个文件,无法锁定文件的某一区域。

参数operation 有下列四种情况:

LOCK_SH 建立共享锁定。多个进程可同时对同一个文件作共享锁定。

LOCK_EX 建立互斥锁定。一个文件同时只有一个互斥锁定。

LOCK_UN 解除文件锁定状态。

LOCK_NB 无法建立锁定时,此操作可不被阻断,马上返回进程。

通常与LOCK_SH 或LOCK_EX 做OR(|)组合。

单一文件无法同时建立共享锁定和互斥锁定,而当使用dup()或fork()时文件描述词不会继承此种锁定。

返回值:返回0表示成功,若有错误则返回-1,错误代码存于errno。


APO没有flock()、fcntl()这2个函数,对于文件的互斥锁定,我感觉是画蛇添足、有其它方法可以使用。至于dup,就是相当于建立一个软连接,dup2相当于建立一个硬连接;这也是属于打开文件表项FTE的属性操作。对打开文件表项FTE的属性操作请参看第十五章。


6、fsync、sync、fdatasync(将缓冲区数据写回磁盘)


fsync(将缓冲区数据写回磁盘)

相关函数:sync、fdatasync

表头文件:#include<unistd.h>

定义函数:int fsync(int fd);

函数说明:fsync()负责将参数fd 所指的文件数据,由系统缓冲区写回磁盘,以确保数据同步。只对单一文件fd起作用,并等待写磁盘操作结束。

返回值:成功则返回0,失败返回-1,errno 为错误代码


sync(将缓冲区数据写回磁盘)

相关函数:fsync、fdatasync

表头文件:#include<unistd.h>

定义函数:int sync(void)

函数说明:只是将所有修改过的块缓冲区排入写队列,然后返回;并不等待实际写磁盘操作结束。sync()负责将系统缓冲区数据写回磁盘,以确保数据同步。通常称为updata的系统守护进程会周期性(30秒)的调用sync函数,保证定期冲洗内核的块缓冲区。

返回值:返回0。

 

fdatasync(将缓冲区数据写回磁盘)

相关函数:sync、fsync

表头文件:#include<unistd.h>

定义函数:int fdatasync(int fd);

函数说明:类同fsync(),但它只影响文件的数据部分。而fsync()还会同步更新文件的属性。

返回值:成功则返回0,失败返回-1,errno 为错误代码

 

APO有类似、等效的方法wflush()。另外,ioctl函数,可以使用APO的ns级赋值指令取代。


三、标准I/O库

 

    标准I/O库是围绕着流(stream)容器进行的;fopen打开或创建一个文件时,就已经使用一个流容器与文件关联;这点与APO的一样。当用fopen()打开或创建一个流时,它会返回一个指向FILE结构的指针,此时称在程序和该文件之间建立了一个流。为了引用一个流,我们将它的FILE指针作为参数传递给标准I/O函数,因此,程序中涉及的都只是指向FILE对象的指针,即“FILE *”。FILE对象由标准I/O库函数内部分配和管理,包含fd、该流的缓冲区指针、缓冲区长度、当前在缓冲区内的字符数、状态标志等。APO是没有指针的,你可以直接赋值操作你声明的流容器变量,也可以使用fd来操作流容器。流容器的指针、长度、当前在流容器内的字符数、状态标志等,都在fd相应的文件打开表项里。


1、fopen(打开文件)、fclose(关闭文件)


fopen(打开文件)

相关函数:open,fclose

表头文件:#include<stdio.h>

定义函数:FILE * fopen(const char * path,const char * mode);

函数说明:参数path字符串包含欲打开的文件路径及文件名,参数mode字符串则代表着流形态。mode有下列几种形态字符串:

r 打开只读文件,该文件必须存在。

r+ 打开可读写的文件,该文件必须存在。

w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。

w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。

a 以附加方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留

a+ 以附加方式打开可读写文件。若文件不存在,则建立该文件,如果文件存在,写入的数据被加到文件尾后,即文件原先的内容会被保留

上述的形态字符串都可以再加一个b字符,如rb、w+b 或ab+等组合,加入b 字符用来告诉函数库打开的文件为二进制文件,而非纯文字文件。不过在POSIX系统,包含Linux都会忽略该字符。由fopen( ) 所建立的新文件会具有

S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH(0666)权限,此文件权限也会参考umask值。

返回值:文件顺利打开后,指向该流的文件指针就会被返回。若果文件打开失败则返回NULL,并把错误代码存在errno 中。

附加说明:一般而言,开文件后会作一些文件读取或写入的动作,若开文件失败,接下来的读写动作也无法顺利进行,所以在fopen()后请作错误判断及处理。

freopen(打开文件)

相关函数:fopen,fclose

表头文件:#include<stdio.h>

定义函数:FILE * freopen(const char * path,const char * mode,FILE *stream);

函数说明:参数path字符串包含欲打开的文件路径及文件名,参数mode请参考fopen()说明。参数stream为已打开的文件指针。freopen()会将原stream所打开的文件流关闭,然后打开参数path的文件。

返回值:文件顺利打开后,指向该流的文件指针就会被返回。如果文件打开失败则返回NULL,并把错误代码存在errno 中。

 

fclose(关闭文件)

相关函数:close,fflush,fopen,setbuf

表头文件:#include<stdio.h>

定义函数:int fclose(FILE * stream);

函数说明:fclose()用来关闭先前fopen()打开的文件。此动作会让缓冲区内的数据写入文件中,并释放系统所提供的文件资源。

返回值:若关文件动作成功则返回0,有错误发生时则返回EOF 并把错误代码存到errno。

错误代码:EBADF 表示参数stream非已打开的文件。


2
、以下的函数,APO如果不是无必要,就是对文件打开表项的属性读写。


clearerr(清除文件流的错误旗标)

相关函数:feof

表头文件:#include<stdio.h>

定义函数:void clearerr(FILE * stream);

函数说明:clearerr()清除参数stream指定的文件流所使用的错误旗标。

返回值:无

fdopen(将文件描述词转为文件指针)

相关函数:fopen,open,fclose

表头文件:#include<stdio.h>

定义函数:FILE * fdopen(int fildes,const char * mode);

函数说明:fdopen()会将参数fildes 的文件描述词,转换为对应的文件指针后返回。参数mode 字符串则代表着文件指针的流形态,此形态必须和原先文件描述词读写模式相同。关于mode字符串格式请参考fopen。

返回值:转换成功时返回指向该流的文件指针。失败则返回NULL,并把错误代码存在errno中。


feof(检查文件流是否读到了文件尾)

相关函数:fopen,fgetc,fgets,fread

表头文件:#include<stdio.h>

定义函数:int feof(FILE * stream);

函数说明:feof()用来侦测是否读取到了文件尾,尾数stream 为fopen()所返回之文件指针。如果已到文件尾则返回非零值,其他情况返回0。

返回值返回非零值代表已到达文件尾。


fileno(返回文件流所使用的文件描述词)

相关函数:open,fopen

表头文件:#include<stdio.h>

定义函数:int fileno(FILE * stream);

函数说明:fileno()用来取得参数stream指定的文件流所使用的文件描述词。

返回值:返回文件描述词。

fseek(移动文件流的读写位置)

相关函数:rewind,ftell,fgetpos,fsetpos,lseek

表头文件:#include<stdio.h>

定义函数int fseek(FILE* stream,long offset,int whence);

函数说明:fseek()用来移动文件流的读写位置。参数stream为已打开的文件指针,参数offset为根据参数whence 来移动读写位置的位移数。参数whence 为下列其中一种:SEEK_SET 从距文件开头offset位移量为新的读写位置。SEEK_CUR 以目前的读写位置往后增加offset个位移量。SEEK_END 将读写位置指向文件尾后再增加offset个位移量。当whence 值为SEEK_CUR 或SEEK_END 时,参数offset 允许负值的出现。下列是较特别的使用方式:

1) 欲将读写位置移动到文件开头时: fseek(FILE *stream,0,SEEK_SET);

2) 欲将读写位置移动到文件尾时: fseek(FILE *stream,0,0SEEK_END);

返回值:当调用成功时则返回0,若有错误则返回-1,errno 会存放错误代码。

附加说明:fseek()不像lseek()会返回读写位置,因此必须使用ftell()来取得目前读写的位置。


ftell(取得文件流的读取位置)

相关函数:fseek,rewind,fgetpos,fsetpos

表头文件:#include<stdio.h>

定义函数:long ftell(FILE * stream);

函数说明:ftell()用来取得文件流目前的读写位置。参数stream为已打开的文件指针。

返回值:当调用成功时则返回目前的读写位置,若有错误则返回-1,errno会

存放错误代码。

错误代码:EBADF 参数stream无效或可移动读写位置的文件流。


rewind(重设文件流的读写位置为文件开头)

相关函数:fseek,ftell,fgetpos,fsetpos

表头文件:#include<stdio.h>

定义函数:void rewind(FILE * stream);

函数说明:rewind()用来把文件流的读写位置移至文件开头。参数stream为已打开的文件指针。此函数相当于调用fseek(stream,0,SEEK_SET)。

返回值:无

 

setbuf(设置文件流的缓冲区)

相关函数:setbuffer,setlinebuf,setvbuf

表头文件:#include<stdio.h>

定义函数:void setbuf(FILE * stream,char * buf);

函数说明:在打开文件流后,读取内容之前,调用setbuf()可以用来设置文件流的缓冲区。参数stream为指定的文件流,参数buf指向自定的缓冲区起始地址。如果参数buf为NULL指针,则为无缓冲IO。setbuf()相当于调用:

setvbuf(stream,buf,buf_IOFBF:_IONBF,BUFSIZ);

返回值:无

setbuffer(设置文件流的缓冲区)

相关函数:setlinebuf,setbuf,setvbuf

表头文件:#include<stdio.h>

定义函数:void setbuffer(FILE * stream,char * buf,size_t size);

函数说明:在打开文件流后,读取内容之前,调用setbuffer()可用来设置文件流的缓冲区。参数stream为指定的文件流,参数buf指向自定的缓冲区起始地址,参数size为缓冲区大小。

返回值:无

setlinebuf(设置文件流为线性缓冲区)

相关函数:setbuffer,setbuf,setvbuf

表头文件:#include<stdio.h>

定义函数:void setlinebuf(FILE * stream);

函数说明:setlinebuf()用来设置文件流以换行为依据的无缓冲IO。相当于调用:setvbuf(stream,(char*)NULL,_IOLBF,0);请参考setvbuf()。

返回值:无

setvbuf(设置文件流的缓冲区)

相关函数:setbuffer,setlinebuf,setbuf

表头文件:#include<stdio.h>

定义函数:int setvbuf(FILE * stream,char * buf,int mode,size_t size);

函数说明:在打开文件流后,读取内容之前,调用setvbuf()可以用来设置文件流的缓冲区。参数stream为指定的文件流,参数buf指向自定的缓冲区起始地址,参数size为缓冲区大小,参数mode有下列几种:

_IONBF 无缓冲IO

_IOLBF 以换行为依据的无缓冲IO

_IOFBF 完全无缓冲IO。如果参数buf为NULL 指针,则为无缓冲IO。

返回值:无


3
、以下函数在APO中用ns级赋值指令取代。


fgetc(由文件中读取一个字符)

相关函数:fopen,fread,fscanf,getc

表头文件:#include<stdio.h>

定义函数:int fgetc(FILE * stream);

函数说明:fgetc()用来从参数stream所指的文件中读取一个字符。若读到文件尾而无数据时便返回EOF。

返回值:fgetc()会返回读取到的字符,若返回EOF 则表示到了文件尾。


fgets(由文件中读取一字符串)

相关函数:fopen,fread,fscanf,getc

表头文件:#include<stdio.h>

定义函数:char * fgets(char * s,int size,FILE * stream);

函数说明:fgets()用来从参数stream所指的文件内读入字符并存到参数s所指的内存空间,直到出现换行字符、读到文件尾或是已读了size-1个字符为止,最后会加上NULL 作为字符串结束。

返回值:fgets()若成功则返回s指针,返回NULL 则表示有错误发生。


fputc(将一指定字符写入文件流中)

相关函数:fopen,fwrite,fscanf,putc

表头文件:#include<stdio.h>

定义函数:int fputc(int c,FILE * stream);

函数说明:fputc会将参数c 转为unsigned char 后写入参数stream 指定的文件中。

返回值:fputc()会返回写入成功的字符,即参数c。若返回EOF 则代表写

入失败。


fputs(将一指定的字符串写入文件内)

相关函数:fopen,fwrite,fscanf,fputc,putc

表头文件:#include<stdio.h>

定义函数:int fputs(const char * s,FILE * stream);

函数说明:fputs()用来将参数s所指的字符串写入到参数stream所指的文件内。

返回值:若成功则返回写出的字符个数,返回EOF 则表示有错误发生。


fread(从文件流读取数据)

相关函数:fopen,fwrite,fseek,fscanf

表头文件:#include<stdio.h>

定义函数:size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream);

函数说明:fread()用来从文件流中读取数据。参数stream 为已打开的文件指针,参数ptr指向欲存放读取进来的数据空间,读取的字符数以参数size*nmemb 来决定。fread()会返回实际读取到的nmemb 数目,如果此值比参数nmemb 来得小,则代表可能读到了文件尾或有错误发生,这时必须用feof()或ferror()来决定发生什么情况。

返回值:返回实际读取到的nmemb 数目。


fwrite(将数据写至文件流)

相关函数:fopen,fread,fseek,fscanf

表头文件:#include<stdio.h>

定义函数:size_t fwrite(const void * ptr,size_tsize,size_t nmemb,FILE * stream);

函数说明:

fwrite()用来将数据写入文件流中。参数stream为已打开的文件指针,参数ptr指向欲写入的数据地址,总共写入的字符数以参数size*nmemb 来决定。fwrite()会返回实际写入的nmemb 数目。

返回值:返回实际写入的nmemb数目。


getc(由文件中读取一个字符)

相关函数:read,fopen,fread,fgetc

表头文件:#include<stdio.h>

定义函数:int getc(FILE * stream);

函数说明:

getc()用来从参数stream所指的文件中读取一个字符。若读到文件尾而无数据时便返回EOF。虽然getc()与fgetc()作用相同,但getc()为宏定义,非真正的函数调用。

返回值:getc()会返回读取到的字符,若返回EOF 则表示到了文件尾。


getchar(由标准输入设备内读进一字符)

相关函数:fopen,fread,fscanf,getc

表头文件:#include<stdio.h>

定义函数:int getchar(void);

函数说明:

getchar()用来从标准输入设备中读取一个字符。然后将该字符从unsigned char 转换成int后返回。

返回值:getchar()会返回读取到的字符,若返回EOF 则表示有错误发生。

附加说明:getchar()非真正函数,而是getc(stdin)宏定义。


gets(由标准输入设备内读进一字符串)

相关函数:fopen,fread,fscanf,fgets

表头文件:#include<stdio.h>

定义函数:char * gets(char *s);

函数说明:

gets()用来从标准设备读入字符并存到参数s 所指的内存空间,直到出现换行字符或读到文件尾为止,最后加上NULL 作为字符串结束。

返回值:gets()若成功则返回s 指针,返回NULL 则表示有错误发生。

附加说明:由于gets()无法知道字符串s的大小,必须遇到换行字符或文件尾才会结束输入,因此容易造成缓冲溢出的安全性问题。建议使用fgets()取代。


putc(将一指定字符写入文件中)

相关函数:fopen,fwrite,fscanf,fputc

表头文件:#include<stdio.h>

定义函数:int putc(int c,FILE * stream);

函数说明:

putc()会将参数c转为unsigned char后写入参数stream指定的文件中。虽然putc()与fputc()作用相同,但putc()为宏定义,非真正的函数调用。

返回值:putc()会返回写入成功的字符,即参数c。若返回EOF 则代表写入失败。


putchar(将指定的字符写到标准输出设备)

相关函数:fopen,fwrite,fscanf,fputc

表头文件:#include<stdio.h>

定义函数:int putchar (int c);

函数说明:putchar()用来将参数c字符写到标准输出设备。

返回值:

putchar()会返回输出成功的字符,即参数c。若返回EOF 则代表输出失败。

附加说明:putchar()非真正函数,而是putc(c,stdout)宏定义。


ungetc(将指定字符写回文件流中)

相关函数:fputc,getchar,getc

表头文件:#include<stdio.h>

定义函数:int ungetc(int c,FILE * stream);

函数说明:

ungetc()将参数c 字符写回参数stream所指的文件流。这个写回的字符会由下一个读取文件流的函数取得

返回值:成功则返回c字符,若有错误则返回EOF。


4、fflush(更新缓冲区),相当于APO的wflush。

相关函数:write,fopen,fclose,setbuf

表头文件:#include<stdio.h>

定义函数:int fflush(FILE* stream);

函数说明:

fflush()会强迫将缓冲区内的数据写回参数stream指定的文件中。如果参数stream为NULL,fflush()会将所有打开的文件数据更新。

返回值:成功返回0,失败返回EOF,错误代码存于errno中。

错误代码:EBADF 参数stream 指定的文件未被打开,或打开状态为只读。

其它错误代码参考write()。

 

四、mmap映射

 

    标准I/O库的效率低,有时需要复制2次数据;一次在内核与标准I/O的缓冲区之间,另一次在标准I/O的缓冲区与用户程序中的缓冲区之间。如果将文件内容直接映射到用户空间的内存中,速度会快一倍,这就是mmap映射的想法。APO也是这样,甚至更为激进、内核可以直接使用用户定义的流容器当做是内核容器;速度要快4倍。而且,用户是使用ns级的赋值指令直接操作流容器,无须经过操作系统,相对速度要快10倍以上;APO不需要使用mmap。

mmap(建立内存映射)

相关函数:munmap,open

表头文件:

#include <unistd.h>

#include <sys/mman.h>

定义函数:*mmap( *start,size_t length,int prot,intflags,int fd,off_toffsize);

函数说明:

mmap()用来将某个文件内容映射到内存中,对该内存区域的存取即是直接对该文件内容的读写。参数start 指向欲对应的内存起始地址,通常设为NULL,代表让系统自动选定地址,对应成功后该地址会返回。参数length代表将文件中多大的部分对应到内存。参数prot 代表映射区域的保护方式,

有下列组合:

PROT_EXEC 映射区域可被执行

PROT_READ 映射区域可被读取

PROT_WRITE 映射区域可被写入

PROT_NONE 映射区域不能存取

参数flags 会影响映射区域的各种特性:

MAP_FIXED 如果参数start所指的地址无法成功建立映射,则放弃映射,不对地址修正。不鼓励用此旗标。

MAP_SHARED 对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。

MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。

MAP_ANONYMOUS 建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。

MAP_DENYWRITE 只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。

MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。

参数fd 为open()返回的文件描述词,代表欲映射到内存的文件。参数offset 为文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。

返回值:若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno中。

错误代码:

EBADF 参数fd 不是有效的文件描述词

EACCES 存取权限有误。如果是MAP_PRIVATE情况下文件必须可读,使用MAP_SHARED 则要有PROT_WRITE 以及该文件要能写入。

EINVAL 参数start、length或offset 有一个不合法。

EAGAIN 文件被锁住,或是有太多内存被锁住。

ENOMEM 内存不足。


munmap(解除内存映射)

相关函数:mmap

表头文件:

#include<unistd.h>

#include<sys/mman.h>

定义函数:int munmap(void *start,size_t length);

函数说明:

munmap()用来取消参数start所指的映射内存起始地址,参数length则是欲取消的内存大小。当进程结束或利用exec相关函数来执行其他程序时,映射内存会自动解除,但关闭对应的文件描述词时不会解除映射。

返回值:如果解除映射成功则返回0,否则返回-1,错误原因存于errno中

错误代码:EINVAL 参数start或length 不合法。

五、高级I/O


   关于高级I/O,本文不多讨论。因为APO的系统方法都是非阻塞的,文件也不使用记录锁;对于流设备的读写模式等,是用户自定义;也不使用I/O多路转接-select和poll函数等等。APO的进程间通信是消息交互、句柄提交、事件驱动。

    


       
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值