select/wait_queue_t/poll_wait/schedule 理解

本文详细介绍了Linux内核中的调度函数schedule及等待队列的基本原理与应用。通过实例展示了如何利用等待队列进行进程间通信,以及在select系统调用中的poll_wait函数的具体工作流程。

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

  • schedule()
...
set_current_state(TASK_INTERRUPTIBLE);
schedule();
...
schedule_timeout();
// 定时唤醒,若指定时间内有其他wake_up,定时到达时不做wake_up操作
  • 在 schedle 函数的执行中,current 进程进入睡眠,而一个新的进程被运行,
  • 当下一次当前进程被唤醒并得到执行权时,又接着 schedule 后面的代码运行,

schedule函数


  • wait_queue_t
DECLARE_WAITQUEUE(wait, current);

#define __WAITQUEUE_INITIALIZER(name, tsk) {                \
    .private    = tsk,                        \
    .func        = default_wake_function,            \
    .task_list    = { NULL, NULL } }

#define DECLARE_WAITQUEUE(name, tsk)                    \
    wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
// 给wait这个等待队列赋值,
// private用来表示被唤醒的进程,
// 这里current代表了本进程, 
// 而func是被唤醒时调用的回调函数, 唤醒private
add_wait_queue(w_wait, wait);
// 将wait加入到等待队列w_wait中, 被唤醒时遍历w_wait队列
wake_up(w_wait);
// 唤醒w_wait, 遍历队列中的wait, 调用wait的func来唤醒private
>用法总结:
task 1
    DECLARE_WAITQUEUE(wait, current);
    add_wait_queue(w_wait, wait);
    __set_current_state(TASK_INTERRUPTIBLE);
    schedule();
    //task1 进程休眠
    ................//唤醒后从这儿执行
task 2 //唤醒task1
   wake_up(w_wait);

  • poll_wait

poll_wait()是用在select系统调用中的

// fs/select.c/do_select()
// struct file_operations fop; fop->poll指向驱动的poll函数, poll函数调用poll_wait() 

> 当用户调用select系统调用时,在内核流程为:
> 1. select系统调用会先调用 poll_initwait(&table); 
> 2. 然后调用你的 fop->poll(); 
> 3. 从而将current加到某个等待队列(这里调用poll_wait()), 并检查是否有效, 如果无效就调用 schedule_timeout() 去睡眠. 
> 4. 事件发生后,schedule_timeout()回来,调用fop->poll(); 检查到可以运行,就调用 poll_freewait(&table); 从而完成select系统调用. 
> 5. 重要的是fop->poll()里面要检查是否就绪, 如果是要返回相应标志 

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
    if (p && wait_address)
        p->qproc(filp, wait_address, p); //是在do_select中的poll_init_wait中为__pollwait
}

// 会在其他task唤醒 然后返回给do_select; do_select实现休眠等待唤醒和select中的延时

void poll_initwait(struct poll_wqueues *pwq)
{
    init_poll_funcptr(&pwq->pt, __pollwait);
    pwq->polling_task = current;
    pwq->triggered = 0;
    pwq->error = 0;
    pwq->table = NULL;
    pwq->inline_index = 0;
}

static void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p)
// Add a new entry 
{
    struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
    struct poll_table_entry *entry = poll_get_entry(pwq);
    if (!entry)
        return;
    get_file(filp);
    entry->filp = filp;
    entry->wait_address = wait_address;
    entry->key = p->key;
    init_waitqueue_func_entry(&entry->wait, pollwake);
    entry->wait.private = pwq;
    add_wait_queue(wait_address, &entry->wait); // 把p中的entry->wait加入到等待队列
}
> 驱动
> 实现为(和do_select配合使用):
  down(&dev->sem);
  poll_wait(filp, &dev->r_wait, wait);
  poll_wait(filp, &dev->w_wait, wait);
  /*fifo非空*/
  if (dev->current_len != 0)
  {
    mask |= POLLIN | POLLRDNORM;
  }
  /*fifo非满*/
  if (dev->current_len != GLOBALFIFO_SIZE)
  {
    mask |= POLLOUT | POLLWRNORM; 
  }
  up(&dev->sem);
### Linux 阻塞与非阻塞 I/O 模型区别 在 Linux 系统中,阻塞和非阻塞 I/O 是两种不同的输入输出处理方式。这两种模式的主要差异在于进程在等待 I/O 操作完成期间的状态。 #### 阻塞 I/O (Blocking I/O) 当一个进程发出阻塞 I/O 请求时,在该请求未得到响应之前,进程会被挂起并进入等待状态,直到数据准备就绪或操作完成为止[^1]。这意味着在此期间,CPU 不会分配时间给这个被挂起的进程,从而可以释放 CPU 资源用于其他任务。 对于阻塞 I/O 的实现,通常采用 `wait_queue` 来管理多个可能需要唤醒的任务列表。一旦有新的事件发生(比如设备准备好读取的数据),内核就会遍历相应的等待队列来激活这些休眠中的线程或进程。 ```c // C code example of blocking read using wait queue in a device driver context. static ssize_t my_device_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { DECLARE_WAIT_QUEUE_HEAD(wq); // Add current process to the waiting list and sleep until data is available or interrupted by signal. add_wait_queue(&wq, &wait); while (!data_available()) { // Check if there's any data ready for reading set_current_state(TASK_INTERRUPTIBLE); schedule(); // Put this thread into sleeping state if (signal_pending(current)) { remove_wait_queue(&wq, &wait); return -ERESTARTSYS; } } // Data processing logic here... remove_wait_queue(&wq, &wait); } ``` #### 非阻塞 I/O (Non-blocking I/O) 相比之下,非阻塞 I/O 并不会让调用它的进程陷入长时间的等待之中。相反,如果当前无法立即满足 I/O 请求,则函数会立刻返回错误码告知用户空间程序此时不可行的操作,并允许应用程序继续执行其他的逻辑而不必停留在原地不动[^2]。为了有效地管理和监控多个文件描述符上的活动情况,Linux 提供了像 `select()` 和 `poll()` 这样的系统调用来支持多路复用机制[^4]。 ```python import select import socket sock = socket.socket() sock.setblocking(False) # Set non-blocking mode on the socket. try: sock.connect(('example.com', 80)) except BlockingIOError as e: pass # Connection attempt has been initiated but not yet established. ready_to_write, _, _ = select.select([], [sock], [], timeout=5) if ready_to_write: print("Socket can now send/receive data.") else: print("Connection timed out without establishing connection.") ``` ### 应用场景分析 - **Web服务器**:由于 Web 服务端往往要同时处理大量并发连接请求,因此非常适合使用基于 `epoll` 或者更传统的 `select/poll` 实现的非阻塞 I/O 多路复用技术来提高效率和服务质量。 - **嵌入式开发**:在资源受限环境下工作的硬件控制系统可能会倾向于选择简单的阻塞 I/O 方案以简化编程复杂度;然而随着实时性和性能需求的增长,也会逐渐转向更为灵活高效的非阻塞设计思路。 - **数据库管理系统**:这类软件产品内部涉及复杂的磁盘访问操作,它们既可以通过配置参数调整默认行为为阻塞还是非阻塞形式,也可以利用高级特性如异步提交日志等手段进一步优化整体吞吐量表现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值