Linux之时钟中断

from:深入分析Linux内核源码(http://oss.org.cn/kernel-book/

时钟中断的产生

Linux的OS时钟的物理产生原因是可编程定时/计数器产生的输出脉冲,这个脉冲送入CPU,就可以引发一个中断请求信号,我们就把它叫做时钟中断。

“时钟中断”是特别重要的一个中断,因为整个操作系统的活动都受到它的激励。系统利用时钟中断维持系统时间、促使环境的切换,以保证所有进程共享CPU;利用时钟中断进行记帐、监督系统工作以及确定未来的调度优先级等工作。可以说,“时钟中断”是整个操作系统的脉搏。

时钟中断的物理产生如图所示:


                                                              图 8253和8259A的物理连接方式

操作系统对可编程定时/计数器进行有关初始化,然后定时/计数器就对输入脉冲进行计数(分频),产生的三个输出脉冲Out0、Out1、Out2各有用途,很多接口书都介绍了这个问题,我们只看Out0上的输出脉冲,这个脉冲信号接到中断控制器8259A_1的0号管脚,触发一个周期性的中断,我们就把这个中断叫做时钟中断,时钟中断的周期,也就是脉冲信号的周期,我们叫做“滴答”或“时标”(tick)。从本质上说,时钟中断只是一个周期性的信号,完全是硬件行为,该信号触发CPU去执行一个中断服务程序,但是为了方便,我们就把这个服务程序叫做时钟中断。

Linux实现时钟中断的全过程

1.可编程定时/计数器的初始化

IBM PC中使用的是8253或8254芯片。有关该芯片的详细知识我们不再详述,只大体介绍以下它的组成和作用,如下表5.1所示:

表         8253/8254的组成及作用


名称


端口地址


工作方式


产生的输出脉冲的用途


计数器0


0x40


方式3


时钟中断,也叫系统时钟


计数器1


0x41


方式2


动态存储器刷新


计数器2


0x42


方式3


扬声器发声


控制寄存器


0x43


/


用于8253的初始化,接收控制字

计数器0的输出就是图5.3中的Out0,它的频率由操作系统的设计者确定,Linux对8253的初始化程序段如下(在/arch/i386/kernel/i8259.c的init_IRQ()函数中):

set_intr_gate(ox20, interrupt[0]);

/*在IDT的第0x20个表项中插入一个中断门。这个门中的段选择符设置成内核代码段的选择符,偏移域设置成0号中断处理程序的入口地址。*/

outb_p(0x34,0x43);     /* 写计数器0的控制字:工作方式2*/

outb_p(LATCH & 0xff , 0x40);   /* 写计数初值LSB  计数初值低位字节*/

outb(LATCH >> 8 , 0x40);   /* 写计数初值MSB 计数初值高位字节*/

LATCH(英文意思为:锁存器,即其中锁存了计数器0的初值)为计数器0的计数初值,在/include/linux/timex.h中定义如下:

#define CLOCK_TICK_RATE    1193180    /* 图5.3中的输入脉冲 */

#define LATCH  ((CLOCK_TICK_RATE + HZ/2) / HZ)  /* 计数器0的计数初值 */

CLOCK_TICK_RATE是整个8253的输入脉冲,如图5.3中所示为1.193180MHz,是近似为1MHz的方波信号,8253内部的三个计数器都对这个时钟进行计数,进而产生不同的输出信号,用于不同的用途。

HZ表示计数器0的频率,也就是时钟中断或系统时钟的频率,在/include/asm/param.h中定义如下:

#define HZ 100

2.与时钟中断相关的函数

下面我们看时钟中断触发的服务程序,该程序代码比较复杂,分布在不同的源文件中,主要包括如下函数:

时钟中断程序:timer_interrupt( );

中断服务通用例程do_timer_interrupt();

时钟函数:do_timer( );

中断安装程序:setup_irq( );

中断返回函数:ret_from_intr( );

前三个函数的调用关系如下:

     timer_interrupt( )

do_timer_interrupt()

do_timer( )

(1) timer_interrupt( )

这个函数大约每10ms被调用一次,实际上, timer_interrupt( )函数是一个封装例程,它真正做的事情并不多,但是,作为一个中断程序,它必须在关中断的情况下执行。如果只考虑单处理机的情况,该函数主要语句就是调用do_timer_interrupt()函数。

(2) do_timer_interrupt()

do_timer_interrupt()函数有两个主要任务,一个是调用do_timer( ),另一个是维持实时时钟(RTC,每隔一定时间段要回写),其实现代码在/arch/i386/kernel/time.c中, 为了突出主题,笔者对以下函数作了改写,以便于读者理解:

static inline void do_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)

{

do_timer(regs); /* 调用时钟函数,将时钟函数等同于时钟中断未尝不可*/

if(xtime.tv_sec > last_rtc_update + 660)

update_RTC();

/*每隔11分钟就更新RTC中的时间信息,以使OS时钟和RTC时钟保持同步,11分钟即660秒,xtime.tv_sec的单位是秒,last_rtc_update记录的是上次RTC更新时的值 */

}

其中,xtime是前面所提到的timeval类型,这是一个全局变量。

(3) 时钟函数do_timer() (在/kernel/sched.c中)

void do_timer(struct pt_regs * regs)

{

(*(unsigned long *)&jiffies)++;  /*更新系统时间,这种写法保证对jiffies

操作的原子性*/

update_process_times();

++lost_ticks;

if( ! user_mode ( regs ) )

++lost_ticks_system;

mark_bh(TIMER_BH);

if (tq_timer)

mark_bh(TQUEUE_BH);

}

其中,update_process_times()函数与进程调度有关,从函数的名子可以看出,它处理的是与当前进程与时间有关的变量,例如,要更新当前进程的时间片计数器counter,如果counter<=0,则要调用调度程序,要处理进程的所有定时器:实时、虚拟、概况,另外还要做一些统计工作。

与时间有关的事情很多,不能全都让这个函数去完成,这是因为这个函数是在关中断的情况下执行,必须处理完最重要的时间信息后退出,以处理其他事情。那么,与时间相关的其他信息谁去处理,何时处理?这就是由第三章讨论的后半部分去去处理。 上面timer_interrupt()(包括它所调用的函数)所做的事情就是上半部分。

在该函数中还有两个变量lost_ticks和lost_ticks_system,这是用来记录timer_bh()执行前时钟中断发生的次数。因为时钟中断发生的频率很高(每10ms一次),所以在timer_bh()执行之前,可能已经有时钟中断发生了,而timer_bh()要提供定时、记费等重要操作,所以为了保证时间计量的准确性,使用了这两个变量。lost_ticks用来记录timer_bh()执行前时钟中断发生的次数,如果时钟中断发生时当前进程运行于内核态,则lost_ticks_system用来记录timer_bh()执行前在内核态发生时钟中断的次数,这样可以对当前进程精确记费。

(4)中断安装程序

从上面的介绍可以看出,时钟中断与进程调度密不可分,因此,一旦开始有时钟中断就可能要进行调度,在系统进行初始化时,所做的大量工作之一就是对时钟进行初始化,其函数time_init ()的代码在/arch/i386/kernel/time.c中,对其简写如下:

void __init time_init(void)

{

xtime.tv_sec=get_cmos_time();

xtime.tv_usec=0;

setup_irq(0,&irq0);

}

其中的get_cmos_time()函数就是把当时的实际时间从CMOS时钟芯片读入变量xtime中,时间精度为秒。而setup_irq(0,&irq0)就是时钟中断安装函数,那么irq0指的是什么呢,它是一个结构类型irqaction,其定义及初值如下:

static struct irqaction irq0  = { timer_interrupt, SA_INTERRUPT, 0, "timer", NULL, NULL};

setup_irq(0, &irq0)的代码在/arch/i386/kernel/irq.c中,其主要功能就是将中断程序连入相应的中断请求队列,以等待中断到来时相应的中断程序被执行。

到现在为止,我们仅仅是把时钟中断程序挂入中断请求队列,什么时候执行,怎样执行,这是一个复杂的过程(参见第三章),为了让读者对时钟中断有一个完整的认识,我们忽略中间过程,而给出一个整体描述。我们将有关函数改写如下,体现时钟中断的大意:

do_timer_interrupt( )          /*这是一个伪函数 */

{

SAVE_ALL                    /*保存处理机现场 */

intr_count += 1;              /* 这段操作不允许被中断 */

timer_interrupt()             /* 调用时钟中断程序 */

intr_count -= 1;

jmp ret_from_intr             /* 中断返回函数 */

}

其中,jmp ret_from_intr 是一段汇编代码,也是一个较为复杂的过程,它最终要调用jmp ret_from_sys_call,即系统调用返回函数,而这个函数与进程的调度又密切相关,,因此,我们重点分析  jmp ret_from_sys_call。

3.系统调用返回函数:

系统调用返回函数的源代码在/arch/i386/kernel/entry.S中

ENTRY(ret_from_sys_call)

cli                 # need_resched and signals atomic test

cmpl $0,need_resched(%ebx)

jne reschedule

cmpl $0,sigpending(%ebx)

jne signal_return

restore_all:

RESTORE_ALL

ALIGN

signal_return:

sti              # we can get here from an interrupt handler

testl $(VM_MASK),EFLAGS(%esp)

movl %esp,%eax

jne v86_signal_return

xorl %edx,%edx

call SYMBOL_NAME(do_signal)

jmp restore_all

ALIGN

v86_signal_return:

call SYMBOL_NAME(save_v86_state)

movl %eax,%esp

xorl %edx,%edx

call SYMBOL_NAME(do_signal)

jmp restore_all

….

reschedule:

call SYMBOL_NAME(schedule)    # test

jmp ret_from_sys_call

这一段汇编代码就是前面我们所说的“从系统调用返回函数”ret_from_sys_call,它是从中断、异常及系统调用返回时的通用接口。这段代码主体就是ret_from_sys_call函数,其执行过程中要调用其它一些函数(实际上是一段代码,不是真正的函数),在此我们列出相关的几个函数:

(1)ret_from_sys_call:主体

(2)reschedule:检测是否需要重新调度

(3)signal_return:处理当前进程接收到的信号

(4)v86_signal_return:处理虚拟86模式下当前进程接收到的信号

(5)RESTORE_ALL:我们把这个函数叫做彻底返回函数,因为执行该函数之后,就返回到当前进程的地址空间中去了。

可以看到ret_from_sys_call的主要作用有:

检测调度标志need_resched,决定是否要执行调度程序;处理当前进程的信号;恢复当前进程的环境使之继续执行。

最后我们再次从总体上浏览一下时钟中断:

每个时钟滴答,时钟中断得到执行。时钟中断执行的频率很高:100次/秒,时钟中断的主要工作是处理和时间有关的所有信息、决定是否执行调度程序以及处理下半部分。和时间有关的所有信息包括系统时间、进程的时间片、延时、使用CPU的时间、各种定时器,进程更新后的时间片为进程调度提供依据,然后在时钟中断返回时决定是否要执行调度程序。下半部分处理程序是Linux提供的一种机制,它使一部分工作推迟执行。时钟中断要绝对保证维持系统时间的准确性,而下半部分这种机制的提供不但保证了这种准确性,还大幅提高了系统性能。

时间: 2024-10-21 03:30:56

Linux之时钟中断的相关文章

linux 时间模块 三

LINUX的时钟中断中涉及至二个全局变量一个是xtime,另一个则是jiffies.有一个与时间有关的时钟:实时时钟(RTC),这是一个硬件时钟,用来持久存放系统时间,系统关闭后靠主板上的微型电池保持计时.系统启动时,内核通过读取RTC来初始化WallTime,并存放在xtime变量中,即xtime是从cmos电路中取得的时间,一般是从某一历史时刻开始到现在的时间,也就是为了取得我们操作系统上显示的日期,它的精度是微秒.这是RTC最主要的作用.jiffies是记录着从电脑开机到现在总共的时钟中断

《Linux Device Drivers》 第七章 时间、延时及延缓操作——note

度量时间差 内核通过定时器中断来跟踪时间流 时钟中断由系统定时硬件以周期性的间隔产生,这个间隔由内核根据HZ的值设定,在常见的x86 PC平台上,默认定义为1000 <linux/param.h> <linux/timex.h> jiffies_64 unsigned long jiffies 使用jiffies计数器 <linux/jiffies.h> int time_after(unsigned long a, unsigned long b); int time

Linux内核中的jiffies及其作用介绍及jiffies等相关函数详解

在LINUX的时钟中断中涉及至二个全局变量一个是xtime,它是timeval数据结构变量,另一个则是jiffies,首先看timeval结构struct timeval{time_t tv_sec; /***second***/susecond_t tv_usec;/***microsecond***/}到底microsecond是毫秒还是微秒?? 1秒=1000毫秒(3个零),1秒=1000 000微秒(6个零),1秒=1000 000 000纳秒(9个零),1秒=1000 000 000

深入分析Linux内核源码oss.org.cn/kernel-book/

本html页面地址:http://oss.org.cn/kernel-book/ 深入分析Linux内核源码 前言         第一章 走进linux 1.1 GNU与Linux的成长 1.2 Linux的开发模式和运作机制 1.3走进Linux内核            1.3.1 Linux内核的特征 1.3.2 Linux内核版本的变化 1.4 分析Linux内核的意义         1.4.1 开发适合自己的操作系统 1.4.2 开发高水平软件 1.4.3 有助于计算机科学的教学和

Linux与Windows中的UTC时间

Linux与Windows中的UTC时间 先介绍几个术语 UTC 协调世界时,又称世界标准时间或世界协调时间,简称UTC(从英文“Coordinated Universal Time”/法文“Temps Universel Cordonné”而来),是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林尼治平时. GMT 格林威治标准时间(中国大陆翻译:格林尼治平均时间或格林尼治标准时间,台.港.澳翻译:格林威治标准时间:英语:Greenwich Mean Time,GMT)是指位

Linux内核jiffies简介

在LINUX的时钟中断中涉及至二个全局变量一个是xtime,它是timeval数据结构变量,另一个则是jiffies,首先看timeval结构struct timeval{time_t tv_sec; /***second***/susecond_t tv_usec;/***microsecond***/}到底microsecond是毫秒还是微秒?? 1秒=1000毫秒(3个零),1秒=1000 000微秒(6个零),1秒=1000 000 000纳秒(9个零),1秒=1000 000 000

Linux内核系列—8.操作系统开发之时钟中断

外部中断的情况复杂一些,因为需要建立硬件中断与向量号之间的对应关系.外部中断分为不可屏蔽中断(NMI)和可屏蔽中断两种,分别由CPU的两根引脚NMI和INTR来接收.如下图所示: 可屏蔽中断与CPU的关系是通过对可编程中断控制器8259A建立起来的.8259A可以认为它是中断机制中所有外围设备的一个代理.在BIOS初始化它的时候,IRQ0~IRQ7被设置为对应向量号08h~0Fh,在保护模式下向量号08h~0Fh已经被占用了,所以我们不得不重新设置主从8259A. 对8259A的设置并不复杂,通

20169217 《Linux内核原理与分析》 第十周作业

实验内容 理解Linux系统中进程调度的时机,可以在内核代码中搜索schedule()函数,看都是哪里调用了schedule(),判断我们课程内容中的总结是否准确: 使用gdb跟踪分析一个schedule()函数 ,验证您对Linux系统进程调度与进程切换过程的理解: 特别关注并仔细分析switch_to中的汇编代码,理解进程上下文的切换机制,以及与中断上下文切换的关系: Linux系统进程调度与进程切换过程 进程调度分为三种类型: 中断处理过程(包括时钟中断.I/O 中断.系统调用和异常)中,

linux 系统进程理解

1.为了对进程从产生到消亡的整个过程进行跟踪和描述,就需要定义各种进程的各种状态并制定相应的状态转换策略,以此来控制进程的运行.      不同的操作系统对进程的状态解释不同,但是最基本的状态都是一样的.包括以下三种:       运行态:进程占用CPU,并在CPU上运行:       就绪态:进程已经具备运行条件,但是CPU还没有分配过来:       阻塞态:进程因等待某件事发生而暂时不能运行:     进程在一生中,都处于上述3中状态之一.       下面是3种状态转换图