-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
理解 Linux 等待队列 #7
Comments
阻塞与非阻塞阻塞(等待队列,中断通知) 与 非阻塞(轮询,异步通知) 是访问设备的两种方式。 阻塞 是指进程访问某个资源时,资源没有就绪,此时进程被挂起在当前资源的等待队列里,调用者表现为“睡着”了一样;当资源就绪时,再唤醒等待队列中的所有进程,进行继续执行。 非阻塞是在资源不就绪时进程不挂起,要么返回错误,要么一直轮询等待,直到资源就绪,在这个过程中,进程一直在运行而非挂起。 要知道的是,阻塞并不是 效率低,如果进程持续轮询等待,会不断浪费 CPU 资源,如果让进程挂起,就可以将 CPU 资源让给其他进程。被挂起的进程进入休眠状态,等资源就绪时,通常会伴随一个中断,然后唤醒休眠的进程继续工作。 |
等待队列的实现在 Linux 内核中,等待队列通过一个双向循环链表来实现。这个双向循环链表由 // tools/include/linux/types.h
// 链表中用于链接前后结点的结构
struct list_head {
struct list_head *next, *prev;
}
// include/linux/wait.h
struct wait_queue_head {
spinlock_t lock; // 自旋锁
struct list_head head; // 指向链表队列中的第一个和最后一个结点
};
typedef struct wait_queue_head wait_queue_head_t; // 链表头
// 链表中的一个结点
struct wait_queue_entry {
unsigned int flags; // 标志,如 WQ_FLAG_EXCLUSIVE,表示等待的进程应该独占资源(解决惊群现象)
void *private; // 等待进程相关信息,如 task_struct
wait_queue_func_t func; // 唤醒函数
struct list_head entry; // 前后结点
}; 抛开唤醒机制,单纯看循环双链表的实现,有两个方法:
|
等待 与 唤醒主要有以下相关函数: wait_event(queue,condition);等待以queue为等待队列头等待队列被唤醒,condition必须满足,否则阻塞
wait_event_interruptible(queue,condition);可被信号打断
wait_event_timeout(queue,condition,timeout);阻塞等待的超时时间,时间到了,不论condition是否满足,都要返回
wait_event_interruptible_timeout(queue,condition,timeout); 有超时同时信号可被打断 我们着重关注第一个: /**
* wait_event - sleep until a condition gets true
* @wq_head: the waitqueue to wait on
* @condition: a C expression for the event to wait for
*
* The process is put to sleep (TASK_UNINTERRUPTIBLE) until the
* @condition evaluates to true. The @condition is checked each time
* the waitqueue @wq_head is woken up.
*
* wake_up() has to be called after changing any variable that could
* change the result of the wait condition.
*/
#define wait_event(wq_head, condition) \
do { \
might_sleep(); \
if (condition) \
break; \
__wait_event(wq_head, condition); \
} while (0) 我们将 #define ___wait_event(wq_head, condition, state, exclusive, ret, cmd) \
({ \
__label__ __out; \
struct wait_queue_entry __wq_entry; \
long __ret = ret; /* explicit shadow */ \
\
init_wait_entry(&__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0); \
for (;;) { \
long __int = prepare_to_wait_event(&wq_head, &__wq_entry, state);\
\
if (condition) \
break; \
\
if (___wait_is_interruptible(state) && __int) { \
__ret = __int; \
goto __out; \
} \
\
cmd; \
} \
finish_wait(&wq_head, &__wq_entry); \
__out: __ret; \
}) 看看 void init_wait_entry(struct wait_queue_entry *wq_entry, int flags)
{
wq_entry->flags = flags;
wq_entry->private = current;
wq_entry->func = autoremove_wake_function;
INIT_LIST_HEAD(&wq_entry->entry);
}
int autoremove_wake_function(struct wait_queue_entry *wq_entry, unsigned mode, int sync, void *key)
{
int ret = default_wake_function(wq_entry, mode, sync, key);
if (ret)
list_del_init_careful(&wq_entry->entry);
return ret;
}
int default_wake_function(wait_queue_entry_t *curr, unsigned mode, int wake_flags,
void *key)
{
WARN_ON_ONCE(IS_ENABLED(CONFIG_SCHED_DEBUG) && wake_flags & ~WF_SYNC);
return try_to_wake_up(curr->private, mode, wake_flags);
}
/**
* try_to_wake_up - wake up a thread
* @p: the thread to be awakened
* @state: the mask of task states that can be woken
* @wake_flags: wake modifier flags (WF_*)
*
* Conceptually does:
*
* If (@state & @p->state) @p->state = TASK_RUNNING.
*
* If the task was not queued/runnable, also place it back on a runqueue.
*
* This function is atomic against schedule() which would dequeue the task.
*
* It issues a full memory barrier before accessing @p->state, see the comment
* with set_current_state().
*
* Uses p->pi_lock to serialize against concurrent wake-ups.
*
* Relies on p->pi_lock stabilizing:
* - p->sched_class
* - p->cpus_ptr
* - p->sched_task_group
* in order to do migration, see its use of select_task_rq()/set_task_cpu().
*
* Tries really hard to only take one task_rq(p)->lock for performance.
* Takes rq->lock in:
* - ttwu_runnable() -- old rq, unavoidable, see comment there;
* - ttwu_queue() -- new rq, for enqueue of the task;
* - psi_ttwu_dequeue() -- much sadness :-( accounting will kill us.
*
* As a consequence we race really badly with just about everything. See the
* many memory barriers and their comments for details.
*
* Return: %true if @p->state changes (an actual wakeup was done),
* %false otherwise.
*/
static int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags); 重点在最后的 |
进程没活干的时候让它“睡眠”,等有活干的时候再“唤醒”它。怎么找到这个已经睡着的进程让它醒来?
这一切,都是通过一个叫 等待队列 的数据结构实现的。
The text was updated successfully, but these errors were encountered: