/******************************************************************
*** 文件 I/O
*** UNIX系统中的大多数文件I/O只需用到5个函数:
*** open, read, write, lseek, close
******************************************************************/
文件描述符:
对于内核而言,所有打开的文件都是通过文件描述符引用。文件描述符是一个非负整数。当打开一个现有文件或者新文件时,内核向进程返回一个文件描述符,当读写一个文件时,使用open/creat返回的文件描述符标识该文件,将其作为参数传递给read或write。
系统使用的文件描述符:
0 <------> 标准输入 <------> STDIN_FILENO
1 <------> 标准输出 <------> STDOUT_FILENO
2 <------> 标准错误输出<------> STDERR_FILENO
open 函数:
open(const char *pathname,int oflag,.../*mode_t mode*/);
只有当创建新文件时才会用到第三个参数 设置文件的权限,
open常用的标志:
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
O_APPEND 追加
O_CREAT 创建
open函数成功返回一个最小文件描述符,失败返回-1;
close函数
close(int filedes); //成功返回 0,失败返回-1;
lseek函数
lseek(int filedes,off_t offset, int whence);//成功返回新的文件偏移量,失败返回-1;
若whence是SEEK_SET,则将该文件的偏移量设置为距文件开头处offset个字节
若whence是SEEK_CUR,则将该文件的偏移量设置为当前值加offset个字节
若whence是SEEK_END,则将该文件的偏移量设置为距文件尾处offset个字节
offset可正可负
lseek仅将当前文件的偏移量记录在内核中,它并不引起任何I/O操作。然后,该偏移量用于下一个读或者写操作。
文件偏移量可以大于文件当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,这一点是允许的,位于文件中但没有写过的字节都被读为0。
read 函数:
ssize_t read(int filedes,void *buf,size_t nbytes);//若成功则返回读到的字节数,若已到文件结尾则返回0,若出错则返回-1
第一个参数是要进行读操作的文件的文件描述符,第二个参数是用来存储读到的内容,第三个参数是要读取的字节数。实际读到的字节数(返回值)小于等于要读取的字节数。
当从终端设备读时,通常一次最多读取一行
当从某些面向记录的设备(如磁带)读时,一次最多返回一个记录。
write 函数:
ssize_t write(int filedes,void *buf,size_t nbytes);//若成功返回写入的字节数,若出错返回-1;
第一个参数是要进行写操作的文件的文件描述符,第二个参数是要写的内容的地址,第三个参数是要写入的字节数。实际写入的字节数(返回值)通常等于要写入的字节数(nbytes)。
对于普通文件,写操作从文件的当前偏移量处开始。如果在打开该文件之前,指定了O_APPEND选项,则在每次写操作之前将文件偏移量设这在文件的当前结尾处。
文件共享:
内核使用三种数据结构表示打开的文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。
(1)、每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:
a、文件描述符标志
b、指向一个文件表项的指针。
(2)、内核为所有打开文件维持一张文件表。每个文件表项包含:
a、文件状态标志(读、写、添加、同步和非阻塞等)
b、当前文件偏移量
c、指向该文件v节点表项的指针。
(3)、每个打开文件(或设备)都有一个v节点(v-node)结构。v节点包含了文件类型和对此文件进此各种操作的函数的指针。对于大多数文件,v节点还包含了该文件的i节点(i-node,索引节点)。这些信息是在打开文件时从磁盘上读入内存的,所以所有关于文件的信息都是快速可供使用的。例如,i节点包含了文件的所有者,文件长度,文件所在的设备,指向文件实际数据块在磁盘上所在位置的指针等等。
给出了这些数据结构之后,现在对前面所述的操作作进一步说明:
* 在完成每个write后,在文件表项中的当前文件偏移量即增加所写的字节数。如果这使当前文件偏移量超过了当前文件长度,则在i节点表项中的当前文件长度被设置为当前文件偏移量。
* 如果用O_APPEND标志打开了一个文件,则相应标志也被设置到文件表项的文件状态标志中。每次对这种具有添加写标志的文件执行写操作时,在文件表项中的当前文件偏移量首先设置为i节点表项中的文件长度。这就使得每次写的数据都添加到文件的当前尾端处。
* 若一个文件用lseek定位到文件当前尾端,则文件表项中的当前文件偏移量被设置为i节点表项中的当前文件长度。
* lseek函数只修改文件表项中的当前文件偏移量,没有进行I/O操作。
/*文件描述符标志和文件状态标志在作用域方面的区别,前者只用于一个进程的一个描
*述符,而后者则适用于指向该给定文件表项的任何进程中的所有描述符。
*/
dup和dup2函数
int dup(int filedes);
int dup2(int filedes, int filedes2);
两个函数返回值:若成功返回新的文件描述符,若出错返回 -1;
由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。用dup2则可以用filedes2参数指定新的文件描述符的数值。如果filedes2已经打开,则先将其关闭。若filedes等于filedes2则dup2返回filedes2,而不关闭它。
新旧文件描述符共享一个文件表项。
sync, fsync和fdatasync函数:
延迟写减少了磁盘读写次数,但是却降低了文件内容的更新速度。为了保证磁盘上实际文件系统与缓冲区中的内容一致,UNIX系统提供了sync,fsync和fdatasync三个函数。
int fsync(int filedes);
int fdatasync(int filedes);
int sync(void);
sync函数只是将所有修改过的块缓冲区排队写队列,然后就返回,它并不等待实际写磁盘操作结束。
fsync函数只对由文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束,然后返回。
fdatasync函数类似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。
fcntl函数
fcntl函数可以改变已打开的文件的性质
int fcntl(int filedes,int cmd, .../*int arg或者结构指针*/ );//成功依赖于cmd,若出错返回-1;
fcntl函数的5中功能:
1)、复制一个现有的描述符(cmd=F_DUPFD).
2)、获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
3)、获得/设置文件状态标志(cmd=F_GETFL或F_SETFL).
4)、获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
5)、获得/设置记录锁(cmd=F_GETLK、F_SETLK或F_SETLKW).
这10种cmd值中的前7种,将涉及与进程表项中各文件描述符相关联的文件描述符标志,以及每个文件表项中的文件状态标志。
ioctl 函数:
int ioctl(int filedes, int request, ... );//失败返回-1,成功返回其他值
第二个参数总是一个头文件的define名字。
对于ISO C 原型,它用省略号表示其余参数,但是,通常只有另外一个参数,他常常是指向一个变量或者结构的指针。
每个设备驱动程序都可以定义它自己专用的一组ioctl命令。系统则为不同种类的设备提供通用的ioctl命令。
/dev/fd
较新的系统都提供名为/dev/fd的目录,其目录项是名为0、1、2等的文件,打开文件/dev/fd/n等效于复制描述符n (假定描述符n是打开的)。