0.序
本文是在学习Nginx中ngx_process_events_and_timers函数中牵涉到的内容。主要学习了
14.2 非阻塞I/O
14.3 记录锁
14.7readv和writev函数
|
其中记录锁用于Nginx中的accept互斥体。
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。