Linux时间子系统(十二) periodic tick
一、tick device概念介绍
1、数据结构
在内核中,使用struct tick_device来抽象系统中的tick设备,如下:
struct tick_device {
struct clock_event_device *evtdev;
enum tick_device_mode mode;
};
从上面的定义就可以看出:所谓tick device其实就是工作在某种模式下的clock event设备。工作模式体现在tick device的mode成员,evtdev指向了和该tick device关联的clock event设备。
tick device的工作模式定义如下:
enum tick_device_mode {
TICKDEV_MODE_PERIODIC,
TICKDEV_MODE_ONESHOT,
};
tick device可以工作在两种模式下,一种是周期性tick模式,另外一种是one shot模式。one shot模式主要和tickless系统以及高精度timer有关,会在另外的文档中描述,本文主要介绍periodic mode。
2、tick device的分类以及和CPU的关系
(1) local tick device。在单核系统中,传统的unix都是在tick驱动下进行任务调度、低精度timer触发等,在多核架构下,系统为每一个cpu建立了一个tick device,如下:
DEFINE_PER_CPU(struct tick_device, tick_cpu_device);
local tick device的clock event device应该具备下面的特点:
(a)该clock event device对应的HW timer必须是和该CPU core是有关联的的(也就是说,该hw
timer的中断是可以送达到该CPU core的)。struct clock_event_device
有一个cpumask成员,它可以指示该clock event device为哪一个或者哪几个CPU core工作。如果采用ARM generic
timer的硬件,其HW timer总是为一个CPU core服务的,我们称之为per cpu timer。
(b)该clock event device支持one shot模式,并且精度最高(rating最大)
(2)global tick device。具体定义如下:
int tick_do_timer_cpu __read_mostly = TICK_DO_TIMER_BOOT;
有些任务不适合在local tick device中处理,例如更新jiffies,更新系统的wall
time,更新系统的平均负载(不是单一CPU core的负载),这些都是系统级别的任务,只需要在local tick
device中选择一个作为global tick device就OK了。tick_do_timer_cpu指明哪一个cpu上的local
tick作为global tick。
(3)broadcast tick device,定义如下:
static struct tick_device tick_broadcast_device;
我们会单独一份文档描述它,这里就不再描述了。
二、初始化tick device
1、注册一个新的clock event device的时候,tick device layer要做什么?
在clock event device的文章中,我们知道:底层的timer硬件驱动在初始化的时候会注册clock
event device,在注册过程中就会调用tick_check_new_device函数来看看是否需要进行tick
device的初始化,如果已经已经初始化OK的tick device是否有更换更高精度clock event device的需求。代码如下:
void tick_check_new_device(struct clock_event_device *newdev)
{
struct clock_event_device *curdev;
struct tick_device *td;
int cpu;cpu = smp_processor_id();---------------------------(1)
if (!cpumask_test_cpu(cpu, newdev->cpumask)) goto out_bc;td = &per_cpu(tick_cpu_device, cpu);---获取当前cpu的tick device
curdev = td->evtdev; ---目前tick device正在使用的clock event deviceif (!tick_check_percpu(curdev, newdev, cpu))-------------------(2)
goto out_bc;if (!tick_check_preferred(curdev, newdev))--------------------(3)
goto out_bc;if (!try_module_get(newdev->owner)) -----增加新设备的reference count
return;if (tick_is_broadcast_device(curdev)) { ----------------------(4)
clockevents_shutdown(curdev);
curdev = NULL;
}
clockevents_exchange_device(curdev, newdev); ---通知clockevent layer
tick_setup_device(td, newdev, cpu, cpumask_of(cpu)); ---------------(5)
if (newdev->features & CLOCK_EVT_FEAT_ONESHOT) ---其他文档中描述
tick_oneshot_notify();
return;out_bc:
tick_install_broadcast_device(newdev); ----其他文档中描述
}
(1)是否是为本CPU服务的clock event device?如果不是,那么不需要考虑per cpu tick
device的初始化或者更换该cpu tick device的clock event device。当然,这是还是可以考虑用在broadcast
tick device的。
(2)第二个关卡是per cpu的检查。如果检查不通过,那么说明这个新注册的clock event
device和该CPU不来电,不能用于该cpu的local tick。如果注册的hw timer都是cpu
local的(仅仅属于一个cpu,这时候该clock event
device的cpumask只有一个bit被set),那么事情会比较简单。然而,事情往往没有那么简单,一个hw
timer可以服务多个cpu。我们这里说HW
timer服务于某个cpu其实最重要的是irq是否可以分发到指定的cpu上。我们可以看看tick_check_percpu的实现:
static bool tick_check_percpu(struct clock_event_device *curdev,
struct clock_event_device *newdev, int cpu)
{
if (!cpumask_test_cpu(cpu, newdev->cpumask))-------------------(a)
return false;
if (cpumask_equal(newdev->cpumask, cpumask_of(cpu)))---------------(b)
return true;
if (newdev->irq >= 0 && !irq_can_set_affinity(newdev->irq))--------------(c)
return false;
if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu)))----------(d)
return false;
return true;
}
(a)判断这个新注册的clock event device是否可以服务该CPU,如果它根本不鸟这个cpu那么不用浪费时间了。
(b)判断这个新注册的clock event device是否只服务该CPU。如果这个clock event device就是服务该cpu的,那么别想三想四了,这个clock event device就是你这个CPU的人了。
(c)如果能走到这里,说明该clock event
device可以服务多个CPU,指定的cpu(作为参数传递进来)只是其中之一而已,这时候,可以通过设定irq affinity将该clock
event device的irq定向到该cpu。当前,前提是可以进行irq affinity的设定,这里就是进行这样的检查。
(d)走到这里,说明该新注册的clock event device是可以进行irq affinity设定的。我们可以通过修改irq
affinity让该hw timer服务于这个指定的CPU。恩,听起来有些麻烦,的确如此,如果当前CPU的tick
device正在使用的clock event device就是special for当前CPU的(根本不鸟其他CPU),有如此专情的clock
event device,夫复何求,果断拒绝新注册的设备。
(3)程序来到这里,说明tick_check_percpu返回true,CPU和该clock event
device之间的已经是眉目传情了,不过是否可以入主,就看该cpu的原配是否有足够强大的能力(精度和特性)。tick_check_preferred代码如下:
static bool tick_check_preferred(struct clock_event_device *curdev,
struct clock_event_device *newdev)
{
if (!(newdev->features & CLOCK_EVT_FEAT_ONESHOT)) {--------------(a)
if (curdev && (curdev->features & CLOCK_EVT_FEAT_ONESHOT))
return false;
if (tick_oneshot_mode_active())
return false;
}return !curdev ||
newdev->rating > curdev->rating ||
!cpumask_equal(curdev->cpumask, newdev->cpumask);-------------(b)
}
(a)首先进行one shot能力比拼。如果新的clock event device没有one
shot能力而原配有,新欢失败。如果都没有one
shot的能力,那么要看看当前系统是否启用了高精度timer或者tickless。本质上,如果clock event
device没有oneshot功能,那么高精度timer或者tickless都是处于委曲求全的状态,如果这样,还是维持原配的委曲求全的状态,新欢失败
(b)如果current是NULL的话,事情变得非常简单,当然是新来的这个clock event
device胜出了(这时候,后面的比较都没有意义了)。如果原配存在的话,那么可以看rating,如果新来的精度高,那也选择新来的clock
event device。是否精度低就一定不选新的呢?也不是,新设备还是有机会力挽狂澜的:如果新来的是local
timer,而原配是非local timer,这时候,也可以考虑选择新的,毕竟新来的clock event device是local
timer,精度低一些也没有关系。
当tick_check_percpu返回true的时候有两种情况:一种是不管current是什么状态,新设备是CPU的local timer(只为这个cpu服务)。另外一种情况是新设备不是CPU的local timer,当然原配也没有那么专一。
我们先看看第一种情况:如果cpumask_equal返回true,那么说明原配也是local
timer,那么没有办法了,谁的rating高就选谁。如果cpumask_equal返回false,那么说明原配不是local
timer,那么即便新来的rating低一些也还是优先选择local timer。
我们再看看第二种情况:这里我绝对逻辑有问题,不知道是代码的问题还是我还没有考虑清楚,先TODO吧。
(4)OK,经过复杂的检查,我们终于决定要用这个新注册的clock event
device来替代current了(当然,也有可能current根本不存在)。在进行替换之前,我们还有检查一下current是否是broadcast
tick device,如果是的话,还不能将其退回clockevents layer,仅仅是设定其状态为shutdown。curdev =
NULL这一句很重要,在clockevents_exchange_device函数中,如果curdev == NULL的话,old
device将不会从全局链表中摘下,挂入clockevents_released链表。
(5)setup tick device,参考下一节描述。
2、如何Setup 一个 tick device?
所谓setup一个tick device就是对tick device心仪的clock event设备进行设置,并将该tick device的evtdev指向新注册的这个clock event device,具体代码如下:
static void tick_setup_device(struct tick_device *td,
struct clock_event_device *newdev, int cpu,
const struct cpumask *cpumask)
{
ktime_t next_event;
void (*handler)(struct clock_event_device *) = NULL;if (!td->evtdev) {
if (tick_do_timer_cpu == TICK_DO_TIMER_BOOT) {--------------(1)
……
}td->mode = TICKDEV_MODE_PERIODIC;------------------(2)
} else {
handler = td->evtdev->event_handler;
next_event = td->evtdev->next_event;
td->evtdev->event_handler = clockevents_handle_noop; ------------(3)
}td->evtdev = newdev; -----终于修成正果了,呵呵
if (!cpumask_equal(newdev->cpumask, cpumask)) ---------------(4)
irq_set_affinity(newdev->irq, cpumask);if (tick_device_uses_broadcast(newdev, cpu)) -------留给broadcast tick文档吧
return;if (td->mode == TICKDEV_MODE_PERIODIC)
tick_setup_periodic(newdev, 0); ----------------------(5)
else
tick_setup_oneshot(newdev, handler, next_event); -----其他文档描述
}
(1)在multi core的环境下,每一个CPU core都自己的tick device(可以称之local tick
device),这些tick device中有一个被选择做global tick device,负责维护整个系统的jiffies。如果该tick
device的是第一次设定,并且目前系统中没有global
tick设备,那么可以考虑选择该tick设备作为global设备,进行系统时间和jiffies的更新。更细节的内容请参考timekeeping文档。
(2)在最初设定tick device的时候,缺省被设定为周期性的tick。当然,这仅仅是初始设定,实际上在满足一定的条件下,在适当的时间,tick device是可以切换到其他模式的,下面会具体描述。
(3)旧的clockevent设备就要退居二线了,将其handler修改为clockevents_handle_noop。
(4)如果不是local timer,那么还需要调用irq_set_affinity函数,将该clockevent的中断,定向到本CPU。
(5)tick_setup_periodic的代码如下(注:下面的代码分析中暂不考虑broadcast tick的情况):
void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
{
tick_set_periodic_handler(dev, broadcast); ----设定event handler为tick_handle_periodicif ((dev->features & CLOCK_EVT_FEAT_PERIODIC) && !tick_broadcast_oneshot_active()) {
clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC);---------(a)
} else {
unsigned long seq;
ktime_t next;do {
seq = read_seqbegin(&jiffies_lock);
next = tick_next_period; -----获取下一个周期性tick触发的时间
} while (read_seqretry(&jiffies_lock, seq));clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT); ---模式设定
for (;;) {
if (!clockevents_program_event(dev, next, false)) ----program next clock event
return;
next = ktime_add(next, tick_period); ------计算下一个周期性tick触发的时间
}
}
}
(a)如果底层的clock event device支持periodic模式,那么直接调用clockevents_set_mode设定模式就OK了
(b)如果底层的clock event device不支持periodic模式,而tick device目前是周期性tick mode,那么要稍微复杂一些,需要用clock event device的one shot模式来实现周期性tick。
三、周期性tick的运作
1、从中断到clock event handler
一般而言,底层的clock event chip driver会注册中断,我们用ARM generic timer驱动为例,注册的代码如下:
…
err = request_percpu_irq(ppi, arch_timer_handler_phys, "arch_timer", arch_timer_evt);
……
具体的timer的中断handler如下:
static irqreturn_t arch_timer_handler_phys_mem(int irq, void *dev_id)
{……
evt->event_handler(evt);
……}
也就是说,在timer interrupt handler中会调用clock event device的event handler,而在周期性tick的场景下,这个event handler被设定为tick_handle_periodic。
2、周期性tick的clock event handler的执行分析
由于每个cpu都有自己的tick device,因此,在每个cpu上,每个tick到了的时候,都会调用tick_handle_periodic函数进行周期性tick中要处理的task,具体如下:
void tick_handle_periodic(struct clock_event_device *dev)
{
int cpu = smp_processor_id();
ktime_t next;tick_periodic(cpu); ----周期性tick中要处理的内容,参考下节描述
if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
return;
next = ktime_add(dev->next_event, tick_period);----计算下一个周期性tick触发的时间
for (;;) {
if (!clockevents_program_event(dev, next, false))---设定下一个clock event触发的时间
return;
if (timekeeping_valid_for_hres())------在其他文档中描述
tick_periodic(cpu);
next = ktime_add(next, tick_period);
}
}
如果该tick device所属的clock event device工作在one shot mode,那么还需要为产生周期性tick而进行一些额外处理。
2、周期性tick中要处理的内容
代码如下:
static void tick_periodic(int cpu)
{
if (tick_do_timer_cpu == cpu) {----global tick需要进行一些额外处理
write_seqlock(&jiffies_lock);
tick_next_period = ktime_add(tick_next_period, tick_period);do_timer(1);-------------更新jiffies,计算平均负载
write_sequnlock(&jiffies_lock);
update_wall_time();----------更新wall time
}update_process_times(user_mode(get_irq_regs()));----更新和当前进程相关的内容
profile_tick(CPU_PROFILING);------和性能剖析相关,不详述了
}
Linux时间子系统(十二) periodic tick的更多相关文章
- Linux时间子系统(十五) clocksource
一.前言 和洋葱一样,软件也是有层次的,内核往往需要对形形色色的某类型的驱动进行抽象,屏蔽掉其具体的特质,获取该类驱动共同的逻辑,而又根据这些逻辑撰写该类驱动的抽象层.嵌入式系统总是会提供timer的 ...
- Linux时间子系统(十四) tick broadcast framework
一.前言 在内核中,有cpuidle framework可以控制cpu的节电:当没有进程调度到该cpu上执行的时候,swapper进程粉墨登场,将该cpu会被推入到idle状态.当然CPU的idle状 ...
- Linux时间子系统(十六) clockevent
一.clock event控制的通用逻辑 1.产生clock event的设备 各种系统的timer硬件形形色色,不过在general clock event device layer,struct ...
- Linux时间子系统之二:表示时间的单位和结构
人们习惯用于表示时间的方法是:年.月.日.时.分.秒.毫秒.星期等等,但是在内核中,为了软件逻辑和代码的方便性,它使用了一些不同的时间表示方法,并为这些表示方法定义了相应的变量和数据结构,本节的内容就 ...
- Linux时间子系统之二:Alarm Timer
一.前言 严格来讲Alarm Timer也算POSIX Timer一部分,包含两种类型CLOCK_REALTIME_ALARM和CLOCK_BOOTTIME_ALARM.分别是在CLOCK_REALT ...
- Linux时间子系统之二:表示时间的单位和结构【转】
本文转载自:http://blog.csdn.net/droidphone/article/details/7979295 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] ...
- Linux时间子系统专题汇总
关于Linux时间子系统有两个系列文章讲的非常好,分别是WowoTech和DroidPhone. 还有两本书分别是介绍: Linux用户空间时间子系统<Linux/UNIX系统编程手册>的 ...
- Linux时间子系统之(十二):periodic tick
专题文档汇总目录 Notes:TickDevice模式,以及clocckevent设备.TickDevice设备的初始化,TickDevice是如何加入到系统中的.周期性Tick的产生. 原文地址:L ...
- Linux时间子系统之(十四):tick broadcast framework
专题文档汇总目录 Notes:BroadcastTick作为cpuidle的waker,硬件基础.BroadcastTick嵌入在当前系统Tick框架中.BroadcastTick设备初始化:周期性T ...
随机推荐
- TabLayout ViewPager Fragment 简介 案例 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- js cookie实例
什么是cookie: △ 用来保存用户信息:用户名.密码... ... △ 同一网站共享一套cookie,大小有限,保存时间 △ 使用doc ...
- c#逐行分元素读取记事本txt数据写进数据库
其实这里最关键的一个方法是 StreamReader类里的 ReadLine();这个方法可以逐行读取txt流里面的数据.写了个简单的demo,已经加上了详细的注释说明. ok,好了,不废话,下面 ...
- [置顶] IOS用CGContextRef画各种图形(文字、圆、直线、弧线、矩形、扇形、椭圆、三角形、圆角矩形、贝塞尔曲线、图片)
首先了解一下CGContextRef: An opaque type that represents a Quartz 2D drawing environment. Graphics Context ...
- 下载RAD
1.登录https://w3-103.ibm.com/software/xl/download/ticket.do 2.输入Intranet ID和pswd,然后选I Agree. 3.然后点Sear ...
- SqlServer日常积累(二)
1.Like运算符:将字符串表达式与 SQL表达式中的模式进行比较匹配. 语法 :expression Like 'pattern' ,expression为匹配字段,pattern为匹配字符串.可以 ...
- JSTL详解(一)
将jstl.jar包导入到工程中 jstldemo1.jsp <%@ taglib prefix="c" uri="http://java.sun.com/jsp/ ...
- UIFont可用字体
label.font = [UIFont fontWithName:@"Arial-BoldItalicMT" size:24]; 字体名如下: Font Family: Amer ...
- Struts2(三)更改字符编码
一.导入包和struts配置文件 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE stru ...
- Linux虚拟机下使用USB转串口线——配置minicom、以及screen的使用
转自:http://bbs.ednchina.com/BLOG_ARTICLE_637212.HTM 环境:Windows XP + (VMware Workstation - Linux) 1.确保 ...