最近由于在我们的文件系统中需要实现posix文件锁,因此研究了下linux内核和gluster的文件锁的实现的代码。主要关注posix文件锁。整理了相关的学习内容,概念方面主要摘自网络和将系统内核的两本书(《linux内核深入理解》和《内核情景分析》)。代码部分参考了书的描述,然后阅读了相关代码。
主要参考网址,请搜索《Linux 2.6 中的文件锁》
1 基本概念
1.1 锁的类型
按读写特性:共享锁(读锁)和排它锁(写锁)
按锁的工作方式:强制锁、劝告锁、共享模式锁、租借锁
1.1.1 劝告锁
内核只提供加锁以及检测文件是否已经加锁的方法,但不参与锁的控制和协调。如果有进程不遵守“游戏规则”,不检查目标文件是否已经由别的进程加了锁就往其中写入数据,那么内核是不会阻拦的。
1.1.2 强制锁
当有系统调用 open()、read() 以及write() 发生的时候,内核都要检查并确保这些系统调用不会违反在所访问文件上加的强制锁约束。Unlink不会受到强制锁的影响。
当进程对文件进行了读或写这样的系统调用时,系统则会检查该文件已经到加强制锁时,会判断文件的状态O_NONBLOCK 标识,如果设置了 O_NONBLOCK,则该进程会出错eagain返回;否则,该进程被阻塞

O_NONBLOCK 标识设置方式是通过fcntl传递参数F_SETFL。
系统支持强制锁的配置:
- Mount –o mand设置文件系统是否支持强制锁(super_block结构中s_flags设置为0或1)
- 修改要加强制锁的文件的权限:设置 SGID 位,并清除组可执行位。
1.2 锁的释放
- 进程对某个文件拥有的各种锁会在文件对应的文件描述符被关闭时自动清除;
- 进程运行结束后,其所加的各种锁也会自动清除。
1.3 锁的继承
- 由fork产生的子进程不会继承父进程的文件锁;
- 在执行exec之后,新程序可以继承原来程序的文件锁。
2 系统调用
2.1 数据结构
- 系统中所有文件的活动的锁都链接在一个全局的单向链表file_lock_list中,inode->i_flock指向链表中属于本文件的第一个锁;所有阻塞的锁被链接在“阻塞列表”blocked_list中。file_lock_lock保护这两个链表。
- file_lock,linux内核描述的所有类型锁的数据结构
- fl_next: 如果file_lock插入在file_lock_list,指向链表中属于本文件的下一个锁;如果file_lock插入blocked_list,指向产生冲突的当前活动的锁
- fl_link: 把file_lock对象插入到file_lock_list和blocked_list所代表的两个链表之一
- fl_block: 给定的活动锁的所有阻塞的锁构成一个循环链表,活动锁的fl_block指向链表的头部,阻塞锁的fl_block指向链表中相邻的下一个元素。
- fl_wait: 由于与当前的锁产生冲突被挂起的进程被插入到相应冲突锁的fl_wait指向等待队列
- fl_owner:struct files_struct * 快速找到进程
- fl_pid
- Fl_file/fl_type/fl_flags/fl_start/fl_end….
2.2 fcntl
Poxis文件锁通过Fcntl系统调用实现: int fcntl (int fd, intcmd, struct flock *lock);
struct flock {
...
short l_type;
short l_whence;
off_t l_start;
off_t l_len;
pid_t l_pid;
...
};
2.2.1 强制锁
F_GETLK/ F_SETLK/ F_SETLKW
-
F_GETLK:获取相应类型的锁,如果存在,返回相应锁的信息(包括l_pid设置为加锁进程的PID);如果不存在,l_type被赋值为F_UNLCK;
fcntl_getlk
- flock_to_posix_lock
- vfs_test_lock:filp->f_op->lock或者posix_test_lock(遍历inode上的所有Posix锁,判断是否冲突)
- 如果存在冲突的锁(file_lock.fl_type!= F_UNLCK),posix_lock_to_flock
2. F_SETLK:设置锁(l_type为F_RDLCK, F_WRLCK或F_UNLCK),如果文件已经到其它锁锁定,直接返回eagain;
F_SETLKW:设置锁,如果文件已经到其它锁锁定,阻塞,进入睡眠;
fcntl_setlk
- 如果要加读锁,文件必须被读模式打开,如果要开写锁,文件必须写模式打开
- flock_to_posix_lock
- if (cmd == F_SETLKW) 设置file_lock->fl_flags |= FL_SLEEP;
- vfs_lock_file 如果文件系统是否提供lock接口,调用;否则调用posix_lock_file
- wait_event_interruptible(fl->fl_wait, !fl->fl_next);
2.2.2 租借锁
F_SETLEASE 和 F_GETLEASE 可以实现租约锁,具体参数(F_RDLCK、F_WRLCK、F_UNLCK)
某个进程可能会对文件执行其他一些系统调用(比如 OPEN() 或者 TRUNCATE()),如果这些系统调用与该文件上由 F_SETLEASE 所设置的租借锁相冲突,内核就会阻塞这个系统调用;同时,内核会给拥有这个租借锁的进程发信号。拥有此租借锁的进程会对该信号进行处理,可能会删除或缩短这个租借锁。
如果拥有租借锁的进程不能在给定时间内完成上述操作,那么系统会强制完成。通过F_SETLEASE 命令将 arg 参数指定为 F_UNLCK 就可以删除这个租借锁。
即使被阻塞的系统调用因为某些原因被解除阻塞,但是对租借锁的租约进行减短或删除这个过程还是会执行的。
租借锁也只能对整个文件生效,而无法实现记录级的加锁。
2.3 Open
open_namei()
如果open的标志包括trunc,会通过locks_mandatory_locked文件是否加了poxis锁,MANDATORY_LOCK判断文件是否支持强制锁,然后遍历所有锁,比较其中强制锁的owner是否是当前,如果不是,返回错误eagain
2.4 close
filp_close()调用locks_remove_posix
- locks_remove_posix (struct file *filp, fl_owner_t owner)
owner is the POSIX threadID. We use the files pointer for this.(files_struct)
- 首先生成file_lock对象,fl_type类型为F_UNLCK调用,lock.fl_owner= owner; lock.fl_pid = current->tgid;
- 然后调用vfs_lock_file如果文件系统是否提供lock接口,调用;否则调用posix_lock_file
2.5 Read/write/truncate
locks_mandatory_area
- 构建file_lock
fl.fl_flags= FL_POSIX | FL_ACCESS;
if (filp && !(filp->f_flags & O_NONBLOCK))
fl.fl_flags|= FL_SLEEP;
fl.fl_type= (read_write == FLOCK_VERIFY_WRITE) ? F_WRLCK : F_RDLCK;
- __posix_lock_file
- wait_event_interruptible(fl.fl_wait, !fl.fl_next); 判断fl_next(如果产生锁冲突,fl被插入blocked_list,fl_next会被赋值),如果不为空,插入fl_wait的等待队列。
注意:没有调用vfs_lock_file.
2.6 __posix_lock_file
- file_lock_lock加锁
-
遍历所有的lock,如果fl_type不是F_UNLCK,检查是否产生冲突,如果是,判断fl_flags是否设置FL_SLEEP(返回-EAGAIN),是否死锁(-EDEADLK),locks_insert_block插入等待列表。
locks_insert_block:
- 将当前锁的fl_block插入到冲突的锁的fl_block链表的尾部;
- 将当前锁的fl_next设置为冲突锁;
- 将当前锁fl_link插入blocked_list中;
3.如果request->fl_flags & FL_ACCESS,退出
4.从inode上找到相同的own的的第一个锁,然后如果是相同类型的锁,判断是否进行合并;如果是不同类型的锁,交叉部分,新锁总是覆盖旧锁的类型(旧锁的部分要wake_up上面阻塞的锁),然后旧锁可能产生拆分。
5. 由于合并或覆盖或F_UNLCK等原因需要调用locks_delete_lock释放锁。
locks_delete_lock:
- fl->fl_next = NULL;
- list_del_init(&fl->fl_link);
- fl_fasync
- locks_wake_up_blocks:遍历fl_block指向的链表上的锁,reset fl_link和fl_block,wake_up(&waiter->fl_wait); 通知阻塞的进程
- locks_free_lock:释放内存空间
6. 把可能产生的拆分和新的file_lock插入(locks_insert_lock)全局锁链表和索引节点链表中
locks_insert_lock:
- 将当前锁的fl_link插入到file_lock_list中;
- 将当前锁的fl_next设置为通过inode的i_flock遍历的插入节点位置pos,同时将*pos设置为当前锁;
7. file_lock_lock解锁