您的位置:首页 > 其它

[AUPE chapter 14] 高级IO

2016-11-06 15:49 211 查看
作者:isshe

日期:2016.10.30

邮箱:i.sshe@outlook.com

github: https://github.com/isshe

1. 基础知识

1.1 问题

1.1.1 非阻塞I/O

a1. 什么是[b]低速系统调用?都有哪些?[/b]

a2. 与磁盘I/O有关的系统调用的相关描述?

a3. 如何指定非阻塞I/O?

1.1.2 记录锁

b1. 记录锁的功能是什么?

b2. 对于记录锁相关的fcntl函数。

b3. 关于加锁解锁区域的说明和注意事项。

b4. 锁的隐含继承和释放3原则。

b0. 注意事项。

[b]1.1.3 readv和writev[/b]

c1.readv和writev介绍。

1.1.4 存储映射I/O

d1.mmap函数介绍。

d0.注意事项

1.2 解答

1. 2.1 非阻塞I/O

a1. 低速系统调用:可能会使进程永远阻塞的一类系统调用,包括:

读操作,如果某些文件类型(读管道、终端设备、网络设备)的数据不存在。

写操作,如果数据不能被相同的文件类型立即接受(如管道无中间空间、网络流控制)

在某种条件发生之前打开某些文件类型可能会发生阻塞。(不大理解这个)

对已经加上强制性记录锁的文件进行读写。

某些ioctl操作。

某些进程间通信函数。(15章)

a2. 不能将与磁盘I/O有关的系统调用视为“低速”,尽管读写磁盘文件会暂时阻塞调用者。

a3. 指定非阻塞的I/O的方法:

指定O_NONBLOCK标志调用open获取描述符。

已打开的描述符,用O_NONBLOCK标志调用fcntl。(apue chapter3的3-12图)

注意:非阻塞I/O常使用轮询的方法,会造成量非CPU时间。有时可用多线程代替非阻塞I/O,但是要考虑线程间同步的开销以及复杂程度。(当然还可以用下面的高级I/O)

1.2.2 记录锁

b1. 记录锁(record locking)的功能是:

当第一个进程在读或修改文件的某个部分时,使用记录锁可以组织其他进程修改同一文件区。(记录锁更合适的术语是:字节范围锁(byte-range-locking)

b2. 对于记录锁相关的fcntl函数:

原型:

#include <unistd.h>
#include <fcntl.h>

//注意第3个参数
int fcntl(int fd, int cmd, ... /* struct flock *flockptr */ );


参数:

cmd:记录锁相关:

F_GETLK: 判断由flockptr所描述的锁是否被另外一把锁所排斥。(如无锁,则l_type = F_UNLCK)

F_SETLK: 设置由flockptr所描述的锁,或者清除锁(l_type = F_UNLCK)。

F_SETLKW: (W代表wait)F_SETLK的阻塞版本。当锁无法设置的时候,休眠等待锁可用再唤醒。

第3个参数:使用一个指向flock结构的指针,结构内容如下:

struct flock {
...
short l_type;    /* 锁类型: F_RDLCK(共享读锁),
F_WRLCK(独占写锁), F_UNLCK(解锁一个区域 */
short l_whence;  /* 和l_start配合,l_whence取值:
SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start;   /* 锁定区域的开始 *偏移*!*/
off_t l_len;     /* 区域的字节长度 */
pid_t l_pid;     /* PID为pid的进程持有的锁可以阻塞当前进程
(set by F_GETLK and F_OFD_GETLK) */
...
};


b3. 关于加锁解锁区域的说明和注意事项:

指定区域起始偏移量的两个元素(l_whence, l_start)和lseek函数的最后两个参数类似。

锁可以在当前文件的尾端处开始 或 越过尾端处开始, 但不能在文件起始位置之前开始。

l_len== 0, 表示锁的方位可以扩大到最大可能偏移量。

对整个文件加锁,设置l_whence和l_start指向文件起始,并指定l_len=0。(_l_whence = SEEK_SET, l_start = 0)

同一进程对文件同一区域多次加锁,则只保留最新的(前一次总被后一次替换)。

读/写锁时,该描述符必须是读/写打开的。

b4. 锁的隐含继承和释放有3条规则:

锁与进程和文件两者相关联。

当进程终止时,它所建立的所有锁都被释放。

当关闭一个描述符时,它关联的文件上的任何一把锁都被释放(这些锁都是该进程建立的)(例如进程中有两个fd0,fd1某个文件的两次打开,当在其中一个lock后,另一个如果关闭,此lock也会被关闭)

由fork产生的子进程不继承父进程锁设置的锁。

在执行exec后,新程序可以继承原程序的锁。(如果设置执行时关闭标志,则锁会被释放)

b0. 注意事项:

1.用F_GETLK测试能否建立一把锁,然后用F_SETLK或F_SETLKW尝试建立那把锁。

2.如果不希望在等待锁变为可用时阻塞,就必须处理由F_SETLK返回的可能的错误。

1.2.3 readv和writev

c1.readv和writev介绍:

原型:

#include <sys/uio.h>

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

//结构:
struct iovec {
void  *iov_base;    /* Starting address */
size_t iov_len;     /* Number of bytes to transfer */
};


功能:用于一次函数调用读/写多个非连续缓冲区。(散布读,聚集写)

参数:

fd:文件描述符。

iov:struct iovec数组,个数由iovcnt指定,最大值受限于IOV_MAX(图2-11)。

返回:已读/已写的字节数,出错返回-1。

c0. 注意事项:

1.2.4 存储映射I/O

d1.mmap函数介绍

* 原型:

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);


功能:将一个给定的文件映射到一个存储区域中。

参数:

addr:指定映射存储区的起始地址(函数返回值通常也是这个)。如果addr == 0, 则由系统选择。

fd:指定要被映射的文件的描述符。(在文件映射前,必须先打开文件)

length:映射的字节数。

offset:要映射字节在文件中的偏移。

prot:指定映射存储区的保护要求。(不能超过open模式访问权限)

prot说明
PROT_READ映射区可读
PROT_WRITE映射区可写
PROT_EXEC映射区可执行
PROT_NONE映射区不可访问
flags:影响映射存储区的多种属性:

MAP_FIXED:返回值必须是addr。(不鼓励使用,因为不利于可移植性)

MAP_SHARED:映射存储区是共享的,存储操作会直接作用到文件。

MAP_PRIVATE:对映射区的存储操作(write)导致创建该映射文件的一个副本,所有后来对该映射区的引用都引用该副本。任何修改只影响副本,不影响原文件(常用于调试程序,p424)

不同实现可能还有不同的MAP_XXX。

d0. 注意事项:

offset和addr的值,通常被要求是系统虚拟存储页长度的倍数。(页长通过_SC_PAGESIZE 或 _SC_PAGE_SIZE的sysconf函数获取)(offset和addr常常为0,所以这种要求一般不重要)

子进程能通过fork继承映射存储区。

关闭文件描述符并不解除映射存储区。

mprotect:更改现有映射的权限。

msync:冲洗共享映射中的修改页到被映射的文件中。

munmp:解除映射。(对共享映射,文件的更新会根据系统算法更新到文件,对MAP_PRIVATE映射,丢弃修改)

拓展知识

哪些是与磁盘I/O有关的系统调用?

参考资料

《unix环境高级编程》

ubuntu16.04 man pages
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息