从硬件到软件,Linux时间子系统全栈解析电子头条

以qcom8核armv8的SOC为例,把cpu本地定时器称为arch_timer,全局计数器Systemcounter不需要专门的dts节点,配置如下:

arch_timer:timer{compatible="arm,armv8-timer";interrupts=,,,;clock-frequency=<19200000>;always-on;};出于虚拟化及安全执行等级考虑,armv8为每个cpu核心提供至少下面4种定时器用于不同的执行环境

4种定时器与dts中的对应关系如下:

软件中断号13:ARCH_TIMER_PHYS_SECURE_PPI,安全世界物理定时器私有中断,SecureEL1physicaltimer

软件中断号14:ARCH_TIMER_PHYS_NONSECURE_PPI,非安全世界物理定时器私有中断,Non-secureEL1physicaltimer

软件中断号11:ARCH_TIMER_VIRT_PPI,虚拟定时器私有中断,vitualtimer

软件中断号12:ARCH_TIMER_HYP_PPI,hypervisor定时器私有中断,Non-secureEL2physicaltimer

虚拟定时器和物理定时器的差别在于,虚拟定时器的Systemcounter计数输入会在实际Systemcounter基础上添加一个偏移量,这个偏移量由host控制,来实现不同guest的无感切换。

一般外设中断都是dts配置硬件中断号,然后软件映射到一个软件中断号,最后用软件中断号向系统注册中断,这里dts配置的直接就是软件中断号,因为linux中断系统中为SGI和PPI预留有专用软件中断号。

上面配置的定时器,cpu可以通过cp15协处理器来访问定时器寄存器,称为cp15类型定时器。arm还提供一种内存映射方式访问的定时器,称为内存映射定时器(MemoryMapTimer),是将定时器寄存器映射到io区域,注册在系统中的名称为“arch_mem_timer”,主要为了没有协处理器的版本使用。

TIMER_OF_DECLARE(armv8_arch_timer,“arm,armv8-timer”,arch_timer_of_init);

在内核初始化流中路径如下:

从开机0s的log中看到arch_timer的初始化情况,初始化了cp15和mmio2种定时器,时钟频率都是19.2MHz,都是虚拟的timer。系统启动早期只初始化cpu0的arch_timer,然后通过cpuhp_setup_state()设置了热插拔cpu时的注册和注销定时器函数,随着后续多核的启动以及下线,其他cpu的arch_timer也会陆续初始化注册或注销。

[0.000000]arch_timer:cp15andmmiotimer(s)runningat19.20MHz(virt/virt).运行时查看当前系统的ClockEventdevice,一个内存映射定时器arch_mem_timer和8个本地定时器arch_sys_timer

adbshellhead/sys/devices/system/clockevents/*/current_device==>/sys/devices/system/clockevents/broadcast/current_device<==arch_mem_timer==>/sys/devices/system/clockevents/clockevent0/current_device<==arch_sys_timer==>/sys/devices/system/clockevents/clockevent1/current_device<==arch_sys_timer==>/sys/devices/system/clockevents/clockevent2/current_device<==arch_sys_timer==>/sys/devices/system/clockevents/clockevent3/current_device<==arch_sys_timer==>/sys/devices/system/clockevents/clockevent4/current_device<==arch_sys_timer==>/sys/devices/system/clockevents/clockevent5/current_device<==arch_sys_timer==>/sys/devices/system/clockevents/clockevent6/current_device<==arch_sys_timer==>/sys/devices/system/clockevents/clockevent7/current_device<==arch_sys_timer通过cat/proc/interrupts查看中断计数,arch_timer中断触发频率非常高,仅次于2个高频的IPI中断,可见其重要性。软件IRQ号11,对应dts中的第3个ARCH_TIMER_VIRT_PPI,硬件中断号27。

IRQCPU0CPU1CPU2CPU3CPU4CPU5CPU6CPU711:741381292651385122237327418698511742GICv327Levelarch_timerIPI0:2848814481304062944032710200242350830199ReschedulinginterruptsIPI1:578917668334417521605160916651262FunctioncallinterruptsIPI2:00000000CPUstopinterruptsIPI3:00000000CPUstop(forcrashdump)interruptsIPI4:00000000TimerbroadcastinterruptsIPI5:362899561139931380919441147371569117867IRQworkinterruptsIPI6:00000000CPUwake-upinterrupts2.3时钟源clockSource初始化初始化完定时器arch_timer,接下来就会初始化时钟源,arm上称为arch_counter,通过clocksource结构体描述,成员初值如下:

staticstructclocksourceclocksource_counter={.name="arch_sys_counter",.rating=400,.read=arch_counter_read,.mask=CLOCKSOURCE_MASK(56),.flags=CLOCK_SOURCE_IS_CONTINUOUS,};.name=“arch_sys_counter”,时钟源名称为”arch_sys_counter”.rating=400表示其精度很高,是理想的时钟源.mask=CLOCKSOURCE_MASK(56),56位有效bit.flags=CLOCK_SOURCE_IS_CONTINUOUS,连续计数

软件初始化过程如下

u64(*arch_timer_read_counter)(void)__ro_after_init=arch_counter_get_cntvct;static__always_inlineu64__arch_counter_get_cntvct(void){u64cnt;asmvolatile(ALTERNATIVE("isb\nmrs%0,cntvct_el0","nop\n"__mrs_s("%0",SYS_CNTVCTSS_EL0),ARM64_HAS_ECV):"=r"(cnt));arch_counter_enforce_ordering(cnt);returncnt;}从开机log看出,创建了一个clocksource,名为arch_sys_counter,mask:0xffffffffffffff表示56位有效位数,然后注册了sched_clock,56bit有效位,19MHz频率,分辨率52ns。系统还有一个jiffies时钟源,但是精度太低了,最后系统选择arch_sys_counter作为clocksourcedevice

[0.000000]clocksource:arch_sys_counter:mask:0xffffffffffffffmax_cycles:0x46d987e47,max_idle_ns:440795202767ns[0.000000]sched_clock:56bitsat19MHz,resolution52ns,wrapsevery4398046511078ns[0.044957]clocksource:jiffies:mask:0xffffffffmax_cycles:0xffffffff,max_idle_ns:7645041785100000ns[0.075704]clocksource:Switchedtoclocksourcearch_sys_counter运行时查看当前时钟源为arch_sys_counter

高精度版本:高精度版本会读取硬件计数器值来保证纳秒级精度,访问临界区时加顺序锁,精度最高,速度相对其他版本较慢;

低精度版本:低精度版本直接返回上次tick更新的base值,不涉及硬件操作,精度低,速度快;

高精度快速版本:相对于高精度版本区别在于访问临界区时加顺序锁类型不同,精度和速度都介于上两者。

下面是提供的内核接口函数,根据函数名和参数类型大致可以顾名思义,函数名以_ts64结尾的表示获取timespec64格式,函数名带_coarse表示低精度版本,带_fast_ns的表示高精度快速版本。

printk()->_printk()->vprintk()->vprintk_default()->vprintk_emit()->vprintk_store()->local_clock()->sched_clock()->read_sched_clock()->arch_counter_read()

vdso整个执行过程代码调用流程如下:

static__always_inlineu64__arch_get_hw_counter(s32clock_mode,70conststructvdso_data*vd){u64res;*IfFEAT_ECVisavailable,usetheself-synchronizingcounter.*Otherwisetheisbisrequiredtopreventthatthecountervalue*isspeculated.*/asmvolatile(ALTERNATIVE("isb\n""mrs%0,cntvct_el0","nop\n"__mrs_s("%0",SYS_CNTVCTSS_EL0),ARM64_HAS_ECV):"=r"(res)::"memory");arch_counter_enforce_ordering(res);returnres;}linux-vdso.so.1这个库不依赖其他库,并且会被系统绝大部分so依赖,包括libc.so,非常有牌面。所有用户进程都会映射vdso到自己的地址空间。

从最早的嵌入式设备到大型计算机系统,定时器都是必备的模块。随着各种硬件的升级,linux上的定时器软件也在不断变化,这里不去过多追溯历史版本和兼容情况,以当前linux6.1版本arm64安卓设备为例做介绍。

在定时器中断到来时进入硬中断处理函数hrtimer_interrupt(),如果最近到期的任务是硬timer,则继续在当前中断环境下处理。如果是软timer,则挂起软中断HRTIMER_SOFTIRQ,软中断在hrtimer_run_softirq()中处理软timer任务。

staticstructcpuhp_stepcpuhp_hp_states[]={[CPUHP_HRTIMERS_PREPARE]={.name="hrtimers:prepare",.startup.single=hrtimers_prepare_cpu,.teardown.single=hrtimers_dead_cpu,},}4.2hrtimer的使用在内核中直接使用hrtimer示例代码如下,每8.3ms周期性触发并打印log。

其初始化入口为

start_kernel()|-->init_timers()|-->init_timer_cpus()|-->posix_cputimers_init_work()|-->open_softirq(TIMER_SOFTIRQ,run_timer_softirq)hrtimer切换到高精度模式时,如下

add_timer(structtimer_list*timer);|-->__mod_timer(timer,timer->expires,MOD_TIMER_NOTPENDING);|-->internal_add_timer(base,timer);|-->idx=calc_wheel_index(timer->expires,base->clk,&bucket_expiry);|-->returncalc_index(expires,lvl,bucket_expiry);|-->enqueue_timer(base,timer,idx,bucket_expiry);staticinlineunsignedcalc_index(unsignedlongexpires,unsignedlvl,unsignedlong*bucket_expiry){unsignedidx;trace_android_vh_timer_calc_index(lvl,&expires);//*expires-=1expires=(expires>>LVL_SHIFT(lvl))+1;*bucket_expiry=expires

staticintcopy_signal(unsignedlongclone_flags,structtask_struct*tsk){#ifdefCONFIG_POSIX_TIMERSINIT_LIST_HEAD(&sig->posix_timers);hrtimer_init(&sig->real_timer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);sig->real_timer.function=it_real_fn;#endif}enumhrtimer_restartit_real_fn(structhrtimer*timer){structsignal_struct*sig=container_of(timer,structsignal_struct,real_timer);structpid*leader_pid=sig->pids[PIDTYPE_TGID];trace_itimer_expire(ITIMER_REAL,leader_pid,0);kill_pid_info(SIGALRM,SEND_SIG_PRIV,leader_pid);returnHRTIMER_NORESTART;}ITIMER_REAL使用示例

SYSCALL_DEFINE1(alarm,unsignedint,seconds){returnalarm_setitimer(seconds);}staticunsignedintalarm_setitimer(unsignedintseconds){structitimerspec64it_new,it_old;#ifBITS_PER_LONG<64if(seconds>INT_MAX)seconds=INT_MAX;#endifit_new.it_value.tv_sec=seconds;it_new.it_value.tv_nsec=0;it_new.it_interval.tv_sec=it_new.it_interval.tv_nsec=0;do_setitimer(ITIMER_REAL,&it_new,&it_old);/**Wecan'treturn0ifwehaveanalarmpending...Andwe'd*betterreturntoomuchthantoolittleanyway*/if((!it_old.it_value.tv_sec&&it_old.it_value.tv_nsec)||it_old.it_value.tv_nsec>=(NSEC_PER_SEC/2))it_old.it_value.tv_sec++;returnit_old.it_value.tv_sec;}进程在调用alarm定时之前,需要设置SIGALRM信号的处理函数。参数很简单,只有一个秒,使用代码示例如下

#include#include#includevoidsig_handler(intsignum){printf("ReceivedSIGALRM,timerexpired!\n");}intmain(){signal(SIGALRM,sig_handler);alarm(5);printf("Waitingforalarm...\n");pause();//Suspendtheprocessuntilasignalisreceivedprintf("Exiting...\n");return0;}6.4PosixtimerPosixtimer大大扩展了itimer的功能,一个进程可以同时创建任意个timer,并且可以指定到期信号。Posixtimer封装了多个syscall接口:

THE END
1.『计算机组成原理』计算机系统概述(考研面试必备)计算机系统由软件和硬件两部分组成(不需要背过,但要有概念) 硬件 硬件系统主要由中央处理器、存储器、输入输出控制系统和各种外部设备组成。中央处理器是对信息进行高速运算处理的主要部件,其处理速度可达每秒几亿次以上操作。存储器用于存储程序、数据和文件,常由快速的内存储器(容量可达数百兆字节,甚至数G字节)和慢https://blog.51cto.com/u_15127565/4701798
2.变频器行业应用技术方案VF系列变频恒压无塔供水系统采用国际上先进的交流电动机变频调速技术,对水泵进行调速以达到恒压供水的目的。该系统由以下几部分组成: (1)压力传感器将管网上压力信号变化量转化为电信号变化量,输给PID控制器。 (2)PID控制系统将电信号经分析运算后,输出给变频调速器。 http://www.360doc.com/content/13/0719/10/1140296_301010157.shtml
3.操作系统01内存往往不够用,可以通过操作系统将不常用的代码放到磁盘中。 覆盖技术(8090年代) 分时共享一块内存空间,有一块常驻内存空间的部分,把程序倒进倒出。 交换技术 虚拟内存 不是把程序所有内容都放在内存中,并且是由操作系统自动完成的。 整体上可能需要很多空间,但是一个程序短时间内只有少部分在内存中,虽然自动完成,https://zhuanlan.zhihu.com/p/114655197
4.计算机保研专业课复习(简答)北大计算机保研笔试内容其中T为该作业估计需要的执行时间,W为作业在后备状态队列中的等待时间。每当要进行作业调度时,系统计算每个作业的响应比,选择其中R最大者投入执行。 算法优点:由于长作业也有机会投入运行,在同一时间内处理的作业数显然要少于SJF法,从而采用HRRN方式时其吞吐量将小于采用SJF 法时的吞吐量。 https://blog.csdn.net/qq_51246603/article/details/131811704
5.自动控制系统主要由哪几部分组成自动控制系统的主要特征是什么1. 自动控制系统主要由哪几部分组成 自动控制系统主要由以下几个部分组成: 1.1 传感器与测量设备 传感器是自动控制系统中重要的组成部分之一,用于感知和测量待控制系统或过程的状态和参数。它们可以测量温度、压力、流量、位置等物理量,并将这些信息转化为电信号供后续处理使用。 https://www.eefocus.com/e/1586367.html
6.RTSP协议探秘:从原理到C++实践,解锁实时流媒体传输之道RTSP协议主要由以下几个部分组成: 请求和响应:RTSP协议使用类似于HTTP的请求-响应机制。客户端通过发送请求命令来控制媒体流的播放、暂停和停止等操作,服务器则返回相应的响应消息。 方法:RTSP协议定义了一系列方法,用于描述客户端和服务器之间的交互操作。常见的RTSP方法包括:OPTIONS、DESCRIBE、SETUP、PLAY、PAUSE、TEARhttps://developer.aliyun.com/article/1463836
7.江苏专转本计算机模拟试题1.计算机系统是由___和___两部分组成的,操作系统属于一种系统___。 它直接与计算机___系统打交道,是对它的第一层扩充。 2.Windows是___操作系统。 3.文件的结构分为___结构和___结构两种类型。 4.设备按共享属性分为___设备和___设备。 5.键盘的设备名为___。 二、选择http://www.ndzzb.com/datas/view-14.html
8.计算机网络章节2~4当同一网络上连接几个多播路由器时,他们能迅速有效选择其中一个来探询主机的成员关系。 IGMP询问报文中有一个数值N,指明一个最长响应时间,当收到询问时主机在0到N之间随机选择发送响应所需经过的时延。对应于最小时延的响应最先发送。 同组内每个主机都要监听响应,本组只要有其他主机先发送了响应自己就可以不再发https://www.jianshu.com/p/8d3ba4105994
9.2022年自考27481控制工程基础复习资料自考1.自动控制系统的工作原理;2.开环控制;3.闭环控制;4.反馈控制系统基本组成及组成的元件;5.自动控制系统的基本类型;6.对控制系统的基本要求。 (三)领会控制理论在机械制造工业中的应用: 1.离心调速器;2.机器人关节司服系统;3.三坐标数控机床;4.六自由度工业机器人;5.感应导线式自动导引车;6.柔性制造系统。https://www.educity.cn/zikao/352170.html
10.前沿科普实时操作系统概念梳理线程死锁应用程序rtos系统响应时间(Systemresponsetime):指系统发出处理要求到系统给出应答信号的时间,也就是从线程请求产生到线程完成之间的时间间隔,需要满足一定的时间约束。控制要满足一定的实时性要求,就是响应时间小于临界时间。系统响应时间由反应时间和处理时间两部分组成,反应时间指外部中断提交到CPU开始处理的时间,处理时间指CPU完成处https://www.163.com/dy/article/I607NBBM05562OP5.html