Linux中__set_bit函数分析

__set_bit函数用于在内存中设置指定位。不同于set_bit,它不保证原子性,可能因并发调用导致只有一个操作成功。该函数通过计算mask并应用到目标地址对应的字长上实现位设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


/**
 * __set_bit - 在内存中设置一位
 * @nr: 要设置的那一位
 * @addr: 开始计数的地址
 *
 * 与set_bit()不同,此函数是非原子的,可以重新排序。如果同时调用同一个内存区域,则可能只有一个操作成功。
 */
static inline void __set_bit(int nr, volatile unsigned long *addr)
{
	unsigned long mask = BIT_MASK(nr);
	unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);

	*p  |= mask;
}
#define BIT_MASK(nr)		(1UL << ((nr) % BITS_PER_LONG))
<think>我们正在讨论Linux内核中的位操作函数,特别是set_bit函数。根据引用内容,我们可以总结如下: 1. set_bit函数用于设置一个特定位(设置为1)。 2. 它有两种变体:原子操作版本(set_bit)和非原子操作版本(__set_bit)。 3. 这些函数在设备驱动程序中很常见,特别是在输入设备(如触摸屏)的初始化中(如引用[2]所示)。 下面我将详细解释set_bit函数的作用、实现细节以及使用方法。 ### 1. set_bit函数的作用 在Linux内核中,set_bit函数用于将某个内存地址中的特定位(bit)设置为1。其函数原型如下: ```c void set_bit(int nr, volatile unsigned long *addr); ``` - `nr`:要设置的位的序号(从0开始计数)。 - `addr`:指向要设置位的内存地址的指针。 例如,如果我们想设置一个unsigned long类型变量`data`的第3位,可以这样调用: ```c set_bit(3, &data); ``` ### 2. 原子操作与非原子操作 Linux内核提供了两套位操作函数: - **原子操作**:`set_bit`、`clear_bit`、`test_and_set_bit`等,这些函数在执行位操作时是原子性的,不会被中断打断,适用于多核或多线程环境。 - **非原子操作**:`__set_bit`、`__clear_bit`等,这些函数不保证原子性,执行效率更高,但只能在确保不会发生并发访问的上下文中使用(例如,在中断被禁用的情况下)。 ### 3. 实现细节 根据引用[4],非原子版本的`__set_bit`实现如下: ```c static inline void __set_bit(int nr, volatile unsigned long *addr) { unsigned long mask = BIT_MASK(nr); unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); *p |= mask; } ``` 其中: - `BIT_MASK(nr)`:创建一个掩码,该掩码只有第`nr`位为1,其余位为0。例如,`nr=3`时,掩码为`0x00000008`(32位系统)或`0x0000000000000008`(64位系统)。 - `BIT_WORD(nr)`:计算包含第`nr`位的字(word)在数组中的索引。在32位系统中,每个字为32位(4字节);在64位系统中,每个字为64位(8字节)。因此,`BIT_WORD(nr)`等于`nr / BITS_PER_LONG`(其中`BITS_PER_LONG`为32或64)。 - `p`:指向包含目标位的字(word)的指针。 - `*p |= mask`:将目标位置1,其他位保持不变。 原子操作版本`set_bit`的实现依赖于具体的体系结构,通常使用特殊的原子指令(如x86的`bts`指令)来实现。其核心是确保在设置位的过程中不会被其他处理器中断。 ### 4. 使用方法 在设备驱动程序中,我们经常使用`set_bit`来设置设备支持的事件类型和属性。以输入设备(如触摸屏)为例(引用[2]): ```c __set_bit(EV_KEY, input_dev->evbit); // 支持按键事件 __set_bit(EV_ABS, input_dev->evbit); // 支持绝对坐标事件(如触摸位置) __set_bit(BTN_TOUCH, input_dev->keybit); // 支持触摸按键 __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); // 设备为直接输入设备(如触摸屏) ``` 这里使用了非原子版本`__set_bit`,因为在设备初始化过程中,通常不会有并发访问的问题(设备尚未注册到系统中)。 ### 5. 原子操作函数`test_and_set_bit` 引用[3]提到了另一个原子操作函数`test_and_set_bit`,该函数设置位并返回该位之前的值: ```c static inline int test_and_set_bit(int nr, unsigned long *addr) { unsigned long mask = BIT_MASK(nr); unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); unsigned long old = *p; *p = old | mask; // 设置位 return (old & mask) != 0; // 返回之前的值 } ``` 这个函数常用于实现锁机制,例如: ```c while (test_and_set_bit(0, &lock) != 0) ; // 自旋等待 ``` ### 6. 总结 - `set_bit`:原子设置位。 - `__set_bit`:非原子设置位,效率更高,但需确保无并发访问。 - 在设备驱动中,常用这些函数来设置设备的能力位图(如事件类型、按键类型等)。 - 原子操作版本用于可能被并发访问的场景,非原子版本用于安全的上下文(如初始化)。 ### 相关问题 1. 在Linux内核中,`clear_bit`函数如何实现? 2. 如何在驱动程序中检测某个位是否被设置? 3. 原子位操作函数在多核系统中如何保证原子性? 4. 在设备驱动中,除了`set_bit`,还有哪些常用的位操作函数? [^1]: 非原子位清除函数`__clear_bit`的实现与`__set_bit`类似 [^2]: 输入设备驱动中设置事件类型的示例 [^3]: `test_and_set_bit`函数的实现与用途 [^4]: `__set_bit`函数的实现细节</think>### Linux 内核中 `set_bit` 函数详解 #### 1. **核心作用** `set_bit` 函数用于**原子性地设置内存中特定位的值(置为 1)**,其函数原型为: ```c void set_bit(int nr, volatile unsigned long *addr); ``` - **`nr`**: 目标位的序号(从 0 开始计数) - **`addr`**: 目标内存地址的指针 - **原子性**: 在多核/多线程环境下确保操作不可分割(避免竞态条件) #### 2. **实现原理** 以 x86 架构为例(源码见 `arch/x86/include/asm/bitops.h`): ```c static __always_inline void set_bit(long nr, volatile unsigned long *addr) { asm volatile("lock; bts %1,%0" : "+m" (*addr) : "Ir" (nr) : "memory"); } ``` - **`lock`**: 指令前缀,锁定内存总线保证原子性 - **`bts`**: 汇编指令(Bit Test and Set),将 `nr` 位设为 1 并返回原值 - **内存屏障**: `"memory"` 约束防止编译器重排序 #### 3. **非原子版本 `__set_bit`** 用于无需原子性的场景(如初始化),实现更简单[^4]: ```c static inline void __set_bit(int nr, volatile unsigned long *addr) { unsigned long mask = BIT_MASK(nr); unsigned long *p = addr + BIT_WORD(nr); *p |= mask; // 直接按位或操作 } ``` - `BIT_MASK(nr)`: 生成位掩码(如 `nr=3` → `0x00000008`) - `BIT_WORD(nr)`: 计算目标位所在长字偏移(`nr / 64`) #### 4. **典型应用场景** ##### (1) 输入设备驱动初始化(如触摸屏) ```c // 设置输入设备支持的事件类型 __set_bit(EV_KEY, input_dev->evbit); // 支持按键事件 __set_bit(EV_ABS, input_dev->evbit); // 支持绝对坐标事件 __set_bit(BTN_TOUCH, input_dev->keybit); // 支持触摸事件 __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); // 直接输入设备[^2] ``` ##### (2) 锁实现 ```c // 使用 test_and_set_bit 实现自旋锁 void spin_lock(atomic_t *lock) { while (test_and_set_bit(0, lock)) // 原子性测试并置位 cpu_relax(); // 忙等待 } ``` #### 5. **原子性 vs 非原子性选择** | **函数** | **原子性** | **使用场景** | **性能影响** | |-------------------|------------|----------------------------------|-------------------| | `set_bit` | 是 | 多核共享数据、中断上下文 | 较高(锁总线) | | `__set_bit` | 否 | 单线程初始化、局部变量操作 | 极低 | | `test_and_set_bit`| 是 | 锁实现、状态标志检测 | 中等(返回原值) | #### 6. **关键衍生函数** - **`clear_bit`**: 原子性清除位(置 0) - **`test_bit`**: 检查位是否置 1 - **`test_and_set_bit`**: 原子性置位并返回原值(锁机制核心)[^3] ```c int old_value = test_and_set_bit(3, &flags); // 置位第3位并返回旧值 ``` ### 常见问题解答 1. **为什么需要 `__set_bit`?** 在单线程初始化阶段(如 `probe` 函数),无需原子性开销,直接位操作效率更高[^4]。 2. **如何安全修改多核共享的位图?** 必须使用 `set_bit/clear_bit` 系列函数,避免使用 `__set_bit`(可能引发数据竞争)。 3. **`test_and_set_bit` 的返回值意义?** 返回目标位操作前的值: - 返回 `1` → 该位原先已置 1 - 返回 `0` → 该位原先为 0 常用于实现自旋锁(返回 0 表示获取锁成功)[^3]。 4. **位操作中的 `volatile` 作用?** 禁止编译器优化内存访问,确保每次操作都直接读写内存(对硬件寄存器操作关键)。 ### 相关问题 1. `clear_bit` 和 `__clear_bit` 的实现有何区别? 2. 如何在内核中安全地遍历位图? 3. `test_and_set_bit` 在自旋锁中如何避免死锁? 4. 在 ARM 架构下 `set_bit` 的实现有何不同? [^1]: 非原子位清除函数 `__clear_bit` 的实现与 `__set_bit` 类似 [^2]: 输入设备驱动中设置事件类型的示例 [^3]: `test_and_set_bit` 函数的实现与用途 [^4]: `__set_bit` 函数的实现细节
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值