Linux编程入门-文件I/O

碎碎念

获取glibc版本:

1
2
#include <gnu/libc-version.h>
const char *gnu_get_libc_version (void);

错误处理:

1
2
3
void perror (const char *__s); //打印错误信息
#include <string.h>
char *strerror (int __errnum); //根据errnum错误码获取描述字符串

通用I/O模型

C语言标准中的文件操作这里略。

open

打开一个已存在的文件或创建并打开一个新文件:

1
2
3
4
5
6
7
8
#define _GNU_SOURCE //功能测试宏 启用Linux特有的非标准扩展
#include <sys/stat.h>
#include <fcntl.h>
int open(
const char* __path, //要打开的文件 或符号链接
int __oflag, //访问模式
...
); //成功返回一文件描述符 错误-1 错误标志errno

新建文件的访问权限可能还受进程umask和父目录默认访问控制列表的影响。常用文件访问标志如下,其中前三个只能指定一种。

标志 含义
O_RDONLY 只读方式打开
O_WRONLY 只写方式打开
O_RDWR 读写方式打开
O_CLOEXEC 设置close-on-exec标志
O_CREAT 文件不存在则创建
O_DIRECTORY 若不是目录则失败
O_EXCL 当与O_CREAT结合使用时,若文件已存在则不打开文件并返回错误
O_NOCTTY 若打开的文件为中断设备,则使其不要称为控制终端
O_NOFOLLOW 不对符号链接进行解引用
O_TRUNC 清空文件
O_APPEND 文件尾追加数据
O_ASYNC I/O操作可行时产生信号通知进程,略
O_DIRECT 无系统缓冲
O_DSYNC 提供同步的I/O数据完整性
O_NOATIME read时不修改文件最近访问时间
O_NONBLOCK 非阻塞方式打开
O_SYNC 同步方式写文件
O_PATH

常见errno错误如下:

错误码 含义
EACCES 无法访问文件
EISDIR 打开目录进行写操作
EMFILE 进程已打开的文件描述符数达到进程资源限制上限
ENFILE 文件打开数量达到系统允许的上限
ENOENT 文件不存在且未指定创建标志,或指定路径目录不存在,或为空符号链接
EROFS 文件隶属于只读文件系统,企图写打开文件
ETXTBSY 所指定文件为正在运行的可执行文件,终止后即可

creat

创建并打开一个新文件,已被open替代。

1
2
3
4
5
int creat( //注意没有“e”
const char* __file,
mode_t __mode
);
//等同于O_WRONLY|O_CREAT|O_TRUNC的open

read

从文件描述符指代的打开文件中读取数据:

1
2
3
4
5
6
#include <unistd.h>
ssize_t read(
int __fd, //文件描述符
void* __buf, //存放输入数据的缓冲区地址
size_t __nbytes //最多读取的字节数
); //成功返回实际读取字节数 EOF返回0 错误-1

write

将数据写入一个已打开的文件中:

1
2
3
4
5
ssize_t write(
int __fd,
const void* __buf,
size_t __n
);

close

关闭一个打开的文件描述符,释放回调用进程,供该进程继续使用。进程终止时自动关闭已打开的所有文件描述符。

1
2
3
int close(
int __fd
);

lseek

调整文件偏移量。若某文件包含N字节数据,从0到N-1,则SEEK_SET为0,SEEK_END为N。

1
2
3
4
5
__off_t lseek(
int __fd, //文件描述符
__off_t __offset, //偏移量
int __whence //参照基点
); //成功返回新偏移量

常用参照基点如下:

基点 含义
SEEK_SET 文件头部
SEEK_CUR 当前偏移量
SEEK_END 文件尾部

当whence为SEEK_SET时offset必须为非负数,其他时可正可负。常见errno错误码有ESPIPE,表示lseek不能应用于管道、FIFO、套接字或终端。

当往文件结尾后一段距离处写入数据时,中间的空间称为文件空洞,读取时返回空字节。

ioctl

用于通用模型以外的操作:

1
2
3
4
5
6
#include <sys/ioctl.h>
int ioctl(
int __fd, //某文件或设备的文件描述符
unsigned long int __request, //控制操作
... //参数
);

fcntl

对一个打开的文件描述符执行一系列控制操作:

1
2
3
4
5
int fcntl(
int __fd,
int __cmd,
...
);

例如获取或修改打开文件的访问模式和状态标志,即在open时设置的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int flags=fcntl(fd,F_GETFL),accessMode;
if(flags==-1){
//...
};
if(flags&O_SYNC){
//...
};
accessMode=flags&O_ACCMODE;
if((accessMode==O_WRONLY)||(accessMode==O_RDWR)){
//...
};
flags|=O_APPEND;
if(fcntl(fd,F_SETFL,flags)==-1){
//...
};

dup/dup2

复制一个打开的文件句柄。其中0为标准输入流,1为标准输出流,2为标准错误流。系统保证返回新描述符一定是编号值最低的未用文件描述符。对于dup2,当目标文件描述符已被占用,则自动先关闭再复制。当dup2的fd无效,则返回EBADF,不关闭fd2。dup3的flags常用值有O_CLOEXEC,含义为为新文件描述符设置close-on-exec标志。

1
2
3
4
5
6
7
8
9
10
11
12
int dup(
int __fd //被复制的打开的文件描述符
); //返回新描述符
int dup2(
int __fd, //被复制的
int __fd2 //目标
); //返回fd2
int dup3(
int __fd,
int __fd2,
int __flags //系统调用行为
);

例如:

1
2
3
4
5
6
7
8
9
10
//法一
newfd=dup(1); //获得3
close(2);
newfd=dup(1); //获得2

//法二
dup2(1,2);

//法三
newfd=fcntl(oldfd,F_DUPFD,startfd); //返回大于等于startfd的最小未用操作符编号

pread/pwrite

再指定偏移量位置进行文件I/O操作,不改变文件当前偏移量:

1
2
3
4
5
6
7
8
9
10
11
12
ssize_t pread(
int __fd,
void* __buf,
size_t __nbytes,
__off_t __offset
);
ssize_t pwrite(
int __fd,
const void* __buf,
size_t __n,
__off_t __offset
);

readv/writev

分别实现分散输入和集中输出,这俩都原子操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct iovec {
void* iov_base; /* Pointer to data. */
size_t iov_len; /* Length of data. */
};
ssize_t readv(
int __fd,
const struct iovec* __iovec, //缓冲区数组
int __count //iovec成员个数
); //成功返回读取字节数 EOF则0
ssize_t writev(
int __fd,
const struct iovec* __iovec,
int __count
);

preadv/pwritev

在指定文件偏移量处进行分散-集中I/O:

1
2
3
4
5
6
7
8
9
10
11
12
13
#define _BSD_SOURCE
ssize_t pwritev(
int __fd,
const struct iovec* __iovec,
int __count,
__off_t __offset
);
ssize_t preadv(
int __fd,
const struct iovec* __iovec,
int __count,
__off_t __offset
);

truncate/ftruncate

将文件大小设为指定值,不修改文件偏移量。

1
2
3
4
5
6
7
8
int truncate(
const char* __file, //路径名字符串 要求有写权限
__off_t __length
);
int ftruncate(
int __fd, //写方式打开的文件描述符
__off_t __length
);