4.2.7 任务延时
4.2.7 任务延时
📚 本节导读
学习时长: 约 10 分钟
难度级别: ⭐⭐☆☆☆
前置知识: 任务状态机、SLEEP 状态、系统时钟
🎯 学习目标
- 掌握
os_task_tsleep()基于节拍的延时 - 掌握
os_task_msleep()基于毫秒的延时 - 理解 SLEEP 状态下的任务行为
- 理解 SLEEP 与 BLOCK 的区别
一、延时机制概述
在 RTOS 中,任务经常需要等待一段时间后再继续执行。OneOS 提供了两种延时方式:
| API | 单位 | 精度 | 说明 |
|---|---|---|---|
os_task_tsleep(tick) | 系统时钟节拍 | 1 tick | 底层延时接口 |
os_task_msleep(ms) | 毫秒 | 1 tick(内部转换) | 便捷延时接口 |
延时期间,任务进入 SLEEP 状态,不再参与调度,让出 CPU 给其他就绪任务。延时到期后,内核自动将任务恢复到 READY 状态。
二、os_task_tsleep() —— 节拍延时
2.1 函数签名
os_err_t os_task_tsleep(os_tick_t tick);2.2 参数说明
| 参数 | 说明 |
|---|---|
tick | 延时时间,单位是系统时钟节拍。必须 < OS_TICK_MAX / 2 |
tick 与时间的换算:
延时时间(ms) = tick × (1000 / OS_TICK_PER_SECOND)例如,当 OS_TICK_PER_SECOND = 100 时:
os_task_tsleep(1)≈ 延时 10msos_task_tsleep(10)= 延时 100msos_task_tsleep(100)= 延时 1s
2.3 特殊值:tick = 0
当 tick = 0 时,os_task_tsleep() 的行为等同于 os_task_yield()——将当前任务移到同优先级就绪链表末尾,立即触发调度:
/* tick = 0,等同于 os_task_yield() */
os_task_tsleep(0);2.4 内部实现
os_err_t os_task_tsleep(os_tick_t tick)
{
current_task = k_task_self();
if (tick > 0)
{
/* 从就绪队列移除 */
k_readyq_remove(current_task);
current_task->state &= ~OS_TASK_STATE_READY;
/* 进入 SLEEP 状态,加入延时队列 */
current_task->state |= OS_TASK_STATE_SLEEP;
k_tickq_put(current_task, tick);
/* 触发调度 */
OS_KERNEL_EXIT_SCHED();
}
else
{
/* tick = 0: 等同于 yield */
if (OS_TRUE == k_readyq_move_tail(current_task))
{
OS_KERNEL_EXIT_SCHED();
}
}
return OS_SUCCESS;
}2.5 使用限制
- 不允许在中断上下文中调用
- 不允许在中断禁用时调用
- 不允许在调度器锁定时调用
2.6 使用示例
void blink_task(void *arg)
{
while (1)
{
led_on();
os_task_tsleep(50); /* 延时 50 tick(500ms,假设 100Hz) */
led_off();
os_task_tsleep(50); /* 延时 50 tick */
}
}三、os_task_msleep() —— 毫秒延时
3.1 函数签名
os_err_t os_task_msleep(uint32_t ms);3.2 参数说明
| 参数 | 说明 |
|---|---|
ms | 延时时间,单位是毫秒 |
3.3 内部实现
os_task_msleep() 是对 os_task_tsleep() 的封装,内部将毫秒转换为 tick:
os_err_t os_task_msleep(uint32_t ms)
{
os_tick_t tick;
/* 毫秒转换为 tick */
tick = os_tick_from_ms(ms);
/* 调用节拍延时 */
return os_task_tsleep(tick);
}3.4 精度说明
os_task_msleep() 的精度受限于系统时钟节拍。例如,当 OS_TICK_PER_SECOND = 100(即 10ms/tick)时:
| 请求延时 | 实际 tick 数 | 实际延时 |
|---|---|---|
| 1ms | 1 tick | 10ms |
| 5ms | 1 tick | 10ms |
| 10ms | 1 tick | 10ms |
| 15ms | 2 tick | 20ms |
| 50ms | 5 tick | 50ms |
注意:实际延时时间始终 ≥ 请求的延时时间,最小延时单位为 1 个 tick。
3.5 使用示例
void sensor_task(void *arg)
{
while (1)
{
/* 读取传感器数据 */
read_sensor();
/* 延时 500ms */
os_task_msleep(500);
}
}四、SLEEP 状态详解
4.1 SLEEP 状态的特点
| 特性 | 说明 |
|---|---|
| 触发方式 | 调用 os_task_tsleep() 或 os_task_msleep() |
| 恢复方式 | 延时到期后自动恢复为 READY 状态 |
| 调度影响 | 任务从就绪队列移除,不参与调度 |
| 可叠加 SUSPEND | ✅ 是。延时期间可被挂起,恢复后需同时满足两个条件 |
4.2 SLEEP 期间的时间线
时间轴(tick):
────────────────────────────────────────────────────►
0 10 20 30 40
任务 A
│ 调用 tsleep(20)
│ 进入 SLEEP
▼
├──── SLEEP 状态 ────┤
│ 延时到期
│ 自动恢复 READY
▼
├── 等待调度 ──┤
│ 被调度选中
│ 继续运行
▼4.3 SLEEP + SUSPEND 叠加
如果任务在延时期间被挂起(os_task_suspend()),任务状态变为 SLEEP | SUSPEND。此时:
- 延时到期后,任务仍处于 SUSPEND 状态,不会自动回到 READY
- 只有当
os_task_resume()被调用后,任务才会变为 READY 状态
/* 场景:延时期间被挂起 */
void task_a(void *arg)
{
os_task_tsleep(100); /* 延时 100 tick */
/* 如果延时期间被挂起,这里不会执行 */
/* 直到被 resume 后才继续 */
}五、SLEEP 与 BLOCK 的区别
| 维度 | SLEEP | BLOCK |
|---|---|---|
| 触发原因 | 主动延时等待 | 等待 IPC 资源(信号量、互斥锁等) |
| 恢复条件 | 延时到期自动恢复 | 资源可用时自动恢复 |
| 超时机制 | 本身就是超时 | 通常支持超时参数(如 os_sem_wait(sem, timeout)) |
| 对应 API | os_task_tsleep() / os_task_msleep() | os_sem_wait() / os_mutex_lock() 等 |
| 队列位置 | 延时队列(按到期时间排序) | IPC 对象的阻塞队列 |
六、延时 API 的最佳实践
6.1 推荐使用 os_task_msleep()
对于大多数应用场景,推荐使用 os_task_msleep() 而非 os_task_tsleep():
- 代码可读性更好(
os_task_msleep(100)比os_task_tsleep(10)更直观) - 不依赖具体的
OS_TICK_PER_SECOND配置 - 移植性更好
6.2 避免极短延时
由于延时的最小精度受限于 tick,避免使用极短的延时值:
/* 不推荐:OS_TICK_PER_SECOND=100 时,实际延时 10ms,不是 1ms */
os_task_msleep(1);
/* 推荐:如果确实需要短延时,考虑使用硬件定时器或忙等待 */6.3 延时作为等待机制
对于简单的周期性任务,延时是合适的等待方式。但对于需要响应外部事件的任务,应使用 IPC 机制(信号量、事件标志组等)配合超时参数,而不是轮询 + 延时:
/* 不推荐:轮询方式 */
while (1)
{
if (data_ready())
{
process_data();
}
os_task_msleep(10); /* 浪费 CPU 且响应延迟大 */
}
/* 推荐:事件驱动方式 */
while (1)
{
os_sem_wait(data_sem, OS_WAIT_FOREVER); /* 阻塞等待,零延迟 */
process_data();
}📝 本节小结
OneOS 提供 os_task_tsleep(tick) 和 os_task_msleep(ms) 两种延时方式。前者基于系统时钟节拍,后者以毫秒为单位(内部转换为 tick)。延时期间任务进入 SLEEP 状态,让出 CPU,延时到期后自动恢复就绪。SLEEP 与 BLOCK 的区别在于:SLEEP 是主动等待时间,BLOCK 是等待 IPC 资源。对于需要响应外部事件的场景,建议使用 IPC 机制配合超时参数,而非轮询 + 延时。