您的位置:首页 > 产品设计 > UI/UE

APUE------文件I/O

2016-04-18 21:56 141 查看

文件描述符

对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。

按照惯例,UNIX系统shell把文件描述符0与进程的标准输入关联,文件描述符1与标准输出关联,文件描述符2与标准错误关联。

在符合POSIX.1的应用程序中,幻数0、1、2虽然已被标准化,但应当把他们替换成符号常量STDIN_FILENO、STDOUT_FILENO和STDERR_FILRNO以提高可读性。这些常亮都在头文件

函数 open 和 openat

调用open或openat函数可以打开或创建一个文件

#include <fcntl.h>
int open(const char *path, int oflag,.../*mode_t mode*/);
int openat(int fd, const char *path, int oflag, .../*mode_t mode*/);
//两函数的返回值:若成功,返回文件描述符;若出错,返回-1


path参数是要打开或创建文件的名字。oflag参数可用来说明此函数的多个选项(O_RDONLY,O_WRONLY,O_RDWR,O_CREAT等)。

fd参数把open和openat函数区分开,共有3种可能性。

1. path参数指定的是绝对路径,在这种情况下,fd参数被忽略,openat函数就相当于open函数。

2. path参数指定的是相对路径名,fd参数指出了相对路径在文件系统中的开始地址。fd参数是通过打开相对路径名所在的目录来获取。

3. path参数指定了相对路径名,fd参数具有特殊值AT_FDCWD。在这种情况下,路径名在当前工作目录中获取,openat函数在操作上与open函数类似。

函数creat

也可以调用create函数创建一个新文件

#include <fcntl.h>
int creat(const char *path, mode_t mode);
//返回值:若成功,返回为只写打开的文件描述符;若出错,返回-1


注意,此函数等效于:

open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);

函数close

可以调用close函数关闭一个打开文件

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


关闭一个文件时还会释放该进程加在该文件上的所有记录锁。

当一个进程终止时,内核自动关闭它所有的打开文件。很多程序都利用了这一功能而不显式地用close关闭打开文件。

函数lseek

每个打开文件都有一个与其相关联的“当前文件偏移量”(current file offset)。它通常是一个非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件偏移量出开始,并使偏移量增加所读写的字节数。按系统默认的情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0。

可以调用lseek显式地为一个打开文件设置偏移量。

#include <unistd.h>
off_t lssek(int fd, off_t offset, int whence);
//返回值:若成功,返回新的文件偏移量;若出错,返回-1


对参数offset的解释与参数whence的值有关。

1. 若whence是SEEK_SET,则将该文件的偏移量设置为距文件开始处offset个字节。

2. 若whence是SEEK_CUR,则将该文件的偏移量设置为其当前值加offset

3. 若whence是SEEK_END,则将该文件的偏移量设置为文件长度加offset

如果文件描述符指向的是一个管道、FIFO或网络套接字,则lseek返回-1,并将errno设置为ESPIPE。

lseek仅将当前文件偏移量记录在内核中,它并不引起任何I/O操作。然后该偏移量用于下一个读或写操作。

函数read

调用read函数从打开文件中读数据

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
//返回值:读到的字节数,若已到文件尾,返回0;若出错,返回-1


有很多中情况实际读到的字节数少于要求读的字节数:

1. 读普通文件时,在读到要求字节之前已经达到了文件尾端。

2. 当从终端设备读时,通常一次最多读一行。

3. 当从管道或FIFO读时,若管道包含的字节少于所需的数量,那么read将只返回实际可用的字节数。

4. 当从某些面向记录的设备读时,一次最多返回一个记录。

5. 当一信号造成中断,而已经读了部分数据量时。

函数write

调用write函数向打开文件写数据

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);
//返回值:若成功,返回已写的字节数;若出错,返回-1


其返回值通常与参数nbytes的值相同,否则表示出错。write出错的一个常见原因是磁盘已写满,或者超过了一个给定进程的文件长度限制。

I/O的效率

大多数文件系统为改善性能都采用某种预读(read ahead)技术。当检测到正进行顺序读取时,系统就试图读入比应用所要求的更多数据,并假象应用很快就会读这些数据。

文件共享

内核使用3中数据结构表示打开文件,他们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。

(1) 每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表,可将其视为矢量,每个描述符占用一项。与每个文件描述符相关联的是:

a. 文件描述符标志。

b. 指向一个文件表项的指针。

(2) 内核为所有打开文件维持一张文件表。每个文件表项包含:

a. 文件状态标志。

b. 当前文件偏移量。

c. 指向该文件V节点表项的指针。

(3) 每个打开文件都有一个V节点结构。V节点包含了文件类型和对此文件进行各种操作函数的指针。对于大多数文件,V节点还包含了该文件的i节点。这些信息都是在打开文件时从磁盘上读入内存的,所以,文件的所有相关信息都是随时可用的。

可能有很多个文件描述符项指向同一个文件表项。在fork后也发生同样的情况,此时父进程、子进程各自的每一个打开文件描述符共享同一个文件表项。

注意,文件描述符标志和文件状态标志在作用范围方面的区别,前者只用于一个进程的一个描述符,而后者则应用于指向该给定文件表项的任何进程中的所有描述符。

一切对于多个进程读取同一文件都能正确工作。每个进程都有它自己的文件表项,其中也有它自己的当前文件偏移量。但是,当多个进程写同一文件时,则可能产生预想不到的结果。为了说明如何避免这种情况,需要理解原子操作的概念。

原子操作

(1) 追加到一个文件

(2) 函数pread和pwrite

#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
//返回值:读到的字节数,若已到文件尾,返回0;若出错,返回-1
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
//返回值:若成功,返回已写的字节数,若出错,返回-1


调用pread相当于调用lseek后调用read,但是pread又与这种顺序调用有下列重要区别。

1. 调用pread时,无法中断其定位和读操作。

2. 不更新当前文件偏移量。

(3) 创建一个文件

一般而言,原子操作(atomic operation)指的是由多步组成的一个操作。如果该操作原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。

函数dup和dup2

下面两个函数都可用来复制一个现有的文件描述符

#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2)
//两个函数的返回值:若成功,返回新的文件描述符;若出错,返回-1


由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。对于dup2,可以用fd2参数指定新描述符的值。如果fd2已经打开,则将其先关闭。如若fd等于fd2,则dup2返回fd2,而不关闭它。否则,fd2的FD_CLOEXEC文件描述符标志就清楚,这样fd2在进程调用exec时是打开状态。

函数sync、fsync和fdatasync

#include <unistd.h>
int fsnyc(int fd);
int fdatasync(int fd);
//返回值:若成功,返回0;若出错,返回-1
void sync(void);


sync只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。

通常,称为update的系统守护进程周期性地调用sync函数。这就保证了定期冲洗(flush)内核的块缓冲区。命令sync(1)也调用sync函数。

fsync函数只对由文件描述符fd指定的一个文件起作用,并且等待写磁盘操作结束才返回。fsync可用于数据库这样的应用程序,这种应用程序需要确保修改过的块立即写到磁盘上。

fdatasync函数类似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。

函数fcntl

fcntl函数可以改变已经打开文件的属性。

#include <fcntl.h>
int fcntl(int fd, int cmd,.../*int arg*/);
//返回值:若成功,则依赖于cmd;若出错,返回-1


fcntl函数有以下5中功能

1. 复制一个已有的描述符(cmd = F_DUPFD或F_DUPFD_CLOEXEC)。

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)。

函数ioctl

ioctl函数一直是I/O操作的杂物箱。不能用本章中其他函数表示的I/O操作通常都能用ioctl表示。终端I/O是使用ioctl最多的地方

#include <sys/IOCtl.h>
int ioctl(int fd, int request,...);
//返回值:若出错,返回-1;若成功,返回其他值


/dev/fd

较新的系统都提供名为/dev/fd的目录,其目录项是0、1、2等的文件。打开文件/dev/fd/n等效于复制描述符n(假定描述符是打开的)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: