Linux内核POSIX文件锁机制深度解析
前言
在操作系统的并发编程世界中,文件锁是确保数据一致性和完整性的关键机制。Linux内核的POSIX文件锁系统是一个精心设计的复杂子系统,它不仅要处理基本的锁获取和释放,还要应对锁冲突、死锁检测、锁合并与分裂等复杂场景。本文将通过深入分析Linux 2.6.10内核中POSIX文件锁的核心实现,揭示其精妙的数据结构设计和高效的算法逻辑。
文件锁机制看似简单,实则蕴含着深刻的设计哲学。从锁冲突检测到死锁预防,从锁合并优化到资源清理,每一个环节都体现了Linux内核开发者对性能、安全性和可靠性的极致追求。通过理解这些底层机制,我们不仅能更好地使用文件锁API,还能从中学习到解决并发问题的通用设计模式
file_lock数据结构关系和作用总结
Linux内核中的文件锁系统通过多个精心设计的链表和指针构建了一个复杂但高效的关系网络,以下是各个关键数据结构的作用和相互关系:
全局文件锁管理系统
file_lock_list(全局文件锁链表)
- 作用:系统范围内跟踪所有活跃的文件锁
- 连接方式:通过
file_lock::fl_link连接 - 用途:
- 系统级锁监控和管理
- 资源统计和调试信息
blocked_list(全局阻塞锁链表)
- 作用:跟踪所有被阻塞的锁请求
- 连接方式:冲突锁通过
file_lock::fl_link连接到此链表 - 用途:
- 死锁检测的核心数据结构
- 系统级阻塞状态监控
文件级锁组织
inode::i_flock(文件锁链表)
- 作用:组织属于同一文件的所有锁
- 连接方式:无冲突锁通过
file_lock::fl_next连接 - 用途:
- 锁合并和分裂操作的基础
- 文件关闭时批量释放所有锁
锁间依赖关系
file_lock::fl_block(阻塞列表)
- 作用:记录所有等待当前锁的其他锁
- 连接方式:等待者通过自身的
file_lock::fl_block节点连接到阻塞者的fl_block列表 - 用途:
- 锁释放时快速找到所有等待者
- 实现锁的依赖关系管理
file_lock::fl_next(等待关系指针)
- 作用:记录锁等待关系
- 取值:
- 无冲突时:指向同一文件链表中的下一个锁
- 有冲突时:指向正在等待的锁
- 用途:
- 死锁检测时遍历等待链
- 维护文件锁链表结构
进程同步机制
file_lock::fl_wait(等待队列头结点)
- 作用:进程等待锁的队列
- 用途:
- 锁冲突时进程睡眠等待
- 锁释放时唤醒等待进程
- 实现阻塞式锁获取
数据结构关系全景图
全局层面:
file_lock_list → 锁A.fl_link → 锁B.fl_link → 锁C.fl_link → ...
blocked_list → 阻塞锁X.fl_link → 阻塞锁Y.fl_link → ...文件层面:
inode->i_flock → 锁1.fl_next → 锁2.fl_next → 锁3.fl_next → ...锁间依赖:
锁X.fl_block → 等待者A.fl_block → 等待者B.fl_block → ...
等待者A.fl_next = 锁X
等待者B.fl_next = 锁X
各数据结构的协同工作流程
-
锁申请阶段:
- 遍历
inode->i_flock检查冲突 - 如有冲突,通过
fl_next建立等待关系,加入blocked_list - 如无冲突,通过
fl_next插入文件链表,通过fl_link加入file_lock_list
- 遍历
-
锁释放阶段:
- 遍历
fl_block列表唤醒所有等待者 - 从
inode->i_flock和file_lock_list中移除
- 遍历
-
死锁检测阶段:
- 通过
fl_next指针遍历等待链 - 检查
blocked_list中的循环依赖
- 通过
POSIX文件锁的核心处理函数__posix_lock_file
static int __posix_lock_file(struct inode *inode, struct file_lock *request)
{struct file_lock *fl;struct file_lock *new_fl, *new_fl2;struct file_lock *left = NULL;struct file_lock *right = NULL;struct file_lock **before;int error, added = 0;/** We may need two file_lock structures for this operation,* so we get them in advance to avoid races.*/new_fl = locks_alloc_lock();new_fl2 = locks_alloc_lock();lock_kernel();if (request->fl_type != F_UNLCK) {for_each_lock(inode, before) {struct file_lock *fl = *before;if (!IS_POSIX(fl))continue;if (!posix_locks_conflict(request, fl))continue;error = -EAGAIN;if (!(request->fl_flags & FL_SLEEP))goto out;error = -EDEADLK;if (posix_locks_deadlock(request, fl))goto out;error = -EAGAIN;locks_insert_block(fl, request);goto out;}}/* If we're just looking for a conflict, we're done. */error = 0;if (request->fl_flags & FL_ACCESS)goto out;error = -ENOLCK; /* "no luck" */if (!(new_fl && new_fl2))goto out;/** We've allocated the new locks in advance, so there are no* errors possible (and no blocking operations) from here on.* * Find the first old lock with the same owner as the new lock.*/before = &inode->i_flock;/* First skip locks owned by other processes. */while ((fl = *before) && (!IS_POSIX(fl) ||!posix_same_owner(request, fl))) {before = &fl->fl_next;}/* Process locks with this owner. */while ((fl = *before) && posix_same_owner(request, fl)) {/* Detect adjacent or overlapping regions (if same lock type)*/if (request->fl_type == fl->fl_type) {if (fl->fl_end < request->fl_start - 1)goto next_lock;/* If the next lock in the list has entirely bigger* addresses than the new one, insert the lock here.*/if (fl->fl_start > request->fl_end + 1)break;/* If we come here, the new and old lock are of the* same type and adjacent or overlapping. Make one* lock yielding from the lower start address of both* locks to the higher end address.*/if (fl->fl_start > request->fl_start)fl->fl_start = request->fl_start;elserequest->fl_start = fl->fl_start;if (fl->fl_end < request->fl_end)fl->fl_end = request->fl_end;elserequest->fl_end = fl->fl_end;if (added) {locks_delete_lock(before);continue;}request = fl;added = 1;}else {/* Processing for different lock types is a bit* more complex.*/if (fl->fl_end < request->fl_start)goto next_lock;if (fl->fl_start > request->fl_end)break;if (request->fl_type == F_UNLCK)added = 1;if (fl->fl_start < request->fl_start)left = fl;/* If the next lock in the list has a higher end* address than the new one, insert the new one here.*/if (fl->fl_end > request->fl_end) {right = fl;break;}if (fl->fl_start >= request->fl_start) {/* The new lock completely replaces an old* one (This may happen several times).*/if (added) {locks_delete_lock(before);continue;}/* Replace the old lock with the new one.* Wake up anybody waiting for the old one,* as the change in lock type might satisfy* their needs.*/locks_wake_up_blocks(fl);fl->fl_start = request->fl_start;fl->fl_end = request->fl_end;fl->fl_type = request->fl_type;fl->fl_u = request->fl_u;request = fl;added = 1;}}/* Go on to next lock.*/next_lock:before = &fl->fl_next;}error = 0;if (!added) {if (request->fl_type == F_UNLCK)goto out;locks_copy_lock(new_fl, request);locks_insert_lock(before, new_fl);new_fl = NULL;}if (right) {if (left == right) {/* The new lock breaks the old one in two pieces,* so we have to use the second new lock.*/left = new_fl2;new_fl2 = NULL;locks_copy_lock(left, right);locks_insert_lock(before, left);}right->fl_start = request->fl_end + 1;locks_wake_up_blocks(right);}if (left) {left->fl_end = request->fl_start - 1;locks_wake_up_blocks(left);}out:unlock_kernel();/** Free any unused locks.*/if (new_fl)locks_free_lock(new_fl);if (new_fl2)locks_free_lock(new_fl2);return error;
}
函数功能概述
变量声明和锁分配
struct file_lock *fl;
struct file_lock *new_fl, *new_fl2;
struct file_lock *left = NULL;
struct file_lock *right = NULL;
struct file_lock **before;
int error, added = 0;/** We may need two file_lock structures for this operation,* so we get them in advance to avoid races.*/
new_fl = locks_alloc_lock();
new_fl2 = locks_alloc_lock();
fl: 遍历锁链表时的当前锁new_fl,new_fl2: 预分配的新锁结构,用于锁合并和分裂操作left,right: 当新锁分裂现有锁时,左右部分的锁before: 指向锁链表指针的指针,用于插入操作added: 标记是否已将请求锁合并到现有锁中- 锁预分配: 提前分配两个锁结构,避免在持有内核锁时进行内存分配
获取内核锁和冲突检查
lock_kernel();
if (request->fl_type != F_UNLCK) {for_each_lock(inode, before) {struct file_lock *fl = *before;if (!IS_POSIX(fl))continue;if (!posix_locks_conflict(request, fl))continue;error = -EAGAIN;if (!(request->fl_flags & FL_SLEEP))goto out;error = -EDEADLK;if (posix_locks_deadlock(request, fl))goto out;error = -EAGAIN;locks_insert_block(fl, request);goto out;}
}
lock_kernel(): 获取大内核锁,保护临界区- 跳过解锁请求: 如果是解锁操作(F_UNLCK),不检查冲突
- 遍历所有锁:
for_each_lock宏遍历inode的锁链表 - 过滤POSIX锁: 只处理POSIX风格的锁
- 冲突检测:
posix_locks_conflict检查锁类型和范围是否冲突 - 非阻塞处理: 如果请求不允许睡眠(FL_SLEEP),立即返回-EAGAIN
- 死锁检测:
posix_locks_deadlock检查是否会产生死锁 - 插入阻塞列表: 将请求锁插入到冲突锁的阻塞列表中
访问检查和资源验证
/* If we're just looking for a conflict, we're done. */
error = 0;
if (request->fl_flags & FL_ACCESS)goto out;error = -ENOLCK; /* "no luck" */
if (!(new_fl && new_fl2))goto out;
- FL_ACCESS标志: 如果只是访问检查(如强制锁定验证),直接返回成功
- 资源检查: 确保两个预分配的锁结构都成功分配,否则返回-ENOLCK
查找相同所有者的锁
before = &inode->i_flock;/* First skip locks owned by other processes. */
while ((fl = *before) && (!IS_POSIX(fl) ||!posix_same_owner(request, fl))) {before = &fl->fl_next;
}/* Process locks with this owner. */
while ((fl = *before) && posix_same_owner(request, fl)) {
- 初始化指针:
before指向锁链表头指针 - 跳过其他进程的锁: 遍历直到找到相同所有者的POSIX锁
- 处理同所有者锁: 只处理相同进程拥有的锁,用于锁合并优化
相同类型锁的合并处理
- 重叠锁的合并(范围有交集)
- 相邻锁的合并(范围紧挨着)
- 包含锁的合并(一个锁完全包含另一个)
范围关系判断
if (fl->fl_end < request->fl_start - 1)goto next_lock;
if (fl->fl_start > request->fl_end + 1)break;
-
当前锁在请求锁的左边且不相邻,继续检查下一个锁
-
当前锁在请求锁的右边且不相邻,终止循环
- 为什么用
break而不是goto next_lock? - 因为链表是按起始位置排序的
- 如果当前锁已经在请求锁的右边,后续的锁都在更右边
- 不需要继续遍历,直接终止循环
- 为什么用
锁范围合并
/* 合并相邻或重叠的锁 */
if (fl->fl_start > request->fl_start)fl->fl_start = request->fl_start;
elserequest->fl_start = fl->fl_start;
if (fl->fl_end < request->fl_end)fl->fl_end = request->fl_end;
elserequest->fl_end = fl->fl_end;
- 这个逻辑计算两个锁的最小并集
new_start = min(fl_start, request_start);new_end = max(fl_end, request_end);
重复合并处理
if (added) {locks_delete_lock(before);continue;
}
request = fl;
added = 1;
- 处理连续合并的情况,一次请求需要合并多个现有锁
added = 1;:这里标记已完成添加,后续不需要单独提供函数locks_insert_lock添加
// 初始链表: 锁A[0-10] → 锁B[15-25] → 锁C[30-40] → NULL
// 请求锁X: [5-35]// 处理过程:
// 第一次迭代(锁A):
// - 合并锁A和锁X: 锁A变为[0-35]
// - added = 0 → request = 锁A, added = 1// 第二次迭代(锁B):
// - 锁B[15-25] 在 锁A[0-35] 范围内
// - added = 1 → 删除锁B, continue// 第三次迭代(锁C):
// - 锁C[30-40] 与 锁A[0-35] 重叠
// - 合并: 锁A变为[0-40]
// - added = 1 → 删除锁C, continue// 最终结果: 锁A[0-40] → NULL
不同类型锁的复杂处理
- 读锁 vs 写锁的冲突处理
- 解锁操作的特殊处理
- 锁分裂场景(一个锁被另一个锁分成几部分)
- 锁替换场景(新锁完全覆盖旧锁)
范围关系判断
if (fl->fl_end < request->fl_start)goto next_lock;
if (fl->fl_start > request->fl_end)break;
-
当前锁在请求锁的左边,没有重叠,继续检查下一个锁
-
当前锁在请求锁的右边,没有重叠,终止循环
-
这里没有使用
+-1,因为不同类型锁即使相邻也不合并
解锁操作处理
if (request->fl_type == F_UNLCK)added = 1;
- 解锁请求不需要创建新锁
- 只需要标记
added = 1表示"已处理" - 后续会根据重叠情况删除或修改现有锁
锁分裂记录
if (fl->fl_start < request->fl_start)left = fl;
if (fl->fl_end > request->fl_end) {right = fl;break;
}
- 场景1:解锁操作 (F_UNLCK)
现有锁: [10, 40] (写锁)
解锁请求: [20, 30] (F_UNLCK)分裂后:
左部分: [10, 19] (写锁)
右部分: [31, 40] (写锁)
中间: [20, 30] (已解锁)
- 场景2:相同所有者的锁类型变更
现有锁: [10, 40] (读锁) - 进程A
新请求: [20, 30] (写锁) - 进程A(锁升级)分裂后:
左部分: [10, 19] (读锁) - 进程A
中间: [20, 30] (写锁) - 进程A
右部分: [31, 40] (读锁) - 进程A
- 因为前面进行了锁合并,所以自然会出现某一部分的锁进行了变更导致的锁分裂
完全覆盖处理
if (fl->fl_start >= request->fl_start) {if (added) {locks_delete_lock(before);continue;}locks_wake_up_blocks(fl);fl->fl_start = request->fl_start;fl->fl_end = request->fl_end;fl->fl_type = request->fl_type;fl->fl_u = request->fl_u;request = fl;added = 1;
}
-
当前锁的开始位置在新锁开始位置的右边或相同,并且前面的判断确保当前锁的结束位置在新锁开始位置的左边或相同
-
场景1:新锁完全覆盖现有锁
当前锁: [20, 30] (读锁)
新请求: [10, 40] (写锁)条件: 20 >= 10 → true
执行: 用新锁完全替换现有锁
重复合并处理
if (added) {locks_delete_lock(before);continue;
}
- 如果已经合并过 (
added = 1),删除当前多余的锁 - 继续处理下一个锁
- 这里是后面还有其他被新锁完全覆盖的旧锁
锁替换
locks_wake_up_blocks(fl);
fl->fl_start = request->fl_start;
fl->fl_end = request->fl_end;
fl->fl_type = request->fl_type;
fl->fl_u = request->fl_u;
request = fl;
added = 1;
locks_wake_up_blocks(fl): 唤醒所有等待旧锁的进程- 更新锁范围:
fl->fl_start = request->fl_startfl->fl_end = request->fl_end
- 改变锁类型:
fl->fl_type = request->fl_type - 复制用户数据:
fl->fl_u = request->fl_u - 重用锁结构:
request = fl(使用现有锁代替新请求) - 标记已处理:
added = 1
- 场景1:锁分裂
// 现有锁: [10, 40] (写锁)
// 新请求: [20, 30] (读锁)// 处理过程:
// - left = 现有锁 (因为10 < 20)
// - right = 现有锁 (因为40 > 30) → break
// 后续会创建:
// 左锁: [10, 19] (写锁)
// 新锁: [20, 30] (读锁)
// 右锁: [31, 40] (写锁)
- 场景2:锁替换
// 现有锁: [20, 30] (读锁)
// 新请求: [10, 40] (写锁)// 处理过程:
// - 20 >= 10 → true
// - 唤醒等待现有锁的进程
// - 更新现有锁: [10, 40] (写锁)
// - 重用现有锁结构
- 场景3:部分重叠
// 现有锁: [10, 25] (读锁)
// 新请求: [20, 35] (写锁)// 处理过程:
// - left = 现有锁 (因为10 < 20)
// - 没有right (因为25 < 35)// 最终结果:
// 左锁: [10, 19] (读锁) ← 由left记录
// 新锁: [20, 35] (写锁) ← 替换后的锁
循环结束和基础检查
next_lock:before = &fl->fl_next;
}error = 0;
if (!added) {if (request->fl_type == F_UNLCK)goto out;locks_copy_lock(new_fl, request);locks_insert_lock(before, new_fl);new_fl = NULL;
}
- 循环结束后的状态
before指针:现在指向链表中合适的位置,新锁应该插入到这里added标志:表示是否已经将请求合并到现有锁中
新锁插入逻辑
if (!added) { // 如果没有合并到现有锁if (request->fl_type == F_UNLCK) // 如果是解锁操作goto out; // 直接退出(不需要插入新锁)locks_copy_lock(new_fl, request); // 复制锁信息到预分配的锁结构locks_insert_lock(before, new_fl); // 将新锁插入到链表中的before位置new_fl = NULL; // 标记这个锁结构已使用
}
右半部分锁处理
if (right) {if (left == right) {left = new_fl2;new_fl2 = NULL;locks_copy_lock(left, right);locks_insert_lock(before, left);}right->fl_start = request->fl_end + 1;locks_wake_up_blocks(right);
}
- 当新锁在现有锁中间时,需要处理右半部分:
现有锁: [10, 40] (被分裂)
新请求: [20, 30]
分裂后:
左部分: [10, 19]
右部分: [31, 40] ← 这就是right
- 特殊处理:左右相同
if (left == right) {// 这意味着同一个锁既提供了左半部分又提供了右半部分// 需要创建右半部分的新锁left = new_fl2; // 使用第二个预分配锁new_fl2 = NULL; // 标记已使用locks_copy_lock(left, right); // 复制原锁信息locks_insert_lock(before, left); // 插入左半部分锁
}
- 调整右半部分范围
right->fl_start = request->fl_end + 1;
locks_wake_up_blocks(right);
-
调整范围:右半部分从新锁结束位置+1开始
-
唤醒等待者:范围改变后唤醒可能正在等待这个锁的进程
-
左半部分锁处理
if (left) {left->fl_end = request->fl_start - 1;locks_wake_up_blocks(left);
}
- 调整左半部分范围
left->fl_end = request->fl_start - 1;
locks_wake_up_blocks(left);
- 调整范围:左半部分结束于新锁开始位置-1
- 唤醒等待者:范围改变后唤醒等待进程
清理和返回
out:
unlock_kernel();
if (new_fl)locks_free_lock(new_fl);
if (new_fl2)locks_free_lock(new_fl2);
return error;
- 释放内核锁:
unlock_kernel() - 释放未使用的锁: 释放预分配但未使用的锁结构
- 返回错误码: 返回操作结果
函数功能总结
主要功能
__posix_lock_file 是POSIX文件锁的核心处理函数,负责:
- 锁冲突检测 - 检查新锁请求与现有锁的冲突
- 锁操作处理 - 处理加锁(F_RDLCK/F_WRLCK)和解锁(F_UNLCK)操作
- 锁优化合并 - 合并相邻或重叠的同类型锁
- 锁分裂处理 - 处理不同类型锁的范围重叠
- 死锁预防 - 检测可能的死锁情况
文件锁冲突检测posix_locks_conflict
static int posix_locks_conflict(struct file_lock *caller_fl, struct file_lock *sys_fl)
{/* POSIX locks owned by the same process do not conflict with* each other.*/if (!IS_POSIX(sys_fl) || posix_same_owner(caller_fl, sys_fl))return (0);/* Check whether they overlap */if (!locks_overlap(caller_fl, sys_fl))return 0;return (locks_conflict(caller_fl, sys_fl));
}
static inline int locks_overlap(struct file_lock *fl1, struct file_lock *fl2)
{return ((fl1->fl_end >= fl2->fl_start) &&(fl2->fl_end >= fl1->fl_start));
}
static int locks_conflict(struct file_lock *caller_fl, struct file_lock *sys_fl)
{if (sys_fl->fl_type == F_WRLCK)return 1;if (caller_fl->fl_type == F_WRLCK)return 1;return 0;
}
posix_locks_conflict 函数
相同所有者检查
if (!IS_POSIX(sys_fl) || posix_same_owner(caller_fl, sys_fl))return (0);
!IS_POSIX(sys_fl): 如果系统锁不是POSIX风格的锁,它们不会冲突- POSIX锁与其他类型锁(如FLOCK锁)可以共存
posix_same_owner(caller_fl, sys_fl): 如果两个锁属于同一个进程,不冲突- 同一个进程可以拥有多个读锁,或者在同一区域有读锁和写锁
- 返回0: 表示没有冲突
范围重叠检查
/* Check whether they overlap */
if (!locks_overlap(caller_fl, sys_fl))return 0;
locks_overlap: 调用辅助函数检查两个锁的范围是否重叠!locks_overlap: 如果锁范围不重叠,肯定没有冲突,返回0
类型冲突检查
return (locks_conflict(caller_fl, sys_fl));
- 如果锁属于不同进程且范围重叠,调用
locks_conflict检查锁类型是否冲突 - 返回最终的冲突检测结果
locks_overlap 函数
static inline int locks_overlap(struct file_lock *fl1, struct file_lock *fl2)
{return ((fl1->fl_end >= fl2->fl_start) &&(fl2->fl_end >= fl1->fl_start));
}
- 条件1:
fl1->fl_end >= fl2->fl_start- 锁1的结束位置 >= 锁2的开始位置
- 锁1的右边界在锁2的左边界的右边
- 条件2:
fl2->fl_end >= fl1->fl_start- 锁2的结束位置 >= 锁1的开始位置
- 锁2的右边界在锁1的左边界的右边
locks_conflict 函数
static int locks_conflict(struct file_lock *caller_fl, struct file_lock *sys_fl)
{if (sys_fl->fl_type == F_WRLCK)return 1;if (caller_fl->fl_type == F_WRLCK)return 1;return 0;
}
系统锁是写锁
if (sys_fl->fl_type == F_WRLCK)return 1;
- 如果已存在的系统锁是写锁(F_WRLCK),任何新的锁请求都会冲突
- 写锁是排他性的,不允许其他任何读锁或写锁
调用者请求写锁
if (caller_fl->fl_type == F_WRLCK)return 1;
- 如果新请求的锁是写锁(F_WRLCK),与任何现有锁都会冲突
- 写锁需要独占访问,不允许有其他读锁或写锁
读锁与读锁
return 0;
- 只有当两个锁都是**读锁(F_RDLCK)**时才不冲突
- 多个进程可以同时持有同一区域的读锁
函数功能总结
posix_locks_conflict 功能
主要目的: 综合判断两个POSIX文件锁是否冲突
检测层次:
- 锁类型过滤 - 只处理POSIX锁之间的冲突
- 所有者检查 - 相同进程的锁不冲突
- 范围检查 - 不重叠的锁不冲突
- 类型冲突检查 - 检查读/写锁的兼容性
locks_overlap 功能
主要目的: 精确判断两个锁的范围是否重叠
算法原理:
- 检查两个区间
[start1, end1]和[start2, end2]是否有交集 - 返回布尔值表示是否重叠
locks_conflict 功能
主要目的: 基于锁类型的冲突检测
冲突规则:
- 写锁 vs 任何锁 = 冲突
- 任何锁 vs 写锁 = 冲突
- 读锁 vs 读锁 = 不冲突
死锁检测函数posix_locks_deadlock
int posix_locks_deadlock(struct file_lock *caller_fl,struct file_lock *block_fl)
{struct list_head *tmp;next_task:if (posix_same_owner(caller_fl, block_fl))return 1;list_for_each(tmp, &blocked_list) {struct file_lock *fl = list_entry(tmp, struct file_lock, fl_link);if (posix_same_owner(fl, block_fl)) {fl = fl->fl_next;block_fl = fl;goto next_task;}}return 0;
}
函数功能详解
函数声明和变量定义
int posix_locks_deadlock(struct file_lock *caller_fl,struct file_lock *block_fl)
{struct list_head *tmp;
caller_fl: 当前请求锁的进程持有的锁(请求者)block_fl: 阻塞当前请求的系统锁(被等待的锁)tmp: 用于遍历链表的临时指针
直接死锁检测
next_task:if (posix_same_owner(caller_fl, block_fl))return 1;
posix_same_owner: 检查两个锁是否属于同一个进程- 返回1: 如果属于同一个进程,表示发现死锁
间接死锁检测
list_for_each(tmp, &blocked_list) {struct file_lock *fl = list_entry(tmp, struct file_lock, fl_link);if (posix_same_owner(fl, block_fl)) {fl = fl->fl_next;block_fl = fl;goto next_task;}}
-
&blocked_list: 全局的阻塞锁链表,包含所有正在等待锁的请求 -
list_for_each: 遍历链表中的每个元素 -
list_entry: 从链表节点获取包含它的 file_lock 结构 -
fl_link: file_lock 结构中用于连接到 blocked_list 的链表节点 -
检查当前遍历的锁
fl是否与阻塞锁block_fl属于同一个进程 -
如果属于同一个进程,说明存在依赖关系链
更新阻塞锁并递归检测
fl = fl->fl_next: 获取这个锁正在等待的下一个锁block_fl = fl: 将阻塞锁更新为新的等待目标goto next_task: 跳回开始,用新的阻塞锁重新检测死锁
返回无死锁
return 0;
}
- 如果遍历完整个阻塞列表都没有发现死锁,返回0表示安全
死锁检测算法详解
算法工作原理
- 从当前请求开始:
caller_fl → block_fl - 查找阻塞链:
block_fl → next_waiting_lock - 检查循环:如果链条回到
caller_fl的拥有者,就是死锁
函数功能总结
posix_locks_deadlock 函数用于检测POSIX文件锁请求是否会导致死锁
检测算法
- 直接检测:检查请求锁的进程是否正在等待自己持有的锁
- 间接检测:通过阻塞链表查找等待依赖链中的循环
- 深度优先搜索:沿着等待链递归检测,直到发现循环或链结束
建立锁之间的阻塞等待关系locks_insert_block
static void locks_insert_block(struct file_lock *blocker, struct file_lock *waiter)
{if (!list_empty(&waiter->fl_block)) {printk(KERN_ERR "locks_insert_block: removing duplicated lock ""(pid=%d %Ld-%Ld type=%d)\n", waiter->fl_pid,waiter->fl_start, waiter->fl_end, waiter->fl_type);__locks_delete_block(waiter);}list_add_tail(&waiter->fl_block, &blocker->fl_block);waiter->fl_next = blocker;if (IS_POSIX(blocker))list_add(&waiter->fl_link, &blocked_list);
}
static inline void __locks_delete_block(struct file_lock *waiter)
{list_del_init(&waiter->fl_block);list_del_init(&waiter->fl_link);waiter->fl_next = NULL;
}
locks_insert_block 函数
函数声明和参数
static void locks_insert_block(struct file_lock *blocker, struct file_lock *waiter)
blocker: 阻塞其他锁的锁(已经持有资源的锁)waiter: 等待资源的锁(被阻塞的锁)
重复插入检查
if (!list_empty(&waiter->fl_block)) {printk(KERN_ERR "locks_insert_block: removing duplicated lock ""(pid=%d %Ld-%Ld type=%d)\n", waiter->fl_pid,waiter->fl_start, waiter->fl_end, waiter->fl_type);__locks_delete_block(waiter);
}
!list_empty(&waiter->fl_block): 检查等待锁是否已经在某个阻塞列表中fl_block链表用于连接被同一个锁阻塞的所有等待者- 如果不为空,说明这个锁已经存在于某个阻塞关系中
- 错误打印: 输出内核错误信息,包括:
waiter->fl_pid: 等待锁的进程IDwaiter->fl_start和waiter->fl_end: 锁的范围waiter->fl_type: 锁类型(读锁/写锁)
__locks_delete_block(waiter): 从之前的阻塞关系中移除这个等待锁
添加到阻塞列表
list_add_tail(&waiter->fl_block, &blocker->fl_block);
&blocker->fl_block: 阻塞锁的阻塞列表头&waiter->fl_block: 等待锁的链表节点list_add_tail: 将等待锁添加到阻塞锁的阻塞列表的尾部- 效果: 现在
waiter出现在blocker的阻塞列表中
设置等待关系
waiter->fl_next = blocker;
waiter->fl_next = blocker: 建立等待关系指针- 表示
waiter正在等待blocker释放 - 这个指针用于死锁检测时遍历等待链
添加到全局阻塞列表
if (IS_POSIX(blocker))list_add(&waiter->fl_link, &blocked_list);
IS_POSIX(blocker): 只有当阻塞锁是POSIX类型时才执行&blocked_list: 全局的阻塞锁链表list_add: 将等待锁添加到全局阻塞列表- 目的: 便于系统范围内跟踪所有被阻塞的锁请求
__locks_delete_block 函数
static inline void __locks_delete_block(struct file_lock *waiter)
{list_del_init(&waiter->fl_block);list_del_init(&waiter->fl_link);waiter->fl_next = NULL;
}
从阻塞列表移除
list_del_init(&waiter->fl_block);
list_del_init: 从链表中删除节点并重新初始化&waiter->fl_block: 等待锁在阻塞锁的阻塞列表中的节点- 效果: 将等待锁从其当前所在的阻塞列表中移除
从全局列表移除
list_del_init(&waiter->fl_link);
&waiter->fl_link: 等待锁在全局阻塞列表中的节点- 效果: 从全局阻塞跟踪列表中移除这个等待锁
清除等待关系
waiter->fl_next = NULL;
waiter->fl_next = NULL: 清除等待关系指针- 表示这个锁不再等待任何其他锁
- 防止悬空指针和错误的死锁检测
函数功能总结
locks_insert_block 功能
主要目的: 建立锁之间的阻塞等待关系
具体操作:
- 重复插入检查 - 防止同一个锁被多次加入阻塞关系
- 添加到阻塞列表 - 将等待锁添加到阻塞锁的等待者列表中
- 设置等待指针 - 建立明确的"谁等待谁"的关系
- 全局跟踪 - 将POSIX锁添加到全局阻塞列表用于死锁检测
__locks_delete_block 功能
主要目的: 完全清除锁的阻塞关系
清理操作:
- 从阻塞列表移除 - 断开与阻塞锁的关系
- 从全局列表移除 - 不再参与系统级死锁检测
- 清除等待指针 - 重置等待关系状态
将新的文件锁插入到合适的位置locks_insert_lock
static void locks_insert_lock(struct file_lock **pos, struct file_lock *fl)
{list_add(&fl->fl_link, &file_lock_list);/* insert into file's list */fl->fl_next = *pos;*pos = fl;if (fl->fl_ops && fl->fl_ops->fl_insert)fl->fl_ops->fl_insert(fl);
}
函数功能详解
static void locks_insert_lock(struct file_lock **pos, struct file_lock *fl)
参数说明:
pos: 指向文件锁链表中某个位置的指针的指针fl: 要插入的新文件锁结构
关键理解:
pos是一个指向指针的指针,它指向链表中某个节点的fl_next字段- 这种设计允许函数直接修改链表中的指针
添加到全局文件锁列表
list_add(&fl->fl_link, &file_lock_list);
&file_lock_list: 全局的文件锁链表头,用于跟踪系统中的所有文件锁&fl->fl_link: 新锁的链表节点(list_head结构)list_add: 内核链表操作函数,将新元素添加到链表头部- 效果: 新锁
fl被添加到全局文件锁列表的开头
插入到文件的特定锁链表
/* insert into file's list */
fl->fl_next = *pos;
*pos = fl;
假设我们有这样的文件锁链表:
// 链表: 锁A → 锁B → 锁C → NULL
// pos 指向 锁A的fl_next 字段(即指向锁B的指针)
第一行:fl->fl_next = *pos;
// *pos 解引用得到的是 锁B 的地址
// 所以: fl->fl_next = 锁B
// 现在新锁指向原来的下一个锁
第二行:*pos = fl;
// *pos 是 锁A.fl_next 字段
// 将 锁A.fl_next 设置为新锁 fl
// 现在 锁A 指向新锁
插入后的结果
// 插入前: 锁A → 锁B → 锁C → NULL
// ↑
// pos 指向 锁A.fl_next// 插入后: 锁A → 新锁fl → 锁B → 锁C → NULL
// ↑
// fl->fl_next = 锁B
调用锁操作的回调函数
if (fl->fl_ops && fl->fl_ops->fl_insert)fl->fl_ops->fl_insert(fl);
fl->fl_ops: 锁操作函数表指针,可能为NULLfl->fl_ops->fl_insert: 插入回调函数指针- 条件检查: 只有当操作表存在且插入回调函数存在时才调用
- 回调功能: 允许特定的锁类型在插入时执行自定义操作
可能的回调用途:
// 例如,网络文件系统可能在锁插入时:
// - 通知远程服务器
// - 更新分布式锁状态
// - 记录审计信息
双重指针的妙用
为什么使用 struct file_lock **pos?
这种设计允许函数统一处理三种插入情况:
- 链表头插入:
pos = &inode->i_flock - 中间插入:
pos = ¤t_lock->fl_next - 末尾插入:
pos = &last_lock->fl_next
全局链表 vs 文件链表
两个独立的链表结构
// 1. 全局文件锁链表 (系统范围)
file_lock_list → 锁X.fl_link → 锁Y.fl_link → 锁Z.fl_link → ...// 2. 每个文件的锁链表 (按inode组织)
inode->i_flock → 锁A.fl_next → 锁B.fl_next → 锁C.fl_next → ...
全局链表 file_lock_list:
- 系统管理:查看所有活跃的文件锁
- 死锁检测:遍历所有锁的等待关系
- 资源监控:统计锁使用情况
文件链表 inode->i_flock:
- 冲突检测:检查同一文件的锁冲突
- 锁合并:优化相同文件的锁范围
- 文件操作:在文件关闭时释放所有锁
函数功能总结
主要功能
locks_insert_lock 负责将新的文件锁插入到合适的位置
- 全局注册 - 将锁添加到系统全局文件锁列表
- 局部插入 - 将锁插入到特定文件的锁链表中指定位置
- 回调通知 - 调用锁类型的特定插入处理函数
删除一个文件锁locks_delete_lock
static void locks_delete_lock(struct file_lock **thisfl_p)
{struct file_lock *fl = *thisfl_p;*thisfl_p = fl->fl_next;fl->fl_next = NULL;list_del_init(&fl->fl_link);fasync_helper(0, fl->fl_file, 0, &fl->fl_fasync);if (fl->fl_fasync != NULL) {printk(KERN_ERR "locks_delete_lock: fasync == %p\n", fl->fl_fasync);fl->fl_fasync = NULL;}if (fl->fl_ops && fl->fl_ops->fl_remove)fl->fl_ops->fl_remove(fl);locks_wake_up_blocks(fl);locks_free_lock(fl);
}
函数功能详解
static void locks_delete_lock(struct file_lock **thisfl_p)
thisfl_p: 指向文件锁链表中某个位置的指针的指针- 这个参数指向链表中要删除的锁的前一个节点的
fl_next字段
获取锁指针和更新链表
struct file_lock *fl = *thisfl_p;*thisfl_p = fl->fl_next;
fl->fl_next = NULL;
第一行:获取要删除的锁
*thisfl_p: 解引用得到实际要删除的锁指针fl: 现在指向要删除的锁结构
第二行:从链表中移除
*thisfl_p = fl->fl_next;
- 关键操作: 将前一个节点的
fl_next指针指向要删除锁的下一个锁 - 效果: 要删除的锁从链表中被"跳过"
第三行:清除next指针
fl->fl_next = NULL;
- 将被删除锁的
fl_next设置为NULL,表示它不再属于任何链表
链表操作示例:
// 删除前: lockA → lockB(要删除) → lockC → NULL
// thisfl_p 指向 lockA->fl_next (即指向lockB的指针)// 执行后:
// *thisfl_p = fl->fl_next; // lockA->fl_next = lockC
// fl->fl_next = NULL; // lockB->fl_next = NULL// 删除后: lockA → lockC → NULL
// lockB 被孤立,准备清理
从全局列表移除
list_del_init(&fl->fl_link);
&fl->fl_link: 要删除锁在全局链表中的节点list_del_init: 内核链表函数,从链表中删除节点并重新初始化- 效果: 锁从全局文件锁列表
file_lock_list中移除
处理异步通知
fasync_helper(0, fl->fl_file, 0, &fl->fl_fasync);
if (fl->fl_fasync != NULL) {printk(KERN_ERR "locks_delete_lock: fasync == %p\n", fl->fl_fasync);fl->fl_fasync = NULL;
}
第一行:清理异步通知
fasync_helper(0, fl->fl_file, 0, &fl->fl_fasync);
fasync_helper: 内核函数,用于管理文件的异步I/O通知- 参数:
0:fd,删除操作不会使用fl->fl_file: 锁关联的文件0: 表示删除操作&fl->fl_fasync: 异步通知结构指针的地址
- 功能: 清理与这个锁相关的异步通知设置
错误检查和清理
if (fl->fl_fasync != NULL) {printk(KERN_ERR "locks_delete_lock: fasync == %p\n", fl->fl_fasync);fl->fl_fasync = NULL;
}
- 检查: 如果异步通知结构仍然存在(不应该发生)
- 错误打印: 输出内核错误信息
- 强制清理: 将指针设为NULL防止后续问题
调用锁操作的回调函数
if (fl->fl_ops && fl->fl_ops->fl_remove)fl->fl_ops->fl_remove(fl);
fl->fl_ops: 锁操作函数表指针fl->fl_ops->fl_remove: 删除回调函数指针- 条件调用: 只有当操作表存在且删除回调函数存在时才调用
- 回调功能: 允许特定的锁类型在删除时执行自定义清理操作
唤醒阻塞的进程
locks_wake_up_blocks(fl);
- 遍历这个锁的阻塞列表 (
fl->fl_block) - 唤醒所有等待这个锁的进程
- 让被阻塞的锁请求重新尝试获取锁
释放锁内存
locks_free_lock(fl);
- 释放
file_lock结构体占用的内存 - 将锁资源返还给系统
- 完成整个删除过程
函数功能总结
主要功能
locks_delete_lock 负责安全彻底地删除一个文件锁,包括所有相关资源的清理
- 链表管理 - 从文件和全局链表中安全移除锁
- 异步清理 - 处理文件异步通知资源
- 回调执行 - 调用锁类型的自定义删除处理
- 进程唤醒 - 唤醒所有等待这个锁的阻塞进程
- 内存释放 - 最终释放锁结构占用的内存
locks_wake_up_blocks/locks_free_lock
static void locks_wake_up_blocks(struct file_lock *blocker)
{while (!list_empty(&blocker->fl_block)) {struct file_lock *waiter = list_entry(blocker->fl_block.next,struct file_lock, fl_block);__locks_delete_block(waiter);if (waiter->fl_lmops && waiter->fl_lmops->fl_notify)waiter->fl_lmops->fl_notify(waiter);elsewake_up(&waiter->fl_wait);}
}
static inline void __locks_delete_block(struct file_lock *waiter)
{list_del_init(&waiter->fl_block);list_del_init(&waiter->fl_link);waiter->fl_next = NULL;
}
static inline void locks_free_lock(struct file_lock *fl)
{if (fl == NULL) {BUG();return;}if (waitqueue_active(&fl->fl_wait))panic("Attempting to free lock with active wait queue");if (!list_empty(&fl->fl_block))panic("Attempting to free lock with active block list");if (!list_empty(&fl->fl_link))panic("Attempting to free lock on active lock list");if (fl->fl_ops) {if (fl->fl_ops->fl_release_private)fl->fl_ops->fl_release_private(fl);fl->fl_ops = NULL;}if (fl->fl_lmops) {if (fl->fl_lmops->fl_release_private)fl->fl_lmops->fl_release_private(fl);fl->fl_lmops = NULL;}kmem_cache_free(filelock_cache, fl);
}
locks_wake_up_blocks 函数
函数声明
static void locks_wake_up_blocks(struct file_lock *blocker)
blocker: 正在释放的锁,它可能阻塞了其他锁
循环处理所有阻塞的锁
while (!list_empty(&blocker->fl_block)) {
&blocker->fl_block: 阻塞列表头,包含所有等待这个锁的锁!list_empty(): 检查阻塞列表是否非空while循环: 持续处理直到所有被阻塞的锁都被唤醒
获取第一个等待者
struct file_lock *waiter = list_entry(blocker->fl_block.next,struct file_lock, fl_block);
blocker->fl_block.next: 阻塞列表中的第一个节点list_entry: 内核宏,从链表节点获取包含它的结构体struct file_lock: 目标结构体类型fl_block: 在file_lock结构体中的链表字段名- 结果:
waiter指向第一个被阻塞的锁
从阻塞关系中移除
__locks_delete_block(waiter);
- 调用辅助函数将等待者从阻塞关系中完全移除
- 包括从阻塞列表和全局列表中删除
通知或唤醒等待者
if (waiter->fl_lmops && waiter->fl_lmops->fl_notify)waiter->fl_lmops->fl_notify(waiter);
elsewake_up(&waiter->fl_wait);
-
自定义通知(如果有锁管理器操作):
waiter->fl_lmops: 锁管理器操作表fl_notify: 自定义通知函数- 用于复杂的锁管理器(如分布式锁)
-
标准唤醒(默认情况):
wake_up(&waiter->fl_wait): 唤醒在等待队列上睡眠的进程waiter->fl_wait: 等待队列头
__locks_delete_block 函数
static inline void __locks_delete_block(struct file_lock *waiter)
{list_del_init(&waiter->fl_block);list_del_init(&waiter->fl_link);waiter->fl_next = NULL;
}
从阻塞列表移除
list_del_init(&waiter->fl_block);
&waiter->fl_block: 等待者在阻塞者fl_block列表中的节点list_del_init: 从链表中删除并重新初始化节点- 效果: 等待者不再属于任何阻塞列表
从全局列表移除
list_del_init(&waiter->fl_link);
&waiter->fl_link: 等待者在全局blocked_list中的节点- 效果: 等待者不再参与系统级死锁检测
清除等待关系
waiter->fl_next = NULL;
waiter->fl_next: 指向被等待锁的指针- 设置为NULL: 表示这个锁不再等待任何其他锁
- 防止悬空指针: 确保指针安全
locks_free_lock 函数
函数声明和空指针检查
static inline void locks_free_lock(struct file_lock *fl)
{if (fl == NULL) {BUG();return;}
- 空指针检查: 如果传入NULL指针,触发BUG(内核错误)
- 安全返回: 防止后续操作崩溃
活跃状态检查
if (waitqueue_active(&fl->fl_wait))panic("Attempting to free lock with active wait queue");if (!list_empty(&fl->fl_block))panic("Attempting to free lock with active block list");if (!list_empty(&fl->fl_link))panic("Attempting to free lock on active lock list");
-
等待队列活跃:
waitqueue_active(&fl->fl_wait): 检查是否有进程在等待这个锁- 如果有,触发panic(严重内核错误)
-
阻塞列表非空:
!list_empty(&fl->fl_block): 检查这个锁是否还阻塞其他锁- 如果有,触发panic
-
全局链表非空:
!list_empty(&fl->fl_link): 检查锁是否还在全局链表中- 如果有,触发panic
锁操作表清理
if (fl->fl_ops) {if (fl->fl_ops->fl_release_private)fl->fl_ops->fl_release_private(fl);fl->fl_ops = NULL;
}
- 检查操作表存在:
if (fl->fl_ops) - 调用私有释放: 如果定义了
fl_release_private回调 - 清空指针:
fl->fl_ops = NULL
锁管理器操作表清理
if (fl->fl_lmops) {if (fl->fl_lmops->fl_release_private)fl->fl_lmops->fl_release_private(fl);fl->fl_lmops = NULL;
}
- 针对锁管理器特定的操作表
- 调用私有资源释放回调
- 清空指针
内存释放
kmem_cache_free(filelock_cache, fl);
filelock_cache: 文件锁对象的内存缓存(slab分配器)kmem_cache_free: 将对象释放回内存缓存- 完成释放: 锁结构占用的内存被回收
函数功能总结
locks_wake_up_blocks 功能
唤醒所有被指定锁阻塞的等待者
- 批量处理 - 循环处理所有被阻塞的锁
- 关系清理 - 彻底移除阻塞关系
- 灵活通知 - 支持标准和自定义唤醒机制
__locks_delete_block 功能
完全清除锁的阻塞关系状态
- 列表移除 - 从阻塞列表和全局列表中删除
- 指针重置 - 清除等待关系指针
- 状态初始化 - 将链表节点重置为初始状态
locks_free_lock 功能
安全释放文件锁内存资源
- 状态验证 - 确保锁处于可释放状态
- 资源清理 - 调用所有必要的清理回调
- 内存回收 - 使用slab分配器高效释放
