2 分钟阅读 次阅读

1 进程休眠机制

进程把自己放入等待队列中,设置为不可执行状态,与等待队列相关的事件发生时,唤醒队列上的进程

1.1 以前的模式

if (!condition) {
    set_current_state(TASK_INTERRUPTIBLE);
    add_wait_queue(&wait_queue, &wait_entry); 
    schedule(); // 直接调度休眠
}
  1. 检查唤醒条件,如果不成立
  2. 放入等待队列中
  3. 设置进程为可唤醒状态
  4. 进入调度,切换进程

1.2 存在的问题

  1. 在设置状态之后,加入队列之前,如果发生了唤醒,则唤醒会丢失。
  2. 没有循环重新检查条件,无法应对虚假唤醒
  • 第一个问题具体指的是:
    1. 进程A检查条件,不满足,然后执行步骤A:设置状态为TASK_INTERRUPTIBLE。
    2. 在加入等待队列之前(即在执行步骤B之前),发生了中断,进程B运行,它使条件满足并调用wake_up()。由于进程A还没有加入队列,所以唤醒操作找不到进程A。
    3. 然后进程B执行完毕,进程A继续运行,它将自己加入等待队列(步骤B),然后调用schedule()休眠。但条件已经满足,而唤醒已经发生,所以进程A将一直休眠。
  • 第二个问题具体指的是:
    • 进程在设置状态并且加入等待队列后,被虚假唤醒(​​在条件变量或等待队列上的等待操作意外返回,尽管程序期望的条件尚未真正满足​,状态被设为RUNNING),此时正确操作应该是继续休眠,但是直接调用了schedule,而由于状态是RUNNING,schedule不会使进程再次进入休眠

2 改进

DEFINE_WAIT(wait);
while (1) {
    prepare_to_wait(&group->notification_waitq, &wait, TASK_INTERRUPTIBLE);
    if (signal_pending(current))
        break;
    schedule();
}
finish_wait(&group->notification_waitq, &wait);
  1. ​prepare_to_wait() 的原子性​​(解决核心问题)
    • 在​​同一把锁​​中完成:插入队列 + 设置进程状态
    • 确保唤醒操作wake_up()必须等待此临界区结束
    • 解决了第一个问题:设置状态与加入等待队列之间的间隔
  2. ​唤醒后的二次检查​​
    • 使用循环,每次从schedule()唤醒后先重新将进程放入等待队列中并设置状态,然后再检查条件是否真的满足了(if (signal_pending(current))):真的满足了则调用finish_wait恢复进程的RUNNING状态并从等待队列中移除;如果不是真的满足了,说明是虚假唤醒,则使用schedule重新进入休眠

add_wait_queue 函数调用链

kernel\wait.c void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) { unsigned long flags;

wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock, flags); } EXPORT_SYMBOL(add_wait_queue);

include\linux\wait.h static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new) { list_add(&new->task_list, &head->task_list); }

include\linux\list.h /**

  • list_add - add a new entry
  • @new: new entry to be added
  • @head: list head to add it after *
  • Insert a new entry after the specified head.
  • This is good for implementing stacks. */ static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); }

include\linux\list.h /*

  • Insert a new entry between two known consecutive entries. *
  • This is only for internal list manipulation where we know
  • the prev/next entries already! */ #ifndef CONFIG_DEBUG_LIST static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } #else extern void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next); #endif

prepare_to_wait 函数调用链

kernel\wait.c void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state) { unsigned long flags;

wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
if (list_empty(&wait->task_list))
	__add_wait_queue(q, wait);
set_current_state(state);
spin_unlock_irqrestore(&q->lock, flags); }

signal_pending 函数调用链

include\linux\sched.h static inline int signal_pending(struct task_struct *p) { return unlikely(test_tsk_thread_flag(p,TIF_SIGPENDING)); }

finish_wait 调用链

kernel\wait.c

/*
 * finish_wait - clean up after waiting in a queue
 * @q: waitqueue waited on
 * @wait: wait descriptor
 *
 * Sets current thread back to running state and removes
 * the wait descriptor from the given waitqueue if still
 * queued.
 */
void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
{
	unsigned long flags;

	__set_current_state(TASK_RUNNING);
	/*
	 * We can check for list emptiness outside the lock
	 * IFF:
	 *  - we use the "careful" check that verifies both
	 *    the next and prev pointers, so that there cannot
	 *    be any half-pending updates in progress on other
	 *    CPU's that we haven't seen yet (and that might
	 *    still change the stack area.
	 * and
	 *  - all other users take the lock (ie we can only
	 *    have _one_ other CPU that looks at or modifies
	 *    the list).
	 */
	if (!list_empty_careful(&wait->task_list)) {
		spin_lock_irqsave(&q->lock, flags);
		list_del_init(&wait->task_list);
		spin_unlock_irqrestore(&q->lock, flags);
	}
}

留下评论