APUE笔记……文件I/O

时间:2021-07-05 10:05:13

 

3章文件I / O
 不带缓存指的是每个r e a d和w r i t e都调用内核中的一个系统调用。这些不带缓存的I / O函数不是ANSI C的组成部分,但是是P O S I X . 1和X P G 3的组成部分。
 
       对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,用open或creat返回的文件描述符标识该文件,将其作为参数传送给read或write。
       UNIX shell使文件描述符0与进程的标准输入相结合,文件描述符1与标准输出相结合,文件描述符2与标准出错输出相结合
       在P O S I X . 1应用程序中,幻数0、1、2应被代换成符号常数S T D I N _ F I L E N O、S T D O U T _F I L E N O和S T D E R R _ F I L E N O。这些常数都定义在头文件< u n i s t d . h >中。
          
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char*pa t h n a m e, int o f l a g,.../*, mode_t m o d e * / ) ;
返回:若成功为文件描述符,若出错为 - 1
 
       对于open函数而言,仅当创建新文件时才使用第三个参数。
       O_EXCL 如果同时指定了O _ C R E AT,而文件已经存在,则出错。这可测试一个文件是否存在,如果不存在则创建此文件成为一个原子操作。3 . 11节将较详细地说明原子操作。
       O_TRUNC 如果此文件存在,而且为只读或只写成功打开,则将其长度截短为0。
       O_SYNC 使每次w r i t e都等到物理I / O操作完成。
       由o p e n返回的文件描述符一定是最小的未用描述符数字。这一点被很多应用程序用来在标准输入、标准输出或标准出错输出上打开一个新的文件。
 
 
    也可用c r e a t函数创建一个新文件。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat(const char* path n a m e, mode_t m o d e) ;
返回:若成功为只写打开的文件描述符,若出错为 - 1
    此函数等效于:
o p e n ( p a t h n a m e, O_WRONLY|O _ C R E A T|O_TRUNC, m o d e) ;
    c r e a t的一个不足之处是它以只写方式打开所创建的文件。
 
 
 可用c l o s e函数关闭一个打开文件:
#include <unistd.h>
int close (int f i l e d e s);             返回:若成功为0,若出错为 - 1
 
 可以调用l s e e k显式地定位一个打开文件。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int f i l e d e s, off_t o f f s e t, int w h e n c e) ;
返回:若成功为新的文件位移,若出错为 - 1
 
 l s e e k仅将当前的文件位移量记录在内核内,它并不引起任何I / O操作。然后,该位移量用于下一个读或写操作。
 文件位移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将延长该文件,并在文件中构成一个空洞,这一点是允许的。位于文件中但没有写过的字节都被读为0。
 
 
    用r e a d函数从打开文件中读数据。
#include <unistd.h>
ssize_t read(int f i l e d e s, void * b u f f, size_t n b y t e s) ;
返回:读到的字节数,若已到文件尾为0,若出错为 - 1
 
 读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加实际读得的字节数。
 用w r i t e函数向打开文件写数据。
#include <unistd.h>
ssize_t write(int f i l e d e s, const void * b u f f, size_t n b y t e s) ;
返回:若成功为已写的字节数,若出错为 - 1
 
    (1) 每个进程在进程表中都有一个记录项,每个记录项中有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:
(a) 文件描述符标志。
(b) 指向一个文件表项的指针。
(2) 内核为所有打开文件维持一张文件表。每个文件表项包含:
(a) 文件状态标志 ( 读、写、增写、同步、非阻塞等 )
(b) 当前文件位移量。
(c) 指向该文件v节点表项的指针。
(3) 每个打开文件(或设备)都有一个v节点结构。v节点包含了文件类型和对此文件进行各种操作的函数的指针信息。对于大多数文件, v节点还包含了该文件的i节点(索引节点)。这些信息是在打开文件时从盘上读入内存的,所以所有关于文件的信息都是快速可供使用的。例如, i节点包含了文件的所有者、文件长度、文件所在的设备、指向文件在盘上所使用的实际数据块的指针等等
 
 在完成每个w r i t e后,在文件表项中的当前文件位移量即增加所写的字节数。如果这使当前文件位移量超过了当前文件长度,则在i节点表项中的当前文件长度被设置为当前文件位移量(也就是该文件加长了)。
 文件描述符标志和文件状态标志在作用范围方面的区别,前者只用于一个进程的一个描述符,而后者则适用于指向该给定文件表项的任何进程中的所有描述符。
 任何一个要求多于1个函数调用的操作都不能成为原子操作,因为在两个函数调用之间,内核有可能会临时挂起该进程(正如我们前面所假定的)。
 
 #include <unistd.h>
int dup(int f i l e d e s) ;
int dup2(int f i l e d e s, int f i l e d e s 2) ;
两函数的返回:若成功为新的文件描述符,若出错为 - 1
 
※ 由d u p返回的新文件描述符一定是当前可用文件描述符中的最小数值。用d u p 2则可以用 f i l e d e s 2参数指定新描述符的数值。如果 f i l e d e s 2已经打开,则先将其关闭。如若 f i l e d e s等于 f i l e d e s 2,则d u p 2返回 f i l e d e s 2,而不关闭它。
 复制一个描述符的另一种方法是使用f c n t l函数  fcntl (filedes, F_DUPFD, 0);
 
 
 
 f c n t l函数可以改变已经打开文件的性质。
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fcntl(int f i l e d e s, int c m d,.../* int a rg * / ) ;
返回:若成功则依赖于 c m d(见下),若出错为 - 1
 f c n t l函数有五种功能:
• 复制一个现存的描述符(c m d=F _ D U P F D)。
• 获得/设置文件描述符标记(c m d = F _ G E T F D或F _ S E T F D)。
• 获得/设置文件状态标志(c m d = F _ G E T F L或F _ S E T F L)。
• 获得/设置异步I / O有权(c m d = F _ G E TO W N或F _ S E TO W N)。
• 获得/设置记录锁(c m d = F _ G E T L K , F _ S E T L K或F _ S E T L K W)。
 
※三个存取方式标志( O _ R D O N LY, O _ W R O N LY,以及O _ R D W R )并不各占1位。(正如前述,这三种标志的值各是0、1和2,由于历史原因。这三种值互斥—一个文件只能有这三种值之一。)因此首先必须用屏蔽字O _ A C C M O D E取得存取方式位,然后将结果与这三种值相比较。