3.1 文件描述符
对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。
例子: 0表示stdin
, 1表示stdout
, 2表示stderr
.
文件描述符的变化范围是0
~OPEN_MAX-1
,早期允许每个进程打开19个文件。对很多系统来说,文件描述符范围是无限的。
3.2 open和openat
调用open或openat函数可以打开或创建一个文件。
int open(const char *path, int oflag, ...);
int openat(int fd, const char *path, int oflag, ...);
//如果path参数指定的是绝对路径名, 在这种情况下fd参数被忽略, openat函数就相当于open函数。
//path参数指定的是相对路径名,则openat要传入fd参数, fd参数是通过打开相对路径名所在的目录来获取。
//path参数指定了相对路径名, fd参数具有特殊值AT_FDCWD。 在这种情况下, 路径名在当前工作目录中获取。
3.3 create
基本不用,等价于
open(path, O_WRONLY| O_CREAT| O_TRUNC, mode) //O_WRONLY 只读打开,O_CREAT 不存在就创建,O_TRUNC 文件清空
3.4 lseek
每个打开文件都有一个偏移量,表示从文件开始处计算的字节数。读、写操作通常都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。lseek显式地为一个打开文件设置偏移量,如果设置失败返回-1。
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence)
/*whence指定偏移基准
SEEK_SET 从开头
SEEK_CUR 从当前
SEEK_END 从结尾
*/
文件偏移量可以大于文件的当前长度,在这种情况下对该文件的下一次写将加长该文件,并在文件中构成一个空洞,空洞内容被置为0.
例子
#include "apue.h"
#include <fcntl.h>
char buf1[] = "abcdefg";
char buf2[] = "ABCDEFG";
int main(void) {
char *file = "huahua3.txt";
int fd;
//以读写方式打开文件,若不存在则创建文件,文件权限wr
if ((fd = open(file, O_RDWR | O_CREAT, 0600)) == -1)
err_sys("open error");
//向文件中写入数据长度10
if (write(fd, buf1, 10) == -1)
err_sys("write error");
//将当前位置定位到16384
lseek(fd, 16384, SEEK_SET);
//向文件中写入数据
if (write(fd, buf2, 10) == -1)
err_sys("write error");
exit(0);
}
验证空洞
ls -l huahua3.txt //查看文件大小
od -c huahua3.txt //以字符格式打印内容
3.5 read
ssize_t read(int, void *, size_t)
返回结果可能是
- 读取成功,返回读字节数
- 文件到达结尾,返回0
- 读取失败,返回-1
3.6 write
ssize_t write(int __fd, const void *__buf, size_t __nbyte)
向文件写入指定大小数据,返回实际写入大小。
读写操作的例子
#include "apue.h"
#include <fcntl.h>
#define BUFFSIZE 128
int main(void) {
char *file = "huahua.txt";
char *file2 = "huahua2.txt";
char *dir = "./";
int fd;
int dir_fd;
char buf[BUFFSIZE];
char *msg = "hello, huahua!";
ssize_t read_bytes;
//以读写方式打开文件,若不存在则创建文件,文件权限wr
if ((fd = open(file, O_RDWR | O_CREAT, 0600)) == -1)
err_sys("open error");
//向文件中写入数据
if (write(fd, msg, strlen(msg)) == -1)
err_sys("write error");
//将当前位置定位到开头
lseek(fd, 0, SEEK_SET);
//读取文件内容,写入控制台
while ((read_bytes = read(fd, buf, strlen(msg))) > 0) {
if (write(STDOUT_FILENO, buf, read_bytes) != read_bytes)
err_sys("write error");
}
if (read_bytes < 0)
err_sys("read error");
//使用打开目录
if ((dir_fd = open(dir, O_RDONLY)) == -1)
err_sys("open error");
//openat 打开目录中的文件
int fd2 = openat(dir_fd, file2, O_RDWR|O_CREAT, 0600);
if (fd2 == -1)
err_sys("openat error");
//向文件中写入数据
if (write(fd2, msg, strlen(msg)) == -1)
err_sys("write error");
//将当前位置定位到开头
lseek(fd2, 0, SEEK_SET);
//读取文件内容,写入控制台
while ((read_bytes = read(fd2, buf, strlen(msg))) > 0) {
if (write(STDOUT_FILENO, buf, read_bytes) != read_bytes)
err_sys("write error");
}
exit(0);
}
3.7 IO效率
缓冲区的大小影响IO效率,当缓冲区达到磁盘块大小时,系统CPU时间最小;由于文件的预读取机制,缓冲区达到32字节之后,进行顺序读取的时钟时间都相差不大。
3.8 文件相关数据结构
内核使用三种数据结构描述一个打开的文件
- 每个进程在进程表中都有一个记录项, 记录项中包含一张打开文件描述符表(文件描述符标志,文件表指针)。
- 内核为所有打开文件维持一张文件表(文件状态标志,偏移量,v节点指针)。
- 每个打开文件或设备都有一个 v 节点结构(文件的具体信息)。
打开该文件的每个进程都获得各自的一个文件表项, 但对一个给定的文件只有一个v节点表项。每个进程维护自己的偏移量。
可能有多个文件描述符指向同一个文件表项,就是多个进程共享一个文件。
3.9 原子操作
由于通常每个进程维护自己的偏移量,定位+读写,两个动作经常需要保持原子执行,否则在多进程下会带来意外结果。
APPEND,在打开文件时指定,能够在每次写的时候自动定位到结尾,而不需要显式调用lseek
pread,pwrite,在执行读取时指定位置并且不会修改文件偏移量。
ssize_t pread(int d, void *buf, size_t nbyte, off_t offset);
3.10 复制文件描述符
dup(fd) //生成一个文件描述符和fd指向同一个文件表项,返回的一定是进程未使用的最小的文件描述符
dup2(fd1, fd2) //使fd2指向fd1, 返回文件描述符和fd1指向同一个文件表项。如果fd1和fd2相等,直接返回fd2; 如果fd2是打开的文件,则先关闭fd2
例子
#include "apue.h"
int main(void) {
FILE *fp;
int fd;
//生成fd,指向stdout
fd = dup(fileno(stdout));
printf("该文本重定向到 stdout\n");
fflush(stdout);
//重新打开stdout, 关联到file.txt,原来的stdout被关闭了
fp = freopen("file.txt", "a", stdout);
printf("该文本重定向到 file.txt\n");
fflush(fp);
//把新的stdout指向保存的旧stdout,重定向回去
dup2(fd, fileno(stdout));
printf("该文本重定向到 stdout\n");
exit(0);
}
3.11 刷新缓存
当我们向文件写入数据时,内核通常先将数据复制到缓冲区中,然后排入队列,晚些时候再写入磁盘。这种方式被称为延迟写.
sync, fsync, fdatasync用来通知系统进行缓存刷新,把缓存中的数据写入磁盘。
3.12 fcntl和ioctl
内容太杂乱了,先空着。
fcntl函数可以改变已经打开文件的属性。
ioctl函数一直是I/O操作的杂物箱。