文件描述符
对于内核而言,所有打开的文件都是通过文件描述符引用.文件描述符通常是一个非负整数.
按照惯例,UNIX系统shell把文件描述符0与进程的标准输入关联,文件描述符1与进程的标准输出关联,文件描述符2与标准错误关联.
<unistd.h>
中定义了符号常量STDIN_FIELNO
,STDOUT_FILENO
,STDERR_FILENO
.
文件描述符的范围是0~OPEN_MAX
-1.
文件的类型
- 普通文件
- 目录文件
- 块特殊文件
- 字节特殊文件
- 命名管道
- 套接字
- 符号链接
open和openat
int open(const char *path, int aflag, ...));
int openat(int fd, const char *path, int aflag, ...);
path
是要打开或创建的文件的路径,aflag
用来说明此函数的多个选项.
open
和openat
函数返回的文件描述符是最小未用的描述符.
creat
int creat(const char *path, mode_t mode);
在早期,open
的第二个参数只能是0,1,2.无法打开一个不存在的文件,所以需要creat
.
他的打开方式只能是只写方式,需要打开,关闭,打开,才能.
现在可以替换成
open(path, O_WRONLY | O_CREAT | O_TRUNC, mode); //只写
open(path, O_RDWR | O_CREAT | O_TRUNC, mode); //读写
close
int close(int fd);
fd
是被操作文件的文件标识符.
当一个进程中止,内核会关闭他的所有文件,所以有时不必显式调用close
.
lseek
off_t lseek(int fd, off_t offset, int whence);
常常有一个非负整数表示文件偏移量,代表文件和文件开头距离的字节数.
除非使用O_APPEND
参数,否则默认打开文件之后偏移量是0.
可以用这个函数调整偏移量.
如果调整成功,返回新的偏移值;否则返回-1
,并且errno
设置成ESPIPE
.
whence 参数 |
作用 |
---|---|
SEEK_SET |
把文件偏移量设置为距离文件开始offset 个字节处 |
SEEK_CUR |
把文件偏移量设置为当前值加offset |
SEEK_END |
把文件偏移量设置为文件长度加offset |
lseek
仅仅将偏移量记录在内核中,不会引起IO操作,只是影响之后的IO操作.
偏移量可以大于文件长度,写的时候文件会被加长.
read
ssize_t read(int fd, void *buf, size_t nbytes);
成功返回读取到的文件数,如果已经到底尾端,返回0,出错返回-1.
从当前的文件偏移量开始读,返回之前设置新的文件偏移量.
ssize_t
是带符号的size_t
,为了返回-1
.
write
ssize_t write(int fd, const void *buf, size_t nbytes);
写入之后的偏移值为写入前的偏移值加写入量.
打开文件的数据结构
每个进程都在进程表中有一个记录,记录中包含一张打开文件描述符表,每个描述符占一项,每一项记录了文件描述符和指向文件表项的指针.
内核维护一张文件表,每一项包含文件状态标识,文件偏移量,指向该文件v节点的指针.
每个打开文件都有一个v节点结构.
多个进程打开同一个文件时,他们各自有一个描述符表,描述符表指向各自的文件表项,但是文件表项的v节点指针指向同一个v节点.
O_APPEND
如果两个进程同时在一个文件的末尾添加内容,可能A进程定位到末尾之后被挂起,B进程开始写,之后A进程将B进程写的东西覆盖.
如果打开时加上O_APPEND
参数,每次写的时候都会把偏移量定位到最后面,不再需要一个lseek
函数.
pread/pwrite
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
ssize_t pwrite(int fd, void *buf, size_t nbytes, off_t offset);
相当于原子地移动偏移量并操作.操作之后不更新文件偏移量.
dup/dup2
int dup(int fd);
int dup2(int fd, int fd2);
返回一个新的文件描述符,指向fd
所指向的文件文件表项(也就是和原本的fd偏移量也相同).
前者返回的文件描述符一定是最小的可用的文件描述符.
后者指定复制成fd2
,如果fd2
被占用则先关闭.如果fd==fd2
,sma叶不干,返回fd2
.
不然返回-1
.
也可以fcntl(fd, F_DUPFD, fd2)
,新描述符必须大于等于fd2.
sync,fsync和fdatasync
void sync(void);
int fsync(int fd);
int fdatasync(int fd);
sync是把所有修改过的块写入队列,然后就返回,不等待写入完成.
fsync函数只对fd所指向的文件生效,并且等待写入完成.
fdatasync只影响文件的数据部分(其他数据只在必要情况下更新).
fcntl
int fcntl(int fd, int cmd, ...);
有以下功能
- 复制一个已有的描述符(F_DUPSF, F_DUPFD_CLOEXEC)
- 获取/设置文件描述符标志(F_GETFD, F_SETFD)
- 获取/设置文件状态标志(F_GETFL, F_SETFL)
- 获取/设置异步IO所有权(F_GETOWN, F_SETOWN)
- 获取/设置记录锁(F_GETLK, F_SETLK,F_SETLKW)
stat
int stat(const char *restrict pathname, struct stat *restrict buf);
int fstat(int fd, struct stat *restrict buf);
int lstat(const char *restrict pathname, struct stat *restrict buf);
int fststat(int fd, const char *restrict pathname, struct stat *restrict buf, int flag);
成功返回0,否则返回-1.
stat是一个结构体,包含了文件的一些属性.
文件访问权限
st_mode值 | 含义 |
---|---|
S_IRUSR | 用户读 |
S_IWUSR | 用户写 |
S_IXUSR | 用户执行 |
S_IRGRP | 组读 |
S_IWGRP | 组写 |
S_IXGRP | 组执行 |
S_IROTH | 其他读 |
S_IWOTH | 其他写 |
S_IXOTH | 其他执行 |
所有者ID是文件的属性,有效者ID是进程的属性.
进程能否操作文件需要对比这几个ID和所设置的权限.root用户除外.
access, faccessat
int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag);
测试文件权限.
mode | 说明 |
---|---|
F_OK | 文件是否存在 |
R_OK | 文件读权限 |
W_OK | 文件写权限 |
X_OK | 文件运行权限 |