apue 第14章 高级I O.docx
- 文档编号:5694250
- 上传时间:2022-12-31
- 格式:DOCX
- 页数:24
- 大小:157.21KB
apue 第14章 高级I O.docx
《apue 第14章 高级I O.docx》由会员分享,可在线阅读,更多相关《apue 第14章 高级I O.docx(24页珍藏版)》请在冰豆网上搜索。
apue第14章高级IO
apue第14章高级I/O
1、引言
2、非阻塞I/O
系统调用分为两类:
低速系统调用和其他。
低速系统调用是可以使进程永远阻塞的一类系统调用
如果某些文件类型(读管道、终端设备和网络设备)的数据并不存在,读操作可能会使调用者永远阻塞
如果数据不能被相同的文件类型立即接受(管道中无空间、网络流控制)。
写操作可能会使调用者永远阻塞
在某种条件发生之前打开某些文件类型可能会发生阻塞
对已经加上强制性记录锁的文件进行写
某些ioctl操作
某些进程间通信函数
非阻塞I/O使我们可以发出open,read和write这样的I/O操作,并使这些操作永远不会阻塞。
如果这种操作不能完成,则调用立即出错返回,表示该操作如继续执行将阻塞
对于一个给定的描述符,有两种为其指定非阻塞I/O的方法
1.如果调用open获得描述符,则可指定O_NONBLOCK标志
2.对于已经打开的一个描述符,则可调用fcntl,由该函数打开O_NONBLOCK文件状态标志
3、记录锁
记录锁的功能是:
当第一个进程正在读或修改文件的某个部分时,使用记录锁可以阻止其他进程修改同一文件区。
一个更适合的术语可能是字节范围锁,因为它锁定的只是文件中的一个区域。
fcntl记录锁
#include
intfcntl(intfd,intcmd,.../*structflock*flockptr*/);
//返回值:
若成功,依赖于cmd,否则,返回-1
cmd是F_GETLK、F_SETLK或F_SETLKW。
第三个参数是一个指向flock结构的指针
structflock
{
shortl_type;/*F_RDLCK,F_WRLCK,orF_UNLCK*/
shortl_whence;/*SEEK_SET,SEEK_CUR,orSEEK_END*/
off_tl_start;/*offsetinbytes,relativetol_whence*/
off_tl_len;/*length,inbytes;0meanslocktoEOF*/
pid_tl_pid;/*returnedwithF_GETLK*/
};
对于flock的结构说明如下:
所希望的锁类型:
F_RDLCK(共享读锁)、F_WRLCK(独占性写锁)或F_UNLCK(解锁一个区域)
要加锁或解锁区域的起始字节偏移量(l_start和l_whence)
区域的字节长度(l_len)
进程的ID(l_pid)持有的锁能阻塞当前进程(仅由F_GETLK返回)
关于加锁和解锁区的说明:
指定区域起始偏移量的两个元素与lseek函数中最后两个参数类似。
l_whence可选用的值是SEEK_SET、SEEK_CUR和SEEK_END
锁可以在当前文件尾端处开始或者越过尾端处开始,但不能在文件起始位置之前开始
若l_len为0,则表示锁的范围可以扩展到最大可能偏移量。
这以为着不管向该文件追加写了多少数据,它们都是可以处于锁的范围内,而且起始位置可以是文件中的任意一个位置
为了对整个文件加锁,我们设置l_start和l_whence指向文件的起始位置,并且指定长度为0。
规则:
任意多个进程在一个给定的字节上可以有一把共享的锁,但是在一个给定字节上只能有一个进程有一把独占写锁。
关于cmd命令
F_GETLK:
判断有flockptr所描述的锁是否会被另一把锁排斥。
如果存在一把锁,它阻止创建由flockptr所描述的锁,则该现有锁的信息将重写flockptr指向的信息。
如果不存在这种情况,则除了将l_type设置为F_UNLCK之外,flockptr所指向结构中的其他信息保持不变
F_SETLK:
设置有flockptr所描述的锁,如果试图获得一把读锁或写锁,而兼容性规则阻止系统给我们这把锁,那么fcntl会立即出错返回,此时errno设置为EACCES或EAGAIN此命令也用来清除有flockptr指定的锁
F_SETLKW:
这个命令是F_SETLK的阻塞版本。
如果所请求的读锁或写锁因另一个进程当前已经对所请求区域的某部分进行了加锁而不能被授予,那么调用进程会置为休眠。
如果请求创建的锁已经可用,或者休眠由信号中断,则该进程被唤醒
在设置或释放文件上的锁时,系统按要求组合或分裂相邻区。
请求和释放一把锁的函数:
#include
#include
#include
#include
intlock_reg(intfd,intcmd,inttype,off_toffset,intwhence,off_tlen)
{
structflocklock;
lock.l_type=type;
lock.l_start=offset;
lock.l_whence=whence;
lock.l_len=len;
return(fcntl(fd,cmd,&lock));
}
测试一把锁
#include
#include
#include
#include
pid_tlock_test(intfd,inttype,off_toffset,intwhence,off_tlen)
{
structflocklock;
lock.l_type=type;
lock.l_start=offset;
lock.l_whence=whence;
lock.l_len=len;
if(fcntl(fd,F_GETLK,&lock)<0)
{
perror("fcntlerror!
");
exit(EXIT_FAILURE);
}
if(lock.l_type==F_UNLCK)
{
return0;
}
return(lock.l_pid);
}
读锁和写锁测试
读锁
#include
#include
#include
#include
#include
#include
#include
voidPerror(constchar*s)
{
perror(s);
exit(EXIT_FAILURE);
}
pid_tlock_test(intfd,inttype,off_toffset,intwhence,off_tlen)
{
structflocklock;
lock.l_type=type;
lock.l_start=offset;
lock.l_whence=whence;
lock.l_len=len;
if(fcntl(fd,F_GETLK,&lock)<0)
{
perror("fcntlerror!
");
exit(EXIT_FAILURE);
}
if(lock.l_type==F_UNLCK)
{
return0;
}
return(lock.l_pid);
}
intlock_reg(intfd,intcmd,inttype,off_toffset,intwhence,off_tlen)
{
structflocklock;
lock.l_type=type;
lock.l_start=offset;
lock.l_whence=whence;
lock.l_len=len;
return(fcntl(fd,cmd,&lock));
}
intmain()
{
//创建文件
intfd=open("./test.tmp",O_RDONLY|O_CREAT|O_EXCL,0777);
if(fd==-1)
{
printf("fileexit!
\n");
fd=open("./test.tmp",O_RDONLY,0777);
}
else
printf("createfilesuccess.\n");
pid_tpid=getpid();
printf("theprocpid:
%d\n",pid);
pid_tlockpid=lock_test(fd,F_RDLCK,0,SEEK_SET,0);
if(lockpid==0)
printf("checkreadlockable,ok\n");
else
printf("checkreadlockable,can't.havewritelock,ownerpid:
%d\n",lockpid);
//setreadlock
if(lock_reg(fd,F_SETLK,F_RDLCK,0,SEEK_SET,0)<0)
printf("setreadlockfailed\n");
else
printf("setreadlocksuccess\n");
sleep(60);
return0;
}
写锁
#include
#include
#include
#include
#include
#include
#include
voidPerror(constchar*s)
{
perror(s);
exit(EXIT_FAILURE);
}
//checklock
pid_tlock_test(intfd,inttype,off_toffset,intwhence,off_tlen)
{
structflocklock;
lock.l_type=type;
lock.l_start=offset;
lock.l_whence=whence;
lock.l_len=len;
if(fcntl(fd,F_GETLK,&lock)<0)
{
perror("fcntlerror!
");
exit(EXIT_FAILURE);
}
if(lock.l_type==F_UNLCK)
{
return0;
}
return(lock.l_pid);
}
//setlockorfreelock
intlock_reg(intfd,intcmd,inttype,off_toffset,intwhence,off_tlen)
{
structflocklock;
lock.l_type=type;
lock.l_start=offset;
lock.l_whence=whence;
lock.l_len=len;
return(fcntl(fd,cmd,&lock));
}
intmain()
{
intfd=open("./test.tmp",O_WRONLY|O_CREAT|O_EXCL,0777);
if(fd==-1)
{
printf("fileexit\n");
fd=open("./test.tmp",O_WRONLY,077);
}
else
printf("createfilesuccess.\n");
pid_tpid=getpid();
printf("theprocpid:
%d\n",pid);
//checkwrite
pid_tlockpid=lock_test(fd,F_WRLCK,0,SEEK_SET,0);
if(lockpid==0)
printf("checkwritelockable,ok\n");
else
printf("checkwritelockable,can't.\
havereadorwritelock,owerpid:
%d",lockpid);
//setwritelock
if(lock_reg(fd,F_SETLK,F_WRLCK,0,SEEK_SET,0)<0)
printf("setwritelockfailed\n");
else
printf("setwritelocksuccess\n");
sleep(60);
return0;
}
设置读锁,再设置读锁
ubuntu@VM-188-113-ubuntu:
~/Code/apue/ch14AdvancedI_O$./readflock
createfilesuccess.
theprocpid:
31858
checkreadlockable,ok
setreadlocksuccess
ubuntu@VM-188-113-ubuntu:
~/Code/apue/ch14AdvancedI_O$./readflock
fileexit!
theprocpid:
31908
checkreadlockable,ok
setreadlocksuccess
设置读锁载设置写锁
ubuntu@VM-188-113-ubuntu:
~/Code/apue/ch14AdvancedI_O$./readflock
fileexit!
theprocpid:
31973
checkreadlockable,ok
setreadlocksuccess
ubuntu@VM-188-113-ubuntu:
~/Code/apue/ch14AdvancedI_O$./writeflock
fileexit
theprocpid:
32018
checkwritelockable,can't.havereadorwritelock,owerpid:
31973setwritelockfailed
锁的隐含继承和释放
进程终止时,它所建立的锁全部释放。
无论一个描述符何时关闭,该进程通过这一描述符引用建立的任何一把锁都会释放。
由fork产生的子进程不继承父进程所设置的锁。
在执行exec后,新程序可以继承原执行程序的锁。
因为执行exec前后还是一个进程。
我们只是改变进程执行的程序,并没有创建新的进程。
在文件尾端加锁
在文件尾端加锁或者解锁时要特别小心,采用SEEK_END和SEEK_CUR是可能不断变化的,所以们最好采用SEEK_SET,使用绝对偏移,否则应时刻记住当前偏移量和文件尾端的位置。
建议性锁和请执行锁(参考:
1.建议性锁机制是这样规定的:
每个使用文件的进程都要主动检查该文件是否有锁存在,当然都是通过具体锁的API,比如fctl记录锁F_GETTLK来主动检查是否有锁存在。
如果有锁存在并被排斥,那么就主动保证不再进行接下来的IO操作。
如果每一个进程都主动进行检查,并主动保证,那么就说这些进程都以一致性的方法处理锁,(这里的一致性方法就是之前说的两个主动)。
但是这种一致性方法依赖于编写进程程序员的素质,也许有的程序员编写的进程程序遵守这个一致性方法,有的不遵守。
不遵守的程序员编写的进程程序会怎么做呢?
也许会不主动判断这个文件有没有加上文件锁或记录锁,就直接对这个文件进行IO操作。
此时这种有破坏性的IO操作会不会成功呢?
如果是在建议性锁的机制下,这种破坏性的IO就会成功。
因为锁只是建议性存在的,并不强制执行。
内核和系统总体上都坚持不使用建议性锁机制,它们依靠程序员遵守这个规定。
(Linux默认是采用建议性锁)
2.强制性锁机制是这样规定的:
所有记录或文件锁功能内核执行的。
上述提到的破坏性IO操作会被内核禁止。
当文件被上锁来进行读写操作时,在锁定该文件的进程释放该锁之前,内核会强制阻止任何对该文件的读或写违规访问,每次读或写访问都得检查锁是否存在。
也就是强制性锁机制,让锁变得名副其实,真正达到了锁的效果,而不是像建议性锁机制那样只是个纸老虎。
==!
设置强制性文件锁的方式比较特别:
chmodg+s
chmodg-x
这是形象的表示,实际编程中应该通过chmod()函数一步完成。
不能对目录、可执行文件设置强制性锁。
4、I/O多路转换
当一个进程有多个输入输出时该如何处理?
直接一个进程阻塞:
任何一个I/O读写阻塞,进程无法进行下去,效率低
fork多个进程处理:
对于操作结束难以处理
在一个进程中使用多线程:
要处理同步问题,复杂性太高
是非非阻塞I/O读取数据:
采用轮训方式,但是比较浪费CPU时间
异步I/O:
当描述符准备好后就用一个信号通知它,但是难于确定是哪个I/O发出的信号
一个比较好的技术是:
I/O多路转换。
为了使用这种技术,先构造一张我们感兴趣的描述符的列表,然后调用一个函数,直到这些描述符中的一个已准备好进行I/O时,该函数才返回。
poll、pselect和select这3个函数使我们能够执行I/O多路转接。
函数select和pselect
select函数告诉内核:
我们所关心的描述符
对于每个描述符我们所关心的条件
愿意等待多长时间
从select返回时,内核告诉我们:
已准备好的描述符的总数量
对于读、写或异常这3个条件中的每一个,哪些描述符已准备好
#include
intselect(intmaxfdp1,fd_set*restrictreadfds,
fd_set*restrictwritefds,fd_set*restrictexcepyfds
structtimeval*restricttvptr);
//返回值:
准备就绪的描述符数目;若超时,返回0;若出错,返回-1
tvptr愿意等待的时间长度
NULL。
用于等待
0。
不等待
不为0。
等待制定的时间
readfds,writefds,excepyfds:
指向描述符集的指针。
这3个描述符集说明了我们关心的可读、可写或处于异常条件的描述符集合。
每个描述符集存储在一个fd_set数据类型中。
对于fd_set数据类型,唯一可以进行的处理是:
分配一个这种类型的变量,将这种类型的一个变量值赋给同类型的另一个变量,或对这种类型的变量使用下列4个函数中的一个。
#include
intFD_ISSET(intfd,fd_set*fdset);//检测是否打开
//返回值:
若fd在描述符集中,返回非0值;否则,返回0
voidFD_CLR(intfd,fd_set*fdset)//清空一位
voidFD_SET(intfd,fd_set*fdset);//设置一位
voidFD_ZERO(fd_set*fdset);//清0
select的中间3个参数中的任意一个可以是空指针,这表示对相应条件并不关心。
如果所有3个指针都是NULL,则select提供了比sleep更精确的定时器
*maxfdp1:
表示要搜寻的最大描述符编号值加1。
*返回值:
-1表示出错,0表示没有描述符准备好,正值说明已经准备好的描述符数。
pselect是select的变体,函数形式如下:
#include
intpselect(intmaxfdp1,fd_set*restrictreadfds,
fd_set*restrictwritefds,fd_set*restrictexceptfds,
conststructtimespec*restricttsptr,
constsigset_t*restrictsigmask);
//返回值:
准备就绪的描述符数目,若超时返回0,若出错返回1.
函数poll
poll函数和select类似。
#include
intpoll(structpollfdfdarray[],nfds_ynfds,inttimeout);
//返回值:
准备就绪的描述符数目:
若超时,返回0;若出错,返回-1
poll构造一个pollfd数组,每个数组元素指定一个描述符编号以及我们对该描述符感兴趣的条件
structpollfd{
intfd;//文件描述符
shortevents;//等待的事件
shortrevents;//实际发生了的事件
};
每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。
每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。
revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。
events域中请求的任何事件都可能在revents域中返回
合法的事件如下:
POLLIN 有数据可读。
POLLRDNORM 有普通数据可读。
POLLRDBAND 有优先数据可读。
POLLPRI 有紧迫数据可读。
POLLOUT 写数据不会导致阻塞。
POLLWRNORM 写普通数据不会导致阻塞。
POLLWRBAND 写优先数据不会导致阻塞。
POLLMSGSIGPOLL 消息可用。
此外,revents域中还可能返回下列事件:
POLLER 指定的文件描述符发生错误。
POLLHUP 指定的文件描述符挂起事件。
POLLNVAL 指定的文件描述符非法。
这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。
timeout参数制定等待的时间:
-1,永远等待;0,不等待;>0,等待timeout毫秒数;
异步I/O
[参考:
同步:
所谓同步,就是在c端发出一个功能调用时,在没有得到结果之前,该调用就不返回。
也就是必须一件一件事做,等前一件做完了才能做下一件事。
例如普通B/S模式(同步):
提交请求->等待服务器处理->处理完毕返回这个期间客户端浏览器不能干任何事
异步:
异步
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- apue 第14章 高级I 14 高级