LINUX内核时钟中断机制
时间篇之linux系统时间和RTC时间

时间篇之linux系统时间和RTC时间⼀、linux系统下包含两个时间:系统时间(刚启动时读取的是rtc时间)和RTC时间。
⼀般情况下都会选择芯⽚上最⾼精度的定时器作为系统时间的定时基准,以避免在系统运⾏较长时间后出现⼤的时间偏移。
特点是掉电后不保存。
所以⼀旦你重启机器后,那么系统需要重新从RTC上重新获取时间,保存到系统内核⽂件中。
RTC(real_time clock)驱动程序,可以在E:\linux内核\linux-2.6.0\linux-2.6.0\drivers\char\rtc.c中找到。
设备接⼝就是 /dev/rtc, 他负责跟rtc打交道,并读取rtc中维护的时间.它是⼀个从系统定时器中独⽴出来的虚拟设备,⽤于设置系统时钟,提供报警器或周期性的定时器.那么系统时间⼀直运⾏吗?显然在操作系统关闭或重启期间,服务器宕机期间,整个服务器的时间就依赖于RTC芯⽚。
从这我们看出linux系统时间和RTC时间是两套独⽴的计时体系,但它们之间⼜是相互依存的:1)刚安装操作系统后,若在安装过程不设置系统时间,那么默认的系统时间就是从服务器的RTC芯⽚中获取当前的硬件时间;2)在linux操作系统中,⼀旦修改系统时间后,⼜重启或关闭Linux系统,则OS通常会将系统时间更新到RTC;3)在操作系统再次启动的时候,Linux OS则会再次从RTC中获取当前的时间。
服务器异常下电后,待操作系统重新启动后,发现系统时间发⽣了跳变?其原因通常是:修改了操作系统时间,在服务器异常下电后,操作系统并未及时将修改后的时间更新到RTC,导致操作系统重新启动后,就会从RTC芯⽚中加载了之前“⽼”的时间,从⽽在操作系统层⾯体现为“时间跳变”⼆、关于jiffies⼀次中断时间间隔叫⼀个tick,即每个触发周期的时间叫做tick,⼀秒内时钟中断的次数(每个时间间隔就是⼀次)等于Hzhz别名就是tick rate(HZ)linux系统查看hz:[root@k3master ~]# cat /boot/config-`uname -r` | grep 'CONFIG_HZ='CONFIG_HZ=10001hz就是每秒1000次中断每次时钟中断处理程序即每发⽣⼀次tick都会增加jiffies该变量的值,jiffies⼀秒内增加的值也就是Hz(频率),⽐如:linux下默认是 1000次时钟中断次数/秒系统运⾏时间以秒为单位,换算⽅法等于jiffies/Hz。
时钟子系统 理解

时钟子系统理解
时钟子系统是Linux内核中用于管理时钟的子系统。
它提供了一组API,用于创建、管理和使用时钟。
时钟子系统对于Linux系统的正常运行至关重要,因为它用于跟踪系统时间、调度进程和计时器等。
一、时钟子系统的基本概念
时钟子系统有以下几个基本概念:
1.时钟:时钟是提供计时功能的硬件或软件模块。
2.时钟源:时钟源是提供时钟信号的硬件或软件模块。
常见的时钟源包括晶振、PLL等。
3.时钟中断:时钟中断是每隔一定时间由时钟硬件产生的中断。
4.时钟滴答:时钟滴答是时钟中断的最小单位。
5.时钟频率:时钟频率是指时钟滴答的速率。
二、时钟子系统的架构
时钟子系统由以下几个模块组成:
1.时钟源:时钟源模块提供时钟信号。
2.时钟控制器:时钟控制器模块负责管理时钟源和时钟中断。
3.时钟提供程序:时钟提供程序模块为用户空间应用程序提供API来访问时钟。
4.时钟用户:时钟用户模块使用时钟来跟踪系统时间、调度进程和计时器等。
三、时钟子系统的功能
时钟子系统提供以下功能:
1.创建时钟:时钟子系统可以创建各种类型的时钟,包括实时时钟、定时器等。
2.管理时钟:时钟子系统可以管理时钟的频率、状态等。
3.使用时钟:时钟子系统可以为用户空间应用程序提供API来访问时钟。
10-5 Linux操作系统 - 中断、异常及系统调用

10.5.4 中断上半部分的处理 一、 中断控制器 •每个硬件设备控制器都能通过中断请求线 发出中断请求(简称IRQ) •所有设备的中断请求线又连到中断控制器 的输入端。 •在x86单CPU的机器上采用两个8259A芯片作 为中断控制器,一主一从。
•当8259A有中断信号输入同时中断信号不被 屏蔽时,主8259A向CPU发出 INT信号,请求 中断。这时如果CPU是处于允许中断状况, CPU就会发信号给8259A进入中断响应周期。 •在对8259A芯片的初始化过程中,第n号中 断在IDT表中的向量号为 n+32
•IDT中向量号的使用情况如下: 0-31 异常与非屏蔽中断使用。 32-47 可屏蔽中断使用32至47 128(0x80)实现系统调用。 其余 未使用 •保存现场 发生异常时在核心栈的程序计数器eip的 值取决于具体情况。一般情况下eip保存的 下一条指令的地址,但对于页面异常,保存 的产生异常的这条指令的地址而不是下一条 指令的地址
中断向量表IDT •IDT是中断/异常处理在内核的入口。IDT表 项还记录了一些其它信息用以安全检查。 •IDT在系统初始化时创建。 •每个中断/异常都有一个向量号,该号的值 在0-255之间,该值是中断/异常在IDT中的 索引。 •每个中断/异常均有其相应的处理函数,中 断/异常在使用前必须在IDT中注册信息以保 证发生中断/异常时能找到相应的处理函数。
struct hw_interrupt_type { const char * typename; unsigned int (*startup)(unsigned int irq); void (*shutdown)(unsigned int irq); void (*enable)(unsigned int irq); void (*disable)(unsigned int irq); void (*ack)(unsigned int irq); void (*end)(unsigned int irq); void (*set_affinity)(unsigned int irq, unsigned long mask); };
hrtimer及内核clock

hrtimer及内核clock/timer子系统[嵌入式]发布时间:2010-06-30 09:54:39转过来,研究android过程中发现linux kernel内核定时器这块较以前2.4版本改动非常大,在网上收到这篇,粗看了下,但时间紧张没仔细看,等忙完android的这个分析,回头仔细看。
kernel-2.6.22中的arm arch加入了对dynticks, clocksource/event支持. 找了些kernelclock及timer子系统近来的变化, 总结一下.一般来说Soft-Timer (timer wheel / hrtimer)都是由Hardware-Timer(时钟中断之类)以及相关的clock source(e.g GPT in Soc)驱动,所以我打算先从clock这层开始介绍, 接着是soft-timer, kernel timekeeping,最后来看一些应用.Clock Sourceclock source定义了一个clock device的基本属性及行为, 这些clock device一般都有计数,定时, 产生中断能力, 比如GPT. 结构定义如下:struct clocksource {char *name;struct list_head list;int rating;cycle_t (*read)(void);cycle_t mask;u32 mult; /* cycle -> xtime interval, maybe two clock cy cle trigger oneinterrupt (one xtime interval) */u32 shift;unsigned long flags;cycle_t (*vread)(void);void (*resume)(void);/* timekeeping specific data, ignore */cycle_t cycle_interval; /* just the rate of GPT count p er OS HZ */u64 xtime_interval; /* xtime_interval = cycle_interv al * mult. */cycle_t cycle_last ____cacheline_aligned_in_smp; /* las t cycle in rate count */u64 xtime_nsec; /* cycle count, remain from _ns ec* now nsec rate count offset = xtime_nsec +* _nsec << shift */s64 error;};最重要的成员是read(), cycle_last和cycle_interval. 分别定义了读取clock device count寄存器当前计数值接口, 保存上一次周期计数值和每个tick周期间隔值. 这个结构内的值,无论是cycle_t, 还是u64类型(实际cycle_t就是u64)都是计数值(cycle), 而不是nsec,sec和jiffies. read()是整个kernel读取精确的单调时间计数的接口,kernel会用它来计算其他时间, 比如:jiffies, xtime.clocksource的引入, 解决了之前kernel各个arch都有自己的clock device的管理方式,基本都隐藏在MSL层, kernel core 及driver很难访问的问题. 它导出了以下接口:1) clocksource_register() 注册clocksource2) clocksource_get_next() 获取当前clocksource设备3) clocksource_read() 读取clock, 实际跑到clocksource->read()当driver处理的时间精度比较高的时, 可以通过上面的接口, 直接拿clock device来读.当然目前ticker时钟中断源也会以clocksource的形式存在.Clock EventClock event的主要作用是分发clock事件及设置下一次触发条件. 在没有clock event之前,时钟中断都是周期性地产生, 也就是熟知的jiffies和HZ.Clock Event device主要的结构:struct clock_event_device {const char *name;unsigned int features;unsigned long max_delta_ns;unsigned long min_delta_ns;unsigned long mult;int shift;int rating;int irq;cpumask_t cpumask;int (*set_next_event)(unsigned long evt,struct clock_event_device *);void (*set_mode)(enum clock_event_mode mode,struct clock_event_device *);void (*event_handler)(struct clock_event_devi ce *);void (*broadcast)(cpumask_t mask);struct list_head list;enum clock_event_mode mode;ktime_t next_event;};最重要的是set_next_event(), event_handler(). 前者是设置下一个clock事件的触发条件,一般就是往clock device里重设一下定时器. 后者是event handler, 事件处理函数.该处理函数会在时钟中断ISR里被调用. 如果这个clock用来做为ticker时钟,那么handler的执行和之前kernel的时钟中断ISR基本相同, 类似timer_tick().事件处理函数可以在运行时动态替换, 这就给kernel一个改变整个时钟中断处理方式的机会,也就给highres tick及dynamic tick一个动态挂载的机会.目前kernel内部有periodic/highres/dynamic tick三种时钟中断处理方式. 后面会介绍.hrtimer & timer wheel首先说一下timer wheel. 它就是kernel一直采用的基于jiffies的timer机制,接口包括init_timer(), mod_timer(), del_timer()等, 很熟悉把.hrtimer 的出现, 并没有抛弃老的timer wheel机制(也不太可能抛弃:)).hrtimer做为kernel里的timer定时器, 而timer wheel则主要用来做timeout定时器.分工比较明确. hrtimers采用红黑树来组织timers, 而timer wheel采用链表和桶.hrtimer精度由原来的timer wheel的jiffies提高到nanosecond.主要用于向应用层提供nanosleep, posix-timers和itimer接口,当然驱动和其他子系统也会需要high resolution的timer.kernel 里原先每秒周期性地产生HZ个ticker(中断),被在下一个过期的hrtimer的时间点上产生中断代替. 也就是说时钟中断不再是周期性的,而是由timer来驱动(靠clockevent的set_next_event接口设置下一个事件中断),只要没有hrtimer加载, 就没有中断. 但是为了保证系统时间(进程时间统计,jiffies的维护)更新, 每个tick_period(NSEC_PER_SEC/HZ,再次强调hrtimer精度是nsec)都会有一个叫做tick_sched_timer的hrtimer加载.接下来对比一下, hrtimer引入之前及之后, kernel里时钟中断的处理的不同. (这里都是基于armarch的source去分析)1)no hrtimerkernel 起来, setup_arch()之后的time_init()会去初始化相应machine结构下的timer.初始化timer函数都在各个machine的体系结构代码中, 初始化完硬件时钟, 注册中断服务函数,使能时钟中断. 中断服务程序会清中断, 调用timer_tick(), 它执行:1. profile_tick(); /* kernel profile, 不是很了解*/2. do_timer(1); /* 更新jiffies */3. update_process_times(); /* 计算进程耗时, 唤起TIMER_SOFTIRQ(timer wheel),重新计算调度时间片等等*/最后中断服务程序设置定时器, 使其在下一个tick产生中断.这样的框架, 使得high-res的timer很难加入. 所有中断处理code都在体系结构代码里被写死,并且代码重用率很低, 毕竟大多的arch都会写同样的中断处理函数.2)hrtimerkernel 里有了clockevent/source的引入, 就把clocksource的中断以一种事件的方式被抽象出来.事件本身的处理交给event handler. handler可以在kernel里做替换从而改变时钟中断的行为.时钟中断ISR会看上去象这样:static irqreturn_t timer_interrupt(int irq, void *dev_id) {/* clear timer interrupt flag */...../* call clock event handler */arch_clockevent.event_handler(&arch_clockevent);....return IRQ_HANDLED;}event_handler 在注册clockevent device时, 会被默认设置成tick_handle_periodic().所以kernel刚起来的时候, 时钟处理机制仍然是periodic的, ticker中断周期性的产生.tick_handle_periodic()会做和timer_tick差不多的事情,然后调用clockevents_program_event() =>arch_clockevent.set_next_event()去设置下一个周期的定时器.tick-common.c里把原来kernel时钟的处理方式在clockevent框架下实现了, 这就是periodictick的时钟机制.hres tick机制在第一个TIMER SOFTIRQ里会替换掉periodic tick, 当然要符合一定条件,比如command line里没有把hres(highres=off)禁止掉,clocksource/event支持hres和oneshot的能力. 这里的切换做的比较ugly,作者的comments也提到了, 每次timer softirq被调度,都要调用hrtimer_run_queues()检查一遍hres是否active,如果能在timer_init()里就把clocksource/event的条件check过, 直接切换到hres就最好了,不知道是不是有什么限制条件. TIMER SOFTIRQ代码如下:static void run_timer_softirq(struct softirq_action *h) {tvec_base_t *base = __get_cpu_var(tvec_bases);hrtimer_run_queues(); /* 有机会就切换到hres或者nohz */if (time_after_eq(jiffies, base->timer_jiffies)) __run_timers(base); /* timer wheel */}切换的过程比较简单, 用hrtimer_interrupt()替换当前clockevent hander, 加载一个hrtimer:tick_sched_timer在下一个tick_period过期, retrigger下一次事件.hrtimer_interrupt ()将过期的hrtimers从红黑树上摘下来,放到相应clock_base->cpu_base->cb_pending列表里,这些过期timers会在HRTIMER_SOFTIRQ里执行.然后根据剩余的最早过期的timer来retrigger下一个event, 再调度HRTIMER_SOFTIRQ. hrtimer softirq执行那些再cb_pending上的过期定时器函数.tick_sched_timer这个hrtimer在每个tick_period都会过期, 执行过程和timer_tick()差不多,只是在最后调用hrtimer_forward将自己加载到下一个周期里去,保证每个tick_period都能正确更新kernel内部时间统计.TimekeepingTimekeeping子系统负责更新xtime, 调整误差, 及提供get/settimeofday接口. 为了便于理解,首先介绍一些概念:Times in Kernelkernel的time基本类型:1) system timeA monotonically increasing value that represents the amount of time the system has been running. 单调增长的系统运行时间, 可以通过time source,xtime及wall_to_monotonic计算出来.2) wall timeA value representing the the human time of day, as seen on a wrist-watch.Realtime时间: xtime.3) time sourceA representation of a free running counter running at a known frequency, usually in hardware, e.g GPT. 可以通过clocksource->read()得到counter值4) tickA periodic interrupt generated by a hardware-timer, typically with a fixed interval defined by HZ: jiffies这些time之间互相关联, 互相可以转换.system_time = xtime + cyc2ns(clock->read() - clock->cycle_last) +wall_to_monotonic;real_time = xtime + cyc2ns(clock->read() - clock->cycle_last)也就是说real time是从1970年开始到现在的nanosecond, 而systemtime是系统启动到现在的nanosecond.这两个是最重要的时间, 由此hrtimer可以基于这两个time来设置过期时间. 所以引入两个clock base.Clock BaseCLOCK_REALTIME: base在实际的wall timeCLOCK_MONOTONIC: base在系统运行system timehrtimer可以选择其中之一, 来设置expire time, 可以是实际的时间, 也可以是相对系统的时间.他们提供get_time()接口:CLOCK_REALTIME 调用ktime_get_real()来获得真实时间,该函数用上面提到的等式计算出realtime.CLOCK_MONOTONIC 调用ktime_get(), 用system_time的等式获得monotonic time.timekeeping提供两个接口do_gettimeofday()/do_settimeofday(), 都是针对realtime操作. 用户空间对gettimeofday的syscall也会最终跑到这里来.do_gettimeofday()会调用__get_realtime_clock_ts()获得时间, 然后转成timeval.do_settimeofday(), 将用户设置的时间更新到xtime, 重新计算xtime到monotonic的转换值,最后通知hrtimers子系统时间变更.int do_settimeofday(struct timespec *tv){unsigned long flags;time_t wtm_sec, sec = tv->tv_sec;long wtm_nsec, nsec = tv->tv_nsec;if ((unsigned long)tv->tv_nsec >= NSEC_PER_SEC) return -EINVAL;write_seqlock_irqsave(&xtime_lock, flags);nsec -= __get_nsec_offset();wtm_sec = wall_to__sec + (_sec - se c);wtm_nsec = wall_to__nsec + (_nsec - nsec);set_normalized_timespec(&xtime, sec, nsec); /* 重新计算x time:用户设置的时间减去上一个周期到现在的nsec */set_normalized_timespec(&wall_to_monotonic, wtm_sec, w tm_nsec); /*重新调整wall_to_monotonic */clock->error = 0;ntp_clear();update_vsyscall(&xtime, clock);write_sequnlock_irqrestore(&xtime_lock, flags);/* signal hrtimers about time change */clock_was_set();return 0;}Userspace Applicationhrtimer的引入, 对用户最有用的接口如下:Clock APIclock_gettime(clockid_t, struct timespec *)获取对应clock的时间clock_settime(clockid_t, const struct timespec *)设置对应clock时间clock_nanosleep(clockid_t, int, const struct timespec *, struct timespec *)进程nano sleepclock_getres(clockid_t, struct timespec *)获取时间精度, 一般是nanosecclockid_t 定义了四种clock:CLOCK_REALTIMESystem-wide realtime clock. Setting this clock requires appropriate privileges. CLOCK_MONOTONICClock that cannot be set and represents monotonic time since some unspecified starting point.CLOCK_PROCESS_CPUTIME_IDHigh-resolution per-process timer from the CPU.CLOCK_THREAD_CPUTIME_IDThread-specific CPU-time clock.前两者前面提到了, 后两个是和进程/线程统计时间有关系, 还没有仔细研究过,是utime/stime之类的时间. 应用层可以利用这四种clock, 提高灵活性及精度.Timer APITimer 可以建立进程定时器,单次或者周期性定时。
Linux设备驱动程序学习(10)-时间、延迟及延缓操作

Linux设备驱动程序学习(10)-时间、延迟及延缓操作Linux设备驱动程序学习(10)-时间、延迟及延缓操作度量时间差时钟中断由系统定时硬件以周期性的间隔产生,这个间隔由内核根据HZ 值来设定,HZ 是一个体系依赖的值,在<linux/param.h>中定义或该文件包含的某个子平台相关文件中。
作为通用的规则,即便如果知道HZ 的值,在编程时应当不依赖这个特定值,而始终使用HZ。
对于当前版本,我们应完全信任内核开发者,他们已经选择了最适合的HZ值,最好保持HZ 的默认值。
对用户空间,内核HZ几乎完全隐藏,用户HZ 始终扩展为100。
当用户空间程序包含param.h,且每个报告给用户空间的计数器都做了相应转换。
对用户来说确切的HZ 值只能通过/proc/interrupts 获得:/proc/interrup ts 的计数值除以/proc/uptime 中报告的系统运行时间。
对于ARM体系结构:在<linux/param.h>文件中的定义如下:也就是说:HZ 由__KERNEL__和CONFIG_HZ决定。
若未定义__KERNEL__,H Z为100;否则为CONFIG_H Z。
而CONFIG_HZ是在内核的根目录的.config文件中定义,并没有在make menuconfig的配置选项中出现。
Linux的\arch\arm\configs\s3c2410_defconfig文件中的定义为:所以正常情况下s3c24x0的HZ为200。
这一数值在后面的实验中可以证实。
每次发生一个时钟中断,内核内部计数器的值就加一。
这个计数器在系统启动时初始化为0,因此它代表本次系统启动以来的时钟嘀哒数。
这个计数器是一个64-位变量( 即便在32-位的体系上)并且称为“jiffies_64”。
但是驱动通常访问jiffies 变量(unsigned long)(根据体系结构的不同:可能是jiffies_64 ,可能是jiffies_64 的低32位)。
中断处理流程

中断处理流程linux中断处理浅析最近在研究异步消息处理, 突然想起linux内核的中断处理, ⾥⾯由始⾄终都贯穿着"重要的事马上做, 不重要的事推后做"的异步处理思想. 于是整理⼀下~第⼀阶段--获取中断号每个CPU都有响应中断的能⼒, 每个CPU响应中断时都⾛相同的流程. 这个流程就是内核提供的中断服务程序.在进⼊中断服务程序时, CPU已经⾃动禁⽌了本CPU上的中断响应, 因为CPU不能假定中断服务程序是可重⼊的.中断处理程序的第⼀步要做两件事情:1. 将中断号压⼊栈中; (不同中断号的中断对应不同的中断服务程序⼊⼝)2. 将当前寄存器信息压⼊栈中; (以便中断退出时恢复)显然, 这两步都是不可重⼊的(如果在保存寄存器值时被中断了, 那么另外的操作很可能就把寄存器给改写了, 现场将⽆法恢复), 所以前⾯说到的CPU进⼊中断服务程序时要⾃动禁⽌中断.栈上的信息被作为函数参数, 调⽤do_IRQ函数.第⼆阶段--中断串⾏化进⼊do_IRQ函数, 第⼀步进⾏中断的串⾏化处理, 将多个CPU同时产⽣的某⼀中断进⾏串⾏化.其⽅法是如果当前中断处于"执⾏"状态(表明另⼀个CPU正在处理相同的中断), 则重新设置它的"触发"标记, 然后⽴即返回. 正在处理同⼀中断的那个CPU完成⼀次处理后, 会再次检查"触发"标记, 如果设置, 则再次触发处理过程.于是, 中断的处理是⼀个循环过程, 每次循环调⽤handle_IRQ_event来处理中断.第三阶段--关中断条件下的中断处理进⼊handle_IRQ_event函数, 调⽤对应的内核或内核模块通过request_irq函数注册的中断处理函数.注册的中断处理函数有个中断开关属性, ⼀般情况下, 中断处理函数总是在关中断的情况下进⾏的. ⽽调⽤request_irq注册中断处理函数时也可以设置该中断处理函数在开中断的情况下进⾏,这种情况⽐较少见, 因为这要求中断处理代码必须是可重⼊的. (另外, 这⾥如果开中断, 正在处理的这个中断⼀般也是会被阻塞的. 因为正在处理某个中断的时候, 硬件中断控制器上的这个中断并未被ack, 硬件不会发起下⼀次相同的中断.)中断处理函数的过程可能会很长, 如果整个过程都在关中断的情况下进⾏, 那么后续的中断将被阻塞很长的时间.于是, 有了soft_irq. 把不可重⼊的⼀部分在中断处理程序中(关中断)去完成, 然后调⽤raise_softirq 设置⼀个软中断, 中断处理程序结束. 后⾯的⼯作将放在soft_irq⾥⾯去做.第四阶段--开中断条件下的软中断上⼀阶段循环调⽤完当前所有被触发的中断处理函数后, do_softirq函数被调⽤, 开始处理软件中断.在软中断机制中, 为每个CPU维护了⼀个若⼲位的掩码集, 每位掩码代表⼀个中断号. 在上⼀阶段的中断处理函数中, 调⽤raise_softirq设置了对应的软中断, 到了这⾥, 软中断对应的处理函数就会被调⽤(处理函数由open_softirq函数来注册).可以看出, 软中断与中断的模型很类似, 每个CPU有⼀组中断号, 中断有其对应的优先级, 每个CPU处理属于⾃⼰的中断. 最⼤的不同是开中断与关中断.于是, ⼀个中断处理过程被分成了两部分, 第⼀部分在中断处理函数⾥⾯关中断的进⾏, 第⼆部分在软中断处理函数⾥⾯开中断的进⾏.由于这⼀步是在开中断条件下进⾏的,这⾥还可能发⽣新的中断(中断嵌套),然后新中断对应的中断处理⼜将开始⼀个新的第⼀阶段~第三阶段。
关于Linux0.00中的时钟中断代码的一点理解

关于Linux0.00中的时钟中断代码的⼀点理解在读Linux0.00 head.s的代码时,时钟中断这段代码折腾了我半天才弄懂,先贴上代码1 /*2 * head.s contains the 32-bit startup code.3 * Two L3 task multitasking. The code of tasks are in kernel area,4 * just like the Linux. The kernel code is located at 0x10000.5 */6 KRN_BASE = 0x100007 TSS0_SEL = 0x208 LDT0_SEL = 0x289 TSS1_SEL = 0X3010 LDT1_SEL = 0x381112 .text13startup_32:14 movl $0x10,%eax15mov %ax,%ds16mov %ax,%es17mov %ax,%fs18mov %ax,%gs19lss stack_ptr,%esp2021 # setup base fields of descriptors.22 movl $KRN_BASE, %ebx23 movl $gdt, %ecx24lea tss0, %eax25 movl $TSS0_SEL, %edi26call set_base27lea ldt0, %eax28 movl $LDT0_SEL, %edi29call set_base30lea tss1, %eax31 movl $TSS1_SEL, %edi32call set_base33lea ldt1, %eax34 movl $LDT1_SEL, %edi35call set_base3637call setup_idt38call setup_gdt39 movl $0x10,%eax # reload all the segment registers40mov %ax,%ds # after changing gdt.41mov %ax,%es42mov %ax,%fs43mov %ax,%gs44lss stack_ptr,%esp4546 # setup up timer 8253 chip.47 movb $0x36, %al48 movl $0x43, %edx49 outb %al, %dx50 movl $11930, %eax # timer frequency 100 HZ51 movl $0x40, %edx52 outb %al, %dx53 movb %ah, %al54 outb %al, %dx5556 # setup timer & system call interrupt descriptors.57 movl $0x00080000, %eax58 movw $timer_interrupt, %ax59 movw $0x8E00, %dx60 movl $0x20, %ecx61lea idt(,%ecx,8), %esi62 movl %eax,(%esi)63 movl %edx,4(%esi)64 movw $system_interrupt, %ax65 movw $0xef00, %dx66 movl $0x80, %ecx67lea idt(,%ecx,8), %esi68 movl %eax,(%esi)69 movl %edx,4(%esi)7071 # unmask the timer interrupt.72 movl $0x21, %edx73 inb %dx, %al74 andb $0xfe, %al75 outb %al, %dx77 # Move to user mode (task 0)78 pushfl79 andl $0xffffbfff, (%esp)80 popfl81 movl $TSS0_SEL, %eax82ltr %ax83 movl $LDT0_SEL, %eax84lldt %ax85 movl $0, current86sti87 pushl $0x1788 pushl $stack0_ptr89 pushfl /* ⼿动设置返回环境*/90 pushl $0x0f /**/91 pushl $task0 /**/92iret9394 /****************************************/95setup_gdt:96lgdt lgdt_opcode97ret9899setup_idt:100lea ignore_int,%edx101 movl $0x00080000,%eax102 movw %dx,%ax /* selector = 0x0008 = cs */103 movw $0x8E00,%dx /* interrupt gate - dpl=0, present */ 104lea idt,%edi105mov $256,%ecx106rp_sidt:107 movl %eax,(%edi)108 movl %edx,4(%edi)109 addl $8,%edi110dec %ecx111jne rp_sidt112lidt lidt_opcode113ret114115 # in: %eax - logic addr; %ebx = base addr ;116 # %ecx - table addr; %edi - descriptors offset.117set_base:118 addl %ebx, %eax119 addl %ecx, %edi120 movw %ax, 2(%edi)121 rorl $16, %eax122 movb %al, 4(%edi)123 movb %ah, 7(%edi)124 rorl $16, %eax125ret126127write_char:128push %gs129 pushl %ebx130 pushl %eax131mov $0x18, %ebx132mov %bx, %gs133 movl scr_loc, %bx134shl $1, %ebx135 movb %al, %gs:(%ebx)136shr $1, %ebx137 incl %ebx138 cmpl $2000, %ebx139jb 1f140 movl $0, %ebx1411: movl %ebx, scr_loc142 popl %eax143 popl %ebx144pop %gs145ret146147 /***********************************************/148 /* This is the default interrupt "handler" :-) */149 .align 2150ignore_int:151push %ds152 pushl %eax153 movl $0x10, %eax154mov %ax, %ds155 movl $67, %eax /* print 'C' */156call write_char157 popl %eax158pop %ds159iret161 /* Timer interrupt handler */162 .align 2163timer_interrupt:164push %ds165 pushl %edx166 pushl %ecx167 pushl %ebx168 pushl %eax169 movl $0x10, %eax170mov %ax, %ds171 movb $0x20, %al172 outb %al, $0x20173 movl $1, %eax174 cmpl %eax, current175je 1f176 movl %eax, current177 ljmp $TSS1_SEL, $0178jmp 2f1791: movl $0, current180 ljmp $TSS0_SEL, $01812: popl %eax182 popl %ebx183 popl %ecx184 popl %edx185pop %ds186iret187188 /* system call handler */189 .align 2190system_interrupt:191push %ds192 pushl %edx193 pushl %ecx194 pushl %ebx195 pushl %eax196 movl $0x10, %edx197mov %dx, %ds198call write_char199 popl %eax200 popl %ebx201 popl %ecx202 popl %edx203pop %ds204iret205206 /*********************************************/207current:.long 0208scr_loc:.long 0209210 .align 2211 .word 0212lidt_opcode:213 .word 256*8-1 # idt contains 256 entries214 .long idt + KRN_BASE # This will be rewrite by code.215 .align 2216 .word 0217lgdt_opcode:218 .word (end_gdt-gdt)-1 # so does gdt219 .long gdt + KRN_BASE # This will be rewrite by code.220221 .align 3222idt: .fill 256,8,0 # idt is uninitialized223224gdt: .quad 0x0000000000000000 /* NULL descriptor */225 .quad 0x00c09a01000007ff /* 8Mb 0x08, base = 0x10000 */ 226 .quad 0x00c09201000007ff /* 8Mb 0x10 */227 .quad 0x00c0920b80000002 /* screen 0x18 - for display */ 228229 .quad 0x0000e90100000068 # TSS0 descr 0x20230 .quad 0x0000e20100000040 # LDT0 descr 0x28231 .quad 0x0000e90100000068 # TSS1 descr 0x30232 .quad 0x0000e20100000040 # LDT1 descr 0x38233end_gdt:234 .fill 128,4,0235stack_ptr:236 .long stack_ptr237 .word 0x10238239 /*************************************/240 .align 3241ldt0: .quad 0x0000000000000000242 .quad 0x00c0fa01000003ff # 0x0f, base = 0x10000243 .quad 0x00c0f201000003ff # 0x17245 .long 0 /* back link */246 .long stack0_krn_ptr, 0x10 /* esp0, ss0 */247 .long 0, 0 /* esp1, ss1 */248 .long 0, 0 /* esp2, ss2 */249 .long 0 /* cr3 */250 .long 0 /* eip */251 .long 0 /* eflags */252 .long 0, 0, 0, 0 /* eax, ecx, edx, ebx */253 .long 0, 0, 0, 0 /* esp, ebp, esi, edi */254 .long 0,0,0,0,0,0 /* es, cs, ss, ds, fs, gs */255 .long LDT0_SEL /* ldt */256 .long 0x8000000 /* trace bitmap */257258 .fill 128,4,0259stack0_krn_ptr:260 .long 0261262 /************************************/263 .align 3264ldt1: .quad 0x0000000000000000265 .quad 0x00c0fa01000003ff # 0x0f, base = 0x10000266 .quad 0x00c0f201000003ff # 0x17267tss1:268 .long 0 /* back link */269 .long stack1_krn_ptr, 0x10 /* esp0, ss0 */270 .long 0, 0 /* esp1, ss1 */271 .long 0, 0 /* esp2, ss2 */272 .long 0 /* cr3 */273 .long task1 /* eip */274 .long 0x200 /* eflags */275 .long 0, 0, 0, 0 /* eax, ecx, edx, ebx */276 .long stack1_ptr, 0, 0, 0 /* esp, ebp, esi, edi */277 .long 0x17,0x0f,0x17,0x17,0x17,0x17 /* es, cs, ss, ds, fs, gs */278 .long LDT1_SEL /* ldt */279 .long 0x8000000 /* trace bitmap */280281 .fill 128,4,0282stack1_krn_ptr:283 .long 0284285 /************************************/286task0:287 movl $0x17, %eax288 movw %ax, %ds289 movl $65, %al /* print 'A' */290int $0x80291 movl $0xfff, %ecx2921: loop 1b293jmp task0294295 .fill 128,4,0296stack0_ptr:297 .long 0298299task1:300 movl $0x17, %eax301 movw %ax, %ds302 movl $66, %al /* print 'B' */303int $0x80304 movl $0xfff, %ecx3051: loop 1b306jmp task1307308 .fill 128,4,0309stack1_ptr:310 .long 0311312 /*** end ***/question:在代码的第177⾏(177 ljmp $TSS1_SEL, $0)和第180⾏(180 ljmp $TSS0_SEL, $0)可以看到,中断程序转移到对应的任务去执⾏了,后边的代码怎么执⾏?iret永远也执⾏不到了么?answer:实际上,程序在开始运⾏时,从第79⾏(79 andl $0xffffbfff, (%esp))可以看到标志寄存器的中断嵌套位NT被设置成了0,也就是说中断返回了esp0,ss0,ldt,trace bitmap设置了外,其余是0.嵌套任务标志NT(Nested Task)嵌套任务标志NT⽤来控制中断返回指令IRET的执⾏。
linux定时器实现原理

linux定时器实现原理Linux定时器是Linux操作系统中的一种机制,用于在指定的时间间隔内执行特定的任务或程序。
它是实现自动化任务和定时执行的重要工具之一。
本文将介绍Linux定时器的实现原理和使用方法。
一、Linux定时器的实现原理Linux定时器的实现原理主要基于操作系统的时钟中断机制。
当系统启动时,操作系统会初始化一个硬件时钟,并且设置一个固定的时间间隔,通常为几毫秒。
当时钟达到设定的时间间隔时,操作系统会触发一个时钟中断,即产生一个中断信号,通知操作系统进行相应的处理。
在Linux内核中,定时器是通过一个称为“定时器列表”的数据结构来实现的。
定时器列表是一个双向链表,用于存储所有的定时器对象。
每个定时器对象包含了定时器的属性和回调函数等信息。
当一个定时器被创建时,它会被加入到定时器列表中,并根据定时器的触发时间,在列表中找到合适的位置插入。
在每次时钟中断发生时,操作系统会遍历定时器列表,检查是否有定时器已经到达触发时间。
如果有定时器到达触发时间,操作系统将调用相应的回调函数执行任务或程序。
二、Linux定时器的使用方法在Linux中,可以使用多种方式来创建和使用定时器。
以下是使用Linux定时器的常见方法:1. 使用系统调用函数:Linux提供了系统调用函数(如timer_create、timer_settime等)来创建和设置定时器。
通过这些系统调用函数,可以设置定时器的触发时间、定时器的属性以及定时器到达触发时间时要执行的任务或程序。
2. 使用命令行工具:Linux还提供了一些命令行工具(如cron、at 等),可以通过命令行来创建和管理定时器。
通过这些命令行工具,可以设置定时器的触发时间、定时器的属性以及定时器到达触发时间时要执行的任务或程序。
3. 使用编程语言:除了系统调用函数和命令行工具,还可以使用编程语言来创建和使用定时器。
在C语言中,可以使用POSIX定时器库(如timer_create、timer_settime等函数)来实现定时器的功能。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Linux内核的时钟中断机制opyright © 2003 by 詹荣开E-mail:zhanrk@Linux-2.4.0Version 1.0.0,2003-2-14摘要:本文主要从内核实现的角度分析了Linux 2.4.0内核的时钟中断、内核对时间的表示等。
本文是为那些想要了解Linux I/O子系统的读者和Linux驱动程序开发人员而写的。
关键词:Linux、时钟、定时器申明:这份文档是按照自由软件开放源代码的精神发布的,任何人可以免费获得、使用和重新发布,但是你没有限制别人重新发布你发布内容的权利。
发布本文的目的是希望它能对读者有用,但没有任何担保,甚至没有适合特定目的的隐含的担保。
更详细的情况请参阅GNU 通用公共许可证(GPL),以及GNU自由文档协议(GFDL)。
你应该已经和文档一起收到一份GNU通用公共许可证(GPL)的副本。
如果还没有,写信给:The Free Software Foundation, Inc., 675 Mass Ave, Cambridge,MA02139, USA欢迎各位指出文档中的错误与疑问。
前言时间在一个操作系统内核中占据着重要的地位,它是驱动一个OS内核运行的“起博器”。
一般说来,内核主要需要两种类型的时间:(1)、在内核运行期间持续记录当前的时间与日期,以便内核对某些对象和事件作时间标记(timestamp,也称为“时间戳”),或供用户通过时间syscall进行检索。
(2)、维持一个固定周期的定时器,以提醒内核或用户一段时间已经过去了。
PC机中的时间是有三种时钟硬件提供的,而这些时钟硬件又都基于固定频率的晶体振荡器来提供时钟方波信号输入。
这三种时钟硬件是:(1)实时时钟(Real Time Clock,RTC);(2)可编程间隔定时器(Programmable Interval Timer,PIT);(3)时间戳计数器(Time Stamp Counter,TSC)。
1、 时钟硬件1.1 实时时钟RTC自从IBM PC AT起,所有的PC机就都包含了一个叫做实时时钟(RTC)的时钟芯片,以便在PC机断电后仍然能够继续保持时间。
显然,RTC是通过主板上的电池来供电的,而不是通过PC机电源来供电的,因此当PC机关掉电源后,RTC仍然会继续工作。
通常,CMOS RAM和RTC被集成到一块芯片上,因此RTC也称作“CMOS Timer”。
最常见的RTC芯片是MC146818(Motorola)和DS12887(maxim),DS12887完全兼容于MC146818,并有一定的扩展。
本节内容主要基于MC146818这一标准的RTC芯片。
具体内容可以参考MC146818的Datasheet。
1.1.1 RTC寄存器MC146818 RTC芯片一共有64个寄存器。
它们的芯片内部地址编号为0x00~0x3F(不是I/O端口地址),这些寄存器一共可以分为三组:(1)时钟与日历寄存器组:共有10个(0x00~0x09),表示时间、日历的具体信息。
在PC 机中,这些寄存器中的值都是以BCD格式来存储的(比如23dec=0x23BCD)。
(2)状态和控制寄存器组:共有4个(0x0A~0x0D),控制RTC芯片的工作方式,并表示当前的状态。
(3)CMOS配置数据:通用的CMOS RAM,它们与时间无关,因此我们不关心它。
时钟与日历寄存器组的详细解释如下:Address Function00 Current second for RTC01 Alarm second02 Current minute03 Alarm minute04 Current hour05 Alarm hour06 Current day of week(01=Sunday)07 Current date of month08 Current month09 Current year(final two digits,eg:93)状态寄存器A(地址0x0A)的格式如下:其中:(1)bit[7]——UIP标志(Update in Progress),为1表示RTC正在更新日历寄存器组中的值,此时日历寄存器组是不可访问的(此时访问它们将得到一个无意义的渐变值)。
(2)bit[6:4]——这三位是“除法器控制位”(divider-control bits),用来定义RTC的操作频率。
各种可能的值如下:Divider bits Time-base frequency Divider Reset Operation ModeDV2 DV1 DV00 0 0 4.194304 MHZ NO YES0 0 1 1.048576 MHZ NO YES0 1 0 32.769 KHZ NO YES1 1 0/1 任何 YES NOPC机通常将Divider bits设置成“010”。
(3)bit[3:0]——速率选择位(Rate Selection bits),用于周期性或方波信号输出。
RS bits 4.194304或1.048578 MHZ 32.768 KHZRS3 RS2 RS1 RS0 周期性中断方波周期性中断方波0 0 0 0 None None None None0 0 0 1 30.517μs 32.768 KHZ 3.90625ms 256 HZ0 0 1 0 61.035μs 16.384 KHZ0 0 1 1 122.070μs 8.192KHZ0 1 0 0 244.141μs 4.096KHZ0 1 0 1 488.281μs 2.048KHZ0 1 1 0 976.562μs 1.024KHZ0 1 1 1 1.953125ms 512HZ1 0 0 0 3.90625ms 256HZ1 0 0 1 7.8125ms 128HZ1 0 1 0 15.625ms 64HZ1 0 1 1 31.25ms 32HZ1 1 0 0 62.5ms 16HZ1 1 0 1 125ms 8HZ1 1 1 0 250ms 4HZ1 1 1 1 500ms 2HZPC机BIOS对其默认的设置值是“0110”。
状态寄存器B的格式如下所示:各位的含义如下:(1)bit[7]——SET标志。
为1表示RTC的所有更新过程都将终止,用户程序随后马上对日历寄存器组中的值进行初始化设置。
为0表示将允许更新过程继续。
(2)bit[6]——PIE标志,周期性中断使能标志。
(3)bit[5]——AIE标志,告警中断使能标志。
(4)bit[4]——UIE标志,更新结束中断使能标志。
(5)bit[3]——SQWE标志,方波信号使能标志。
(6)bit[2]——DM标志,用来控制日历寄存器组的数据模式,0=BCD,1=BINARY。
BIOS总是将它设置为0。
(7)bit[1]——24/12标志,用来控制hour寄存器,0表示12小时制,1表示24小时制。
PC机BIOS总是将它设置为1。
(8)bit[0]——DSE标志。
BIOS总是将它设置为0。
状态寄存器C的格式如下:(1)bit[7]——IRQF标志,中断请求标志,当该位为1时,说明寄存器B中断请求发生。
(2)bit[6]——PF标志,周期性中断标志,为1表示发生周期性中断请求。
(3)bit[5]——AF标志,告警中断标志,为1表示发生告警中断请求。
(4)bit[4]——UF标志,更新结束中断标志,为1表示发生更新结束中断请求。
状态寄存器D的格式如下:(1)bit[7]——VRT标志(Valid RAM and Time),为1表示OK,为0表示RTC已经掉电。
(2)bit[6:0]——总是为0,未定义。
1.1.2 通过I/O端口访问RTC在PC机中可以通过I/O端口0x70和0x71来读写RTC芯片中的寄存器。
其中,端口0x70是RTC的寄存器地址索引端口,0x71是数据端口。
读RTC芯片寄存器的步骤是:mov al, addrout 70h, al ; Select reg_addr in RTC chipjmp $+2 ; a slight delay to settle thingin al, 71h ;写RTC寄存器的步骤如下:mov al, addrout 70h, al ; Select reg_addr in RTC chipjmp $+2 ; a slight delay to settle thingmov al, valueout 71h, al1.2 可编程间隔定时器PIT每个PC机中都有一个PIT,以通过IRQ0产生周期性的时钟中断信号。
当前使用最普遍的是Intel 8254 PIT芯片,它的I/O端口地址是0x40~0x43。
Intel 8254 PIT有3个计时通道,每个通道都有其不同的用途:(1)通道0用来负责更新系统时钟。
每当一个时钟滴答过去时,它就会通过IRQ0向系统产生一次时钟中断。
(2)通道1通常用于控制DMAC对RAM的刷新。
(3)通道2被连接到PC机的扬声器,以产生方波信号。
每个通道都有一个向下减小的计数器,8254 PIT的输入时钟信号的频率是1193181HZ,也即一秒钟输入1193181个clock-cycle。
每输入一个clock-cycle其时间通道的计数器就向下减1,一直减到0值。
因此对于通道0而言,当他的计数器减到0时,PIT就向系统产生一次时钟中断,表示一个时钟滴答已经过去了。
当各通道的计数器减到0时,我们就说该通道处于“Terminal count”状态。
通道计数器的最大值是10000h,所对应的时钟中断频率是1193181/(65536)=18.2HZ,也就是说,此时一秒钟之内将产生18.2次时钟中断。
1.2.1 PIT的I/O端口在i386平台上,8254芯片的各寄存器的I/O端口地址如下:Port Description40h Channel 0 counter(read/write)41h Channel 1 counter(read/write)42h Channel 2 counter(read/write)43h PIT control word(write only)其中,由于通道0、1、2的计数器是一个16位寄存器,而相应的端口却都是8位的,因此读写通道计数器必须进行进行两次I/O端口读写操作,分别对应于计数器的高字节和低字节,至于是先读写高字节再读写低字节,还是先读写低字节再读写高字节,则由PIT的控制寄存器来决定。
8254 PIT的控制寄存器的格式如下:(1)bit[7:6]——Select Counter,选择对那个计数器进行操作。