第5章:并发与竞态条件-16:Bit Operations

In continuation of the previous text 第5章:并发与竞态条件-15:Atomic Variables, let's GO ahead.

Bit Operations

The atomic_t type is good for performing integer arithmetic. It doesn’t work as well,
however, when you need to manipulate individual bits in an atomic manner. For that
purpose, instead, the kernel offers a set of functions that modify or test single bits
atomically. Because the whole operation happens in a single step, no interrupt (or
other processor) can interfere.

atomic_t 类型适用于整数算术运算,但当你需要以原子方式操作单个比特位时,它就不再适用了。针对这类场景,内核提供了一组函数,可原子化地修改或测试单个比特位 —— 由于整个操作在单个步骤中完成,任何中断(或其他处理器)都无法干扰操作过程。

Atomic bit operations are very fast, since they perform the operation using a single
machine instruction without disabling interrupts whenever the underlying platform
can do that. The functions are architecture dependent and are declared in <asm/
bitops.h>. They are guaranteed to be atomic even on SMP computers and are useful
to keep coherence across processors.

原子位操作的执行速度极快:只要底层硬件平台支持,这些操作会通过单条机器指令完成,且无需禁用中断。这类函数与架构强相关,声明在 <asm/bitops.h> 头文件中;它们能保证在 SMP(对称多处理)系统中仍具备原子性,可有效维持多处理器间的数据一致性。

Unfortunately, data typing in these functions is architecture dependent as well. The
nr argument (describing which bit to manipulate) is usually defined as int but is
unsigned long for a few architectures. The address to be modified is usually a pointer
to unsigned long, but a few architectures use void * instead.

需要注意的是,这些函数的参数类型也与架构相关:

  • nr 参数(指定要操作的比特位序号)通常定义为 int 类型,但部分架构下为 unsigned long

  • 待修改数据的地址参数,通常为指向 unsigned long 的指针,但少数架构中使用 void * 类型。

The available bit operations are:

// 将addr指向的数据中第nr位设为1
void set_bit(nr, void *addr);

/*
 * 将addr指向的unsigned long类型数据中第nr位清零。
 * 其他语义与set_bit一致。
 */
void clear_bit(nr, void *addr);

// 翻转addr指向数据中第nr位的值(0变1,1变0)
void change_bit(nr, void *addr);

/*
 * 该函数是唯一无需原子执行的位操作函数;
 * 仅返回addr指向数据中第nr位的当前值。
 */
test_bit(nr, void *addr);

// 原子化设置第nr位,并返回该位操作前的原值
int test_and_set_bit(nr, void *addr);
// 原子化清零第nr位,并返回该位操作前的原值
int test_and_clear_bit(nr, void *addr);
/*
 * 原子化翻转第nr位,语义与上述函数一致,
 * 同时返回该位操作前的原值。
 */
int test_and_change_bit(nr, void *addr);

When these functions are used to access and modify a shared flag, you don’t have to
do anything except call them; they perform their operations in an atomic manner.
Using bit operations to manage a lock variable that controls access to a shared vari-
able, on the other hand, is a little more complicated and deserves an example. Most
modern code does not use bit operations in this way, but code like the following still
exists in the kernel.

当使用这些函数访问 / 修改共享标志位时,只需直接调用即可 —— 它们本身已保证操作的原子性。但如果想用位操作实现 “控制共享变量访问的锁”,逻辑会稍复杂,以下是一个示例说明(尽管现代内核代码已很少这样用,但内核中仍存在此类遗留代码)。

A code segment that needs to access a shared data item tries to atomically acquire a
lock using either test_and_set_bit or test_and_clear_bit. The usual implementation is
shown here; it assumes that the lock lives at bit nr of address addr. It also assumes
that the bit is 0 when the lock is free or nonzero when the lock is busy.

若代码段需要访问共享数据,可通过 test_and_set_bittest_and_clear_bit 原子化获取锁。典型实现逻辑如下:

  • 假设锁对应 addr 地址的第 nr 位;

  • 约定:比特位为 0 表示锁空闲,非 0 表示锁被占用。

/* 尝试获取锁 */
while (test_and_set_bit(nr, addr) != 0)
    wait_for_a_while(); // 锁被占用,等待

/* 执行临界区操作 */

/* 释放锁,并检查状态 */
if (test_and_clear_bit(nr, addr) == 0)
    something_went_wrong(); // 锁已被释放:出现错误

If you read through the kernel source, you find code that works like this example. It
is, however, far better to use spinlocks in new code; spinlocks are well debugged,

  • they handle issues like interrupts and kernel preemption, and others reading your

code do not have to work to understand what you are doing.

在内核源码中能找到类似上述逻辑的代码,但新代码应优先使用自旋锁而非位操作实现锁机制:自旋锁经过充分调试,能妥善处理中断、内核抢占等问题,且其他开发者阅读代码时,无需额外分析即可理解你的锁逻辑。

补充说明:

1. 原子位操作的核心特性

  • 原子性:操作全程无中断 / 抢占干扰,多 CPU 并发修改同一位时不会出现竞争;

  • 轻量级:无锁的上下文切换 / 自旋开销,仅单条硬件指令(如 x86 的bts/btr指令);

  • 架构兼容性:<asm/bitops.h> 会根据 CPU 架构(x86/ARM/RISC-V)封装底层实现,上层代码无需适配。

2. test_bit 非原子性的原因

  • test_bit 仅读取位值,不修改数据 —— 即使多 CPU 同时读取同一位,结果也不会出错,因此无需原子化;而 set_bit/clear_bit 等写操作必须原子化,否则会出现 “读 - 改 - 写” 竞争。

3. 位操作实现锁的缺陷

  • 无抢占 / 中断保护:持有位锁时若发生中断,中断处理程序若也尝试获取该锁,会导致死锁;

  • 无锁排序机制:多把位锁同时获取时,易出现死锁,且无统一的排序规则;

  • 可读性差:需开发者自行约定位的含义,不如自旋锁语义清晰。

4. 原子位操作的正确适用场景

  • 共享标志位:如设备 “忙 / 闲” 状态、中断 “使能 / 禁用” 标记;

  • 位图管理:如内存页分配的位图、设备中断掩码的位操作;

  • 轻量级状态切换:无需完整锁机制的单比特状态修改(如开关类操作)。

5. 现代内核的替代方案

  • 锁机制:优先使用 spinlock_t/mutex_t 替代位操作实现锁;

  • 标志位管理:仍可使用原子位操作,但仅用于纯状态标记,不用于临界区保护;

  • 多比特操作:若需原子化修改多个比特位,需结合自旋锁 + 普通位操作。

Linux驱动程序开发核心知识体系 第一Linux驱动程序概述 驱动程序的作用和特点 操作系统硬件之间的桥梁 控制硬件设备并提供统一接口 驱动程序的开发基础 开发环境搭建 内核模块编写、编译加载流程 Linux内核重要头文件目录 <linux/module.h>:模块初始化退出 <linux/fs.h>:文件系统接口定义 其他常用头文件支持驱动功能实现 Linux内核设备类型 字符设备:逐字节访问,如串口、蜂鸣器 块设备:以块为单位读写,如硬盘 网络设备:处理网络数据包收发 Linux内存管理机制 kmalloc:分配小块物理连续内存 vmalloc:分配虚拟连续大内存 内存释放函数:kfree、vfree 字符设备驱动实例:BEEP驱动 蜂鸣器控制原理 完整开发流程演示:注册、操作结构体实现、设备访问 第二:字符设备驱动程序 字符设备驱动基本结构 设备号申请(静/动) cdev结构体初始化注册 file_operations结构体实现常见操作接口 gfree、page相关函数 get_zeroed_page:获取清零的内存页 __get_free_pages / __free_pages:多页内存管理 页大小内存对齐概念 vmalloc及其辅助函数 分配非连续物理但连续虚拟内存 应用于大内存缓冲区场景 vfree释放vmalloc分配的内存 虚拟地址物理地址关系 地址映射机制:页表管理 ioremap用于I/O内存映射 内核空间中的地址转换函数 第三并发控制 并发问题 多任务环境下共享资源冲突 数据不一致风险 原子操作 整数原子操作:atomic_t类型 位原子操作:set_bit、clear_bit等 不可中断的单一指令操作 自旋锁(Spinlock) 忙等待机制,适用于短临界区 中断上下文可用 注意避免死锁和长时间持有 信号量互斥体 信号量(semaphore):允许多个并发访问 互斥体(mutex):仅允许一个持有者 用户内核同步机制差异 完成量(completion) 实现线程间事件通知 wait_for_completion complete 配对使用 适用于主线程等待工作完成场景 第四:阻塞和非阻塞 I/O 阻塞方式 read/write调用挂起直到条件满足 使用等待队列实现睡眠唤醒机制 非阻塞方式 即时返回错误码 -EAGAIN 或 -EWOULDBLOCK 用户程序轮询处理 Poll、Select机制 支持多文件描述符监听 驱动需实现poll函数指针 返回POLLIN/POLLOUT等事件状 第五:中断和时钟 中断处理程序结构 request_irq注册中断处理函数 中断服务例程(ISR)快速响应 中断顶半部和底半部 顶半部:禁中断执行,响应快 底半部机制: tasklet:软中断基础上的延迟处理 工作队列(workqueue):可在进程上下文运行 中断共享 多设备共用同一IRQ线 中断处理函数中通过硬件状判断来源 注册时需提供设备标识 内核定时器 struct timer_list定义定时任务 mod_timer设置超时时间 回调函数在软中断上下文中执行 第六:内存 I/O I/O端口和I/O内存 I/O端口:x86架构专用IO空间(in/out指令) I/O内存:内存映射型寄存器(MMIO) I/O操作函数 端口读写:inb/outb, inw/outw, inl/outl 内存映射寄存器读写:ioread32/iowrite32 访问具有屏障语义的操作函数 申请释放设备I/O资源 request_region / release_region:申请IO端口区间 request_mem_region / release_mem_region:申请内存映射区域 防止资源冲突,提升系统稳定性 生成思维导图
11-14
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeplyMind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值