1.概述
所有执行I/O操作的系统调用都以文件描述符,一个非负整数,来指代打开的文件。文件描述符用来表示所有以打开的文件类型
按照惯例,大多数的程序都会期望3种文件描述符
文件描述符 | 用途 | stdio流 |
---|---|---|
0 | 标准输入 | stdin |
1 | 标准输出 | stdout |
2 | 标准错误 | stderr |
在程序中指代这些文件描述符时,可以直接用(0,1,2)表示,或者采用POSIX的标准名称
2.通用I/O
(1)打开文件open()
#include<sys/stat.h>
#include<fcntl.h>
int open (const char *pathname,int flags,.../*mode_t mode*/);
//返回文件描述符
open()调用既能打开一个已有文件,也能创建并打开一个新文件,参数pathname是所要打开或创建的文件的绝对路径,参数flags为位掩码,指定文件的访问方式
访问模式 | 描述 |
---|---|
O_RDONLY | 以只读的方式打开 |
O_WRONLY | 以只写的方式打来 |
O_RDWR | 以读写的方式打开 |
位掩码参数指定的文件的访问权限,当然新建文件的访问权限不仅仅依赖mode有时也会没umask屏蔽掉一些权限
open调用的flags参数介绍
标志 | 用途 |
---|---|
O_RDONLY | 以只读方式打开 |
O_WRONLY | 以只写方式打开 |
O_RDWR | 以读写方式打开 |
O_CLOEXEC | 设置close-on-exec标志 |
O_CREAT | 若文件不存在则创建之 |
O_DIRECT | 无缓冲的输入/输出 |
O_EXCL | 结合CREAT参数使用 |
O_APPEND | 总在文件尾添加数据 |
O_NONBLOCK | 以非阻塞的方式打开 |
O_SYNC | 以同步方式写入文件 |
1)其中我们可以调用fcntl()的F_GETFL和F_SETFL操作可以分别检索和修改此类标志
2)O_EXCL如果文件以存在,则调用失败
(2)读取文件内容read()
#include<unistd.h>
ssize_t read(int fd,void *buffer,size_t count);
//返回读到的字节数
此函数从fd所指代的文件中读取数据,buffer参数提供了用来存放数据的缓冲区地址,count确定要读取的字节数,若read()调用成功,则返回读取的字节数,返回的值可以小于count
(3)数据写入文件write()
#include<unistd.h>
ssize_t write(int fd,void *buffer,size_t count);
//返回值为实际写入的字节数
read()调用的参数和read()的参数类似
(4)关闭文件close()
#include<unistd.h>
int close(int fd);
//成功返回0失败-1
(5)改变文件偏移量:lseek()
#include<unistd.h>
off_t lseek(int fd,off_t offset,int whence);
//返回新的文件指针的位置
offset指定了以字节为单位数值的偏移量,whence参数的实参如下:
whence | 说明 |
---|---|
SEEK_SET | 将文件偏移量设置成从文件头部开始的offset个字节 |
SEEK_CUR | 相对于当前文件偏移量,将文件偏移量调整为offset |
SEEK_END | 将文件偏移量设置为起始于文件尾部的offset个字节,offset可以为负 |
深入探究文件I/O
(1)原子操作和竞争条件
所有的系统调用都是以原子操作执行的,期间不会被其他进程或线程所中断
原子性是某些操作圆满成功的关键所在,特别是它规避了竞争状态,竞争状态是这样一种情形:操作共享资源的俩个进程(线程),其结果取决与一个无法预期的顺序
(2)文件控制操作fcntl()
#include<fcntl.h>
int fcntl(int fd,int cmd,...);
//成功返回cmd,否则返回-1
fcntl()系统调用对一个打开的文件描述符执行一系列控制操作参数cmd常见的有F_GETFL获得文件由open()调用中flag参数指定的设置,F_SETFL设置flag中的某项属性
可以用fcntl()的F_SETFL命令来修改或打开文件的某些状态,操作的时候只需按位与,具体的状态标志有O_APPEND,O_NONBLOCK,O_NOATIME,O_ASYNC和O_DIRECT
之所以可以对这些操作按位与,是因为这些宏以二进制表示时,其中只有一个bite位为1
判定文件的访问模式有点复杂,这是因为O_RDONLY(0),OWRONLY(1),O_RDWR(2)这三个常量()中对应的数字并不与打开文件状态中的单个比特位对应,因此要判断访问模式时只需获得flags的后俩位,与上述三个常量的值做比较就可以
(3)文件描述符和打开文件之间的关系
表面上文件描述符似乎和文件是一一对应的关系,但实际并不是这样,多个文件描述符指向同一个文件这既有可能也属必要
要理解具体如何,需要查看内核维护的3个数据结构
1)进程级的文件描述符表
进程级文件描述符表 |
---|
控制文件描述符的一组标志 |
对打开文件句柄的应用 |
针对每一个进程,内核内核为其维护打开文件的描述符表
(2)系统级的打开文件表
打开文件表 |
---|
当前文件偏移量 |
打开文件时所使用的状态标志 |
文件访问模式 |
对该文件i-node对象的引用 |
以上表格就时系统表存储的打开文件的全部信息
(3)文件系统的i-node表
i-node表 |
---|
文件类型 |
一个指针,指向该文件持有锁的列表 |
文件的各种属性 |
文件描述符与文件的关系要点为:
1)俩个不同的文件描述符,若指向同一个打开的文件句柄,将共享同一文件偏移量
2)文件描述符标志(close-on-exec)为进程和文件描述符所私有,对这一标志的修改将不会影响同一进程或不同进程中的其他文件描述符
(4)复制文件描述符
基本函数
#include<unistd.h>
int dup(int oldfd);
int dup2(int oldfd,int newfd);
int dup3(int oldfd,int newfd,int flags);
dup()调用复制一个打开的文件描述符oldfd,并返回一个新的文件描述符,俩者都指向同一句柄,系统会保证新描述符会是编号值最低的未用的描述符
dup2()函数调用中的newfd可以指定文件描述符,例如 dup2(4,1)则会将标准输出的文件描述符复制给文件描述符为4的文件,此时标准输出文件以被调用close()关闭
dup3()完成的工作与dup2()相同,只是新加了附加参数flag,这是一个可以修改系统调用行为的掩码,目前dup3()只支持O_CLOEXEC标志
(5)分散输入和集中输出readv(),writev()
#include<sys/uio.h>
ssize_t readv(int fd,const struct iovec *iov,int iovcnt);
ssize_t writev(int fd,const struct iovec *iov,int iovcnt);
此俩个系统调用是同时将多空分散的缓存区同时读或写
其中fd为要读写的文件描述符,iov为一结构体数组
struct iovec
{
void *iov_base; //块起始地址
size_t iov_len; //这块地址的大小
}
参数iovcnt则为具体读或写的块数
(6)创建临时文件
有些时候需要创建一些临时文件,仅供其在运行期间使用,程序终止后即删除
#include<stdlib.h>
int mkstemp(char *template);
//成功返回文件描述符,失败返回-1
文件拥有者对mkstemp创建的文件拥有读写权限,且在打开文件时使用了O_EXCL标志,以保证调用这以独占方式访问文件
通常打开文件不久,程序就会使用unlink系统调用将其删除
#include<stdlib.h>
FILE *tempfile(void);
tempfile调用成功将返回一个文件流供stdio函数库使用,文件流关闭后将自动删除文件