基础文章2:APUE chap14 高级I/O


0.序
本文是在学习Nginx中ngx_process_events_and_timers函数中牵涉到的内容。主要学习了
14.2 非阻塞I/O
14.3 记录锁
14.7readv和writev函数

其中记录锁用于Nginx中的accept互斥体。




14.1引言

14.2 非阻塞I/O
     大学宿舍,同学A住在宿舍617室,有外校的同学B提前告诉同学A要来访问同学A,但是没有告知同学A时间,同时同学A也没有告知同学B其宿舍号。那么同学A为了能够接到同学B,那么其有几种方式可以选择:1)一直在宿舍大门处等待同学B 2)每隔一段时间,来查看同学B是否到来,没有到来,那么同学A返回宿舍,做其他事情 3)当同学B到来时,由宿管阿姨带领同学B逐个房间去查找同学A 4)当同学B到来时,宿管告知同学B 关于同学A的一些信息,由同学B自己去找同学A。
     在这个例子中同学A就是进程,同学B就是要处理的事件。选择1)为阻塞; 选择2)为非阻塞 ;选择3)为select/poll类型;选择4)为epoll类型
     
     非阻塞I/O:我们可以调用open、read、write这样的I/O操作,并且使这些操作永远不会阻塞。当一个fd设置为非阻塞时,那么进程对这个fd发一个I/O操作,如果有数据就进行处理;如果没有数据就立即出错返回。
/*chap14 14-1 长的非阻塞write p357
filename: chap14_1.c*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
void set_fl(int fd,int flags)
{
   int val;
   if( (val = fcntl(fd,F_GETFL,0)) < 0)
           printf("fcntl error\n");
   val |= flags;
   if(fcntl(fd,F_SETFL,val) < 0)
           printf("fcntl error\n");
}
 void clr_fl(int fd,int flags)
{
   int val;
   if( (val = fcntl(fd,F_GETFL,0)) < 0)
           printf("fcntl error\n");
   val &= ~flags;
   if(fcntl(fd,F_SETFL,val) < 0)
           printf("fcntl error\n");
}

char buf[50];

int main(void)
{

   int ntowrite,nwrite;
   char *ptr;
   ntowrite = read(STDIN_FILENO,buf,sizeof(buf));
   printf("read %d bytes\n",ntowrite);
   /*set non-block*/
  // fcntl(STDIN_FILENO,F_SETFL,(fcntl(STDIN_FILENO,F_GETFL,0) |= O_NONBLOCK));
   set_fl(STDOUT_FILENO,O_NONBLOCK);
   ptr = buf;
   while(ntowrite > 0){
      nwrite = write(STDOUT_FILENO,ptr,ntowrite);
      printf("write %d \n",nwrite);
      if(nwrite > 0){
          ptr += nwrite;
          ntowrite -= nwrite;
      }
   }/*while*/
   clr_fl(STDOUT_FILENO,O_NONBLOCK);
   exit(0);
}
运行没有得到书上的结果


14.3 记录锁
     记录锁,确切的叫字节范围锁,用于锁定文件中的某一个区域。其作用是:当一个进程正在读或者修改某个文件的某个部分时,可以阻止其他进程修改这个部分。

     记录锁的功能:当一个进程在读或者修改文件的某个部分时,记录锁可以阻止其他进程修改同一文件区。
     记录这个词是一种误用,因为UNIX系统内核根本没有使用文件记录这种概念。更适合的术语是字节范围锁(byte-range locking),因为它锁定的只是文件中的一个区域(也可能是整个文件)。

     1.历史
     2.fcntl记录锁
          
#include <fcntl.h>
int fcntl(int fileds,int cmd,...../*struct flock *flockptr*/);
参数: 对于记录锁,参数cmd是F_SETLK,F_SETLKW,F_GETLK,分别是设置、清除、检查记录锁。
对于记录锁,第三个参数是一个结构体:
 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_GETLK only) */
               ...
           };
成员变量 l_whence,  l_start,  and l_len指定了我们希望加锁的特定字节范围。
为了锁整个文件,我们设置l_start和l_where,使锁的起点在文件起始处,并且说明长度l_len为0.即l_start指定为0,l_whence指定为SEEK_SET。
举例:
ngx_err_t
ngx_trylock_fd(ngx_fd_t fd)
{
    struct flock  fl;

    ngx_memzero(&fl, sizeof(struct flock));
    fl.l_type = F_WRLCK;
    fl.l_whence = SEEK_SET;

     if (fcntl(fd, F_SETLK, &fl) == -1) {
        return ngx_errno;
    }

    return 0;
}


   死锁:
     如果两个进程相互等待对方持有并且锁定的资源时,则这两个进程就处于死锁状态。如果一个进程已经控制了文件中的一个加锁区域,然后它又试图对另一个进程控制的区域加锁,则他就会休眠,这种情况下,有发生死锁的可能性。

3.锁的隐含继承和释放
     关于记录锁的自动继承和释放有三条规则
     1)锁与进程和文件两方面有关。有两重含义:
          1.当一个进程终止时,它所建立的锁全部释放;
          2.任何时候关闭一个描述符时,则该进程通过这一描述符可以引用的文件上的一把锁都被释放(这些锁都是该进程设置的)。这就意味着如果执行下列四步:则在关闭fd2后,在fd1上设置的锁被释放。 多个fd指向同一个文件时,关闭其中一个fd,那么该文件上的所有锁都会被释放。
fd1 = open(pathname,....)
read_lock(fd1,...);
fd2 = dup(fd1); //fd2=open(pathname,.....)
close(fd2);
     2)由fork产生的子进程不继承父进程所设置的锁。动动小脑想想也是,人家记录锁就是为了防止多个进程访问同一个文件区,你父进程上锁了,子进程又上锁,那岂不是父子进程都能访问了。
     3)在执行exec后,新程序可以继承原执行程序的锁。但是注意,如果对一个文件描述符设置了close-on-exec标识,那么当作为exec的一部分关闭该文件描述符时,对相应文件的所有锁都被释放了。


4.FreeBSD的实现
5.在文件尾端加锁
6.建议性锁和强制性锁


14.4 STREAMS 系统V流机制
14.5I/O复用
14.6异步I/O
14.7readv和writev函数

   
  结构体struct iovec{
                      void * iov_base;
                      size_t iov_len;
               }
 writev 与readv

函数原型 
#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);
作用:在多个buffer中读取或写入
返回值:传输字节数,出错时返回-1
描述: 将多个数据存储在一起,将驻留在两个或更多的不连接的缓冲区中的数据一次写出去。
             使用writev,可以指定一系列的缓冲区,收集要写的数据,使可以安排数据保存在多个缓冲区中,然后同时写出去,从而避免出现Nagle和延迟ACK算法的相互影响。
示例:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/uio.h>
int main(void)
{
  struct iovec iov[3];
  int len;
  char * buf1 = "123";
  char * buf2 = "456";
  char * buf3 = "7890";
len = 0;
  iov[0].iov_base = buf1;
  iov[0].iov_len = strlen(buf1);

  iov[1].iov_base = buf2;
  iov[1].iov_len = strlen(buf2);

iov[2].iov_base = buf3;
  iov[2].iov_len = strlen(buf3);
  
len = len + iov[0].iov_len + iov[1].iov_len + iov[2].iov_len;

  if( (writev(STDOUT_FILENO,iov,3)) == -1){
      printf("writev error\n");
  }
  return 0;
}

/*测试用例2:这个测试用例是APUE chap17.5 open服务器版本1中关于writev的部分 */
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/uio.h>

#define CL_OPEN "open"
int main(void)
{
  char * buf2 ="456";
  char * buf3 ="789";
  struct iovec iov[3];
  iov[0].iov_base = CL_OPEN " ";
  iov[0].iov_len = strlen(CL_OPEN) + 1 ;

  iov[1].iov_base = buf2;
  iov[1].iov_len = strlen(buf2);

iov[2].iov_base = buf3;
  iov[2].iov_len = strlen(buf3);
 

  if( (writev(STDOUT_FILENO,iov,3)) == -1){
      printf("writev error\n");
  }
  return 0;
}
运行结果:
open 456789
如果将 iov[0].iov_base = CL_OPEN " "; 变为  iov[0].iov_base = CL_OPEN;
则运行结果为 open456789 


14.8 readn 和writen函数
14.9 存储映射I/O
     存储映射I/O(memory-mapped I/O):使一个磁盘文件与存储空间中的一个缓冲区相映射。当从缓冲区取数据,就相当于读文件中的相应字节;当向缓冲区写数据,则相应字节就自动写入文件。这样就可以在不使用read和write的情况下执行I/O。


你可能感兴趣的:(基础文章2:APUE chap14 高级I/O)