文件锁与I/O复用
当多个用户共同使用、操作一个文件的情况,这时,Linux 通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态。文件锁包括建议性锁和强制性锁。建议性锁要求每个上锁文件的进程都要检查是否有锁存在,并且尊重已有的锁。在一般情况下,内核和系统都不使用建议性锁。强制性锁是由内核执行的锁,当一个文件被上锁进行写入操作的时候,内核将阻止其他任何文件对其进行读写操作。采用强制性锁对性能的影响很大,每次读写操作都必须检查是否有锁存在。在 Linux 中,实现文件上锁的函数有lock和fcntl,其中flock用于对文件施加建议性锁,而fcntl不仅可以施加建议性锁,还可以施加强制锁。同时,fcntl还能对文件的某一记录进行上锁,也就是记录锁。
记录锁又可分为读取锁和写入锁,其中读取锁又称为共享锁,它能够使多个进程都能在文件的同一部分建立读取锁。而写入锁又称为排斥锁,在任何时刻只能有一个进程在文件的某个部分上建立写入锁。当然,在文件的同一部分不能同时建立读取锁和写入锁。
以验证写入锁又称为排斥锁为例:
1 /*fcntl.c*/ 2 #include <unistd.h> 3 #include <sys/file.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 void lock_set(int fd, int type); 9 /*lock_set函数*/ 10 void lock_set(int fd, int type) 11 { 12 struct flock lock; 13 lock.l_whence = SEEK_SET;//赋值lock结构体 14 lock.l_start = 0; 15 lock.l_len =0; 16 while(1) 17 { 18 lock.l_type = type; 19 /*根据不同的type值给文件上锁或解锁*/ 20 if((fcntl(fd, F_SETLK, &lock)) == 0) 21 { 22 if( lock.l_type == F_RDLCK ) 23 printf("read lock set by %d\n",getpid()); 24 else if( lock.l_type == F_WRLCK ) 25 printf("write lock set by %d\n",getpid()); 26 else if( lock.l_type == F_UNLCK ) 27 printf("release lock by %d\n",getpid()); 28 return; 29 } 30 /*判断文件是否可以上锁*/ 31 fcntl(fd, F_GETLK,&lock); 32 /*判断文件不能上锁的原因*/ 33 if(lock.l_type != F_UNLCK) 34 { 35 /*该文件已有写入锁*/ 36 if( lock.l_type == F_RDLCK ) 37 printf("read lock already set by %d\n",lock.l_pid); 38 /*该文件已有读取锁*/ 39 else if( lock.l_type == F_WRLCK ) 40 printf("write lock already set by %d\n",lock.l_pid); 41 getchar(); 42 } 43 } 44 } 45 void main(void) 46 { 47 int fd; 48 fd=open("hello",O_RDWR | O_CREAT, 0666); 49 if(fd < 0) 50 { 51 perror("open ERROR"); 52 exit(1); 53 } 54 /*给文件上写入锁*/ 55 lock_set(fd, F_WRLCK); 56 getchar(); 57 /*给文件接锁*/ 58 lock_set(fd, F_UNLCK); 59 getchar(); 60 close(fd); 61 exit(0); 62 }
同时运行2个终端可以观察到:
前面的fcntl函数解决了文件的共享问题,接下来该处理I/O 复用的情况了。总的来说,I/O 处理的模型有5 种。
· 阻塞 I/O模型:在这种模型下,若所调用的I/O 函数没有完成相关的功能就会使进程挂起,直到相关数据到才会出错返回。如常见对管道设备、终端设备和网络设备进行读写时经常会出现这种情况。
· 非阻塞模型:在这种模型下,当请求的I/O 操作不能完成时,则不让进程睡眠,而且返回一个错误。非阻塞I/O 使用户可以调用不会永远阻塞的I/O 操作,如open、write和read。如果该操作不能完成,则会立即出错返回,且表示该I/O 如果该操作继续执行就会阻塞。
· I/O多路转接模型:在这种模型下,如果请求的I/O操作阻塞,且它不是真正阻塞I/O,而是让其中的一个函数等待,在这期间,I/O 还能进行其他操作。如本节要介绍的select函数和poll函数,就是属于这种模型。
· 信号驱动 I/O 模型:在这种模型下,通过安装一个信号处理程序,系统可以自动捕获特定信号的到来,从而启动I/O。这是由内核通知用户何时可以启动一个I/O 操作决定的。
· 异步 I/O模型:在这种模型下,当一个描述符已准备好,可以启动I/O 时,进程会通知内核。现在,并不是所有的系统都支持这种模型。
struct timeval {
long tv_sec; /* second */
long tv_unsec; /* and microseconds*/
}
该例中主要实现将文件hello1 里的内容读出,并将此内容每隔10s 写入hello2 中去。在这里建立了两个描述符集,其中一个描述符集inset1 是用于读取文件内容,另一个描述符
集inset2是用于写入文件的:
1 /*select.c*/ 2 #include <fcntl.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <stdlib.h> 6 #include <time.h> 7 void main(void) 8 { 9 int fds[2]; 10 char buf[7]; 11 int i,rc,maxfd; 12 fd_set inset1,inset2; 13 struct timeval tv; 14 tv.tv_sec=2; 15 tv.tv_usec=0; 16 /*首先按一定的权限打开hello1文件*/ 17 if((fds[0] = open ("hello1", O_RDWR|O_CREAT,0666))<0) 18 perror("open hello1 ERROR"); 19 /*再按一定的权限打开hello2文件*/ 20 if((fds[1] = open ("hello2", O_RDWR|O_CREAT,0666))<0) 21 perror("open hello2 ERROR"); 22 if((rc = write(fds[0],"Hello!\n",7))) 23 printf("rc=%d\n",rc); 24 lseek(fds[0],0,SEEK_SET); 25 /*取出两个文件描述符中的较大者*/ 26 maxfd = fds[0]>fds[1] ? fds[0] : fds[1]; 27 /*初始化读集合inset1,并在读集合中加入相应的描述集*/ 28 FD_ZERO(&inset1); 29 FD_SET(fds[0],&inset1); 30 /*初始化写集合inset2,并在写集合中加入相应的描述集*/ 31 FD_ZERO(&inset2); 32 FD_SET(fds[1],&inset2); 33 /*循环测试该文件描述符是否准备就绪,并调用select函数对相关文件描述符做对应操作*/ 34 while(FD_ISSET(fds[0],&inset1)||FD_ISSET(fds[1],&inset2)) 35 { 36 if(select(maxfd+1,&inset1,&inset2,NULL,&tv)<0) 37 perror("select ERROR"); 38 else 39 { 40 if(FD_ISSET(fds[0],&inset1)) 41 { 42 rc = read(fds[0],buf,7); 43 if(rc>0) 44 { 45 buf[rc]='\0'; 46 printf("read: %s\n",buf); 47 } 48 else 49 perror("read ERROR"); 50 } 51 if(FD_ISSET(fds[1],&inset2)) 52 { 53 rc = write(fds[1],buf,7); 54 if(rc>0) 55 { 56 buf[rc]='\0'; 57 printf("rc=%d,write: %s\n",rc,buf); 58 } 59 else 60 perror("write ERROR"); 61 sleep(10); 62 } 63 } 64 } 65 exit(0); 66 }