nanosleep (高分辨率睡眠)可实现纳秒级的睡眠,暂停调用线程的执行。在 Linux 内核中是如何实现的?下面基于 arm64 cpu 架构去分析。
#include <time.h>
int nanosleep(const struct timespec *req, struct timespec *rem);
nanosleep(…) 暂停调用线程的执行,直到至少过了 *req 中指定的时间,或者传递一个触发调用线程中处理程序调用或终止进程的信号。
如果调用被信号处理程序中断,nanosleep(…) 返回 -1,将 errno 设置为 EINTR,并将剩余时间写入 rem 指向的结构中(除非 rem 为 NULL)。然后可以使用 *rem 的值再次调用 nanosleep(…) 并完成指定的暂停。
结构 timespec 用于以纳秒精度指定时间间隔。其定义如下:
struct timespec {
time_t tv_sec; /* 秒 */
long tv_nsec; /* 纳秒 */
};
“纳秒”字段的取值范围为 0 ~ 999999999。
与 sleep(3) 和 usleep(3) 相比,nanosleep(…) 具有以下优点:在指定睡眠间隔时提供了更高的分辨率;POSIX.1 显式指定它不与信号交互;它使恢复被信号处理器中断的睡眠任务变得更容易。
现在以 bionic libc 来分析,起点如下:
bionic/libc/include/time.h
int nanosleep(const struct timespec* __request, struct timespec* __remainder);
实现定义在 nanosleep.S 中。具体系统调用流程可以参考《从 Java sleep 来看 arm64 Linux 内核都干了些什么?》。
bionic/libc/arch-arm64/syscalls/nanosleep.S
/* Generated by gensyscalls.py. Do not edit. */
#include <private/bionic_asm.h>
ENTRY(nanosleep)
mov x8, __NR_nanosleep
svc #0
cmn x0, #(MAX_ERRNO + 1)
cneg x0, x0, hi
b.hi __set_errno_internal
ret
END(nanosleep)
接下来进入内核。从 asm-generic/unistd.h 头文件不难得出如果只考虑兼容 arm64 架构,nanosleep 这个系统调用在内核中实现的函数名是 __arm64_sys_nanosleep。
include/uapi/asm-generic/unistd.h
/* kernel/hrtimer.c */
#define __NR_nanosleep 101
__SC_COMP(__NR_nanosleep, sys_nanosleep, compat_sys_nanosleep)
SYSCALL_DEFINE2 展开后刚好就会对应 __arm64_sys_nanosleep 这个符号。最终会调用到 hrtimer_nanosleep 进一步处理,处理之前先调用 get_timespec64 将 timespec 结构转为 timespec64,另外 current->restart_block.nanosleep 设置 type 和 rmtp。HRTIMER_MODE_REL 表示相对于现在的时间,定义在 hrtimer.h 中。CLOCK_MONOTONIC(表示从系统启动这一刻起开始计时,不受系统时间被用户改变的影响的 clock id) 是 clock id。
kernel/time/hrtimer.c
#if !defined(CONFIG_64BIT_TIME) || defined(CONFIG_64BIT)
SYSCALL_DEFINE2(nanosleep, struct __kernel_timespec __user *, rqtp,
struct __kernel_timespec __user *, rmtp)
{
struct timespec64 tu;
if (get_timespec64(&tu, rqtp))
return -EFAULT;
if (!timespec64_valid(&tu))
return -EINVAL;
current->restart_block.nanosleep.type = rmtp ? TT_NATIVE : TT_NONE;
current->restart_block.nanosleep.rmtp = rmtp;
return hrtimer_nanosleep(&tu, HRTIMER_MODE_REL, CLOCK_MONOTONIC);
}
#endif
- dl_task(…) 判断当前进程是否是 DEADLINE 进程,rt_task(…) 则判断当前进程是否为实时进程,如果是二者之一,则将 slack 置为 0,它是表示传递给 hrtimer 的触发范围,表示时钟最久会在 slack 纳秒后触发。
- 调用 hrtimer_init_on_stack(…) 在栈上初始化 hrtimer。
- 调用 hrtimer_set_expires_range_ns(…) 设置定时器到期的时间范围。
- 调用 do_nanosleep(…) 进一步处理 nanosleep 流程。
- 对于返回错误码 ERESTART_RESTARTBLOCK,它需要使用 restart_syscall 系统调用,而不是使用原来的系统调用。假如一个进程调用 nanosleep 来暂停 20ms, 10ms 后由于一个信号处理发生(从而激活这个进程),如果信号处理后重新启动这个系统调用,那么它在重启的时候不能直接再次调用 nanosleep,否则将会导致该进程睡眠 30ms(10ms + 20ms)。 事实上 nanosleep 会在当前进程的 thread_info 的 restart_block 中填写下如果需要重启 nanosleep 需要调用哪一个函数(hrtimer_nanosleep_restart),而如果其被信号处理中断,那么它会返回 -ERESTART_RESTARTBLOCK,而在重启该系统调用时,sys_restart_syscall 会根据 restart_block 中的信息调用相应的函数,通常这个函数会计算出首次调用与再次调用的时间间距,然后再次暂停剩余的时间段。如果返回的不是 ERESTART_RESTARTBLOCK,就直接 goto 到 out 标签处调用 destroy_hrtimer_on_stack(…) 销毁定时器。
- 绝对定时器不更新 rmtp 值并重新启动,如果 do_nanosleep 返回 -ERESTART_RESTARTBLOCK,但定时器模式设置为绝对时间,那么将返回值替换为 -ERESTARTNOHAND,如此系统可直接重新调用 nanosleep,不再像 ERESTART_RESTARTBLOCK 重新计算需要睡眠多久。毕竟是绝对时间,到了相应的时间点定时器触发即可。同样此时直接 goto 到 out 标签处处理。
- do_nanosleep 返回错误码 ERESTART_RESTARTBLOCK,对 restart_block 相应字段赋值,重启使用的函数被赋值为了 hrtimer_nanosleep_restart,nanosleep.clockid 和之前的定时器保持一致,nanosleep.expires 到期时间调用 hrtimer_get_expires_tv64(…) 获取得到,这是一个内联函数直接返回 hrtimer 结构的 node.expires 到期时间。
kernel/time/hrtimer.c
long hrtimer_nanosleep(const struct timespec64 *rqtp,
const enum hrtimer_mode mode, const clockid_t clockid)
{
struct restart_block *restart;
struct hrtimer_sleeper t;
int ret = 0;
u64 slack;
slack = current->timer_slack_ns;
if (dl_task(current) || rt_task(current))
slack = 0;
hrtimer_init_on_stack(&t.timer, clockid, mode);
hrtimer_set_expires_range_ns(&t.timer, timespec64_to_ktime(*rqtp), slack);
ret = do_nanosleep(&t, mode);
if (ret != -ERESTART_RESTARTBLOCK)
goto out;
/* Absolute timers do not update the rmtp value and restart: */
if (mode == HRTIMER_MODE_ABS) {
ret = -ERESTARTNOHAND;
goto out;
}
restart = ¤t->restart_block;
restart->fn = hrtimer_nanosleep_restart;
restart->nanosleep.clockid = t.timer.base->clockid;
restart->nanosleep.expires = hrtimer_get_expires_tv64(&t.timer);
out:
destroy_hrtimer_on_stack(&t.timer);
return ret;
}
restart_block 结构体内部使用了联合体 union,根据注释不难看出它提供了 futex_wait 和 futex_wait_requeue_pi、nanosleep、poll 需要的重启使用的结构。前面谈到不更新 rmtp 指的是不更新 nanosleep 结构的 rmtp 字段。
include/linux/restart_block.h
/*
* System call restart block.
*/
struct restart_block {
long (*fn)(struct restart_block *);
union {
/* For futex_wait and futex_wait_requeue_pi */
struct {
u32 __user *uaddr;
u32 val;
u32 flags;
u32 bitset;
u64 time;
u32 __user *uaddr2;
} futex;
/* For nanosleep */
struct {
clockid_t clockid;
enum timespec_type type;
union {
struct __kernel_timespec __user *rmtp;
struct compat_timespec __user *compat_rmtp;
};
u64 expires;
} nanosleep;
/* For poll */
struct {
struct pollfd __user *ufds;
int nfds;
int has_timeout;
unsigned long tv_sec;
unsigned long tv_nsec;
} poll;
};
};
- 调用 hrtimer_init_sleeper 给定时器设置到期后触发的函数为 hrtimer_wakeup,sleeper task 为当前进程的 task;
- do-while 循环中首先调用 set_current_state(…) 将当前进程的状态置为 TASK_INTERRUPTIBLE(可中断),接着调用 hrtimer_start_expires(…) 激活定时器,接下来调用 freezable_schedule() 进行任务调度,最后调用 hrtimer_cancel(…) 取消定时器。do-while 循环条件中判断 hrtimer_sleeper task(当前进程)不为空并且是否存在信号需要处理, signal_pending(…) 检查当前进程是否有信号处理,返回不为 0 表示有信号需要处理。如果有信号需要处理就会跳出 do-while 循环。
- 调用 __set_current_state(…) 设置当前进程状态为 TASK_RUNNING(运行态)。
- 如果 hrtimer_sleeper task 为空了,do_nanosleep 直接返回 0,说明进程睡眠时间已到,定时器触发 hrtimer_wakeup 函数,其内部将 hrtimer_sleeper task 置为空,nanosleep 正常结束了。
- restart 赋值为当前进程的 restart_block,如果其 nanosleep.type 不为 TT_NONE,则调用 hrtimer_expires_remaining(…) 从定时器获取剩余时间,调用 ktime_to_timespec64(…) 将剩余时间 ktime_t 转换为 struct timespec64 结构,最后调用 nanosleep_copyout(…) 将 struct timespec64 拷贝到 restart_block 结构中的 rmtp 字段,内部使用了 copy_to_user(…) 从内核往用户空间拷贝。
- 最终返回 -ERESTART_RESTARTBLOCK。
kernel/time/hrtimer.c
static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mode)
{
struct restart_block *restart;
hrtimer_init_sleeper(t, current);
do {
set_current_state(TASK_INTERRUPTIBLE);
hrtimer_start_expires(&t->timer, mode);
if (likely(t->task))
freezable_schedule();
hrtimer_cancel(&t->timer);
mode = HRTIMER_MODE_ABS;
} while (t->task && !signal_pending(current));
__set_current_state(TASK_RUNNING);
if (!t->task)
return 0;
restart = ¤t->restart_block;
if (restart->nanosleep.type != TT_NONE) {
ktime_t rem = hrtimer_expires_remaining(&t->timer);
struct timespec64 rmt;
if (rem <= 0)
return 0;
rmt = ktime_to_timespec64(rem);
return nanosleep_copyout(restart, &rmt);
}
return -ERESTART_RESTARTBLOCK;
}
hrtimer_init_sleeper(…) 内部将 hrtimer_sleeper timer.function 赋值为 hrtimer_wakeup,定时器超时后会调用它。hrtimer_sleeper task 就被赋值为传入指向 task_struct 结构的指针。
nanosleep_copyout(…) 内部则进一步调用 compat_put_timespec64(…) 或 put_timespec64(…) 将 timespec64 中的数据拷贝到 restart_block nanosleep.compat_rmtp 或 nanosleep.rmtp 字段。
kernel/time/hrtimer.c
void hrtimer_init_sleeper(struct hrtimer_sleeper *sl, struct task_struct *task)
{
sl->timer.function = hrtimer_wakeup;
sl->task = task;
}
EXPORT_SYMBOL_GPL(hrtimer_init_sleeper);
int nanosleep_copyout(struct restart_block *restart, struct timespec64 *ts)
{
switch(restart->nanosleep.type) {
#ifdef CONFIG_COMPAT_32BIT_TIME
case TT_COMPAT:
if (compat_put_timespec64(ts, restart->nanosleep.compat_rmtp))
return -EFAULT;
break;
#endif
case TT_NATIVE:
if (put_timespec64(ts, restart->nanosleep.rmtp))
return -EFAULT;
break;
default:
BUG();
}
return -ERESTART_RESTARTBLOCK;
}
- 调用 container_of 宏获取 hrtimer_sleeper 结构。
- 从 hrtimer_sleeper 结构得到 task_struct 结构。
- 将 hrtimer_sleeper task 赋值为 NULL。
- 调用 wake_up_process(…) 唤醒调用 nanosleep 睡眠的进程。
- 返回 HRTIMER_NORESTART,表示不需要重启定时器了。
kernel/time/hrtimer.c
/*
* Sleep related functions:
*/
static enum hrtimer_restart hrtimer_wakeup(struct hrtimer *timer)
{
struct hrtimer_sleeper *t =
container_of(timer, struct hrtimer_sleeper, timer);
struct task_struct *task = t->task;
t->task = NULL;
if (task)
wake_up_process(task);
return HRTIMER_NORESTART;
}
最后总结一下: