第三章 文件IO
1,文件描述符
对于内核而言所有打开的文件都通过文件描述符引用,为一个非负整数。Unix shell 把文件描述符0与进程的标准输入关联,1与标准输出关联,2与标准错误关联。
1. STDIN_FILENEO
2. STDOUT_FILENO
3. STDERR_FILENO
2,函数open,openat
#include<fcntl.h>
---
int open(const char* path,int oflag,.../\*mode_t mode\*/)
int openat(int fd ,char* path ,int oflag,.../\*mode_t mode\*/)
若成功返回文件描述符,出错返回-1.
最后一个参数 … ,ISOC中表示余下的参数的数量以及类型可变的。仅当open创建新文件是才使用最后一个参数。
oflag参数用来说明此函数的多个选项
1. O_RDONLY 只读打开
2. O_WRONLY 只写打开
3. O_RDWR 读,写打开
4. O_EXEC 只执行打开
5. O_SEARCH 只搜索打开(本书系统尚不支持)
6. O_APPEND 每次执行写时都追加到文件末尾
7. O_CLOEXEC 把文件描述符标志设置为 FD_CLOEXEC
8. O_CREATE 若文件不存在则创建,需要第三个参数
9. O_DIRECTORY 若不是目录则报错
10. O_EXCL 若同时指定O_CREATE,而文件已经存在则报错。这使测试和创建两者成为一个原子操作
11. O_NOCTTY 如果path引用的事终端设备,则不把该设备分配为进程的控制终端
12. O_NOFOLLOW 如果path引用一个符号链接则出错
13. O_NONBLOCK 后续的I/O操作设置为非阻塞方式
14. O_SYNC 每次write等待物理I/O操作完成,包括write引起的文件属性更新
15. O_TUNC 此文件已存在且已只读或者读写方式打开,则将其长度截为0
16. O_DSNYC 每次操作等待物理I/O完成,但如果没有改变文件内容则不需要等待文件属性更新
17. O_RSYNC 使每一个文件描述符作为参数的read操作等待,直至所有对文件同一部分的挂起写操作都完成。
3,函数close
#include<unistd.h>
int close(int fd);
若成功返回0出错返回-1
4,lseek函数
#include<unistd.h>
off_t lseek(int fd,off_t offset,int whence);
若成功返回新文件的偏移量,若出错返回-1
whence参数
1. SEEK_SET从文件头开始
2. SEEK_CUR从当前位置开始
3. SEEK_END从文件结尾处开始
可创建空洞文件。
5,read函数
#include<unistd.h>
ssize_t read(fd,void *buf,size_t nbytes)
若成功返回读取到的字节数,若已到文件尾,返回0,出错返回-1.
6,write函数
#include<unistd.h>
ssize_t write(fd,const void* buf,size_t nbytes)
若成功返回写入的字节数,若出错返回-1.
7,dup,dup2函数
内核使用三种数据结构表示打开的文件。
1. 每个进程在进程表中有一个记录项,记录项中包含一张进程打开的文件描述符表。
2. 内核为所有打开的文件维持一张文件表
3. 每个打开的文件都有一个V节点结构。
#include<unistd.h>
int dup(int fd);
int dup2(int fd,int fd2);
若成功返回新的文件描述符,若出错返回-1
这两个函数返回的新文件描述符与参数fd共用一个文件表。新文件的描述符标志close-on-exec被清除。
8,sync,fsync,fdatasync函数
#include<unistd.h>
int sync(void);
int fsync(int fd);
int fdatasync(int fd);
若成功返回0,出错返回-1.
传统的Unix实现内核中设有缓冲区高速缓存或页缓存,大多数磁盘IO都通过缓冲区进行。当我们向文件中写入数据时,内核通常先将数据复制到缓冲区中,然后排入队列,晚些时候在写入磁盘。这种方式称为延迟写。通常内核需要重用缓冲区时,会把所有延迟写的数据写入磁盘。为了保证磁盘上的实际数据与缓冲区的一致性UNIX提供了这三个函数
sync只是将所有修改的块缓冲区排入写队列,然后返回,它实际上并不等磁盘操作结束。
fsync,fdatasync要等待磁盘操作结束才返回。
9,fcntl函数
#<include fnctl.h>
int fcntl(int fd,int cmd,... /\* int arg \*/)
fnctl 函数有一下5类功能
1. 复制一个已有的文件描述符 (cmd=F_DUPFD 或F_DUPFD_CLOEXEC)
2. 获取/设置文件描述符标志(cmd = F_GETFD或F_SETFD)
3. 获取/设置文件状态标志(cmd= F_GETFL 或 F_SETFL)
4. 获取/设置异步IO所有权—接受sigio,sigusr信号的进程id或进程组id(cmd = F_GETOWN 或F_SETOWN)
5. 获取/设置记录锁(cmd=F_GETLK,F_SETLK或 F_SETLKW)
fcntl的返回值与命令有关。如果出错所有的命令返回-1,如果成功返回某个值
10,原子操作
1,追加一个到一个文件
早期Unix不支持open的O_APPEND选项时,追加到一个文件时通常这样写。
if(lseek(fd,OL,2)<0)
err_sys('lseek error');
if(write(fd,buff,100)!=100)
err_sys('writ error');
在单进程的程序中这样做并没问题。但是多个进程同时执行这段程序时就会有问题。
例如两个进程A,B分别对同一个日志文件末尾追加内容。A进程打开文件(不以O_APPEND方式打开),然后设置偏移量为1500(文件末尾),然后内核切换进程,B进程运行,以同样的方式打开文件(此时各数据结构如3.8图),并设置偏移量为1500,然后写入一百个字节,此时文件inode节点中的偏移量是1600,而A进程,的文件表中偏移量仍然是1500。紧接着内核切换进程,A进程再次运行从偏移量1500处写入100字节时会覆盖B进程写入的内容。
问题出在逻辑操作“先定位到文件尾端,然后写”,它使用的是两个分开的函数调用。==任何一个多与一个函数调用的操作都不是原子操作,因为在两个函数调用期间内核可能会临时挂起进程。== 解决的方法是使这两个操作对于其他进程而言是一个原子操作
2,pread,pwrit函数。
原子性的定位并执行I/O的函数:pread,pwrite。这两个函数定位并操作后,并不更新当前文件偏移量。
#include<unistd.h>
ssize_t pread(int fd,void *buff,size_t nbytes,offset_t offset);
返回值读到的字节数,若已到文件尾返回0,出错返回-1。
ssize_t pwrite(int fd,const void *buf,size_t nbytes,off_t offset);
若成功返回写入的字节数,若失败返回-1.
==感觉对上面假设的A,B两个进程的情形并没用虽然定位然后IO操作时原子的,但获取文件长度,然后调用定位读写并不是原子操作==
3,创建一个文件
当一个文件不存在,则创建之。我们通常写这样的代码
if((fd = open(pathname,O_WRONLY)<0)
if(errno == ENOENT)
if((fd = create(path,mode)<0)
sys_err('create error');
else
err_sys("open error");
如果在open和create之间另一个进程创建了该文件则会出现问题。解决方法是在open时使用 O_CREATE和O_EXCL(不存在则创建,存在open调用失败)
cat 命令使用‘-’代表标准输入。
cat 命令使用‘-’代表标准输入。