linux 进程睡眠与wait_even t_interruptible()分析

本文详细解析了Linux内核中进程的调度机制,特别是针对I/O操作中的等待队列和进程状态转换的过程。通过具体代码示例,阐述了如何在内核中实现进程的睡眠与唤醒,并提供了一个串口网卡驱动的实例来展示这一机制的应用。

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

驱动中最底层处理buffer通常会对该进程睡眠和唤醒操作,比如上层写入buffer时,内核buffer缓冲区是满的,那么写入进程需要阻塞住,直到内核buffer缓冲区可以容纳上层写入的buffer才唤醒该进程。

首先必须认清一个事实:
先将进程置为INTERRUPTIBLE,再调用schedule()后本进程才睡眠;而不是执行set_current_state(TASK_INTERRUPTIBLE)后就睡眠了。
set_current_state(TASK_INTERRUPTIBLE);    
schedule();       

下面是对wait_event_interruptible()的流程分析,有助于自己理解对进程的处理。wait_event_interruptible()只是封装了对进程处理的函数,理解后我们可以单独操作等待队列加进程处理函数而实现睡眠与唤醒功能。

#define wait_event_interruptible(wq, condition) 
({ 
int __ret = 0; 
if (!(condition)) 
__wait_event_interruptible(wq, condition, __ret); 
__ret; 
})

#define __wait_event_interruptible(wq, condition, ret) 
do { 
DEFINE_WAIT(__wait); 

for (;;) { 
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); //将进程设置成INTERRUPTIBLE 
if (condition)             //通常第一次进来条件为假 
break; 
if (!signal_pending(current)) { //signal_pending无信号处理返回0,反之非0 
schedule(); //调用schedule()一直等待返回,进程睡眠直到有wake_up唤醒后 schedule()返回 执行continue结束本次循环,接着执行下次循环condition为真break出来
//wait_event_interruptible()返回0;
continue; 
} 
ret = -ERESTARTSYS; 
break; 
} 
finish_wait(&wq, &__wait); 
} while (0)



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);
}
EXPORT_SYMBOL(prepare_to_wait);

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);
}
}


还有一个实例,串口网卡驱动的buffer读取功能,发送很好实现将socket里面的buffer丢给串口文件的write函数即可。但是实现有串口buffer过来就读取到socket buffer里面就不容易。
该网卡驱动实现方法:在register net设备的时候起一个RX进程,然后去poll串口文件,poll_schedule()实现进程进程睡眠,poll_initwait(&wait_table)会实现软中断当有buffer进来时就会wake_up()唤醒该进程,进而读取buffer 上交到socket buffer里面

static int wi629_poll(struct file *filep)
{
int ret;
unsigned int mask = 0;
struct poll_wqueues wait_table;
mm_segment_t oldfs;

poll_initwait(&wait_table);   /* register callback function: __pollwait() */

for(;;) {
set_current_state(TASK_INTERRUPTIBLE);

oldfs = get_fs(); set_fs(get_ds());
if (filep->f_op->poll) 
mask = filep->f_op->poll(filep, &wait_table.pt);
set_fs(oldfs);

if (mask & POLLIN) {  /* got data */
ret = 0;
break;
}

if (wait_table.error) {
printk(KERN_INFO "  error in f_op->poll()\n");
ret = wait_table.error;
break;
}

if (signal_pending(task_rx)) {  /* got signal */
ret = -ERESTARTSYS;
break;
}

poll_schedule(&wait_table,TASK_INTERRUPTIBLE); /* sleep, and wait to be wakeup */
}
set_current_state(TASK_RUNNING);
poll_freewait(&wait_table);

return ret;
}


<think>好的,我现在需要解释`wait_for_completion_interruptible_timeout`函数的作用。首先,我得回想一下Linux内核中的同步机制。用户可能是在内核开发或驱动编写时遇到了这个函数,想了解它的用途和使用场景。 用户之前问了`spi_async`,现在转向`wait_for_completion_interruptible_timeout`,可能是在处理异步操作后的等待机制。这个函数属于内核的completion机制的一部分,用于线程间的同步。我需要明确这个函数的功能、参数、返回值以及注意事项。 首先,函数名分解:`wait_for_completion_interruptible_timeout`。关键词是“completion”,“interruptible”,“timeout”。所以,它应该是在等待某个完成事件,可以被打断,且有超时机制。 接下来,我需要确认它的作用。根据记忆,completion机制用于一个线程等待另一个线程完成某个任务。比如,一个线程启动一个操作,然后等待另一个线程发出完成信号。`wait_for_completion_interruptible_timeout`允许在等待时被信号中断,同时设置超时,避免无限等待。 参数方面,函数通常需要指向`struct completion`的指针,超时时间(可能是jiffies)。返回值可能表示剩余时间,或者错误码,比如被信号打断或超时。 使用场景可能是在驱动程序中,当需要等待某个硬件操作完成,但不想无限阻塞,同时允许用户空间进程被信号中断。比如,等待DMA传输完成,但用户按Ctrl+C时可以中断等待。 需要对比其他类似函数,比如`wait_for_completion`(不可中断,无超时)和`wait_for_completion_timeout`(可超时但不可中断)。而`wait_for_completion_interruptible_timeout`结合了两者:可中断且有超时。 在解释时,要强调它的三个特性:等待完成、可中断、超时。同时,给出典型的使用示例,比如在驱动中启动一个异步操作后,调用此函数等待完成,处理可能的返回值,如超时或中断。 还需要注意,调用前需要初始化completion变量,通常用`init_completion`或`DECLARE_COMPLETION`。另外,完成事件由`complete()`或`complete_all()`触发。 可能的错误处理包括检查返回值是否为0(超时)、正数(剩余时间),或负数(被信号中断)。使用时需确保在正确的上下文中调用,比如进程上下文,因为可能睡眠。 需要验证这些信息是否正确,可能需要回忆内核源码或文档中的相关内容,确保没有记错函数名、参数顺序或返回值含义。如果有不确定的地方,比如timeout的单位是否是jiffies,需要确认。是的,超时时间通常以jiffies为单位。 总结,这个函数的作用是允许当前线程等待一个完成事件,直到事件发生、超时或被信号中断。适用于需要灵活控制等待时间的场景,同时提高系统的响应性。</think>`wait_for_completion_interruptible_timeout` 是 Linux 内核中用于 **等待某个“完成事件”(Completion Event)** 的函数。它结合了 **可中断等待** 和 **超时机制**,适用于需要灵活控制线程/进程阻塞时间的场景。以下是详细说明: --- ### **1. 核心功能** - **等待完成事件**:阻塞当前线程/进程,直到某个任务完成(由其他线程触发完成信号)。 - **可被信号中断**:在等待过程中,如果线程收到用户空间的信号(如 `SIGKILL`),可提前退出等待。 - **超时限制**:若在指定时间内未等到完成事件,函数自动返回,避免无限阻塞。 --- ### **2. 函数原型** ```c long wait_for_completion_interruptible_timeout( struct completion *comp, unsigned long timeout ); ``` - **参数**: - `comp`:指向 `struct completion` 的指针,表示要等待的完成事件。 - `timeout`:超时时间(单位为 **jiffies**,需用 `msecs_to_jiffies()` 转换毫秒)。 - **返回值**: - `> 0`:剩余未超时的时间(jiffies)。 - `0`:超时后仍未等到完成事件。 - `-ERESTARTSYS`:等待被信号中断。 --- ### **3. 典型使用场景** 1. **驱动开发**:等待硬件操作完成(如 DMA 传输、设备响应)。 2. **内核线程同步**:协调多个线程的执行顺序。 3. **超时控制**:避免因设备无响应导致系统死锁。 --- ### **4. 工作流程** 1. **初始化完成事件**: ```c struct completion comp; init_completion(&comp); // 或 DECLARE_COMPLETION(comp); ``` 2. **在等待方调用**: ```c long ret = wait_for_completion_interruptible_timeout(&comp, timeout); ``` 3. **在完成方触发事件**: ```c complete(&comp); // 唤醒一个等待者 complete_all(&comp); // 唤醒所有等待者 ``` --- ### **5. 关键特性** #### **可中断性(Interruptible)** - 允许等待过程被用户空间的 **信号(Signal)** 打断。 - 若被中断,返回 `-ERESTARTSYS`,通常需在调用处处理信号(如重启系统调用)。 #### **超时机制(Timeout)** - 超时时间单位为 **jiffies**(内核时间单位),需用 `msecs_to_jiffies(ms)` 转换毫秒值。 ```c unsigned long timeout = msecs_to_jiffies(5000); // 5秒超时 ``` - 若超时前完成事件未触发,返回 `0`。 --- ### **6. 其他完成函数的对比** | 函数名 | 可中断 | 超时支持 | 行为 | |---------------------------------------|--------|----------|----------------------------------------------------------------------| | `wait_for_completion()` | ❌ | ❌ | 无限阻塞,直到完成事件触发。 | | `wait_for_completion_interruptible()` | ✔️ | ❌ | 可被信号中断,但无超时。 | | `wait_for_completion_timeout()` | ❌ | ✔️ | 支持超时,但不可被信号中断。 | | **`wait_for_completion_interruptible_timeout()`** | ✔️ | ✔️ | **同时支持中断和超时**。 | --- ### **7. 使用示例(驱动代码片段)** ```c #include <linux/completion.h> struct completion data_ready; // 初始化完成事件 init_completion(&data_ready); // 等待方(例如读取数据的线程) long ret = wait_for_completion_interruptible_timeout(&data_ready, msecs_to_jiffies(3000)); if (ret == 0) { printk("等待超时!\n"); } else if (ret == -ERESTARTSYS) { printk("等待被信号中断!\n"); } else { printk("数据已就绪!\n"); } // 完成方(例如中断处理函数) void irq_handler(void) { complete(&data_ready); // 触发完成事件 } ``` --- ### **8. 注意事项** 1. **上下文限制**: - 需在 **进程上下文** 中调用(可能睡眠),不可在原子上下文(如中断)使用。 2. **资源竞争**: - 确保在调用 `wait_for_completion_*` 前,完成事件尚未被触发。 3. **超时精度**: - 超时时间依赖内核的 jiffies 时钟,精度受 `HZ` 配置影响(通常为 1ms~10ms)。 4. **信号处理**: - 若返回 `-ERESTARTSYS`,通常需向上层返回 `-EINTR`,由用户空间决定是否重试。 --- ### **总结** `wait_for_completion_interruptible_timeout` 是 Linux 内核中 **兼顾灵活性和安全性的同步机制**,适用于需控制等待时间且允许用户中断的场景(如设备驱动、异步任务协调)。正确使用需结合超时设置和信号处理,以确保系统稳定性和响应性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值