概述
文件I/O
引言
先说明可用的文件I/O函数————打开文件,读文件,写文件等。UNIX系统大多数文件I/O只需要用到5个函数:open、read、write、lseek以及close。然后说明不同缓冲长度对read和write函数的影响。
本章描述的函数经常被称为不带缓冲的I/O,(将于标准的I/O对照)。术语不带缓冲指的是每个read和write都调用内核中的一个系统调用。
文件描述符
对于内核而言,所有打开的文件都可以通过文件描述符引用。它是一个非负整数,变化范围时0~OPEN_MAX-1。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,将这个文件描述符作为参数传递给read或write。
UNIX系统shell把文件描述符0与进程的标准输入关联,文件描述1与标准输出关联,文件描述符2与标准错误关联。
在符合POSIX.1的应用程序中,幻数0、1、2虽然已被标准化,但应当把他们替换成符号常量STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO以提高可读性。这些常量都在头文件<unistd.h>中。
函数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
我们将最后一个参数写为…,ISO C 用这种方法表示可变参数和类型。对于open而言,仅当创建新文件时,才使用最后这个参数。在函数原型中此参数放置在注释中。
path参数时要打开或创建的文件名,oflag参数可用来说明此函数的多个选项。用下列一个或多个常量进行”或“运算构成oflag参数。
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读写打开
O_EXEC 只执行打开
O_SEARCH 只搜索打开(应用于目录)
这5个常量必须指定一个且只能指定一个。下列是可选的。
O_APPEND 每次写时都追加在文件末尾
O_CLOEXEC 把FD_CLOEXEC常量设置为文件描述符标志
O_CREAT 若此文件不存在则创建它。使用此选项时,需说明mode参数,mode指定文件的访问权限
O_DIRECTORY 如果path引用的不是目录,则报错
O_EXCL 如果同时指定了O_CREAT,而文件已存在,则出错。用此可测试文件是否存在
O_NOCTTY 如果path引用的时终端设备,则不将该设备分配为此进程的控制终端
O_NOFOLLOW 如果path引用的是一个符号链接,则出错
O_NONBLOCK 如果path引用的是一个FIFO、一块特殊文件或一个字符特殊文件,则设置I/O操作为非阻塞
O_SYNC 使每次write等待物理I/O操作完成
O_TRUNC 如果此文件存在,而且为只写或读写打开,则将其长度截断为0
O_TTY_INIT 如果打开一个还未打开的终端设备,设置非标准termios参数。
函数create
#include <fcntl.h>
int create(const char *path, mode_t mode); //返回值:若成功,返回文件描述符;否则返回-1
注意,此函数等效于: open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);create的不足之处在于它以只写方式打开创建的文件,如果文件需要读的,则不方便,一般不使用。
函数close
#include <fcntl.h>
int close(int fd); //返回值:若成功,返回0;否则返回-1
关闭一个文件时还会释放该进程加在该文件上的所有记录锁。当一个进程终止时,内核自动关闭它所有打开的文件。利用这一功能而不显示地调用close关闭打开文件。
函数lseek
每个打开文件都有一个与其相关联的”当前文件偏移量“。它是一个非负整数,用以度量文件开始处计算的字节数。通常读写操作都是从当前文件偏移量开始的,并使偏移量增加所读写的字节数。按系统默认,打开一个文件时,除指定O_APPEND,否则该偏移量被设置为0.
#include <fcntl.h>
off_t lseek(int fd, off_t offset, int whence); //返回值:若成功,返回文件偏移量;否则返回-1
对参数offset的解释和参数whence的值相关。
- 若whence时SEEK_SET,则将该文件的偏移量设置为距文件开始处offset个字节
- 若whence时SEEK_CUR,则将该文件的偏移量设置为当前值加offset,offset可正可负
- 若whence时SEEK_END,则将该文件的偏移量设置为文件长度加offset,offset可正可负
通常,文件当前偏移量是一个非负整数,但是,某些设备允许负的偏移量。所以测试时,不要测试它是否小于0,而要测试它是否等于-1。
函数read
调用函数read从打开文件中读数据
#include <unistd.h>
ssize_t read(int fd, void* buf, size_t nbytes); //返回值:读到的字节数,若已到文件末尾,返回0;如出错,返回-1
有多种情况可使实际读到的字节数少于要求读的字节数:
- 读普通文件时,在读到要求字节数前已到文件按尾端。
- 当从终端设备读时,通常一次最多读一行
- 当从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数。
- 当从管道或FIFO读时,如若管道包含的字节少于所需的数量,那么read将只返回实际的字节数
- 当从某些面向记录的设备(如磁带)读时,一次最多返回一个记录
- 当一信号中断,而已经读了部分数据,读操作从文件的当前偏移量开始,在成功返回前,该偏移量将增加实际读到的字节数。
函数write
调用函数write从打开文件中写数据
#include <unistd.h>
ssize_t write(int fd, void* buf, size_t nbytes); //返回值:返回已写字节数;如出错,返回-1
其返回值通常与参数nbytes的值相同,否则表示出错。write出错的一个常见原因是磁盘写满,或者超过一个给定进程文件长度限制。
I/O的效率
大多数文件系统为改善性能都采用某种预读(read ahead) 技术。当检测正进行顺序读取时,系统就试图读入比应用所要求的更多数据,并假想应用很快会读这些数据。预读的效果,缓冲区长度小至32字节的时钟时间与较大缓冲区长度的时钟时间几乎一样。
原子操作
1. 追加到一个文件
if (lseek(fd, OL, 2) < 0) // positon to EOF
err_sys("lseek error");
if (write(fd, buf, 100) != 100) //write
err_sys("write error");
对于多个进程同时使用上述方法将数据追加到同一文件,则会产生问题。假定有两个独立的进程A和进程B都对同一文件进行追加写操作。每个进程都已打开了该文件,但未使用O_APPEND标志。每个进程都有它自己的文件表项,但共享一个v节点表项。假定A调用了lseek,它将进程A的当前偏移量设置为1500(当前文件尾端处)。然后内核切换进程B,进程B也将对该文件的当前偏移量设置为1500。然后B调用write,它将B的该文件当前偏移量增加至1600。然后内核又切换,使进程A运行。当A调用write使,就从其1500处开始将数据写入文件。这样也就覆盖了进程B刚才写入该文件的数据。
Unix系统为这样的操作提供了一种原子操作方法,即在打开文件时设置O_APPEND标志。
2. 函数pread和pwrite
原子性地定位并执行I/O。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, void* buf, size_t nbytes, off_t offset); //返回值:返回已写字节数;如出错,返回-1
调用pread相当于调用lseek后调用read,但是pread又与这种顺序调用有下列重要区别。
- 调用pread时,无法中断其定位和读操作。
- 不更新当前文件偏移量
3. 创建一个文件
读open函数的O_CREAT和O_EXCL选项进行说明时,我们就见到有关原子操作的例子。
函数dup和dup2
下面两个函数都可以用来复制一个现有的文件描述符。
#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);
fcntl函数
fcntl函数可以改变已经打开文件的性质
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fcntl(int filedes, int cmd,.../*int arg*/);
返回:若成功则依赖于cmd(见下),若出错-1
fcntl函数有5中功能:
-
赋值一个现存的描述符(cmd=F_DUPFD)
-
获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
-
获取/设置文件状态标志(cmd=F_GETFL或F_SETFL)
-
获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
-
获得/设置记录所(cmd=F_GETLK,F_SETLK或F_SETLKW)
文件状态标志 | 说明 |
---|---|
O_RDONLY | 只读打开 |
O_WRONLY | 只写打开 |
O_RDWR | 读/写打开 |
O_APPEND | 写时添加至文件尾 |
O_NONBLOCK | 非阻塞方式 |
O_SYNC | 等待写完成 |
O_ASYNC | 异步I/O |
下面显示了从KornShell调用该程序时的几种情况:
$ a.out 0 < /dev/tty
read only
$ a.out 1 > temp.foo
write only
$ a.out 2 >> temp.foo
write only, append
$ a.out 5 5<> temp.foo
read write
函数sync、fsync和fdatasync
通常,当内核需要重用缓冲区来存放其他磁盘块数据时,它会把所有延迟写数据块写入磁盘。为了保证磁盘实际文件系统与缓冲区内容的一致性,UNIX系统提供了sync、fsync和fdatasync三个函数。
#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
返回值:若成功,返回0;出错,返回-1
void sync(void);
ioctl函数
#include <unistd.h> //SVR4
#include <sys/ioctl.h> //4.3+BSD
int ioctl(int filedes, int req,...);
返回:若出错则为-1;成功为其他值
目前,ioctl的主要用途分类展示于下表中
类型 | 常数名 | 头文件 | ioctl数 |
---|---|---|---|
盘标号 | DIOxxx | <disklabel.h> | 10 |
文件I/O | FIOxxx | <ioctl.h> | 7 |
磁带I/O | MTIOxxx | <mtio.h> | 4 |
套接口I/O | SIOxxx | <ioctl.h> | 25 |
终端I/O | TIOxxx | <ioctl.h> | 35 |
最后
以上就是落寞柚子为你收集整理的Unix环境高级编程学习——文件IO 文件I/O fcntl函数ioctl函数的全部内容,希望文章能够帮你解决Unix环境高级编程学习——文件IO 文件I/O fcntl函数ioctl函数所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复