Linux之时钟中断详解

更新时间:2023-05-12 13:24:40 阅读: 评论:0

Linux之时钟中断详解
⽬录
时钟中断的产⽣
Linux实现时钟中断的全过程
1.可编程定时/计数器的初始化
2.与时钟中断相关的函数
3.系统调⽤返回函数:
总结
在Linux的0号中断是⼀个定时器中断。在固定的时间间隔都发⽣⼀次中断,也是说每秒发⽣该中断的频率都是固定的。该频率是常量HZ,该值⼀般是在100 ~ 1000之间。该中断的作⽤是为了定时更新系统⽇期和时间,使系统时间不断地得到跳转。另外该中断的中断处理函数除了更新系统时间外,还需要更新本地CPU统计数。指的是调⽤scheduler_tick递减进程的时间⽚,若进程的时间⽚递减到0,进程则被调度出去⽽放弃CPU使⽤权。
时钟中断的产⽣
Linux的OS时钟的物理产⽣原因是可编程定时/计数器产⽣的输出脉冲,这个脉冲送⼊CPU,就可以引发⼀个中断请求信号,我们就把它叫做时钟中断。
“时钟中断”是特别重要的⼀个中断,因为整个操作系统的活动都受到它的激励。系统利⽤时钟中断维持系统时间、促使环境的切换,以保证所有进程共享CPU;利⽤时钟中断进⾏记帐、监督系统⼯作以及确定未来的调度优先级等⼯作。可以说,“时钟中断”是整个操作系统的脉搏。
时钟中断的物理产⽣如图所⽰:
操作系统对可编程定时/计数器进⾏有关初始化,然后定时/计数器就对输⼊脉冲进⾏计数(分频),产⽣的三个输出脉冲Out0、Out1、Out2各有⽤途,很多接⼝书都介绍了这个问题,我们只看Out0上的输出脉冲,这个脉冲信号接到中断控制器8259A_1的0号管脚,触发⼀个周期性的中断,我们就把这个中断叫做时钟中断,时钟中断的周期,也就是脉冲信号的周期,我们叫做“滴答”或“时标”(tick)。从本质上说,时钟中断只是⼀个周期性的信号,完全是硬件⾏为,该信号触发CPU去执⾏⼀个中断服务程序,但是为了⽅便,我们就把这个服务程序叫做时钟中断。
Linux实现时钟中断的全过程
1.可编程定时/计数器的初始化
IBM PC中使⽤的是8253或8254芯⽚。有关该芯⽚的详细知识我们不再详述,只⼤体介绍以下它的组成和作⽤,如下表5.1所⽰:
表 8253/8254的组成及作⽤
名称端⼝地址⼯作⽅式产⽣的输出脉冲的⽤途
计数器00x40⽅式3时钟中断,也叫系统时钟
计数器10x41⽅式2动态存储器刷新
计数器20x42⽅式3扬声器发声
控制寄存器0x43/⽤于8253的初始化,接收控制字
计数器0的输出就是图中的Out0,它的频率由操作系统的设计者确定,Linux对8253的初始化程序段如下(在/arch/i386/kernel/i8259.c的
init_IRQ()函数中):
t_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( );
中断安装程序:tup_irq( );
中断返回函数:ret_from_intr( );
(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_c > last_rtc_update + 660)
update_RTC();
/
*每隔11分钟就更新RTC中的时间信息,以使OS时钟和RTC时钟保持同步,11分钟即660秒,xtime.tv_c的单位是秒,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( ! ur_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_c=get_cmos_time();
xtime.tv_uc=0;
tup_irq(0,&irq0);
}
其中的get_cmos_time()函数就是把当时的实际时间从CMOS时钟芯⽚读⼊变量xtime中,时间精度为秒。⽽tup_irq(0,&irq0)就是时钟中断安装函数,那么irq0指的是什么呢,它是⼀个结构类型irqaction,其定义及初值如下:
static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT, 0, "timer", NULL, NULL};
tup_irq(0, &irq0)的代码在/arch/i386/kernel/irq.c中,其主要功能就是将中断程序连⼊相应的中断请求队列,以等待中断到来时相应的中断程序被执⾏。
struct irqaction {
irq_handler_t handler;  //中断处理函数,注册时提供
unsigned long flags;  //中断标志,注册时提供
cpumask_t mask;    //中断掩码
const char *name;  //中断名称
void *dev_id;      //设备id,本⽂后⾯部分介绍中断共享时会详细说明这个参数的作⽤
struct irqaction *next;  //如果有中断共享,则继续执⾏,
int irq;        //中断号,注册时提供
struct proc_dir_entry *dir; //指向IRQn相关的/proc/irq/n⽬录的描述符
};
这个结构体包含了处理⼀种中断所需要的各种信息,它代表了内核接受到特定IRQ之后应该采取的操作。
1.handler:该指针所指向的函数就是在中断服务程序,当中断发⽣时内核便会调⽤这个指针指向的函数。
2.flags:该标志位可以是0,也可以是:
SA_INTERRUPT:表⽰此中断处理程序是⼀个快速中断处理程序,在2.6中默认情况下没有这个标志;设置该标志位,中断处理程序禁⽌任何中断运⾏,没有该标志,仅屏蔽正在运⾏的IRQ线;
SA_SAMPLE_RANDOM:表⽰这个中断对内核池有贡献,在中断时产⽣⼀些随机数;
SA_SHIRQ:此标志位表⽰允许多个中断服务程序共享⼀个中断号,如不设则⼀个程序对应⼀个中断线;
3.mask:在x86上不会⽤到。
4.name:产⽣中断的硬件的名字.
5.dev_id:该标志位主要在共享中断号时使⽤,即你设置flags=SA_SHIRQ时,有多个中断服务程序共享⼀个中断号时,内核就需要知道在⽤完中断程序后该删除那个中断服务程序。不共享时此成员为null。
<:如果flags=SA_SHIRQ,那么这就是指向对列中下⼀个struct irqaction结构体的指针,否则为空。
7.irq:不⽤说这就是中断号了。
到现在为⽌,我们仅仅是把时钟中断程序挂⼊中断请求队列,什么时候执⾏,怎样执⾏,这是⼀个复杂的过程(参见第三章),为了让读者对时钟中断有⼀个完整的认识,我们忽略中间过程,⽽给出⼀个整体描述。我们将有关函数改写如下,体现时钟中断的⼤意:
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提供的⼀种机制,它使⼀部分⼯作推迟执⾏。时钟中断要绝对保证维持系统时间的准确性,⽽下半部分这种机制的提供不但保证了这种准确性,还⼤幅提⾼了系统性能。
总结
以上就是本⽂关于Linux之时钟中断详解的全部内容,希望对⼤家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不⾜之处,欢迎留⾔指出。感谢朋友们对本站的⽀持!

本文发布于:2023-05-12 13:24:40,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/888115.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:中断   时钟   函数   时间
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图