常用函数
- open(文件名,文件模式[, 文件权限]), 返回值是文件描述符
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); // 多个flag用|连接,eg: flag1|falg2|... int open(const char *pathname, int flags, mode_t mode);
flag | 描述 |
O_RDONLY | 只读 |
O_WRONLY | 只写 |
O_RDWR | 可读可写 |
O_CREAT | 如果文件不存在就建立 |
O_EXCL | 与CREAT合用,用户建立文件(原子操作) |
O_APPEND | 追加模式,将指针移动到文件末尾 |
O_DSYNC | 效果是每次write后将内核缓冲区的数据立即写入磁盘 |
O_SYNC | 效果是每次write后将内核缓冲区的数据和元数据写入磁盘 |
mode | 说明 |
S_IRUSR | 允许文件所属者读文件 |
S_IWUSR | 允许文件所属者写文件 |
S_IXUSR | 允许文件所属者执行文件 |
S_IRWXU | 允许文件所属者读、写、执行文件 |
S_IRGRP | 允许同组用户读文件 |
S_IWGRP | 允许同组用户写文件 |
S_IXGRP | 允许同组用户执行文件 |
S_IRWXG | 允许同组用户读、写、执行文件 |
S_IROTH | 允许其他用户读文件 |
S_IWOTH | 允许其他用户写文件 |
S_IXOTH | 允许其他用户执行文件 |
S_IRWXO | 允许其他用户读、写、执行文件 |
S_ISUID | set-user-ID(特殊权限) |
S_ISGID | set-group-ID(特殊权限) |
S_ISVTX | sticky(特殊权限) |
- mode可直接使用数字型,xxxx(S-U-G-O),方便些
- close(文件描述符)
#include <unistd.h> int close(int fd);
read(文件描述符,首地址,读取大小),每次读是从内核缓冲区读取的
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
write(文件描述符, 首地址,写入大小),先写入内核缓冲区
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t count);
lseek(文件描述符,偏移量,参照位置)
#include <sys/types.h> #include <unistd.h> off_t lseek(int fd, off_t offset, int whence);
- whence:
- SEEK_SET: 文件头部
- SEEK_CUR: 当前位置
- SEEK_END: 文件结尾
- fcntl()
#include<unistd.h> #include<fcntl.h> int fcntl(int fd, int cmd, .../* arg */)
fd:文件描述符。
cmd:操作命令。此参数表示我们将要对 fd 进行什么操作,cmd 参数支持很多操作命令,可以打开 man 手册查看到这些操作命令的详细介绍,这些命令都是以 F_XXX 开头的,譬如 F_DUPFD、F_GETFD、F_SETFD 等,不同的 cmd 具有不同的作用,cmd 操作命令大致可以分为以下 5 种功能:
复制文件描述符(cmd=F_DUPFD 或 cmd=F_DUPFD_CLOEXEC);
获取/设置文件描述符标志(cmd=F_GETFD 或 cmd=F_SETFD);文件权限标志(O_RDONLY、O_WRONLY、O_RDWR)以及文件创建标志(O_CREAT、O_EXCL、O_NOCTTY、O_TRUNC)不能被设置、会被忽略;
获取/设置文件状态标志(cmd=F_GETFL 或 cmd=F_SETFL);
获取/设置异步 IO 所有权(cmd=F_GETOWN 或 cmd=F_SETOWN);
获取/设置记录锁(cmd=F_GETLK 或 cmd=F_SETLK);
其他知识点
- fsync(), 将参数fd所指的文件内容和元数据写入磁盘。
#include <unistd.h> int fsync(int fd);
fdatasync(), 将参数fd所指的文件内容写入磁盘,
#include <unistd.h> int fdatasync(int fd);
- sync(), 将所有的文件IO内核缓冲区的文件内容数据和元数据更新到磁盘。
#include <unistd.h> void sync(void);
以上所介绍的文件IO操作函数发生错误时会返回错误返回值-1。但我们并不能知道具体是啥错误,可喜的是以上函数发生错误时,系统会将一个全局变量errno置为错误代码(需要头文件<errno.h>)。
但它任然是一个错误代码,需要去查资料解析他的意思。有二个个函数可以帮我们解析,
- char *strerror(int errno), 返回对应错误的字符指针(<sting.h>)
- void perror(char *s), 直接答应错误,不需要传递参数errno,需要传递补充的提示信息(可不传)(<stdio.h>)
- 空洞文件, 当lseek()到大于该文件的位置是也是可以读写的,不会报错(应用: 多线程同时操作文件,就像修公路一样,每个队伍修一部分)。但前后之间的不会占用实际物理空间。但在计算文件大小时是算在内的。
对同一个文件,可打开多次,但内核的缓冲区只有一个(动态文件)。静态文件:存储在硬件设备如磁盘,U盘等等存储设备。对于这些的读取是以块为单位的(一般为4K),也就是说不能单个字节单个字节的操作,故使用
- 动态文件的方式即可加快读取时间,也可单个字节的方式操作文件。
- 复制文件描述符(文件表没有复制)
- int dup(int fd), 返回新的文件描述符,但文件表是同一个,
- int dup2(int fd, int new_fd), new_fd为用户指定的新的描述符,如果已经存在就找一个比它的文件数字,返回新的文件描述符。
文件共享问题
当多个进程(线程)访问同一个文件时会出现竞争冒险的问题,因为进程的调度是不确定的,所以需要原子操作(将多个步骤合为一体,我猜是其内部实现了禁用中断,禁用调度器的调度)
- O_APPEND, 将移动指针到末尾和写入数据组成了原子操作
- O_EXCL|O_CREAT, 将检测文件是否存在和建立文件组成了原子操作
- 函数,读写操作和偏移指针位置组成原子操作
#include <unistd.h> ssize_t pread(int fd, void *buf, size_t count, off_t offset); ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
直接IO读写: 绕过内核缓冲, mode加上O_DIRECT(还需要定义一个宏定义_GUN_SOURCE). eg:
#define _GNU_SOURCE static char buf[8192] __attribute((aligned (4096))); // 对齐 fd = open(filepath, O_WRONLY|O_DIRECT);