Linux/UNIX先进I/O
先进I/O
非阻塞IO
非阻塞I/O因此,我们可以称之为open、read和write这种I/O操作,而这些操作不会永久阻止。我们假设,该操作不能完成,然后调用立即返回一个错误。则表示该操作将继续作为堵塞。
对于一个给定的描写叙述符有两种方法对其指定非堵塞I/O:
1) 假设调用open获得描写叙述符。则可指定O_NONBLOCK标志
2) 对于已打开的一个描写叙述符,则可调用fcntl。由该函数打开O_NONBLOCK文件状态标志。
记录锁
记录锁的功能是:当一个进程正在读或改动文件的某个部分时,它能够阻止其它进程改动同一文件区。
锁定的是文件里的一个区域。
记录锁函数fcntl:
#include<unistd.h>
#include<fcntl.h>
int fcntl(intfd, int cmd, ... /* arg */ );
对于记录锁。cmd是F_GETLK、F_SETLK或F_SETLKW。
当中第三个參数arg是一个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_GETLKonly) */
...
};
flock结果说明例如以下:
所希望的所类型:由l_type决定, 可为F_RDLCK(共享锁)、F_WRLCK(独占性写锁)或F_UNLCK(解锁一个区域)。
要加锁或解锁区域的起始字节偏移量。这由l_whence和l_start决定。
区域字节:由l_len决定。
若l_len为0,则表示锁的区域从起点開始直至最大可能偏移量为止。
具有能堵塞当前进程的锁,其持有进程的ID存放在l_pid中(由F_GETLK情况下返回)
共享读锁和独占写锁:多个进程在一个给定的字节上能够有一把共享的读锁。可是在一个给定字节上仅仅能有一个进程独用一把锁。
共享读锁和独占写锁概念是针对不同进程提出锁的请求。不适用于但个进程提出多个锁请求。
当一个进程对一个文件区间已经有了一把锁,后来该进程有企图在同一文件区间再加上一把锁。那么新锁将替换老锁。
加读锁时,该描写叙述符必须是读打开;加写锁时,必须是写的打开。
下面说明fcntl函数的三种命令:
F_GETLK:推断由arg中所描写叙述的锁是否会被另外一把锁排斥(堵塞)。
假设存在一把锁。他阻止创建由arg所描写叙述的锁。则把该现存锁的信息写到arg指向的结构中。假设不存这样的情况,则除了l_type设置为F_UNLCK之外,arg所指向结构中的其它信息保持不变。
F_SETLK:设置由arg所描写叙述的锁。
假设试图建立一把读锁或写锁。但实际情况不同意建立锁(比方已经有写锁),则fcntl出错返回。
F_SETLKW:它是F_SETLK的堵塞版本号。
锁的隐含继承和释放:
关于记录锁的自己主动继承和释放有三条规则:
1. 锁与进程文件双方面有关:第一点非常明显。当一个进程终止时,它所建立的锁所有释放;第二点指的是不论什么时候关闭一个描写叙述符时。则该进程通过这一文件描写叙述符引用的文件上的不论什么一把锁都被释放。
2. 由fork产生的子进程不能继承父进程所设置锁。
这意味着。若一个进程得到一把锁。然后调用fork。那么对于父进程获得的锁而言,子进程被视为还有一个进程,对于继承过来的任一描写叙述符。子进程须要调用fcntl才干获得它自己的锁。这与锁的作用相一致,锁的作用是阻止多个进程同一时候写同一个文件。假设子进程继承父进程的锁,则父子进程就能够同一时候写同一个文件。
3. 在运行exec后,新程序能够继承原运行程序的锁。除非队以文件描写叙述符设置了close-on-exec标志,那么运行exec时,将释放锁。
锁的数据结构实现
考虑一个进程。它运行下列语句:
fd1= open(pathname, … );
write_lock(fd1,0, SEEK_SET, 1);
if((pid== fork()) > 0){
fd2= dup(fd1);
fd3= open(pathname, …);
}else if (pid ==0){
read_lock(fd1,1,SEEK_SET,1)
}
当中write_lock和read_lock调用fcntl的加锁实现。
下图显示了父子进程暂停后的数据结构情况:
有了记录锁之后,在原来的数据结构上添加了lockf结构,他们由i节点開始相互连接起。
注意,每个lock结构说明了一个给定进程的一个加锁区域(有偏移量和长度定义)。图中显示了两个lockf结构。一个是由父进程调用write_lock形成的,还有一个则由子进程调用read_lock形成的。
每个结构都包括了对应的进程ID。
在父进程中,关闭fd1、fd2和fd3中不论什么一个都将释放由父进程设置的写锁。
建议性锁和强制性锁
建议性锁是这样规定的:每一个使用上锁文件的进程都要检查是否有锁存在,当然还得尊重已有的锁。
内核和系统整体上都坚持不使用建议性锁。它们依靠程序猿遵守这个规定。(Linux默认是採用建议性锁)
强制性锁是由内核运行的。当文件被上锁来进行写入操作时,在锁定该文件的进程释放该锁之前,内核会阻止不论什么对该文件的读或写訪问。每次读或写訪问都得检查锁是否存在。
样例:
例1,我有几个进程(不一定有亲缘关系)都通过fctnl机制来操作文件,这个就叫一致的方法。
可是。假设同一时候。又有个流氓进程。管它3721,冲上去。open, write。这时候那几个进程fcntl对这样的方式无能为力,这样就叫不一致。文件最后的状态就不定了。正由于这样的锁约束不了其他的訪问方式。所以叫建议行锁。
强制性锁须要内核支持的,对read, write, open都会检查锁。
例2,所谓建议性锁就是假定人们都会遵守某些规则去干一件事。
比如,人与车看到红灯都会停。而看到绿灯才会继续走。我们能够称红绿等为建议锁。但这仅仅是一种规则而已,你并不防止某些人强闯红灯。而强制性锁是你想闯红灯也闯不了。
STREAMS
I/O多路转接
I/O多路转接:先构造一张有关描写叙述符的列表,然后调用一个函数,知道这些描写叙述符中的一个已准备好进行I/O时,该函数返回。
在返回时。它告诉进程哪些描写叙述符已准备好能够进行I/O。
poll、pselect和select这三个函数使我们可以运行I/O多路转接。
select和pselect函数
#include<sys/select.h>
int select(intnfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, structtimeval *timeout);
select函数使我们能够运行I/O多路转接。
最后一个參数,它指定愿意等待的时间:
structtimeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
由三种情况:
timeout== NULL:永远等待。
假设捕捉到一个信号则中断此无限期等待。当所指定的描写叙述符中的一个已准备好或捕捉到的一个信号则返回。假设捕捉到一个信号。则select返回-1,errno设置为EINTR。
tvptr->tv_sec== 0 && tvptr->tv_usec == 0:全然不等待。測试全部指定的描写叙述符并马上返回。这是得到多个描写叙述符的状态而不堵塞select函数的轮询方法。
tvptr->tv_sec!= 0 || tvptr->tv_usec != 0:等待指定的秒数和微妙数。当指定的描写叙述符之中的一个已准备好,或当指定的时间值已超过时马上返回。
假设在超时时还没有一个描写叙述符准备好,则返回值是0。
与第一种情况一样,这样的等待可被捕捉到的信号中断。
中间的三个參数readfds,writefds和exceptfds是指向描写叙述符集的指针。这三个描写叙述符集说明了我们关心的可读、可写或处于异常条件的各个描写叙述符。每一个描写叙述符集存放在一个fd_set数据类型中。对fd_set数据类型而进行的处理是:分配一个这样的类型的变量;将这样的类型的变量赋予同类型的还有一个变量;或对于这样的类型的变量使用下列四个函数的一个。
#include <sys/types.h>
void FD_CLR(int fd, fd_set *set); //将一个指定位清除
int FD_ISSET(int fd, fd_set*set); //測试一指定为是否设置。若fd存在,返回非0
//值;否则返回0。
void FD_SET(int fd, fd_set *set); //设置一个fd_set变量的指定位
void FD_ZERO(fd_set *set); //讲一个指定的fd_set变量的全部位设置为0
測试程序例如以下:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/select.h> int main() { fd_set rfds; struct timeval tv; int retval; char buf[1024]; for(;;) { FD_ZERO(&rfds); FD_SET(STDIN_FILENO, &rfds); /* Wait up to five seconds. */ tv.tv_sec = 5; tv.tv_usec = 0; retval = select(1, &rfds, NULL, NULL, &tv); /* Don't rely on the value of tv now! */ if (retval) { printf("Data is availablenow.\n"); if(FD_ISSET(STDIN_FILENO, &rfds)) { read(STDIN_FILENO,buf,1024); printf("Read buf is:%s\n",buf); } } else printf("No data within five seconds.\n"); } exit(0); }
运行结果
hello
Data is available now.
Read buf is: hello
No data within five seconds.
No data within five seconds.
world
Data is available now.
Read buf is: world
No data within five seconds.
pselect与select功能同样。但其超时值用timespec结构(秒和纳秒)指定。可选择信号屏蔽字。
超时值被声明为const。
#include <sys/select.h>
int pselect(int nfds, fd_set *readfds,fd_set *writefds,fd_set *exceptfds,
const struct timespec*timeout,const sigset_t *sigmask);
poll函数
poll函数类似于select,可是其程序接口不同。
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds,int timeout);
与select函数不同,poll不是为每一个状态构造个描写叙述符集,而是构造一个pollfd结果数组。
结构定义例如以下:
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
fds数组中元素个数由nfds指定。
timeout == -1:永远等待
timeout == 0:不等待
timeout > 0:等待timeout毫秒
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);
这两个函数的第二个參数是指向iovec结果数组的一个指针:
struct iovec {
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};
iovec结构第一个元素指向缓冲区起始地址,第二个元素指定长度。iovec数组元素个数由iovcnt指定。
writev以顺序iovec[0],iovec[1], iovec[2]从缓冲区中聚集输出数据,返回总的输出字节数。
readv同理。
存储映射I/O
存储映射I/O使磁盘文件与存储空间中的一个缓冲区相映射。
于是当从缓冲区中取数据。就相当于读文件里的对应字节。与此类似,将数据存入缓冲区。则对应字节就自己主动写入文件。这样就能够在不使用read和write的情况下运行I/O。
为实现这样的功能,应首先告诉内核将一个给定的文件映射到一个存储区域中。这是由mmap函数时实现的。
munmap能够去除映射关系。
#include<sys/mman.h>
void *mmap(void*addr, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void*addr, size_t length);
当中addr參数用于指定映射存储区的起始地址。
通常设置为0。表示由系统选择该映射区的起始地址。此函数的返回地址是该映射区的事实上地址。
fd指定要被映射文件的描写叙述符。在映射该文件到一个地址空间之前。先要打开该文件。len是映射的字节数。
off是要映射字节在文件里的起始偏移量。
prot參数说明对映射区的保护要求。能够为PROT_NONE。或者PROT_READ、PROT_WRITE、PROT_EXEC随意组合的按位或。对指定映射存储区的保护要求不能超过文件open模式訪问权限。
flag:可设为
MAP_FIXED:返回值必须等于addr
MAP_SHARED:这一标志说明本进程对映射区所进行的存储操纵的配置。指定存储操作改动映射文件。
MAP_PRIVATE:本标志说明。对映射区的存储操作导致创建该映射文件的一个私有副本。全部后来对该映射区的引用都是引用该副本。而不是原文件。
调用mprotect能够更改一个现存映射存储区的权限。
#include<sys/mman.h>
intmprotect(const void *addr, size_t len, int prot);
假设共享存储映射区中的页已被改动,能够调用msync将该页冲洗到被映射的文件里。
#include<sys/mman.h>
int msync(void*addr, size_t length, int flags);
例如以下程序是使用mmap的样例:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> int main(int argc, char *argv[]) { int fdin, fdout; void *src, *dst; struct stat statbuf; if (argc != 3) printf("usage: %s <fromfile> <tofile>", argv[0]); if ((fdin = open(argv[1], O_RDONLY)) < 0) printf("can't open %s for reading", argv[1]); if ((fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) printf("can't creat %s for writing", argv[2]); if (fstat(fdin,&statbuf) < 0) /* need size ofinput file */ printf("fstat error"); /*set size of output file */ if (lseek(fdout, statbuf.st_size - 1, SEEK_SET) == -1) printf("lseek error"); if (write(fdout, "", 1) != 1) printf("write error"); if ((src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) == MAP_FAILED) printf("mmap error for input"); if ((dst = mmap(0, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fdout, 0)) == MAP_FAILED) printf("mmap error for output"); memcpy(dst, src, statbuf.st_size); /* does the file copy */ exit(0); }
该程订购完整的类似cp特征。
版权声明:本文博主原创文章,博客,未经同意不得转载。