1. 文件描述符(File Description)

【1】文件描述符是一个非负整数,变化范围为1~OPEN_MAX。
【2】0,1,2经常替换成符号常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,在头文件<unistd.h>中。
【3】读写时,open和creat返回的文件描述符标识该文件,并将其作为参数返回给read和write使用。

2. open函数

#include <fcntl.h>
//若成功啧返回文件描述符,失败返回-1
int open(const char *pathname ,int ofalg ,.../* mode_t mode*/};

【1】…标识余下的参数数量以及类型根据具体点的调用会不同(可实现重载)。
【2】pathname为打开或创造文件的名字。
【3】oflag为函数的多个选项,用一个或多个常量用|构成。
【4】oflag必须从以下三个常量中指定一个且仅能指定一个:

以下常量是可选的:

每次进行写操作时,文件表项的当前文件偏移量首先被设置成i节点中的文件长度。使每次写数据都在文件尾端。

以下选项也可以选其一:

其中当文件用O_DSYN打开使用,重写文件部分内容时,文件时间大小等属性并不会同步更新,而使用O_SYNC打开是,每次write操作都会等待文件时间等属性更新完成后才会返回。
【5】open返回的文件描述符一定是最小的未用文件描述符数值,关于这点,dup函数也是如此。
【6】mode为权限设置位,为下表的或 ,也可以用下下表中八进制数字的和表示。最终权限为(mode & ~umask)

3. creat函数

#include <fcntl.h>
//成功返回以O_WRONLY打开的文件描述符,失败返回-1
int creat(const char *pathname ,mode_t mode)
//等效于open(pathname , O_WRONLY | O_CREAT | O_TRUNC ,mode)

【1】mode控制权限:

【2】若读,需要creat,close然后再open,不如直接用open替代:
open(pathname , O_RDWR | O_CREAT | O_TRUNC ,mode)

4.close函数

#include <unistd.h>
int close(int filedes);//成功返回0,出错返回-1

【1】进程终止时,内核自动关闭它打开的文件。
【2】关闭文件时还会释放该进程加在文件上的所有记录锁。

5.lseek函数

lseek函数可以显式的为一个打开的文件设置文件偏移量(fie offset)。

#include <unistd.h>
off_t lseek(int filedes,off_t offset,int whence);//成功则返回新的文件偏移量,失败-1

【1】偏移量通常是一个以“字节”为单位的非负整数(普通文件必须非负,某些设备可能允许负偏移量)。
【2】打开文件时,除非指定O_APPEND,否则偏移量初始均为0。
【3】whence为SEEK_SET时,偏移量设置为offset;
whence为SEEK_CUR时,偏移量设置为当前值+offset,其中offset可为负;
whence为SEEK_END时,偏移量设置为文件长度+offset,其中offset可为负;
【4】可通过以下方式获得当前的偏移量

off_t cur=lseek(fd , 0 , SEEK_CUR);

【5】偏移量可以大于文件的当前长度,这种情况下对文件的下一次写将加长文件并形成空洞。空洞并不要求在磁盘上占用存储区,不需要分配磁盘块,但文件大小是通过文件长度计算。

6. read函数

#include <unistd.h>
//若成功啧返回读到的字节数,若读到文件结尾啧返回0,若出错则返回-1
ssize_t read(int filedes, void *buf, size_t nbyte);//从对应文件读nbyte字节给buf

【1】一下情况会出现实际读到的字节数小于要求读的字节数:

  • 读普通文件时,读到要求字节数之前就到达了文件尾端。
  • 读终端设备时,通常一次只读一行。
  • 从网络读时,网络的缓冲机构可能造成返回值小于要求读的字节数。
  • 读面向记录的设备(如磁带)时,一次最多返回一个记录。
  • 从管道或FIFO读时,若内包含字节小于所需的数量,只返回实际可用量。
  • 某一信号造成中断且已读部分数据量时。

7. write函数

#include <unistd.h>
//若成功则返回已写的字节数,若出错则返回-1
ssize_t write(int filedes, void *buf, size_t nbyte);//从buf写nbyte字节的数据给filedes

【1】对于普通文件,写操作从当前偏移量开始。但如果打开文件时指定了O_APPEND,则在每次写操作之前,将文件偏移量设置在文件的当前结尾处。每次进行写操作时,文件表项的当前文件偏移量首先被设置成i节点中的文件长度。

8. 文件共享

【1】内核使用三种数据结构来标书打开的文件:

  1. 每个进程在进程表中有一个记录向,记录向中包含一张打开文件描述符表,其中包括:(a)文件描述符标志 (b)指向一个文件表的指针
  2. 内核为所有打开文件维持一张文件表,其中包含:
    (a)文件状态标志(读,写,读写,同步,非阻塞等)。
    (b)当前文件偏移量。
    (c)指向该文件v节点表项的指针。
  3. 每个打开文件(或设备)均有一个v节点(v-node)结构,包含了文件类型和对文件进行各种操作的函数的指针。对大多文件还包括了i节点(i-node,索引节点),其中信息是打开文件时从磁盘读入内存,包括文件所有者,文件长度,所在设备,实际位置等信息。

【2】对于一个文件 只有一个v节点表项。
【3】多个进程打开同一文件,每个进程都有自己的文件表项,因为每个进程都有他自己对应该文件的当前偏移量。但仅有一个v节点。

【4】dup函数会使同一个进程的多个fd标志的文件指针指向同一个文件表。
【5】fork函数得到的父子进程对于每个打开的文件描述符共享用一个文件表项。
【6】使用A_APPEND标志打开文件,相应标志也被设置到文件表项的文件状态标志中。每次进行写操作时,文件表项的当前文件偏移量首先被设置成i节点中的文件长度。
【7】write操作在文件表项中的当前文件偏移量增加所写的字符数。若当前文件偏移量大于当前文件长度,则将i节点表项中的当前文件长度设置成当前文件偏移量。
【8】若使用lseek定位到文件尾端,啧文件表项中的当前文件偏移量被设置为i节点表项中的当前文件长度。(与O_APPEND不同)

9. 原子操作

指的是多步组成的操作,要么执行整个过程,要么一步不执行。

#include <unistd.h>
/*
lseek(filedes,SEEK_SET,offset);
read(filedes,buf,nbytes);
*/
ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset);
/*
lseek(filedes,SEEK_SET,offset);
write(filedes,buf,nbytes);
*/
ssize_t pwrite(int filedes, const void *buf, size_t nbytes, off_t offset);

10.dup和dup2函数

复制一个现存的文件描述符(使多个文件描述符指向同一个文件表项)

#include <unistd.h>
//若成功则返回新的文件描述符,出错返回-1.
int dup(int filedes); //等效于int fcntl(filedes, F_DUPFD, 0);
int dup2(int filedes, int filedes2);

【1】dup返回的新文件描述符一定是当前可用文件描述符的最小值。
【2】dup2可以用filedes2指向新描述符的数值,若filedes2已打开,则先将其关闭。
【3】返回的新文件描述符与filedes共享一个文件表项。

10. sync、fsync和fdatasync函数

【1】延迟写:UNIX在内核中设有缓冲区。写入时,内核通常先将该数据复制到一个缓冲区,缓冲区写满或内核需要重用该缓冲区时,才将该缓冲排入输出队列,然后待其达到队首时才进行I/O操作。
【2】延迟写减少磁盘读写次数,但降低了更新速度,故障时可能造成更新内容丢失。为了保证磁盘实际文件系统和缓冲区中内容的一致性,提供sync、fsync和fdatasync函数。

#include <unistd.h>
//成功返回0,失败返回-1
int fsync(int filedes);
int fdatasync(int filedes);
void sync(void);

【3】sync将所有修改过的快缓冲区排入写队列就返回,不等待实际写磁盘结束。
【4】fsync只对filedes指定的单一文件起作用,且等待写磁盘操作结束再返回。
【5】fdatasync类似fsync,但只影响文件的数据部分而不同步更新文件属性。

11. fcntl函数

#include <fcntl.h>
int fcntl(int filedes,int cmd, ... /*int arg*/ );//成功依赖于cmd,失败返回-1

【1】fcntl函数有5个功能:



【2】修改文件描述符标志或文件状态标志时必须谨慎,先去的先有的标志值然后根据需求修改。

val |= flags;     // turn on flags
val &= ~flags;    // turn off flags

【3】F_GETFL模式下的O_RDONLY,O_WRONLY,O_RDWR并不各占1位(分别为0,1,2,互斥)因此必须先用O_ACCMODE获得访问模式位,再将结果与这三种比较

switch(val & O_ACCMODE){
    case O_RDONLY:/*...*/
    case O_WRONLY:/*...*/
    CASE O_RDWR:/*...*/
    default:/*..*/
    }

【4】fsync和fdatasync函数在我们需要的时候更新文件内容,O_SYNC标志在我们每次写至文件的时候更新文件内容。

12. ioctl函数

#include <unistd.h>    /*System V*/
#include <sys/ioctl.h> /*BSD and Linux*/
#include <stropts.h>   /*XSI STREAMS*/
int ioctl(int filedes, int request, ...);//出错返回-1

【1】Linux把设备抽象成文件,无法用开、关、读、写等动作抽象的动作都用ioctl控制。
【2】系统提供通用的ioctl命令,每个设备驱动可以定义他自己专用的ioctl命令。

13. /dev/fd

【1】/dev/fd目录中为0、1、2等文件,打开文件/dev/fd/n相当于复制描述符n。

//下面两行代码是等效的。
fd=open("/dev/df/0", mode);
fd=dup(0);

【2】这种方式下大多系统会忽略mode,即便fd=open("/dev/df/0", O_RDWR);调用成功,依然不能对fd进行写操作。