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相关的函数即可。

原文地址:https://www.cnblogs.com/alantu2018/p/8448305.html

时间: 2024-10-09 23:10:56

Linux时间子系统(五) POSIX Clock的相关文章

Linux时间子系统(六) POSIX timer

一.前言 在用户空间接口函数文档中,我们描述了和POSIX timer相关的操作,主要包括创建一个timer.设定timer.获取timer的状态.获取timer overrun的信息.删除timer.本文将沿着这些用户空间的接口定义来看看内核态的实现.虽然POSIX timer可以基于各种不同的clock创建,本文主要描述real time clock相关的timer. 本文第二章描述了POSIX timer的基本原理,第三章描述系统调用的具体实现,第四章主要讲real time clock的

Linux时间子系统之一:clock source(时钟源)【转】

转自:http://blog.csdn.net/droidphone/article/details/7975694 clock source用于为linux内核提供一个时间基线,如果你用linux的date命令获取当前时间,内核会读取当前的clock source,转换并返回合适的时间单位给用户空间.在硬件层,它通常实现为一个由固定时钟频率驱动的计数器,计数器只能单调地增加,直到溢出为止.时钟源是内核计时的基础,系统启动时,内核通过硬件RTC获得当前时间,在这以后,在大多数情况下,内核通过选定

Linux时间子系统专题汇总

DroidPhone关于Linux时间子系统专题: http://blog.csdn.net/DroidPhone/article/category/1263459 Linux时间子系统之一:clock source(时钟源) Linux时间子系统之二:表示时间的单位和结构 Linux时间子系统之三:时间的维护者:timekeeper Linux时间子系统之四:定时器的引擎:clock_event_device Linux时间子系统之五:低分辨率定时器的原理和实现 Linux时间子系统之六:高精

Linux时间子系统(十五) clocksource

一.前言 和洋葱一样,软件也是有层次的,内核往往需要对形形色色的某类型的驱动进行抽象,屏蔽掉其具体的特质,获取该类驱动共同的逻辑,而又根据这些逻辑撰写该类驱动的抽象层.嵌入式系统总是会提供timer的硬件block,软件需要对timer硬件提供的功能进行抽象:linux kernel将timer类型的硬件抽象成两个组件,一是free running的counter,另外一个是指定的counter值上产生中断的能力.本文主要描述第一个组件,在内核中被称作clock source. 二.什么是clo

Linux时间子系统(一) 基本概念

本文使用Q & A的方式来和大家以前探讨一下时间的基本概念 一.什么是时间? 这个问题实在是太复杂了,我都不知道这是一个物理学.宇宙学.还是热力学异或是哲学问题,我只是想从几个侧面来了解一下时间这个概念.本节内容都是我坐在公交车上瞎想的,对物理学有兴趣的人可以指出我的错误(一个搞linux kernel的人不会有太深刻的物理学知识的),对Linux时间子系统有兴趣的人还是忽略这个小节吧. 1.时间和空间以及相对性 有没有绝对时间的概念呢?时间是否是独立于一切存在的呢?相信有绝对时间的存在比较符合

Linux时间子系统(十七) ARM generic timer驱动代码分析

一.前言 关注ARM平台上timer driver(clocksource chip driver和clockevent chip driver)的驱动工程师应该会注意到timer硬件的演化过程.在单核时代,各个SOC vendor厂商购买ARM core的IP,然后自己设计SOC上的peripherals,这里面就包括了timer的硬件.由于没有统一的标准,各个厂商的设计各不相同,这给驱动工程师带来了工作量.然而,如果仅仅是工作量的话就还好,实际上,不仅仅如此.linux的时间子系统要求硬件t

Linux时间子系统之八:动态时钟框架(CONFIG_NO_HZ、tickless)

在前面章节的讨论中,我们一直基于一个假设:Linux中的时钟事件都是由一个周期时钟提供,不管系统中的clock_event_device是工作于周期触发模式,还是工作于单触发模式,也不管定时器系统是工作于低分辨率模式,还是高精度模式,内核都竭尽所能,用不同的方式提供周期时钟,以产生定期的tick事件,tick事件或者用于全局的时间管理(jiffies和时间的更新),或者用于本地cpu的进程统计.时间轮定时器框架等等.周期性时钟虽然简单有效,但是也带来了一些缺点,尤其在系统的功耗上,因为就算系统目

Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现

上一篇文章,我介绍了传统的低分辨率定时器的实现原理.而随着内核的不断演进,大牛们已经对这种低分辨率定时器的精度不再满足,而且,硬件也在不断地发展,系统中的定时器硬件的精度也越来越高,这也给高分辨率定时器的出现创造了条件.内核从2.6.16开始加入了高精度定时器架构.在实现方式上,内核的高分辨率定时器的实现代码几乎没有借用低分辨率定时器的数据结构和代码,内核文档给出的解释主要有以下几点: 低分辨率定时器的代码和jiffies的关系太过紧密,并且默认按32位进行设计,并且它的代码已经经过长时间的优化

Linux时间子系统(二) 软件架构

一.前言 本文的主要内容是描述内核时间子系统的软件框架.首先介绍了从旧的时间子系统迁移到新的时间子系统的源由,介绍新的时间子系统的优势.第三章汇整了时间子系统的相关文件以及内核配置.最后描述各种内核配置下的时间子系统的数据流和控制流. 二.背景介绍 1.传统内核时间子系统的软件架构 让我们先回到远古的2.4内核时代,其内核的时间子系统的软件框架如下: 首先,在每个architecture相关的代码中要有实现clock event和clock source模块.这里听起来名字是高大上,实际上,这里