• 《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字节,并试图将它们写到标准输出上。

    View Code
     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)锁与进程、文件两方面有关:第一,当一个进程终止时,它锁建立的锁全部释放;第二,任何时候关闭一个描述符,则该进程通过这一描述符可以访问的文件上的任何一把锁都被释放。
    (2)由fork产生的子进程不继承父进程所设置的锁。
    (3)在执行exec后,新程序可以继承原执行程序的锁。
    3、I/O多路转接
      其基本思想是:先构造一张有关描述符的表,然后调用一个函数,它要到这些描述符中的一个已准备好进行I/O时才返回。在返回时,它告诉进程哪一个描述符已准备好。select、pselect和poll函数能执行I/O多路转接。函数原型如下:
    int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);返回准备就绪的描述符数,超时则为0,出错为-1
    参数timeout指定内核等待的时间,其结构如下:
    struct timeval {
    long tv_sec; /* seconds */
    long tv_usec; /* microseconds */
    }
    传给select的参数告诉内核:我们所关心的描述符;对于每个描述符我们所关心的条件;希望等待多长时间。
    从select返回时,内核告诉我们:已准备好的描述符数量;哪一个描述符已准备好读、写或异常条件。
    写个程序练习select函数,每隔5秒进行读取操作,程序如下:
     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 }

    程序执行结果如下:

    int pselect (int maxfdp1, fd_set *readset, fd_set * writeset, fd_set * exceptset, const struct timespec * timeout, const sigset_t *sigmask);
    返回:准备好的描述字个数,0-超时,-1-出错。
    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    struct pollfd {
    int fd; /* file descriptor */
    short events; /* requested events */
    short revents; /* returned events */
    };
    关于I/O多路转接将会在Unix网络编程中详细学习,在本章主要是了解这思想。
     4、异步I/O
      SVR4和4.3+BSD提供了使用一个信号(在SVR4中是SIGPOLL,在4.3+BSD中是SIGIO)的异步I/O方法,该信号通知进程,对某个描述符所关心的某个事件已经发生。异步I/O的一个限制是每个进程只有一个信号。
    5、readv和writev函数
    头文件:#include <sys/uio.h>
    函数原形:     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
    struct iovec {
      void *iov_base; /* 起始地址 */
      size_t iov_len; /* 需要传输的字节数 */
    };
    readv() 系统调用从文件描述符 fd 关联的文件里读取数据到 iovcnt 个由 iov 结果描述的缓存区里。(分散读)

    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 }

    程序结果如下:

  • 相关阅读:
    数据持久化
    在职场久了,才知道这样安排工作日程,方能实现真正的高效
    HIS系统-如何设置单病种结算方式
    HIS系统-你给我制作一个二级库吧!
    开机的一篇英文是怎么回事呢?
    系统故障之-冲动360
    每天看一遍你潦倒至今的原因
    如何配置给自己配置一台适合自己的台式机
    如何配置给自己配置一台电脑
    单网卡、双网卡如何实现同时上内网和外网
  • 原文地址:https://www.cnblogs.com/Anker/p/2828341.html
Copyright © 2020-2023  润新知