《APUE》读书笔记-第十四章高级I/O
1、非阻塞I/O
对低速设备的I/O操作可能会使进程永久阻塞,这类系统调用主要有如下情况:
(1)如果数据并不存在,则读文件可能会使调用者永远阻塞(例如读管道、终端设备和网络设备)。
(2)如果数据不能立即被接受,则写这些同样的文件也会使调用者永远阻塞;
(3)在某些条件发生之前,打开文件会被阻塞(例如以只写方式打开一个FIFO,那么在没有其他进程已用读方式打开该FIFO时);
(4)对已经加上强制性锁的文件进行读、写;
(5)某些ioctl操作;
(6)某些进程间通信函数;
非阻塞I/O调用open、read和write等I/O操作函数使上述的慢速系统调用在不能立即完成的情况下,立即出错返回。
对一个给定的描述符有两种方法设置其为非阻塞:
(1)如果是调用open以获得该描述符,则可指定O_NONBLOCK标志;
(2)对于已经打开的一个描述符,则可调用fcntl打开O_NONBLOCK文件状态标志(注意:设置文件状态标志的方法)。
写个非阻塞I/O的程序,程序功能是从标准输入读入100 000字节,并试图将它们写到标准输出上。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <sys/types.h> 6 #include <fcntl.h> 7 8 char buf[100000]; 9 //将描述符设置为阻塞状态 10 void set_fl(int fd,int flags) 11 { 12 int val; 13 val = fcntl(fd,F_GETFL,0); //获取描述符 14 val |= flags; //添加状态 15 fcntl(fd,F_SETFL,val); //设置描述符 16 } 17 void clr_fl(int fd,int flags) 18 { 19 int val; 20 val = fcntl(fd,F_GETFL,0); 21 val &= ~flags; // 取消状态 22 fcntl(fd,F_SETFL,val); 23 } 24 int main() 25 { 26 int ntowrite,nwrite; 27 char *ptr; 28 ntowrite = read(STDIN_FILENO,buf,sizeof(buf)); 29 fprintf(stderr,"read %d bytes\n",ntowrite); 30 set_fl(STDOUT_FILENO,O_NONBLOCK); //设置为阻塞状态 31 ptr = buf; 32 while(ntowrite > 0) 33 { 34 errno = 0; 35 nwrite = write(STDOUT_FILENO,ptr,ntowrite); 36 fprintf(stderr,"nwrite = %d,errno =%d\n",nwrite,errno); 37 if(nwrite > 0) 38 { 39 ptr += nwrite; 40 ntowrite -= nwrite; 41 } 42 } 43 clr_fl(STDOUT_FILENO,O_NONBLOCK); //设置为非阻塞状态 44 exit(0); 45 }
程序执行结果如下:若标准输出是普通文件,则可以期望write只执行一次。
若标识输出是终端,则期望write有时会返回小于100000的一个数字,有时会出错返回。按照下面要求执行,结果如图所示:
anker@killer-anker:~/Programs$ ./nonblock </lib/ld-linux.so.2 2> temp.file
anker@killer-anker:~/Programs$ cat temp.file
程序发出很多write操作,只有几个是正确输出数据的,其余的则出错返回。这种方式叫做轮询,在多用法系统上面浪费了CPU时间。
2、记录锁
记录锁的功能是:一个进程正在读或修改文件的某个部分时,可以阻止其他进程修改同一文件区。对于UNIX,实际上,由于内核没有“记录”的概念,因此,“记录锁”实际上是“区域锁”。
fcntl记录锁:int fcntl(int fd, int cmd, ……/* struct flock *flockptr */);对于记录锁,cmd是F_GETLK,f_SETLK,或F_SETLKW。第三个参数是一个指向flock结构的指针:
struct flock {
short l_type; /* F_RDLCK, F_WRLCK, or F_UNLCK */
off_t l_start;
short l_whence;
off_t l_len;
pid_t l_pid;
}
关于加锁和解锁区域需要注意以下几点:该区域可以在当前文件尾端处开始或越过其尾端处开始,但不能在文件起始位置之前或越过该起始位置;若l_len为0,则表示锁的区域从其起点开始,直至最大可能位置为止。
两种锁:共享读锁(L_RDLCK)和独占写锁(L_WRLCK)的基本规则是,多个进程在一个给定的字节上可以有一把共享的读锁。但是在一个给定字节上的写锁则只能由一个进程独用。加读锁时,该描述符必须是读打开;加写锁时,该描述符必须是写打开的。
|
读锁
|
写锁
|
无锁
|
允许
|
允许
|
一把或多把读锁
|
允许
|
拒绝
|
一把写锁
|
拒绝
|
拒绝
|
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <sys/types.h> 6 #include <sys/select.h> 7 8 int main() 9 { 10 fd_set rfds; 11 struct timeval tv; 12 int retval; 13 char buf[1024]; 14 for(;;) 15 { 16 FD_ZERO(&rfds); 17 FD_SET(STDIN_FILENO, &rfds); 18 /* Wait up to five seconds. */ 19 tv.tv_sec = 5; 20 tv.tv_usec = 0; 21 retval = select(1, &rfds, NULL, NULL, &tv); 22 /* Don't rely on the value of tv now! */ 23 if (retval) 24 { 25 printf("Data is available now.\n"); 26 if(FD_ISSET(STDIN_FILENO, &rfds)) 27 { 28 read(STDIN_FILENO,buf,1024); 29 printf("Read buf is: %s\n",buf); 30 } 31 } 32 else 33 printf("No data within five seconds.\n"); 34 } 35 exit(0); 36 }
程序执行结果如下:
函数原形: ssize_t readv(int filedes,const struct iovec *iov,int iovcnt);
ssize_t writev(int filedes,const struct iovec *iov,int iovcnt);
参数:filedes 文件描述符
iov 指向iovec结构数组的一个指针。
iovcnt 数组元素的个数
返回值:若成功则返回已读、写的字节数,若出错则返回-1
writev() 系统调用把 iovcnt 个由 iov 结构描述的缓存区数据写入文件描述符 fd 关联的文件里。(聚合写)
写个程序从标准输入读取数据存放到多个缓冲区中,然后从标准输出显示结果,程序如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <sys/uio.h> 6 #include <fcntl.h> 7 #include <string.h> 8 9 int main(int argc,char *argv[]) 10 { 11 int fd1,fd2; 12 char *buf1 = malloc(10); 13 char *buf2 = malloc(1024); 14 struct iovec iov[2]; 15 memset(buf1,0,11); 16 memset(buf2,0,1025); 17 ssize_t nwritten; 18 iov[0].iov_base = buf1; 19 iov[0].iov_len = 10; 20 iov[1].iov_base = buf2; 21 iov[1].iov_len = 1024; 22 readv(STDIN_FILENO,iov,2); 23 printf("call readv:\n"); 24 printf("buf1 is: %s\tlength is: %d\n",buf1,strlen(buf1)); 25 printf("buf2 is: %s\tlength is: %d\n",buf2,strlen(buf2)); 26 printf("call writev:\n"); 27 iov[0].iov_base = buf1; 28 iov[0].iov_len = strlen(buf1); 29 iov[1].iov_base = buf2; 30 iov[1].iov_len = strlen(buf2); 31 nwritten = writev(STDOUT_FILENO, iov, 2); 32 free(buf1); 33 free(buf2); 34 exit(0); 35 }
程序结果如下: