Linux wait_on_buffer函数研究

本文深入探讨Linux0.11内核中的wait_on_buffer函数,解析其在关闭和开启中断之间的目的,以及如何保护临界区,避免并发修改。通过分析,揭示了该函数如何在内核态下保证进程或任务的连续性,并讨论了中断禁止对系统的影响。

      Linux0.11内核中的wait_on_buffer和wait_on_inode函数是非常有代表性的延迟性函数处理过程,网上关于这两个函数的讨论

也很多,最主要的一个问题是为什么要在判断b_lock之前关中断,这个问题也困扰了我很长时间,查了不少帖子,学到不少东西,但总是

觉得有些细节没有弄清楚,因此借着自己实践开发一个小OS的过程,研究了一下wait_on_buffer,还是学到不少东西的。贴出来,分享

给大家,欢迎讨论~

 

Linux0.11版函数定义:

static inline void wait_on_buffer(struct buffer_head * bh)
{
    cli();
    while (bh->b_lock)
        sleep_on(&bh->b_wait);
    sti();
}

 

分析如下:

1.       首先可以明确,wait_on_buffer是工作在内核态的函数。更进一步应当理解为两种可能性:

          或者是当前某个用户态的进程请求数据进而引发的,那么可以认为是当前进程进入了内核态;又或者是内核本身的某个任务

          引发的操作。不管怎样wait_on_buffer背后是代表这某个进程或者任务的。

 

2.       wait_on_buffer中通过cli和sti起到一个保

static int binder_thread_read(struct binder_proc *proc, struct binder_thread *thread, binder_uintptr_t binder_buffer, size_t size, binder_size_t *consumed, int non_block) { void __user *buffer = (void __user *)(uintptr_t)binder_buffer; void __user *ptr = buffer + *consumed; void __user *end = buffer + size; int ret = 0; bool nothing_to_do = false; bool force_spawn = false; int wait_for_proc_work; 1. Condition *consumed == 0, taking true branch. if (*consumed == 0) { 2. Condition __range_ok(__p, 4UL /* sizeof (*__p) */), taking true branch. 3. Switch case value 4. 4. Breaking from switch. 5. Falling through to end of if statement. 6. Condition ({...; __pu_err;}), taking false branch. if (put_user(BR_NOOP, (uint32_t __user *)ptr)) return -EFAULT; ptr += sizeof(uint32_t); } retry: 7. lock: Locking proc->inner_lock. binder_inner_proc_lock(proc); 8. def: Assigning data that might be protected by the lock to wait_for_proc_work. wait_for_proc_work = binder_available_for_proc_work_ilocked(thread); 9. unlock: Unlocking proc->inner_lock. wait_for_proc_work might now be unreliable because other threads can now change the data that it depends on. binder_inner_proc_unlock(proc); thread->looper |= BINDER_LOOPER_STATE_WAITING; 10. Condition wait_for_proc_work, taking false branch. 11. Condition !!thread->transaction_stack, taking true branch. 12. unlock: Unlocking proc->inner_lock. wait_for_proc_work might now be unreliable because other threads can now change the data that it depends on. 13. Condition !binder_worklist_empty(proc, &thread->todo), taking true branch. trace_binder_wait_for_work(wait_for_proc_work, !!thread->transaction_stack, !binder_worklist_empty(proc, &thread->todo)); 14. Condition wait_for_proc_work, taking false branch. if (wait_for_proc_work) { if (!(thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED))) { binder_user_error("%d:%d ERROR: Thread waiting for process work before calling BC_REGISTER_LOOPER or BC_ENTER_LOOPER (state %x)\n", proc->pid, thread->pid, thread->looper); wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2); } trace_android_vh_binder_restore_priority(NULL, current); binder_restore_priority(thread, &proc->default_priority); } 15. Condition non_block, taking true branch. if (non_block) { 16. Condition wait_for_proc_work, taking false branch. 17. Condition !binder_has_work(thread, wait_for_proc_work), taking false branch. if (!binder_has_work(thread, wait_for_proc_work)) ret = -EAGAIN; 18. Falling through to end of if statement. } else { ret = binder_wait_for_work(thread, wait_for_proc_work); } thread->looper &= ~BINDER_LOOPER_STATE_WAITING; 19. Condition ret, taking false branch. if (ret) return ret; while (1) { uint32_t cmd; struct binder_transaction_data_secctx tr; struct binder_transaction_data *trd = &tr.transaction_data; struct binder_work *w = NULL; struct list_head *list = NULL; struct binder_transaction *t = NULL; struct binder_thread *t_from; size_t trsize = sizeof(*trd); 20. lockagain: Locking proc->inner_lock again. binder_inner_proc_lock(proc); 178628: (#2 of 2): Value not atomically updated (ATOMICITY) use: Using an unreliable value of wait_for_proc_work inside the second locked section. If the data that wait_for_proc_work depends on was changed by another thread, this use might be incorrect. CID 178628:(#1 of 2):Value not atomically updated (ATOMICITY) [ "select issue" ] trace_android_vh_binder_select_worklist_ilocked(&list, thread, proc, wait_for_proc_work); trace_android_vh_binder_select_special_worklist(&list, thread, proc, wait_for_proc_work, &nothing_to_do); if (list) goto skip; else if (nothing_to_do) goto no_work; if (!binder_worklist_empty_ilocked(&thread->todo)) list = &thread->todo; else if (!binder_worklist_empty_ilocked(&proc->todo) && wait_for_proc_work) list = &proc->todo; else { } 这段代码报了 值未进行原子更新 帮我看一下
最新发布
06-18
### 修复 `binder_thread_read` 函数中 `wait_for_proc_work` 的非原子性使用问题 在多线程环境下,`wait_for_proc_work` 的值可能被多个线程同时访问或修改。如果未采取适当的同步机制,则可能导致竞态条件,从而引发不可预期的行为。以下是针对该问题的具体分析和修复方案。 #### 问题分析 1. **竞态条件的来源**: - 在函数中,`wait_for_proc_work` 被多次读取和写入[^2]。 - 某些操作(如 `trace_android_vh_binder_select_worklist_ilocked` 和 `trace_android_vh_binder_select_special_worklist`)依赖于 `wait_for_proc_work` 的值,但这些操作并未确保对 `wait_for_proc_work` 的访问是原子的。 - 如果其他线程在此期间修改了 `wait_for_proc_work` 的值,则可能导致不一致的状态。 2. **潜在影响**: - 线程可能基于过时的 `wait_for_proc_work` 值做出错误决策,例如跳过某些工作项或重复处理同一项工作。 - 在极端情况下,这种竞态条件可能导致 Binder 驱动程序中的死锁或资源泄漏。 #### 修复方案 为了解决上述问题,可以采用以下方法之一: 1. **使用原子操作**: - 将 `wait_for_proc_work` 定义为原子类型(如 Linux 内核中的 `atomic_t` 或 C11 标准中的 `stdatomic.h`),并使用原子操作对其进行更新和读取。 - 示例代码如下: ```c atomic_t wait_for_proc_work; // 更新 wait_for_proc_work atomic_set(&proc->wait_for_proc_work, 1); // 读取 wait_for_proc_work int value = atomic_read(&proc->wait_for_proc_work); ``` 2. **引入细粒度锁**: - 使用自旋锁或其他轻量级锁保护对 `wait_for_proc_work` 的访问。 - 示例代码如下: ```c spinlock_t wait_lock; // 初始化锁 spin_lock_init(&proc->wait_lock); // 更新 wait_for_proc_work spin_lock(&proc->wait_lock); proc->wait_for_proc_work = 1; spin_unlock(&proc->wait_lock); // 读取 wait_for_proc_work spin_lock(&proc->wait_lock); int value = proc->wait_for_proc_work; spin_unlock(&proc->wait_lock); ``` 3. **结合内存屏障**: - 在关键点插入内存屏障以确保对共享变量的更新能够被其他线程及时看到。 - 示例代码如下: ```c // 更新 wait_for_proc_work smp_wmb(); // 写内存屏障 proc->wait_for_proc_work = 1; // 读取 wait_for_proc_work int value = proc->wait_for_proc_work; smp_rmb(); // 读内存屏障 ``` 4. **重构逻辑**: - 如果可能,尽量减少对 `wait_for_proc_work` 的直接依赖,转而使用信号量或事件通知机制来管理线程间的工作分配。 - 示例代码如下: ```c sem_t work_available_sem; // 初始化信号量 sem_init(&proc->work_available_sem, 0, 0); // 通知有新工作可用 sem_post(&proc->work_available_sem); // 等待工作 sem_wait(&proc->work_available_sem); ``` #### 具体修复示例 以下是结合原子操作和内存屏障的修复示例: ```c #include <linux/atomic.h> #include <linux/compiler.h> // 定义 wait_for_proc_work 为原子类型 atomic_t wait_for_proc_work; // 更新 wait_for_proc_work void update_wait_for_proc_work(struct binder_proc *proc) { atomic_set(&proc->wait_for_proc_work, 1); // 使用原子操作设置值 smp_wmb(); // 写内存屏障,确保更新对其他 CPU 可见 } // 读取 wait_for_proc_work int read_wait_for_proc_work(struct binder_proc *proc) { smp_rmb(); // 读内存屏障,确保读取值是最新的 return atomic_read(&proc->wait_for_proc_work); // 使用原子操作读取值 } // 修改 trace_android_vh_binder_select_worklist_ilocked 和 trace_android_vh_binder_select_special_worklist 的调用 void trace_functions(struct binder_proc *proc, struct list_head **list, bool *nothing_to_do) { int value = read_wait_for_proc_work(proc); // 使用原子读取 if (!binder_worklist_empty_ilocked(&thread->todo)) { *list = &thread->todo; } else if (!binder_worklist_empty_ilocked(&proc->todo) && value) { *list = &proc->todo; } else { *nothing_to_do = true; } } ``` #### 修复后的优势 - 确保对 `wait_for_proc_work` 的访问是原子的,避免竞态条件。 - 提高代码的可维护性和可读性。 - 在多核处理器上,通过内存屏障确保线程间的一致性。 ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值