基于 arm64 Linux nanosleep 系统调用流程分析

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
  1. dl_task(…) 判断当前进程是否是 DEADLINE 进程,rt_task(…) 则判断当前进程是否为实时进程,如果是二者之一,则将 slack 置为 0,它是表示传递给 hrtimer 的触发范围,表示时钟最久会在 slack 纳秒后触发。
  2. 调用 hrtimer_init_on_stack(…) 在栈上初始化 hrtimer。
  3. 调用 hrtimer_set_expires_range_ns(…) 设置定时器到期的时间范围。
  4. 调用 do_nanosleep(…) 进一步处理 nanosleep 流程。
  5. 对于返回错误码 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(…) 销毁定时器。
  6. 绝对定时器不更新 rmtp 值并重新启动,如果 do_nanosleep 返回 -ERESTART_RESTARTBLOCK,但定时器模式设置为绝对时间,那么将返回值替换为 -ERESTARTNOHAND,如此系统可直接重新调用 nanosleep,不再像 ERESTART_RESTARTBLOCK 重新计算需要睡眠多久。毕竟是绝对时间,到了相应的时间点定时器触发即可。同样此时直接 goto 到 out 标签处处理。
  7. 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 = &current->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;
	};
};
  1. 调用 hrtimer_init_sleeper 给定时器设置到期后触发的函数为 hrtimer_wakeup,sleeper task 为当前进程的 task;
  2. do-while 循环中首先调用 set_current_state(…) 将当前进程的状态置为 TASK_INTERRUPTIBLE(可中断),接着调用 hrtimer_start_expires(…) 激活定时器,接下来调用 freezable_schedule() 进行任务调度,最后调用 hrtimer_cancel(…) 取消定时器。do-while 循环条件中判断 hrtimer_sleeper task(当前进程)不为空并且是否存在信号需要处理, signal_pending(…) 检查当前进程是否有信号处理,返回不为 0 表示有信号需要处理。如果有信号需要处理就会跳出 do-while 循环。
  3. 调用 __set_current_state(…) 设置当前进程状态为 TASK_RUNNING(运行态)。
  4. 如果 hrtimer_sleeper task 为空了,do_nanosleep 直接返回 0,说明进程睡眠时间已到,定时器触发 hrtimer_wakeup 函数,其内部将 hrtimer_sleeper task 置为空,nanosleep 正常结束了。
  5. 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(…) 从内核往用户空间拷贝。
  6. 最终返回 -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 = &current->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;
}
  1. 调用 container_of 宏获取 hrtimer_sleeper 结构。
  2. 从 hrtimer_sleeper 结构得到 task_struct 结构。
  3. 将 hrtimer_sleeper task 赋值为 NULL。
  4. 调用 wake_up_process(…) 唤醒调用 nanosleep 睡眠的进程。
  5. 返回 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;
}

最后总结一下:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/tyyj90/article/details/126129306