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_nanosleepsvc     #0cmn     x0, #(MAX_ERRNO + 1)cneg    x0, x0, hib.hi    __set_errno_internalret
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_TIMEcase TT_COMPAT:if (compat_put_timespec64(ts, restart->nanosleep.compat_rmtp))return -EFAULT;break;
#endifcase 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;
}

最后总结一下:

基于 arm64 Linux nanosleep 系统调用流程分析相关推荐

  1. 基于IMX6Q的uboot启动流程分析(3):_main函数之relocate_code与board_init_r

    基于IMX6Q的uboot启动流程分析(1):uboot入口函数 基于IMX6Q的uboot启动流程分析(2):_main函数之board_init_f 基于IMX6Q的uboot启动流程分析(3): ...

  2. 基于 LPC1114 的 M0 启动流程分析

    基于LPC1114的M0启动流程分析 作者:解琛 时间:2020 年 8 月 1 日 基于LPC1114的M0启动流程分析 一..s 启动进程 1.1 堆栈配置 1.2 中断向量表 1.3 定义中断服 ...

  3. Linux open系统调用流程

    Linux open系统调用流程(1) http://blog.csdn.net/chenjin_zhong/article/details/8452453 1.概述 我们知道,Linux把设备看成特 ...

  4. LINUX 路由子系统流程分析

    title: LINUX 路由子系统流程分析 date: 2020-11-28 categories: Linux tags: Linux Routing-Subsystem 上次分析了Linux协议 ...

  5. Linux开机启动流程分析

    Linux开机启动十步骤 收藏分享2012-2-6 11:15| 发布者: 红黑魂| 查看数: 1366| 评论数: 0|来自: 比特网 摘要: 开机过程指的是从打开计算机电源直到LINUX显示用户登 ...

  6. linux意外重启分析,Linux关机重启流程分析

    linux下的关机和重启流程对于一般的桌面应用和网络服务器来说并不重要,但是在用户自己定义的嵌入式系统内核中就有一定的研究意义,通过了解Linux 关机重启的流程,我们对它可以修改和自定义,甚至以此为 ...

  7. Linux关机重启流程分析

    linux下的关机和重启流程对于一般的桌面应用和网络服务器来说并不重要,但是在用户自己定义的嵌入式系统内核中就有一定的研究意义,通过了解Linux 关机重启的流程,我们对它可以修改和自定义,甚至以此为 ...

  8. linux 音频驱动的流程,Intel平台下Linux音频驱动流程分析

    [软件框架] 在对要做的事情一无所知的时候,从全局看看系统的拓扑图对我们认识新事物有很大的帮助.Audio 部分的驱动程序框架如下图所示: 这幅图明显地分为 3 级. 上方蓝色系的 ALSA Kern ...

  9. linux select系统调用函数分析,Linux select系统调用

    linux系统提供了系统调用select,它允许程序挂起,并等待从不止一个文件描述符的输入.原理很简单: 1. 获取所需要的文件描述符列表: 2. 将此列表传给select: 3. select挂起直 ...

最新文章

  1. flash里alert
  2. php web框架 symfony简介
  3. Django1.10文档学习笔记二
  4. 关于SAP物料的历史库存
  5. unity怎么做水面_防水博士小课堂 | 什么是背水面防水? 背水面防水施工到底该怎么做?...
  6. check corners_免费下载:将Mac样式的Hot Corners添加到Windows 10
  7. 蓝桥杯 ADV-194算法提高 盾神与积木游戏(贪心)
  8. unity3D-Gear VR字体由小变大效果
  9. STL vector简介
  10. 需求方案撰写之售前方案
  11. 拓端tecdat|R语言有限混合模型聚类FMM、广义线性回归模型GLM混合应用分析威士忌市场和研究专利申请、支出数据
  12. 2018美赛b题论文翻译
  13. 计算机操作系统试题及答案带解析,计算机操作系统期末考试试题及答案新
  14. 一级倒立摆MATLAB仿真程序(搬运)
  15. C语言if( x)的意思,c语言中if(x)是什么意思?_后端开发
  16. SNN识别手写数字—MNIST数据集
  17. 零基础学C语言必备书籍,抖音编程达人推荐(进群交流学习互动)
  18. TSP问题解析篇之自适应大邻域搜索(ALNS)算法深度通读(附python代码)
  19. Centos7下turn off cpu throttling
  20. 那绿色的叶对着柔和的阳光

热门文章

  1. C++线程池QueueUserWorkItem
  2. java printwriter乱码_Java servlet 使用 PrintWriter 时的编码与乱码的示例代码
  3. Mybatis实现自定义分页插件
  4. 用U盘安装FreeBSD
  5. echarts柱状图和曲线组合图
  6. SparkSQL之DataFrame 编程(创建DataFrame ,DataFrame数据运算操作 ,输出存储DataFrame)(11)
  7. 博客发在win10.me
  8. react native 上拖拽元素
  9. 神经网络-1 利用年龄身高体重判断性别
  10. JavaWeb之【转发与重定向】