14.7 文件锁

Linux内核提供了基于文件的互斥锁,而Nginx框架封装了3个方法,提供给Nginx模块使用文件互斥锁来保护共享数据。下面首先介绍一下这种基于文件的互斥锁是如何使用的,其实很简单,通过fcntl方法就可以实现。


int fcntl(int fd,int cmd,struct flock*lock);


这个方法接收3个参数,其中参数fd是打开的文件句柄,参数cmd表示执行的锁操作,参数lock描述了这个锁的信息。下面依次说明这3个参数。

参数fd必须是已经成功打开的文件句柄。实际上,nginx.conf文件中的lock_file配置项指定的文件路径,就是用于文件互斥锁的,这个文件被打开后得到的句柄,将会作为fd参数传递给fcntl方法,提供一种锁机制。

这里的cmd参数在Nginx中只会有两个值:F_SETLK和F_SETLKW,它们都表示试图获得互斥锁,但使用F_SETLK时如果互斥锁已经被其他进程占用,fcntl方法不会等待其他进程释放锁且自己拿到锁后才返回,而是立即返回获取互斥锁失败;使用F_SETLKW时则不同,锁被占用后fcntl方法会一直等待,在其他进程没有释放锁时,当前进程就会阻塞在fcntl方法中,这种阻塞会导致当前进程由可执行状态转为睡眠状态。

参数lock的类型是flock结构体,它有5个成员是需要用户关心的,如下所示。


struct flock

{

……

//锁类型,取值为F_RDLCK、F_WRLCK或F_UNLCK

short l_type;

//锁区域起始地址的相对位置

short l_whence;

//锁区域起始地址偏移量,同l_whence共同确定锁区域

long l_start;

//锁的长度,0表示锁至文件末

long l_len;

//拥有锁的进程ID

long l_pid;

……

};


从flock结构体中可以看出,文件锁的功能绝不仅仅局限于普通的互斥锁,它还可以锁住文件中的部分内容。但Nginx封装的文件锁仅用于保护代码段的顺序执行(例如,在进行负载均衡时,使用互斥锁保证同一时刻仅有一个worker进程可以处理新的TCP连接),使用方式要简单得多:一个lock_file文件对应一个全局互斥锁,而且它对master进程或者worker进程都生效。因此,对于l_start、l_len、l_pid,都填为0,而l_whence则填为SEEK_SET,只需要这个文件提供一个锁。l_type的值则取决于用户是想实现阻塞睡眠锁还是想实现非阻塞不会睡眠的锁。

对于文件锁,Nginx封装了3个方法:ngx_trylock_fd实现了不会阻塞进程、不会使得进程进入睡眠状态的互斥锁;ngx_lock_fd提供的互斥锁在锁已经被其他进程拿到时将会导致当前进程进入睡眠状态,直到顺利拿到这个锁后,当前进程才会被Linux内核重新调度,所以它是阻塞操作;ngx_unlock_fd用于释放互斥锁。下面我们一一列举它们的源代码。


ngx_err_t ngx_trylock_fd(ngx_fd_t fd)

{

struct flock fl;

//这个文件锁并不用于锁文件中的内容,填充为0

fl.l_start=0;

fl.l_len=0;

fl.l_pid=0;

//F_WRLCK意味着不会导致进程睡眠

fl.l_type=F_WRLCK;

fl.l_whence=SEEK_SET;

//获取fd对应的互斥锁,如果返回-1,则这时的ngx_errno将保存错误码

if(fcntl(fd,F_SETLK,&fl)==-1){

return ngx_errno;

}

return 0;

}


使用ngx_trylock_fd方法获取互斥锁成功时会返回0,否则返回的其实是errno错误码,而这个错误码为NGX_EAGAIN或者NGX_EACCESS时表示当前没有拿到互斥锁,否则可以认为fcntl执行错误。

ngx_lock_fd方法将会阻塞进程的执行,使用时需要非常谨慎,它可能会导致worker进程宁可睡眠也不处理其他正常请求,如下所示。


ngx_err_t ngx_lock_fd(ngx_fd_t fd)

{

struct flock fl;

fl.l_start=0;

fl.l_len=0;

fl.l_pid=0;

//F_WRLCK会导致进程睡眠

fl.l_type=F_WRLCK;

fl.l_whence=SEEK_SET;

//如果返回-1,则表示fcntl执行错误。一旦返回0,表示成功地拿到了锁

if(fcntl(fd,F_SETLKW,&fl)==-1){

return ngx_errno;

}

return 0;

}


只要ngx_lock_fd方法返回0,就表示成功地拿到了互斥锁,否则就是加锁操作出现错误。ngx_unlock_fd方法用于释放当前进程已经拿到的互斥锁,如下所示。


ngx_err_t ngx_unlock_fd(ngx_fd_t fd)

{

struct flock fl;

fl.l_start=0;

fl.l_len=0;

fl.l_pid=0;

//F_UNLCK表示将要释放锁

fl.l_type=F_UNLCK;

fl.l_whence=SEEK_SET;

//返回0表示成功

if(fcntl(fd,F_SETLK,&fl)==-1){

return ngx_errno;

}

return 0;

}


当关闭fd句柄对应的文件时,当前进程将自动释放已经拿到的锁。