前言
在现代操作系统的安全架构中,用户身份管理是构建权限隔离和安全边界的基石。Linux内核通过一套精密的用户身份切换机制,实现了进程权限的动态调整和安全控制。本文将以Linux 2.6.10内核为例,深入剖析用户身份管理的完整技术栈,从最上层的系统调用接口到底层的原子操作实现,揭示Linux如何在高性能和高安全性之间取得完美平衡。
文章将系统性地解析五个关键层次组成的完整调用链:sys_setuid ------作为用户身份切换的系统调用入口,承担权限验证和流程控制的核心职责;set_user ------执行实际的用户切换操作,处理资源限制检查和内存管理;switch_uid ------完成用户上下文的原子切换,更新进程计数和安全上下文;alloc_uid ------通过高效的哈希表缓存机制管理用户数据结构;atomic_dec_and_lock------利用无锁编程和条件锁获取优化并发性能。通过对这一完整技术栈的逐层分析,我们将展现Linux内核如何在多核并发环境下,通过能力机制、引用计数、内存屏障和乐观并发控制等先进技术,构建出既符合POSIX标准又具备卓越性能的用户身份管理系统。
用户身份切换sys_setuid
c
asmlinkage long sys_setuid(uid_t uid)
{
int old_euid = current->euid;
int old_ruid, old_suid, new_ruid, new_suid;
int retval;
retval = security_task_setuid(uid, (uid_t)-1, (uid_t)-1, LSM_SETID_ID);
if (retval)
return retval;
old_ruid = new_ruid = current->uid;
old_suid = current->suid;
new_suid = old_suid;
if (capable(CAP_SETUID)) {
if (uid != old_ruid && set_user(uid, old_euid != uid) < 0)
return -EAGAIN;
new_suid = uid;
} else if ((uid != current->uid) && (uid != new_suid))
return -EPERM;
if (old_euid != uid)
{
current->mm->dumpable = 0;
wmb();
}
current->fsuid = current->euid = uid;
current->suid = new_suid;
key_fsuid_changed(current);
return security_task_post_setuid(old_ruid, old_euid, old_suid, LSM_SETID_ID);
}
函数功能分析
sys_setuid函数实现了Linux系统中的用户身份切换机制,通过精细的权限检查和状态管理,确保进程用户标识的安全变更,同时遵循POSIX标准和Linux安全模块的要求。
变量声明与初始化
c
int old_euid = current->euid;
int old_ruid, old_suid, new_ruid, new_suid;
int retval;
- 当前状态保存 :
old_euid保存当前有效用户ID,用于后续比较和恢复 - 多标识符管理 :声明
real uid、saved uid的旧值和新值变量 - 返回值准备 :
retval用于存储安全检查的返回结果
Linux安全模块检查
c
retval = security_task_setuid(uid, (uid_t)-1, (uid_t)-1, LSM_SETID_ID);
if (retval)
return retval;
- LSM钩子调用:调用Linux安全模块进行权限变更前的安全检查
- 参数说明 :只设置
uid,其他参数为-1表示不修改,LSM_SETID_ID表示setuid操作 - 提前返回:如果安全模块拒绝操作,立即返回错误码
用户标识符状态初始化
c
old_ruid = new_ruid = current->uid;
old_suid = current->suid;
new_suid = old_suid;
real uid初始化 :old_ruid和new_ruid初始化为当前real uidsaved uid保存 :old_suid保存当前saved uid状态- 新值预设 :
new_suid默认保持不变,后续根据权限调整
特权进程处理路径
c
if (capable(CAP_SETUID)) {
if (uid != old_ruid && set_user(uid, old_euid != uid) < 0)
return -EAGAIN;
new_suid = uid;
}
- 能力检查 :
capable(CAP_SETUID)检查进程是否有设置用户ID的能力 - 用户切换 :如果目标
uid与当前real uid不同,调用set_user进行实际用户切换 - 资源限制 :
set_user失败返回-EAGAIN,通常是因为超出资源限制 saved uid更新 :特权进程设置saved uid为目标uid
非特权进程限制检查
c
else if ((uid != current->uid) && (uid != new_suid))
return -EPERM;
- 权限限制 :非特权进程只能将
effective uid设置为当前real uid或saved uid - 错误返回 :如果尝试设置其他
uid,返回权限错误-EPERM - POSIX合规 :遵循POSIX标准对非特权进程
setuid的限制
核心转储权限管理
c
if (old_euid != uid)
{
current->mm->dumpable = 0;
wmb();
}
- 权限变更检测 :检查
effective uid是否实际发生变化 - 核心转储禁用 :当
effective uid变化时,设置进程为不可核心转储状态 - 内存屏障 :
wmb()写内存屏障确保修改对其他CPU可见,防止指令重排
用户标识符实际更新
c
current->fsuid = current->euid = uid;
current->suid = new_suid;
effective uid设置 :更新进程的effective uid为目标值- 文件系统
uid同步 :fsuid与effective uid保持同步,用于文件系统权限检查 saved uid更新 :根据之前逻辑设置新的saved uid
安全子系统通知
c
key_fsuid_changed(current);
return security_task_post_setuid(old_ruid, old_euid, old_suid, LSM_SETID_ID);
- 密钥环通知 :
key_fsuid_changed通知内核密钥环子系统用户ID变更 - 事后安全钩子 :调用LSM的
post_setuid钩子,传递旧值用于审计 - 操作完成:返回安全模块的最终结果
函数功能总结
sys_setuid系统调用是Linux用户身份管理的核心组件,通过多层级的权限验证和状态管理,实现了安全可靠的用户标识符切换机制:
- 安全优先:集成LSM框架,在操作前后进行完整的安全检查
- 权限分离:区分特权进程和非特权进程的不同行为模式
- 状态一致性 :确保
effective uid、real uid、saved uid和fsuid的同步更新 - 安全防护:在权限降低时自动禁用核心转储,防止信息泄露
- 子系统协同:通知密钥环等子系统关于用户标识符的变更
- 标准兼容:遵循POSIX标准和System V的SAVED_IDS语义
该实现体现了Linux在保持向后兼容性的同时,通过能力机制和LSM框架实现了更现代、更安全的权限管理模型,为setuid-root程序等特权操作提供了坚实的基础设施支持。
执行用户切换set_user
c
static int set_user(uid_t new_ruid, int dumpclear)
{
struct user_struct *new_user;
new_user = alloc_uid(new_ruid);
if (!new_user)
return -EAGAIN;
if (atomic_read(&new_user->processes) >=
current->signal->rlim[RLIMIT_NPROC].rlim_cur &&
new_user != &root_user) {
free_uid(new_user);
return -EAGAIN;
}
switch_uid(new_user);
if(dumpclear)
{
current->mm->dumpable = 0;
wmb();
}
current->uid = new_ruid;
return 0;
}
函数功能分析
set_user函数是setuid操作的底层实现核心,负责分配新的用户数据结构、检查进程数限制、执行用户切换并管理核心转储权限,确保用户身份变更的资源安全和权限控制。
新用户结构分配
c
struct user_struct *new_user;
new_user = alloc_uid(new_ruid);
if (!new_user)
return -EAGAIN;
- 用户结构指针 :
new_user用于指向新用户的内部数据结构 - 资源分配 :
alloc_uid(new_ruid)根据新的真实用户ID分配或查找对应的user_struct - 分配失败处理 :如果内存不足无法分配用户结构,返回
-EAGAIN表示资源暂时不可用
进程数限制检查
c
if (atomic_read(&new_user->processes) >=
current->signal->rlim[RLIMIT_NPROC].rlim_cur &&
new_user != &root_user) {
free_uid(new_user);
return -EAGAIN;
}
- 原子读取 :
atomic_read(&new_user->processes)安全地读取当前用户的进程计数 - 资源限制比较 :检查是否超过
RLIMIT_NPROC限制(每个用户的进程数限制) - root用户豁免 :
root_user不受进程数限制,这是特权用户的特殊待遇 - 资源释放 :如果超过限制,释放之前分配的用户结构并返回
-EAGAIN
用户身份切换执行
c
switch_uid(new_user);
- 关键切换操作 :调用
switch_uid函数执行实际的用户身份切换 - 内部状态更新:更新内核中所有与用户身份相关的内部状态
- 引用计数管理:调整新旧用户结构的引用计数
核心转储权限管理
c
if(dumpclear)
{
current->mm->dumpable = 0;
wmb();
}
- 条件性清理 :仅在
dumpclear参数为真时禁用核心转储dumpclear机制在权限变化时保护敏感信息
- 安全防护 :
current->mm->dumpable = 0设置进程为不可核心转储状态 - 内存屏障 :
wmb()写内存屏障确保修改对所有CPU核心可见,防止指令重排导致的竞态条件
真实用户ID更新
c
current->uid = new_ruid;
return 0;
- 最终状态设置:将进程的真实用户ID设置为目标值
- 成功返回:返回0表示用户切换操作成功完成
函数功能总结
set_user函数是Linux用户身份管理的关键底层组件,通过精细的资源管理和安全控制,实现了安全可靠的用户切换机制:
- 资源安全:严格检查RLIMIT_NPROC限制,防止用户进程数超限
- 内存管理:正确分配和释放user_struct,维护引用计数完整性
- 权限控制:在权限变更时智能管理核心转储能力,保护敏感信息
- 并发安全:使用原子操作和内存屏障确保多核环境下的数据一致性
- 错误恢复:统一的错误码和资源清理,确保操作的事务性
该函数体现了Linux内核在用户身份管理方面的核心设计原则:安全性优先、资源可控、状态一致。作为setuid系统调用的核心实现部分,它为进程权限管理提供了可靠的基础设施支持。
执行进程用户身份的原子切换switch_uid
c
void switch_uid(struct user_struct *new_user)
{
struct user_struct *old_user;
old_user = current->user;
atomic_inc(&new_user->processes);
atomic_dec(&old_user->processes);
switch_uid_keyring(new_user);
current->user = new_user;
free_uid(old_user);
suid_keys(current);
}
函数功能分析
switch_uid函数负责执行进程用户身份的原子切换,更新进程计数、密钥环和其他用户相关的安全上下文,确保用户切换过程中资源统计的正确性和安全性。
旧用户结构保存
c
struct user_struct *old_user;
old_user = current->user;
- 旧用户指针保存 :
old_user保存当前进程的用户结构指针 - 现状分析 :此时假设资源限制检查已在
set_user中完成
进程计数更新
c
atomic_inc(&new_user->processes);
atomic_dec(&old_user->processes);
- 新用户进程计数增加 :
atomic_inc原子性地增加新用户的进程计数 - 旧用户进程计数减少 :
atomic_dec原子性地减少旧用户的进程计数 - 统计完整性:确保系统范围内每个用户的进程计数准确
- 原子性保证:使用原子操作防止并发更新导致计数错误
密钥环上下文切换
c
switch_uid_keyring(new_user);
- 安全上下文切换:切换到新用户对应的密钥环
当前用户指针更新
c
current->user = new_user;
- 身份标识更新:将进程的当前用户指针指向新的用户结构
- 原子性切换:这是实际的用户身份切换点
- 后续操作基础:所有后续的用户相关操作都基于新的用户身份
旧用户资源释放
c
free_uid(old_user);
- 引用计数管理:减少旧用户结构的引用计数
- 条件性释放:如果引用计数降为零,释放用户结构内存
- 资源回收:清理不再使用的用户相关资源
- 生命周期管理:基于引用计数的自动内存管理
会话密钥更新
c
suid_keys(current);
- 会话密钥重置:更新与用户ID相关的会话密钥
分配和管理用户数据结构alloc_uid
c
struct user_struct * alloc_uid(uid_t uid)
{
struct list_head *hashent = uidhashentry(uid);
struct user_struct *up;
spin_lock(&uidhash_lock);
up = uid_hash_find(uid, hashent);
spin_unlock(&uidhash_lock);
if (!up) {
struct user_struct *new;
new = kmem_cache_alloc(uid_cachep, SLAB_KERNEL);
if (!new)
return NULL;
new->uid = uid;
atomic_set(&new->__count, 1);
atomic_set(&new->processes, 0);
atomic_set(&new->files, 0);
atomic_set(&new->sigpending, 0);
new->mq_bytes = 0;
new->locked_shm = 0;
if (alloc_uid_keyring(new) < 0) {
kmem_cache_free(uid_cachep, new);
return NULL;
}
/*
* Before adding this, check whether we raced
* on adding the same user already..
*/
spin_lock(&uidhash_lock);
up = uid_hash_find(uid, hashent);
if (up) {
key_put(new->uid_keyring);
key_put(new->session_keyring);
kmem_cache_free(uid_cachep, new);
} else {
uid_hash_insert(new, hashent);
up = new;
}
spin_unlock(&uidhash_lock);
}
return up;
}
函数功能分析
alloc_uid函数负责分配和管理用户数据结构,通过哈希表缓存实现高效的用户结构查找和分配,采用无锁查找加锁分配的模式优化性能,确保用户资源的正确引用计数管理。
哈希表查找准备
c
struct list_head *hashent = uidhashentry(uid);
struct user_struct *up;
spin_lock(&uidhash_lock);
up = uid_hash_find(uid, hashent);
spin_unlock(&uidhash_lock);
- 哈希桶定位 :
uidhashentry(uid)根据用户ID计算哈希值,定位到对应的哈希桶 - 短暂加锁 :使用自旋锁
uidhash_lock保护哈希表的并发访问 - 快速查找 :
uid_hash_find在哈希链表中查找是否已存在该用户ID的结构 - 立即释放锁:查找完成后立即释放锁,减少锁竞争时间
缓存未命中慢速路径
c
if (!up) {
- 缓存检查 :如果
up为NULL,说明在哈希表中没有找到现有的用户结构
新用户结构分配
c
struct user_struct *new;
new = kmem_cache_alloc(uid_cachep, SLAB_KERNEL);
if (!new)
return NULL;
new->uid = uid;
atomic_set(&new->__count, 1);
atomic_set(&new->processes, 0);
atomic_set(&new->files, 0);
atomic_set(&new->sigpending, 0);
new->mq_bytes = 0;
new->locked_shm = 0;
- Slab分配 :
kmem_cache_alloc(uid_cachep, SLAB_KERNEL)从专用slab缓存分配用户结构 - 内存不足处理:如果分配失败返回NULL,向上层传递错误
- 基础字段初始化 :
uid:设置用户ID__count:引用计数初始化为1processes:进程计数初始化为0files:文件计数初始化为0sigpending:待处理信号计数初始化为0
- 资源字段清零 :
mq_bytes:消息队列字节数清零locked_shm:锁定共享内存清零
密钥环分配与错误处理
c
if (alloc_uid_keyring(new) < 0) {
kmem_cache_free(uid_cachep, new);
return NULL;
}
- 密钥环分配 :
alloc_uid_keyring为用户分配专用的密钥环结构 - 分配失败处理:如果密钥环分配失败,释放之前分配的用户结构内存
- 资源清理:确保分配过程的事务性,要么全部成功,要么全部回滚
竞争条件检查与哈希表插入
c
spin_lock(&uidhash_lock);
up = uid_hash_find(uid, hashent);
if (up) {
key_put(new->uid_keyring);
key_put(new->session_keyring);
kmem_cache_free(uid_cachep, new);
} else {
uid_hash_insert(new, hashent);
up = new;
}
spin_unlock(&uidhash_lock);
- 重新加锁检查:在插入前再次检查是否其他线程已创建相同用户结构
- 竞争处理 :如果发现竞争(
up不为NULL),清理新分配的结构 - 资源释放 :
key_put:释放密钥环引用kmem_cache_free:释放用户结构内存
- 哈希表插入:如果没有竞争,将新结构插入哈希表并设置为返回结果
结果返回
c
}
return up;
- 统一返回:无论通过缓存找到还是新分配,都返回有效的用户结构指针
- 引用计数:返回的结构已有正确的引用计数
关键设计模式
检查-分配-检查模式 (Check-Allocate-Check)
c
// 第一阶段:无锁快速检查
// 第二阶段:分配资源
// 第三阶段:加锁验证并插入
// 这种模式平衡了性能和正确性
乐观并发控制
c
// 假设没有竞争,先分配资源
// 在提交前验证假设,如果失败则回滚
// 避免在持有锁的情况下进行可能阻塞的分配操作
数据结构管理
引用计数机制
c
// atomic_set(&new->__count, 1) 初始引用计数为1
// 后续通过get_uid/put_uid管理生命周期
资源统计字段
c
// processes: 跟踪该用户的进程数量
// files: 跟踪打开文件数量
// sigpending: 待处理信号计数
// mq_bytes/locked_shm: 资源使用限制
函数功能总结
alloc_uid函数是Linux用户管理子系统的核心基础设施,通过精巧的并发设计和资源管理,实现了高效可靠的用户结构分配:
- 性能优化:采用无锁查找和乐观分配策略,最小化锁竞争
- 并发安全:通过检查-分配-检查模式正确处理竞争条件
- 资源管理:完整的引用计数和资源统计,支持正确的生命周期管理
- 错误恢复:分层错误处理和资源清理,确保系统稳定性
- 内存效率:使用slab缓存减少内存分配开销
- 安全集成:与密钥环子系统深度集成,支持用户级安全特性
未来完整的用户资源跟踪系统user_struct
c
/*
* Some day this will be a full-fledged user tracking system..
*/
struct user_struct {
atomic_t __count; /* reference count */
atomic_t processes; /* How many processes does this user have? */
atomic_t files; /* How many open files does this user have? */
atomic_t sigpending; /* How many pending signals does this user have? */
/* protected by mq_lock */
unsigned long mq_bytes; /* How many bytes can be allocated to mqueue? */
unsigned long locked_shm; /* How many pages of mlocked shm ? */
#ifdef CONFIG_KEYS
struct key *uid_keyring; /* UID specific keyring */
struct key *session_keyring; /* UID's default session keyring */
#endif
/* Hash table maintenance information */
struct list_head uidhash_list;
uid_t uid;
};
/*
* UID task count cache, to get fast user lookup in "alloc_uid"
* when changing user ID's (ie setuid() and friends).
*/
#define UIDHASH_BITS 8
#define UIDHASH_SZ (1 << UIDHASH_BITS)
#define UIDHASH_MASK (UIDHASH_SZ - 1)
#define __uidhashfn(uid) (((uid >> UIDHASH_BITS) + uid) & UIDHASH_MASK)
#define uidhashentry(uid) (uidhash_table + __uidhashfn((uid)))
static kmem_cache_t *uid_cachep;
static struct list_head uidhash_table[UIDHASH_SZ];
static spinlock_t uidhash_lock = SPIN_LOCK_UNLOCKED;
struct user_struct root_user = {
.__count = ATOMIC_INIT(1),
.processes = ATOMIC_INIT(1),
.files = ATOMIC_INIT(0),
.sigpending = ATOMIC_INIT(0),
.mq_bytes = 0,
.locked_shm = 0,
#ifdef CONFIG_KEYS
.uid_keyring = &root_user_keyring,
.session_keyring = &root_session_keyring,
#endif
};
/*
* These routines must be called with the uidhash spinlock held!
*/
static inline void uid_hash_insert(struct user_struct *up, struct list_head *hashent)
{
list_add(&up->uidhash_list, hashent);
}
static inline void uid_hash_remove(struct user_struct *up)
{
list_del(&up->uidhash_list);
}
static inline struct user_struct *uid_hash_find(uid_t uid, struct list_head *hashent)
{
struct list_head *up;
list_for_each(up, hashent) {
struct user_struct *user;
user = list_entry(up, struct user_struct, uidhash_list);
if(user->uid == uid) {
atomic_inc(&user->__count);
return user;
}
}
return NULL;
}
数据结构分析
user_struct - 用户资源跟踪结构
c
struct user_struct {
atomic_t __count; /* reference count */
atomic_t processes; /* How many processes does this user have? */
atomic_t files; /* How many open files does this user have? */
atomic_t sigpending; /* How many pending signals does this user have? */
/* protected by mq_lock */
unsigned long mq_bytes; /* How many bytes can be allocated to mqueue? */
unsigned long locked_shm; /* How many pages of mlocked shm ? */
#ifdef CONFIG_KEYS
struct key *uid_keyring; /* UID specific keyring */
struct key *session_keyring; /* UID's default session keyring */
#endif
/* Hash table maintenance information */
struct list_head uidhash_list;
uid_t uid;
};
核心字段说明:
__count:引用计数,用于生命周期管理processes:该用户拥有的进程数量统计files:该用户打开的文件数量统计sigpending:待处理信号数量统计mq_bytes:消息队列可分配字节数(受mq_lock保护)locked_shm:mlock状态的共享内存页数- 密钥环字段:用户特定的密钥环和会话密钥环
- 哈希表维护 :
uidhash_list用于哈希链表,uid存储用户ID
哈希表系统设计
哈希表参数定义
c
#define UIDHASH_BITS 8
#define UIDHASH_SZ (1 << UIDHASH_BITS) // 256个桶
#define UIDHASH_MASK (UIDHASH_SZ - 1) // 0xFF掩码
#define __uidhashfn(uid) (((uid >> UIDHASH_BITS) + uid) & UIDHASH_MASK)
#define uidhashentry(uid) (uidhash_table + __uidhashfn((uid)))
哈希算法分析:
c
// 示例:uid = 1000 (0x3E8)
// __uidhashfn(1000):
// 1000 >> 8 = 3
// 3 + 1000 = 1003
// 1003 & 0xFF = 235
// 所以uid=1000映射到哈希桶235
全局变量声明
c
static kmem_cache_t *uid_cachep; // 用户结构slab缓存
static struct list_head uidhash_table[UIDHASH_SZ]; // 哈希表数组
static spinlock_t uidhash_lock = SPIN_LOCK_UNLOCKED; // 哈希表保护锁
root用户特殊初始化
root用户静态定义
c
struct user_struct root_user = {
.__count = ATOMIC_INIT(1),
.processes = ATOMIC_INIT(1),
.files = ATOMIC_INIT(0),
.sigpending = ATOMIC_INIT(0),
.mq_bytes = 0,
.locked_shm = 0,
#ifdef CONFIG_KEYS
.uid_keyring = &root_user_keyring,
.session_keyring = &root_session_keyring,
#endif
};
root用户特权:
- 引用计数:初始化为1,确保root用户结构不会被释放
- 进程计数:初始化为1,表示至少有一个root进程
- 密钥环:预定义root用户的专用密钥环
哈希表操作函数
插入操作
c
static inline void uid_hash_insert(struct user_struct *up, struct list_head *hashent)
{
list_add(&up->uidhash_list, hashent);
}
- 链表插入:将用户结构添加到指定哈希桶的链表头部
- 调用要求 :必须在持有
uidhash_lock时调用
删除操作
c
static inline void uid_hash_remove(struct user_struct *up)
{
list_del(&up->uidhash_list);
}
- 链表删除:从哈希链表中移除用户结构
- 内存管理:只移除链表链接,不释放内存
- 调用要求 :必须在持有
uidhash_lock时调用
查找操作
c
static inline struct user_struct *uid_hash_find(uid_t uid, struct list_head *hashent)
{
struct list_head *up;
list_for_each(up, hashent) {
struct user_struct *user;
user = list_entry(up, struct user_struct, uidhash_list);
if(user->uid == uid) {
atomic_inc(&user->__count);
return user;
}
}
return NULL;
}
- 遍历链表 :
list_for_each宏遍历哈希桶中的所有元素 - 类型转换 :
list_entry将链表节点转换为user_struct结构 - UID匹配:比较用户ID是否匹配目标UID
- 引用计数:找到匹配项时增加引用计数并返回
- 未找到:返回NULL表示用户结构不存在
原子递减并条件获取锁atomic_dec_and_lock
c
#define atomic_dec_and_lock(atomic,lock) __cond_lock(_atomic_dec_and_lock(atomic,lock))
int _atomic_dec_and_lock(atomic_t *atomic, spinlock_t *lock)
{
int counter;
int newcount;
repeat:
counter = atomic_read(atomic);
newcount = counter-1;
if (!newcount)
goto slow_path;
asm volatile("lock; cmpxchgl %1,%2"
:"=a" (newcount)
:"r" (newcount), "m" (atomic->counter), "0" (counter));
/* If the above failed, "eax" will have changed */
if (newcount != counter)
goto repeat;
return 0;
slow_path:
spin_lock(lock);
if (atomic_dec_and_test(atomic))
return 1;
spin_unlock(lock);
return 0;
}
这是一个原子递减并条件获取锁的函数。它使用__cond_lock宏来配合(sparse)静态分析工具,确保锁的正确使用。
函数功能分析
_atomic_dec_and_lock函数实现了一个高效的原子递减操作,当计数值减到零时自动获取锁,否则无锁快速返回,这是一种常见的"最后释放者获取锁"模式,用于资源引用计数的优化管理。
宏定义包装器
c
#define atomic_dec_and_lock(atomic,lock) __cond_lock(_atomic_dec_and_lock(atomic,lock))
- 接口封装:提供用户友好的宏接口
- 静态分析支持 :
__cond_lock宏帮助稀疏工具理解锁的获取是条件性的
局部变量声明与初始递减
c
int counter;
int newcount;
repeat:
counter = atomic_read(atomic);
newcount = counter-1;
- 状态变量 :
counter存储当前原子值,newcount存储期望的新值 - 原子读取 :
atomic_read(atomic)安全地读取原子变量的当前值 - 预计算新值 :计算递减后的期望值
counter-1
快速路径检查
c
if (!newcount)
goto slow_path;
- 零值检测:如果新值将为0,跳转到慢速路径获取锁
- 性能优化:这是关键的性能检查,避免在非零情况下不必要的锁操作
- 快速返回:大多数情况下计数值不为零,可以快速返回
原子比较交换操作
c
asm volatile("lock; cmpxchgl %1,%2"
:"=a" (newcount)
:"r" (newcount), "m" (atomic->counter), "0" (counter));
-
内联汇编 :使用x86的
lock cmpxchgl指令实现原子比较交换 -
操作语义 :如果
atomic->counter == counter,则atomic->counter = newcountasm; 指令: lock cmpxchgl 通用寄存器, [atomic->counter] ; ; 操作数对应: ; EAX = counter (通过"0"约束) ; 通用寄存器 = newcount (通过"r"约束) ; 目标内存 = atomic->counter (通过"m"约束) ; ; 实际语义: ; if (EAX == [atomic->counter]) { ; [atomic->counter] = 通用寄存器; // 即 atomic->counter = newcount ; ZF = 1; ; } else { ; EAX = [atomic->counter]; // EAX更新为当前内存值 ; ZF = 0; ; } -
约束说明:
"=a" (newcount):结果输出到EAX寄存器和newcount变量"r" (newcount):新值作为输入"m" (atomic->counter):内存操作数"0" (counter):期望值输入到EAX寄存器
竞争条件重试
c
/* If the above failed, "eax" will have changed */
if (newcount != counter)
goto repeat;
return 0;
- 竞争检测 :如果
newcount != counter,说明其他线程修改了原子变量- 如果未发生竞争,
newcount的值会保持和counter的值一致 - 如果发生竞争,
newcount的值会和[atomic->counter]的值一致
- 如果未发生竞争,
- 重试机制 :跳转回
repeat标签重新尝试递减操作 - 成功返回:如果原子操作成功且计数值不为零,返回0表示未获取锁
慢速路径锁获取
c
slow_path:
spin_lock(lock);
if (atomic_dec_and_test(atomic))
return 1;
spin_unlock(lock);
return 0;
- 锁获取 :
spin_lock(lock)获取自旋锁 - 最终递减测试 :
atomic_dec_and_test(atomic)原子递减并测试是否为零 - 成功条件:如果递减后为零,返回1表示成功获取锁
- 锁释放:如果其他线程已递减到零,释放锁并返回0
函数功能总结
_atomic_dec_and_lock函数是一个精心设计的并发原语,具有以下核心价值:
- 性能优化:通过无锁快速路径处理大多数情况,避免不必要的锁开销
- 正确性保证:使用原子指令和锁机制确保并发安全
- 竞争处理:通过CAS重试机制优雅处理多线程竞争
- 资源效率:只在真正需要时(引用计数归零)才获取锁
- 架构优化 :针对x86架构使用高效的
lock cmpxchgl指令
该函数体现了Linux内核并发编程的精髓:在保证正确性的前提下,通过精细的算法设计最大化性能。它是实现高效资源生命周期管理的基石,广泛应用于各种需要引用计数和条件锁获取的场景。