apue学习笔记(第十四章 高级I/O)
本章涵盖了从多概念和函数:非阻塞I/O、记录锁、I/O多路转换、异步I/O、readv和writev函数以及存储映射I/O
非阻塞I/O
非阻塞I/O使我们可以发出open、read和write这样的I/O操作,并使这些操作不会永远阻塞。如果这种操作不能完成,则调用立即返回出错。
对于一个给定的文件描述符,有两种为其制定非阻塞I/O的方法:
1.如果调用open获得描述符,则可制定O_NONBLOCK标志(第三章)
2.对于已经打开的一个描述符,则可调用fcntl,由该函数打开O_NONBLOCK文件状态标志
下面程序时一个非阻塞I/O的实例
#include "apue.h"
#include <errno.h>
#include <fcntl.h> char buf[]; int
main(void)
{
int ntowrite, nwrite;
char *ptr; ntowrite = read(STDIN_FILENO, buf, sizeof(buf));
fprintf(stderr, "read %d bytes\n", ntowrite); set_fl(STDOUT_FILENO, O_NONBLOCK); /* set nonblocking */ ptr = buf;
while (ntowrite > ) {
errno = ;
nwrite = write(STDOUT_FILENO, ptr, ntowrite);
fprintf(stderr, "nwrite = %d, errno = %d\n", nwrite, errno); if (nwrite > ) {
ptr += nwrite;
ntowrite -= nwrite;
}
} clr_fl(STDOUT_FILENO, O_NONBLOCK); /* clear nonblocking */ exit();
}
记录锁
记录锁的功能是:当第一个进程正在读或修改文件的某个部分时,使用记录锁可以阻止其它进程修改同一文件区(锁住文件的一个区域)。
fcntl记录锁
#include <fcntl.h>
int fcntl(int fd,int cmd,.../* struct flock *flockptr */);
对于记录锁,cmd是F_GETLK、F_SETLK或F_SETLKW。第三个参数是一个指向flock结构的指针
struct flock {
...
short l_type; /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Starting offset for lock */
off_t l_len; /* Number of bytes to lock */
pid_t l_pid; /* PID of process blocking our lock(F_GETLK only) */
...
};
对flock结构的说明如下:
1.所希望的锁类型:F_RDLCK(共享读锁)、F_WRLCK(独占性写锁)或F_UNLCK(解锁)
2.要加锁或解锁区域的起始字节偏移量(l_start和l_whence)
3.区域的字节长度(l_len)
4.进程的ID(l_pid)持有的锁能阻塞当前进程(仅由F_GETLK返回)
5.如若l_len为0,则表示锁的返回可以扩展到最大可能偏移量
上面提到了两种类型的锁:F_RDLCK和F_WRLCK。下面演示在多个进程中的其兼容性规则
在单个进程中情况就不一样:如果一个进程对一个文件区间已经有了一把锁,后来该进程又企图在同一文件区间再加一把锁,那么心锁将替换已有锁。
下面说明fcntl函数的3种命令
F_GETLK 判断由flockptr所描述的锁是否会被另外一把锁排斥。如果存在这样的锁,则讲该锁的信息重写到flockptr指向的信息中,如果不存在,则将l_type设置为F_UNLCK
F_SETLK 设置由flockptr锁描述的锁。如果该操作不符合兼容性规则,则函数立即出错返回,并将errno设置为EACCES或EAGAIN
F_SETLKW 这个命令是F_SETLK的阻塞版本(W代表wait)。如果该操作不符合兼容性规则,则调用进程被置为休眠,知道请求创建的锁已经可用,才将该进程唤醒
对fcntl记录锁函数的封装
int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len )
{
struct flock lock;
lock.l_type = type; /* F_RDLCK, F_WRLCK, F_UNLCK */
lock.l_start = offset; /* byte offset, relative to l_whence */
lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
lock.l_len = len; /* #bytes (0 means to EOF) */
return( fcntl(fd, cmd, lock) );
} #define read_lock(fd,offset,whence,len)
lock_reg(fd,F_SETLK,F_RDLCK,offset,whence,len)
#define needw_lock(fd,offset,whence,len)
lock_reg(fd,F_SETLKW,F_RDLCK,offset,whence,len)
#define write_lock(fd,offset,whence,len)
lock_reg(fd,F_SETLK,F_WRLCK,offset,whence,len)
#define writew_lock(fd,offset,whence,len) lock_reg(fd,F_SETLKW,F_WRLCK,offset,whence,len)
#define un_lock(fd,offset,whence,len) lock_reg(fd,F_SETLK,F_UNLCK,offset,whence,len)
锁的隐含继承和释放有3条规则
1.记录锁和文件两者相关联:(a)当一个进程终止时,它所建立的锁全部释放;(b)无论一个描述符何时关闭,该进程通过这一描述符引用的文件的任何一把锁都会释放
如果执行下列4步:
fd1=open(pathname,...);
read_lock(fd1,...);
fd2=dup(fd1); //fd2=open(pathname,...);
close(fd2);
则在close(fd2)后,在fd1上设置的锁被释放,如果将dup替换为open,效果也一样。
2.由fork产生的子进程不继承父进程所设置的锁
3.在执行exec后,新程序可以继承原执行程序的锁。
在文件尾端加锁
考虑下列代码序列:
writew_lcok(fd,0,SEEK_END,0);
write(fd,0,SEEK_END,0);
un_lock(fd,0,SEEK_END);
write(fd,buf,1);
这段代码序列造成的文件锁状态如下图所示
可见,在对相对于文件尾端的字节范围加锁或解锁时需要特别小心。
建议性锁和强制性锁
强制性锁会让内核检查每一个open、read和write,验证调用进程是否违背了正在访问的文件上的某一把锁。
如果一个进程试图读或写一个强制性锁起作用的文件,有以下8种情况
如果欲打开的文件具有强制性记录锁,而且open调用中的标志指定为O_TRUNC或O_CREAT,则open立即出错返回,errno设置为EAGAIN。
下面程序可以用来测试系统是否支持强制性锁
#include "apue.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h> int
main(int argc, char *argv[])
{
int fd;
pid_t pid;
char buf[];
struct stat statbuf; if (argc != ) {
fprintf(stderr, "usage: %s filename\n", argv[]);
exit();
}
if ((fd = open(argv[], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < )
err_sys("open error");
if (write(fd, "abcdef", ) != )
err_sys("write error"); /* turn on set-group-ID and turn off group-execute */
if (fstat(fd, &statbuf) < )
err_sys("fstat error");
if (fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < )
err_sys("fchmod error"); TELL_WAIT(); if ((pid = fork()) < ) {
err_sys("fork error");
} else if (pid > ) { /* parent */
/* write lock entire file */
if (write_lock(fd, , SEEK_SET, ) < )
err_sys("write_lock error"); TELL_CHILD(pid); if (waitpid(pid, NULL, ) < )
err_sys("waitpid error");
} else { /* child */
WAIT_PARENT(); /* wait for parent to set lock */ set_fl(fd, O_NONBLOCK); /* first let's see what error we get if region is locked */
if (read_lock(fd, , SEEK_SET, ) != -) /* no wait */
err_sys("child: read_lock succeeded");
printf("read_lock of already-locked region returns %d\n",
errno); /* now try to read the mandatory locked file */
if (lseek(fd, , SEEK_SET) == -)
err_sys("lseek error");
if (read(fd, buf, ) < )
err_ret("read failed (mandatory locking works)");
else
printf("read OK (no mandatory locking), buf = %2.2s\n",
buf);
}
exit();
}
I/O多路转换
函数select和pselect
#include <sys/select.h>
int select(int maxfdp1,fd_set *restrict readfds,fd_set *restrict writefds,fd_set *restrict exceptfds,struct timeval *restrict tvptr);
参数tvptr指定愿意等待的时间,有下面3种情况
1.tvptr==NULL 永远等待,直到所指定的描述符中的一个已经准备好或捕捉到一个信号返回。如果捕捉到一个信号,则select返回-1,errno设置为EINTR
2.tvptr->tv_sec==0 && tvptr->tv_usec==0 不等待,测试所有指定的描述符并立即返回
3.tvptr->tv_sec!=0 || tvptr->tv_usec!=0 等待指定的秒数和微秒数。当指定的描述符之一已准备好,或当指定的时间值已经超过时立即返回
中间3个参数readfds、writefds和exceptfds是指向描述符集的指针。这3个描述符集说明了我们关心的可读、可写和处于异常条件的描述符集合。
对于描述符集fd_set结构,提供了下面4个函数
#include <sys/select.h>
int FD_ISSET(int fd,fd_set *fdset);
void FD_CLR(int fd,fd_set *fdset);
void FD_SET(int fd,fd_set *fdset);
void FD_ZERO(fd_set *fdset);
select第一个参数maxfdp1的意思是“最大文件描述符编号值加1”,在上面3个描述符集中找到最大描述符编号值,然后加1就是第一个参数值。
select有3个可能的返回值
1.返回值-1表示出错。如果在所指定的描述符一个都没准备好时捕捉到一个信号,则返回-1
2.返回0表示没有描述符准备好,指定的时间就过了。
3.一个正返回值说明了已经准备好的描述符数,在这种情况下,3个描述符集中依旧打开的位对应于已准备好的描述符。
POSIX.1也定义了一个select的变体,称为pselect
#include <sys/select.h>
int pselect(int maxfdp1,fd_set *restrict readfds,fd_set *restrict writefds,fd_set *restrict exceptfds,const struct timespec *restrict tsptr,const sigset_t *restrict sigmask);
除了下列几点外,pselect与select相同
1.超时值使用的结构不一样,pselect使用的是timespec结构
2.pselect的超时值被声明为const,保证了调用pselect不会改变此值
3.pselect可使用可选信号屏蔽字,在调用pselect时,以原子操作的方式安装该信号屏蔽字。在返回时,恢复以前的信号屏蔽字。
函数poll
#include <poll.h>
int poll(struct pollfd fdarray[],nfds_t nfds,int timeout);
与select不同,poll构造一个pollfd结构的数组,每个数组元素指定一个描述符编号以及我们对描述符感兴趣的条件。
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
fdarray数组中的元素数由nfds指定
结构中的events成员应设置为下图所示值的一个或几个
后三个只能作为描述字的返回结果存储在revents中,而不能作为测试条件用于events中。
poll最后一个参数指定的是我们愿意等待多久时间(毫秒值)
异步I/O
使用一个信号(System V是SIGPOLL,在BSD中是SIGIO)通知进程,对某个描述符锁关心的某个时间已经发生。
函数readv和writev
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);
// 两个函数的返回值:已读或一些的字节数;出错返回-1
这两个函数的第二个参数是指向iovec结构数组的一个指针:
struct iovec {
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};
下图显示了iovec结构
iov数组中元素数由iovcnt指定
writev函数从缓冲区中聚集输出数据的顺序是:iov[0]、iov[1]知道iov[iovcnt-1]
readv函数则将读入的数据按上述同样顺序散布到缓冲区中
函数readn和writen
通常,在读、写一个管道、网络设备或终端时,需要考虑一下特性:
1.一次read操作所返回的数据可能少于所要求的数据,即使还没达到文件尾端也可能是这样。这不是一个错误,应当继续读该设备。
2.一次write操作的返回值也可能少于指定输出的字符数。
下面两个函数的功能分别是读、写指定的N字节数据,并处理返回值可能少于要求值的情况。
#include "apue.h"
ssize_t readn(int fd,void *buf,size_t nbytes);
ssize_t writen(int fd,void *buf,size_t nbytes);
下面是这两个函数的实现
readn函数
#include "apue.h" ssize_t /* Read "n" bytes from a descriptor */
readn(int fd, void *ptr, size_t n)
{
size_t nleft;
ssize_t nread; nleft = n;
while (nleft > ) {
if ((nread = read(fd, ptr, nleft)) < ) {
if (nleft == n)
return(-); /* error, return -1 */
else
break; /* error, return amount read so far */
} else if (nread == ) {
break; /* EOF */
}
nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}
writen函数
#include "apue.h" ssize_t /* Write "n" bytes to a descriptor */
writen(int fd, const void *ptr, size_t n)
{
size_t nleft;
ssize_t nwritten; nleft = n;
while (nleft > ) {
if ((nwritten = write(fd, ptr, nleft)) < ) {
if (nleft == n)
return(-); /* error, return -1 */
else
break; /* error, return amount written so far */
} else if (nwritten == ) {
break;
}
nleft -= nwritten;
ptr += nwritten;
}
return(n - nleft); /* return >= 0 */
}
存储映射I/O
存储映射I/O能将一个磁盘文件映射到存储空间中的一个缓冲区上。于是,当从缓冲区取数据时,就相当于读文件中的相应字节。
与此类似,将数据存入缓冲区,相应字节就自动写入文件。
apue学习笔记(第十四章 高级I/O)的更多相关文章
- 《机器学习实战》学习笔记第十四章 —— 利用SVD简化数据
相关博客: 吴恩达机器学习笔记(八) —— 降维与主成分分析法(PCA) <机器学习实战>学习笔记第十三章 —— 利用PCA来简化数据 奇异值分解(SVD)原理与在降维中的应用 机器学习( ...
- JavaScript高级程序设计学习笔记第十四章--表单
1.在 HTML 中,表单是由<form>元素来表示的,而在 JavaScript 中,表单对应的则是 HTMLFormElement 类型. HTMLFormElement 继承了 HT ...
- apue学习笔记(第十七章 高级进程间通信)
本章介绍一种高级IPC---UNIX域套接字机制,并说明它的应用方法 UNIX域套接字 UNIX域套接字用于在同一台计算机上运行的进程(无关进程)之间的(全双工)通信.相比于因特网套接字,UNIX域套 ...
- apue学习笔记(第四章 文件和目录)
本章将描述文件系统的其他特性和文件的性质. 函数stat.fstat.fstatat和lstat #include <sys/stat.h> int stat(const char *re ...
- 学习笔记 第十四章 使用CSS3动画
第14章 使用CSS3动画 [学习重点] 设计2D动画 设计3D动画 设计过渡动画 设计帧动画 能够使用CSS3动画功能设计页面特效样式 14.1 设计2D动画 CSS2D Transform表 ...
- [HeadFirst-HTMLCSS学习笔记][第十四章交互活动]
表单 <form action="http://wickedlysmart.com/hfhtmlcss/contest.php" method="POST" ...
- Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第四章:Direct 3D初始化
原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第四章:Direct 3D初始化 学习目标 对Direct 3D编程在 ...
- VSTO学习笔记(十四)Excel数据透视表与PowerPivot
原文:VSTO学习笔记(十四)Excel数据透视表与PowerPivot 近期公司内部在做一种通用查询报表,方便人力资源分析.统计数据.由于之前公司系统中有一个类似的查询使用Excel数据透视表完成的 ...
- Python学习笔记(十四)
Python学习笔记(十四): Json and Pickle模块 shelve模块 1. Json and Pickle模块 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不 ...
- python3.4学习笔记(二十四) Python pycharm window安装redis MySQL-python相关方法
python3.4学习笔记(二十四) Python pycharm window安装redis MySQL-python相关方法window安装redis,下载Redis的压缩包https://git ...
随机推荐
- 使TileCache配合OpenLayers,产生地图瓦块的一些资料(转)
在tilecache.cfg中配置好被切割地图的参数,比如: [mytestmap]layers=3,5,7,8type=WMSurl=http://localhost/arcgis/services ...
- 【神题】AtCoder 028 C Min Cost Cycle
TO BE DONE 思维题 十分巧妙的转化
- MFC 对话框阴影效果
在 OnInitDialog(Cdialog)里面添加 SetClassLong(this->m_hWnd, GCL_STYLE, GetClassLong(this->m_hWnd, G ...
- jquery 获取被点击元素的id属性值
有时候可能需要获取被点击元素的一些信息,此处就以id属性为例子,进行演示一下. $(document).click(function (e){ var v_id=e.target.id; consol ...
- css sticky footer 布局 手机端
什么是css sticky footer 布局? 通常在手机端写页面 会遇到如下情况 页面长度很短不足以撑起一屏,此时希望页脚在页面的底部 而当页面超过一屏时候,页脚会在文章的底部 ,网上有许多办法, ...
- 無法使用 adb push file,Read-only file system
adb root adb remount adb push xxx /system/etc/xxx failed to copy 'xxx' to '/system/etc/xxx': couldn' ...
- LeetCode OJ-- Valid Sudoku
https://oj.leetcode.com/problems/valid-sudoku/ 给出数独的一部分来,验证给出的这一部分是否为 valid 行上无重复的,且是从 ‘1’ 到‘9’ 列上无重 ...
- Java IO 学习(一)同步/异步/阻塞/非阻塞
关于IO,同步/异步/阻塞/非阻塞,这几个关键词是经常听到的,譬如: “Java oio是阻塞的,nio是非阻塞的” “NodeJS的IO是异步的” 但是这些东西听多了就容易迷糊,比方说同步是否就是阻 ...
- HDFS Scribe Integration 【转】
It is finally here: you can configure the open source log-aggregator, scribe, to log data directly i ...
- luogu P1314 聪明的质监员
题目描述 小T 是一名质量监督员,最近负责检验一批矿产的质量.这批矿产共有 n 个矿石,从 1到n 逐一编号,每个矿石都有自己的重量 wi 以及价值vi .检验矿产的流程是: 1 .给定m 个区间[L ...