APUE笔记-第三章文件I/O

第三章 文件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节点结构。

image

#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 命令使用‘-’代表标准输入。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值