Linux时间子系统(五) POSIX Clock
一、前言
clock是timer的基础,任何一个timer都需要运作在一个指定的clock上来。内核中维护了若干的clock,本文第二章描述了clock的基本概念和一些静态定义的posix clock。根据计时的特点,clock分成两种:一种是真实世界的时间概念,另外一个是仅仅计算CPU执行时间 ,这两种clock分别在第三和第四章描述。从clock的生命周期来看,可以分成静态和动态的posix clock,静态是一直存在于内核中的,而动态clock有创建和销毁的概念,本文第五章描述了dynamic posix clock。
二、基本概念
1、核心数据结构
所谓clock,实际上就是一种计时工具,可能是硬件,也可能是软件,当然对于POSIX clock而言,当然是指软件抽象了。clock能够记录一段时间的流逝,这段时间可能是真实的墙上时间,也可能是虚拟的时间,例如基于某个进程或者线程的CPU执行时间。在linux kernel中,用struct k_clock来抽象,具体定义如下:
struct k_clock {
int (*clock_getres) (const clockid_t which_clock, struct timespec *tp);
int (*clock_set) (const clockid_t which_clock, const struct timespec *tp);
int (*clock_get) (const clockid_t which_clock, struct timespec * tp);
int (*clock_adj) (const clockid_t which_clock, struct timex *tx);
int (*timer_create) (struct k_itimer *timer);
int (*nsleep) (const clockid_t which_clock, int flags, struct timespec *, struct timespec __user *);
long (*nsleep_restart) (struct restart_block *restart_block);
int (*timer_set) (struct k_itimer * timr, int flags, struct itimerspec * new_setting,
struct itimerspec * old_setting);
int (*timer_del) (struct k_itimer * timr);
void (*timer_get) (struct k_itimer * timr, struct itimerspec * cur_setting);
};
clock作为一个计时工具当然有计时精度,通过clock_getres函数可以获取该clock的时间精度,需要说明的是这个精度是和timer相关的,用于将用户设定的timer超时时间规整到clock精度允许的数值上。clock_get和clock_set函数可以分别获取和设定当前的时间,这个时间值是一个绝对时间值(对于时间轴而言,这个绝对时间也是相对的,是相对于该timeline的epoch而言),标记了当前时间点。clock计时有可能是不准确的,例如基于系统晶振的clock。一方面本身晶振的精度有限,时间累积长了会出现较大误差。另外,晶振也会随着使用时间的推移、温度的变化等等因素而导致误差。clock_adj函数允许系统根据外部的精确时间信息对本clock进行调整。nsleep和nsleep_restart这两个成员函数可以让进程sleep一段时间。timer_xxx系列函数是和POSIX
interval timer相关,具体会在POSIX timer文档中描述。
2、静态定义的clock
static struct k_clock posix_clocks[MAX_CLOCKS];
posix_clocks数组定义了系统支持的所有的clock,相关的定义如下:
#define CLOCK_REALTIME 0
#define CLOCK_MONOTONIC 1
#define CLOCK_PROCESS_CPUTIME_ID 2
#define CLOCK_THREAD_CPUTIME_ID 3
#define CLOCK_MONOTONIC_RAW 4
#define CLOCK_REALTIME_COARSE 5
#define CLOCK_MONOTONIC_COARSE 6
#define CLOCK_BOOTTIME 7
#define CLOCK_REALTIME_ALARM 8
#define CLOCK_BOOTTIME_ALARM 9
#define CLOCK_SGI_CYCLE 10 /* Hardware specific */
#define CLOCK_TAI 11#define MAX_CLOCKS 16
POSIX标准定义了4种类型的clock,CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_PROCESS_CPUTIME_ID和CLOCK_THREAD_CPUTIME_ID,其他是linux
specific。如果一个clock的timeline是基于CPU运行时间的,那么我们称之CPU-time clock。CPU-time
clock主要是用来为某个进程或者线程的执行时间进行计时的,一旦线程(进程)被切换,那么该clock就停掉了,直到下次调度器切换回该线程(进程)执行。
各个具体的操作系统实现可以定义自己特有的clock,对于Linux
kernel,我们定义了若干种clock。CLOCK_MONOTONIC_RAW启动时间点被设成0,此后一直不断累加,而且能设定,不会随NTP调整。CLOCK_REALTIME_COARSE、CLOCK_MONOTONIC_COARSE的概念和CLOCK_REALTIME、CLOCK_MONOTONIC的概念是类似的,只不过是精度是比较粗的版本。有时候,timer没有必要要求那么高的精度,那么我们可以使用这种clock,从而可以获取更好的性能。CLOCK_BOOTTIME和CLOCK_MONOTONIC类似,也是单调上述,在系统初始化的时候设定的基准数值是0,不过CLOCK_BOOTTIME计算系统suspend的时间,也就是说,不论是running还是suspend(这些都算是启动时间),CLOCK_BOOTTIME都会累积计时,直到系统reset或者shutdown。
CLOCK_REALTIME_ALARM和CLOCK_BOOTTIME_ALARM主要用于Alarmtimer,这种timer是基于RTC的,更详细的内容请参考本站Alarmtimer的文档。CLOCK_TAI是原子钟的时间,和基于UTC的CLOCK_REALTIME类似,不过没有leap
second。
用户空间的clock_xxx函数会传递clock id的参数,在内核态,根据id作为index在posix_clocks数组中可以索引到对应的clock,然后调用clock对应的callback函数就OK了。当然基本意思就是这样,具体实现如下:
static struct k_clock *clockid_to_kclock(const clockid_t id)
{
if (id < 0)
return (id & CLOCKFD_MASK) == CLOCKFD ?
&clock_posix_dynamic : &clock_posix_cpu;if (id >= MAX_CLOCKS || !posix_clocks[id].clock_getres)
return NULL;
return &posix_clocks[id];
}
clockid_to_kclock这个函数用来将clock id和具体的posix clock的k_clock
数据结构对应起来。在linux平台上,clockid是int类型的数据,共32个bit,高29个bit用来保存一个pid(用于CPU-time
clock)或者fd(动态分配的clock),bit 2用来说明该CPU-time clock是一个进程clock还是线程clock。bit
1和bit 0用来说明该clock id的类型:PROF=0, VIRT=1, SCHED=2, or FD=3。
当clock id小于0的时候,要么是CPU-time clock,要么是动态分配的clock,可以根据clock id的类型来判断。CPU-time clock和动态分配的clock后面会具体介绍。
三、各种real timeclock的定义
系统初始化的时候会调用init_posix_timers函数对各种静态定义的real time clock进行注册。注:monotonic clock也是real time clock的一种,全称是monotonic real time clock。
1、real time clock的定义如下(timer相关内容不在本文描述):
struct k_clock clock_realtime = {
.clock_getres = hrtimer_get_res,
.clock_get = posix_clock_realtime_get,
.clock_set = posix_clock_realtime_set,
.clock_adj = posix_clock_realtime_adj,
.nsleep = common_nsleep,
.nsleep_restart = hrtimer_nanosleep_restart,
};
real time
clock需要调用timekeeping模块的接口来获取和设定当前时间值。对于获取当前时间值的函数posix_clock_realtime_get而言,是调用ktime_get_real_ts函数,该函数是timekeeping模块的接口函数,以timespec的格式回了real
time
clock的当前值。posix_clock_realtime_set函数主要是调用do_settimeofday这个timekeeping模块的接口函数。posix_clock_realtime_adj是调用do_adjtimex接口函数来实现具体的功能。
纳秒级别的sleep是通过高精度timer实现的,real time clock的精度和hrtimer相关,具体可以参考hrtimer相关文档。
2、monotonic clock的定义如下:
struct k_clock clock_monotonic = {
.clock_getres = hrtimer_get_res,
.clock_get = posix_ktime_get_ts,
.nsleep = common_nsleep,
.nsleep_restart = hrtimer_nanosleep_restart,
};
monotonic
clock没有clock_set函数,不能被设定。通过ktime_get_ts这个timekeeping模块的接口可以获得monotonic
clock的当前值。纳秒级别的sleep以及精度的获取函数和real time clock一样。
3、monotonic raw clock的定义如下:
struct k_clock clock_monotonic_raw = {
.clock_getres = hrtimer_get_res,
.clock_get = posix_get_monotonic_raw,
};
posix_get_monotonic_raw函数是调用timekeeping模块getrawmonotonic接口函数实现获取monotonic
raw clock当前时间数值的。和monotonic clock一样,不能设定。和monotonic
clock不同的是该clock没有timer相关的callback函数。
4、coarse clock
struct k_clock clock_realtime_coarse = {
.clock_getres = posix_get_coarse_res,
.clock_get = posix_get_realtime_coarse,
};
struct k_clock clock_monotonic_coarse = {
.clock_getres = posix_get_coarse_res,
.clock_get = posix_get_monotonic_coarse,
};
这两个clock的精度都是和tick相关的,KTIME_LOW_RES定义就是tick的纳秒数值。clock_get函数分别调用current_kernel_time和get_monotonic_coarse获取当前时间点的值。
CLOCK_BOOTTIME和CLOCK_TAI的clock实现非常简单,大家自行阅读代码就OK了。
四、CPU-time clock
1、概述
从用户空间的角度看,有两种CPU-time clock的应用场景:
(1)调用clock_xxx函数并传递CLOCK_PROCESS_CPUTIME_ID或者CLOCK_THREAD_CPUTIME_ID给该函数
(2)调用clock_getcpuclockid或者pthread_getcpuclockid函数来获取指定进程或者线程的clock id,之后调用clock_xxx函数并传递该clock id参数
应对第一种场景,系统初始化的时候会调用init_posix_cpu_timers函数对静态定义的CPU-time clock进行注册。对于第二种场景,内核静态定义了一个clock_posix_cpu的clock来应对这种需求。
2、指定进程或者线程的CPU-time clock
内核静态定义了一个clock如下(去掉了timer的callback函数):
struct k_clock clock_posix_cpu = {
.clock_getres = posix_cpu_clock_getres,
.clock_set = posix_cpu_clock_set,
.clock_get = posix_cpu_clock_get,
.nsleep = posix_cpu_nsleep,
.nsleep_restart = posix_cpu_nsleep_restart,
};
(1)获取精度信息
static int posix_cpu_clock_getres(const clockid_t which_clock, struct timespec *tp)
{
int error = check_clock(which_clock);--------参数校验
if (!error) {
tp->tv_sec = 0;
tp->tv_nsec = ((NSEC_PER_SEC + HZ - 1) / HZ);
if (CPUCLOCK_WHICH(which_clock) == CPUCLOCK_SCHED) {
tp->tv_nsec = 1;
}
}
return error;
}
该函数的执行逻辑分成两个部分,一部分是参数校验,一部分是返回精度。参数校验需要检查的包括:
(a)clock id中的高29个bit包含了pid,获取pid的代码如下:
#define CPUCLOCK_PID(clock) ((pid_t) ~((clock) >> 3))
从代码可知,实际上并不是将pid放到高29个bit,而是将反码保存到了高29个bit。为何保存反码?这样做为了确保clock
id是一个负数(MSB是1),还记得clockid_to_kclock的实现吗?要获取该clock id的精度,要确保该pid的task存在
(b)如果该clock
id是一个进程相关的(调用clock_getcpuclockid获得),那么这个进程id应该是一个实实在在的进程id。在linux
kernel中,pid实际上是线程ID,POSIX标准的进程ID,也就是PID在内核中被成为线程组ID。因此,所谓一个“实实在在的进程id”就是说该线程的id(pid)和tgid一样,该pid标识的线程是线程组leader。当然,就是获取精度而已,实际上要求并不要那么严格,也许该pid标识的线程leader会退出,因此实际上要求该pid标识的task有thread
group leader就OK了。(这里有可能理解有误,TODO)
(c)如果该clock id是一个线程相关的(调用pthread_getcpuclockid获得),那么调用者必须和该线程(clock id中指明的那个线程)属于一个进程(线程组)。
返回精度部分的代码逻辑很简单,对于PROF和VIRT类型的CPU-time clock,其精度是tick,对于SCHED类型,精度是1ns。
(2)获取当前时间值
同样的,首先需要从clock id中获取pid的值,然后根据pid的值获取对应的task sturct,如果pid等于0,那么不需要费劲去寻找。得到task struct之后,可以调用posix_cpu_clock_get_task函数获取时间值:
static int posix_cpu_clock_get_task(struct task_struct *tsk, const clockid_t which_clock,
struct timespec *tp)
{
int err = -EINVAL;
unsigned long long rtn;if (CPUCLOCK_PERTHREAD(which_clock)) {---per 线程的cpu clock
if (same_thread_group(tsk, current))---必须和调用者是同一个线程组,也就是同一个进程
err = cpu_clock_sample(which_clock, tsk, &rtn);
} else {if (tsk == current || thread_group_leader(tsk))---进程的cpu clock
err = cpu_clock_sample_group(which_clock, tsk, &rtn);
}if (!err)
sample_to_timespec(which_clock, rtn, tp); ---给返回值赋值return err;
}
这里仍然存在校验问题,也就是说是否允许调用者获取该task的CPU-time clock。对于进程,只允许调用者进程获取自己的CPU-time
clock,在多线程环境下,主线程(线程组leader)可以获取整个进程的CPU-time
clock信息。对于per线程的操作,必须和调用者是同一个线程组,也就是同一个进程。
(a)获取线程的clock信息
static int cpu_clock_sample(const clockid_t which_clock, struct task_struct *p,
unsigned long long *sample)
{
switch (CPUCLOCK_WHICH(which_clock)) {
default:
return -EINVAL;
case CPUCLOCK_PROF:
*sample = prof_ticks(p);----获取该task在用户空间加上在kernel space的执行时间
break;
case CPUCLOCK_VIRT:
*sample = virt_ticks(p);----获取该task在用户空间的执行时间
break;
case CPUCLOCK_SCHED:
*sample = task_sched_runtime(p);----和调度器相关的cpu clock
break;
}
return 0;
}
计算进程或者线程在cpu上的执行时间是一个挺烦人的事,一方面想要精度高,另外一方面又不想计算量大。因此,实际上CPU-time
clock有三种,CPUCLOCK_PROF和CPUCLOCK_VIRT这两种都是比较粗略估计CPU执行时间的clock,它的工作原理就是在周期性tick中进行进程cpu
time的统计,如果该tick是用户态(timer中断了用户态程序的执行),那么整个tick的时间都是该进程的用户态执行时间。如果该tick是内核态,并且是用户程序进行系统调用而陷入内核,那么整个tick的时间都是该进程的系统态执行时间。
CPUCLOCK_SCHED clock和上面的方法不一样,它的精度是纳秒级别的,是在调度器上进行计算进程时间。具体的计算方法还是留到调度器文章中再描述吧。
(b)cpu_clock_sample_group函数概念类似,不过是统计一个进程上所有线程的时间而已。
3、CLOCK_PROCESS_CPUTIME_ID 类型的clock
struct k_clock process = {
.clock_getres = process_cpu_clock_getres,
.clock_get = process_cpu_clock_get,
.nsleep = process_cpu_nsleep,
.nsleep_restart = process_cpu_nsleep_restart,
};
process_cpu_clock_getres用来获取时间精度,该函数实际是调用posix_cpu_clock_getres(PROCESS_CLOCK,
tp)来完成的。process_cpu_clock_get用来获取当前时间值,实际上是通过调用posix_cpu_clock_get完成。posix_cpu_clock_xxx函数在上一节中已经描述。
4、CLOCK_THREAD_CPUTIME_ID类型的clock
很简单,大家自行学习吧。
五、动态分配clock
1、源由
某些硬件提供了计时的能力,可以实现成一个posix clock,同时,这些硬件又类似USB设备那样可以热拔插,这也就意味着该posix
clock不能静态定义。此外,除了标准的timer和clock相关的操作,这些提供计时能力的硬件还需要一些其他的类似字符设备界面的控制接口,在这样的需求推动下,内核提供了dynamic
posix clock。
2、dynamic posix clock
系统中的每一个dynamic posix clock用struct posix_clock来抽象,如下:
struct posix_clock {
struct posix_clock_operations ops;--------------(1)
struct cdev cdev;----------------------(2)
struct kref kref;
struct rw_semaphore rwsem;
bool zombie;------------------------(3)
void (*release)(struct posix_clock *clk);-------------(4)
};
(1)ops是该dynamic posix
clock的操作函数集,分成两个group,一个是timer(例如:timer_create、timer_delete等)以及clock操作相关(例如clock_gettime、clock_settime等),另外一个是普通字符设备的操作函数(例如:open、read、write等)。
(2)该dynamic posix clock对应的cdev数据结构。在struct
posix_clock_operations中有一个owner,其实在cdev中也有一个指向moudle的owner成员,看起来似乎是重复定义了。同样的疑问也存在与kref成员,因为在cdev中有kobject成员,kobject抽象了内核最基础的对象类别,包括名字、引用计数等,因此,我觉得只要struct
posix_clock包括了cdev成员,struct posix_clock_operations中的owner以及struct
posix_clock中的kref应该没有存在的必要了。
(3)zombie记录了底层硬件的状态,对于hotplug的外设,有可能硬件被拔除。rwsem用来保护该状态信息
(4)当reference count等于0的时候会调用release函数释放dynamic posix clock占用的资源。
3、注册和注销
底层的有计时能力的硬件driver可以调用posix_clock_register和posix_clock_unregister来注册或者注销一个posix clock,注册代码如下:
int posix_clock_register(struct posix_clock *clk, dev_t devid)
{
int err;kref_init(&clk->kref);
init_rwsem(&clk->rwsem);cdev_init(&clk->cdev, &posix_clock_file_operations);-----VFS接口的操作函数集合
clk->cdev.owner = clk->ops.owner;
err = cdev_add(&clk->cdev, devid, 1);return err;
}
VFS接口的操作函数集合都非常简单,基本上都是struct posix_clock_operations上的字符设备操作函数集合上。这样,用户空间的程序可以通过标准的文件描述符进行设备操作。
4、clock和timer接口
通过clock_xxx或者timer_xxx函数可以指定clock id,对于dynamic posix clock可以通过下面的操作来生成一个dynamic posix clock ID:
#define FD_TO_CLOCKID(fd) ((~(clockid_t) (fd) << 3) | CLOCKFD)
其中fd是通过设备节点打开的那个有计时能力的硬件。在内核态会通过clockid_to_kclock操作将clock id转换成
static struct k_clock *clockid_to_kclock(const clockid_t id)
{
if (id < 0)
return (id & CLOCKFD_MASK) == CLOCKFD ?
&clock_posix_dynamic : &clock_posix_cpu;……
}
clock_posix_dynamic可以将dynamic posix clock ID转换成对应的posix_clock,然后调用struct posix_clock_operations上的time和clock相关的函数即可。
Linux时间子系统(五) POSIX Clock的更多相关文章
- Linux时间子系统(六) POSIX timer
一.前言 在用户空间接口函数文档中,我们描述了和POSIX timer相关的操作,主要包括创建一个timer.设定timer.获取timer的状态.获取timer overrun的信息.删除timer ...
- Linux时间子系统之一:clock source(时钟源)
clock source用于为Linux内核提供一个时间基线,如果你用linux的date命令获取当前时间,内核会读取当前的clock source,转换并返回合适的时间单位给用户空间.在硬件层,它通 ...
- Linux时间子系统之一:clock source(时钟源)【转】
转自:http://blog.csdn.net/droidphone/article/details/7975694 clock source用于为linux内核提供一个时间基线,如果你用linux的 ...
- Linux时间子系统专题汇总
关于Linux时间子系统有两个系列文章讲的非常好,分别是WowoTech和DroidPhone. 还有两本书分别是介绍: Linux用户空间时间子系统<Linux/UNIX系统编程手册>的 ...
- Linux时间子系统之(五):POSIX Clock
专题文档汇总目录 Notes: 本章主要介绍了若干种类的静态时钟,这些时钟都可以通过k_clock表示,注册到posix_clocks中.这些都是静态时钟,可以分为三大类:各种REALTIME时钟.带 ...
- Linux时间子系统之(六):POSIX timer
专题文档汇总目录 Notes:首先讲解了POSIX timer的标识(唯一识别).POSIX Timer的组织(管理POSIX Timer).内核中如何抽象POSIX Timer:然后分析了POSIX ...
- Linux时间子系统之(十五):clocksource
专题文档汇总目录 Notes:clocksource基本概念,struct clocksource详解:注册和注销clocksource:内核如何选取clocksource:clocksource相关 ...
- Linux时间子系统(十五) clocksource
一.前言 和洋葱一样,软件也是有层次的,内核往往需要对形形色色的某类型的驱动进行抽象,屏蔽掉其具体的特质,获取该类驱动共同的逻辑,而又根据这些逻辑撰写该类驱动的抽象层.嵌入式系统总是会提供timer的 ...
- Linux时间子系统之(一):时间的基本概念
专题文档汇总目录 Notes:Linux时间基准点:Linux时间和broken-down time(struct tm):不同精度的时间表示time_t.timeval.timespec. 原文地址 ...
随机推荐
- Android之SlideMenu实例Demo
年末加班基本上一周都没什么时候回家写代码,回到家就想睡觉,周末难得有时间写个博客,上次写了一篇关于SlideMenu开源项目的导入问题,这次主要讲讲使用的问题,SlideMenu应用的广泛程度就不用说 ...
- dp 密度 分辨率 屏幕 状态栏 标题栏 适配
一篇总结的非常完善的博文:http://www.jianshu.com/p/ec5a1a30694b 屏幕像素参数相关信息表格 屏幕级别 像素密度 每英寸像素数 通常分辨率 分辨率别称 默认 ...
- 基于Java spring框架的微信企业号开发中关于js-sdk的配置
在调用js-sdk的第一步,我们需要引入js-sdk的js链接,然后执行wx.config,官方示例如下所示: wx.config({ debug: true, // 开启调试模式,调用的所有api的 ...
- Cocos2d-x教程(31)-TableView的滚动栏
欢迎增加Cocos2d-x 交流群:193411763 转载时请注明原文出处 :http://blog.csdn.net/u012945598/article/details/38587659 在非常 ...
- Http具体解释
一.Http是位于网络层的超文本传输协议. 我们用浏览器浏览网页就是用的http协议实现的. 二.Http请求详细分析 我们能够用httpwatch工具对http进行抓包分析 下面就是请求www.si ...
- 【js】利用闭包消除回调函数启动时值已经发生变化的影响
在以下代码中,timeFun异步执行了一个匿名函数,当输出color的值时已经由green变成red,因此输出为red. function timedFun(callback){ setTimeout ...
- 缺少dll文件的解决方法
1.什么是dll文件 从专业的角度来说,dll文件,即动态连接库,是一种不可执行的二进制文件,它允许程序共享执行特殊任务所必需的代码和其他资源.打个比方,相当于你去饭店吃饭,只人带上钱或卡就可以了,不 ...
- GP开发示例:数据库去重
这个例子专业讲解基于ArcEngine使用GP开发的过程及遇到的问题.更多GP使用方法:GP使用心得 功能需求:现在外业第一次数据(简称调绘.mdb)和第二次数据(简称检查.mdb)有重复.第二次是在 ...
- PHP高级教程-异常处理(Exception)
PHP 异常处理 异常用于在指定的错误发生时改变脚本的正常流程. 异常是什么 PHP 5 提供了一种新的面向对象的错误处理方法. 异常处理用于在指定的错误(异常)情况发生时改变脚本的正常流程.这种情况 ...
- code[VS] 1048 石子归并
题目描写叙述 Description 有n堆石子排成一列,每堆石子有一个重量w[i], 每次合并可以合并相邻的两堆石子.一次合并的代价为两堆石子的重量和w[i]+w[i+1]. 问安排如何的合并顺序, ...