《第2部分第6次课linux2.6内核之系统调用及定时器详解》由会员分享,可在线阅读,更多相关《第2部分第6次课linux2.6内核之系统调用及定时器详解(84页珍藏版)》请在金锄头文库上搜索。
1、嵌入式Linux内核体系架构李超PART ONEPART ONE 嵌入式嵌入式Linux系统调用系统调用系统调用v概念所谓系统调用,就是内核提供的、功能十分强大的一系列的函数。系统调用发生在用户进程,通过一些特殊的函数(如open)来请求内核提供服务,这时,用户进程挂起,内核验证用户请求,尝试之行,并把结果反馈给用户进程,接着用户进程重新启动系统调用是在内核中实现的,再通过一定的方式把系统调用给用户,一般都通过门(gate)陷入(trap)实现。系统调用是用户程序和内核交互的接口。v系统调用的作用系统调用是用户接口在内核中的实现,如果没有系统调用,用户就不能利用内核。API/POSIX及C库l
2、API:应用程序接口lPOSIX接口(PortableOperatingSystemInterface):是IEEE提出的标准,用于定义一个可移植的操作系统接口,它应用于UNIX、DECOpenVMS以及windowsNTlC库中实现了UNIX系统的主要API,包括标准的C库函数和系统调用系统调用执行流程应用程序C库int$0x80或swi系统调用总入口sys_call()ni系统内核函数入口0exit系统内核函数入口1fork系统内核函数入口2.sys_call_tablesys_fork()系统调用执行流程l执行用户程序(如:fork) l根据glibc中的函数实现,取得系统调用号并执行i
3、nt $0x80产生中断。 l进行地址空间的转换和堆栈的切换,执行SAVE_ALL。(进行内核模式) l进行中断处理,根据系统调用表调用内核函数。 l执行内核函数。 l执行RESTORE_ALL并返回用户模式系统调用sethostname()分析l功能:设置计算机在网络中的主机名l使用:intsethostname(constchar*name,size_tlen);lSethostname()是库函数,定义在/usr/lib/libc.a基于X86平台添加系统调用(1) 添加源代码第一个任务是编写加到内核中的源程序,即将要加到一个内核文件中去的一个函数,该函数的名称应该是新的系统调用名称前面
4、加上sys_标志。假设新加的系统调用为mycall(int number),在/usr/src/linux/kernel/sys.c文件中添加源代码,如下所示:asmlinkage int sys_mycall(int number)return number;l(2)添加新的系统调用后,下一个任务是使Linux内核的其余部分知道该程序的存在。为了从已有的内核程序中增加到新的函数的连接,需要编辑两个文件。l第一个要修改的文件是:/usr/src/linux/include/asm-i386/unistd.h该文件中包含了系统调用清单,用来给每个系统调用分配一个唯一的号码。文件中每一行的格式如下
5、:#define _NR_name NNN其中,name用系统调用名称代替,而NNN则是该系统调用对应的号码。应该将新的系统调用名称加到清单的最后,并给它分配号码序列中下一个可用的系统调用号。我们的系统调用如下:#define _NR_mycall 191l第二个要修改的文件是:/usr/src/linux/arch/i386/kernel/entry.S该文件中有类似如下的清单:.longSYMBOL_NAME()该清单用来对sys_call_table数组进行初始化。该数组包含指向内核中每个系统调用的指针。这样就在数组中增加了新的内核函数的指针。我们在清单最后添加一行:.longSYMBO
6、L_NAME(sys_mycall)l(3)重建新的Linux内核 为使新的系统调用生效,需要重建Linux的内核。这需要以超级用户身份登录。#pwd/usr/src/linux#超级用户在当前工作目录(/usr/src/linux)下,才可以重建内核。#make config#make dep#make clearn#make bzImagel编译完毕后,系统生成一可用于安装的、压缩的内核映象文件:/usr/src/linux/arch/i386/boot/bzImage 用新的内核进行启动系统l(4)使用新的系统调用在应用程序中使用新添加的系统调用mycall。同样为实验目的,我们写了一个
7、简单的例子xtdy.c。/*xtdy.c*/_syscall1(int,mycall,int,ret)main()printf(%dn,mycall(100);基于ARM-Linux平台的系统调用第一步编写源程序添加到内核,该函数名称应该是新的系统调用名称前面加上sys_标志,假如新加的系统调用mycall(int *number), 在/armsys2410/kernel/kernel/sys.c文件中添加源代码,如下所示:asmlinkageintsys_mycall(int*num)int*number,data=0;number=&data;if(copy_from_user(numb
8、er,num,sizeof(*num)/从用户空间得到数据printk(copy_from_usern);return0;data+=10;/对数据进行运算if(copy_to_user(num,number,sizeof(data)/将数据从内核空间返回到用户空间printk(copy_to_usern);return0;return*num;第2步l添加完新的系统调用后,下一个任务是使LINUX内核的其余部分知道该程序的存在.为了从已有的内核程序中增加到新的函数的连接,需要编辑两个文件l第一个文件是:/armsys2410/kernel/include/asm-arm/unistd.h用来
9、给系统调用分配一个唯一的号码.格式如下:#define_NR_nameNNN我们的系统调用如下:#define_NR_mycall226l第二个文件是:/armsys2410/kernel/arch/arm/kernel/call.S该文件中的有类似如下的清单:.longSYMBOL_NAME()该清单用来对sys_calltable数组进行初始化.该数组包含指向内核中每个系统调用的指针.我们在最后增加新的内核函数的指针:.longSYMBOL_NAME(sys_mycall)第三步l重建新的LINUX内核#pwd/armsys2410/kernel#makedep/#makeclean#ma
10、kezImage最后内核映象文件在:/armsys2410/kernel/arch/arm/boot/zImagel将新内核下载到开发板上第四步l使用新的系统调用在应用程序中使用新添加的系统调用mycall(),我们写了一个简单的例子test_sys.c/*test_sys.c*/#includeinterrno;_syscall1(int,mycall,int*,ret)/每一个参数:函数返回类型,第二个系统调用名,第三个传入参数类型intmain(void)int*p,number=100;p=&number;number=mycall(p);printf(weget%dn,number)
11、;return0;PART TWOPART TWO嵌入式实时时钟和定时器PARTI时钟基本概念硬时钟硬时钟的实现电路往往都被放置在嵌入式微处理器中。一般情况下硬时钟就是一个振荡的RC电路,只要有电源的供应,总会不停的振荡下去。硬时钟供电电路软时钟l对于硬时钟而言,软时钟(又称为系统时钟)是虚构的时钟,是由嵌入式操作系统来进行维护的,它的出现是为了方便上层软件读取硬时钟的时间而诞生的。每次系统启动时,读取硬时钟的时间,赋给软时钟,系统掉电后,操作系统关闭,软时钟也就不存在了l操作系统及应用程序都使用软时钟系统时钟的计数l在Linux系统中,设备加电时会读取硬时钟的值给系统时钟,系统启动后就会一直
12、不停的更新系统时钟的值,使其和硬时钟保持一致。这样在整个ARM-Linux环境下,所有部件都可以使用系统时钟做为自己的行动依据。l系统时钟的值并不像我们平时讲到的“年月日时分秒”,而是一个整数值,在Linux内核中,系统时钟的值是用从1970年1月1日00:00:00开始算起的累积秒数来表示的。我们把1970年1月1日00:00:00称为新纪元时间,简称为Epoch。时钟周期(clockcycle)的频率l硬时钟实际上由一个晶体振荡器产生。晶体振荡器在1秒时间内产生的时钟脉冲个数就是时钟周期的频率。lLinux用宏CLOCK_TICK_RATE来表示输入时钟脉冲的频率,该宏定义在include
13、/asm-arm/arch-s3c2400/timex.h头文件中: #define CLOCK_TICK_RATE (CLK_INPUT / (CLK_PRESCALE + ) / DIVIDER)内核中的时间间隔内核中的时间间隔l时钟中断的发生频率设定为HZ,HZ是一个与体系结构无关的常数,在文件中定义。至少从2.0版到版,Alpha平台上Linux定义HZ的值为1024,而其他平台上定义为100。l当时钟中断发生时,jiffies值就加1。因此,jiffies值就是自操作系统启动以来的时钟滴答的数目时钟滴答(clocktick)l当计数器减到0值时,它就会产生一次时钟中断,也即一次时钟滴
14、答。通道0的计数器的初始值决定了要过多少时钟周期才产生一次时钟中断,因此也就决定了一次时钟滴答的时间间隔长度。 PARTII系统时钟数据结构系统时钟数据结构体struct timeval time_ttv_sec;/* seconds */suseconds_ttv_usec;/*microseconds */;ltv_sec是从1970年1月1日开始计算的秒数ltv_usec则是当前秒内的微秒数(10E-6second)l数据类型time_t和suseconds_t在ARM-linux系统中就是long类型。l在ARM-Linux中,定义了一个全局变量xtime,该变量中保存了系统的当前时间
15、,xtime的数据类型就是struct timeval。 系统时区数据结构体struct timezone int tz_minuteswest; int tz_dsttime;l在ARM-Linux系统中,每个用户都可以设置自己的时区,但是系统内核保持的是格林尼治时间。在系统时区数据结构体中,tz_minuteswest成员表示本时区和格林尼治时间的差值;tz_dsttime成员表示时间的修正方式,之所以要修正时间,是因为夏令治的出现,现在在Linux内核中已经废弃了夏令治,所以这个成员就没有什么意义了,在初始化时被赋值为0,表示不需要修正。l在ARM-Linux系统中,定义了一个全局变量s
16、ys_tz,这个变量中保存了系统的当前时区。时间结构体l电脑使用秒及epoch来表示其时间,但是对于现实世界中的人来说,我们往往更习惯于使用“年月日时分秒”这样的计时方式,所以在ARM-Linux系统中提供了转换的方式和对应的数据结构。Linux系统下提供了数据结构structtm来描述时间structtm inttm_sec; inttm_min; inttm_hour; inttm_mday; inttm_mon; inttm_year; inttm_wday; inttm_yday; inttm_isdst; ; PARTIII系统时钟全局变量全局变量jiffiesl这是一个32位的无符
17、号整数,用来表示自系统启动以来的时钟滴答数。每发生一次时钟滴答,内核的时钟中断处理函数timer_interrupt()都要将该全局变量jiffies加1。l该变量定义在kernel/timer.c源文件中,如下所示: lunsigned long volatile jiffies; l使用限定符volatile表示jiffies是一个易该变的变量,因此编译器将使对该变量的访问从不通过CPU内部cache来进行。全局变量xtimel它是一个timeval结构类型的变量,用来表示当前时间距UNIX时间基准1970010100:00:00的相对秒数值。lLinux内核通过timeval结构类型的全
18、局变量xtime来维持当前时间,该变量定义在kernel/timer.c文件中,如下所示: lvolatile struct timeval xtime _attribute_ (aligned (16); l全局变量xtime所维持的当前时间通常是供用户来检索和设置的,而其他内核模块通常很少使用它(其他内核模块用得最多的是jiffies),因此对xtime的更新并不是一项紧迫的任务,所以这一工作通常被延迟到时钟中断的底半部分(bottom half)中来进行 全局变量sys_tzl它是一个timezone结构类型的全局变量,表示系统当前的时区信息。结构类型timezone定义在include
19、/linux/time.h头文件中。基于上述结构,Linux在kernel/time.c文件中定义了全局变量sys_tz表示系统当前所处的时区信息,如下所示: struct timezone sys_tz;PARTIV系统时钟初始化l系统启动时,ARM-Linux需要对软硬件时钟进行初始化/ init/main.cstart_kernel()time_init();void _init time_init(void)xtime.tv_usec = 0;xtime.tv_sec = 0;setup_timer();/ include/asm-arm/arch-s3c2410/time.hstat
20、ic inline void setup_timer(void)struct timer_counts *timer_count = count_values; unsigned long pclk;gettimeoffset = s3c2410_gettimeoffset;set_rtc = s3c2410_set_rtc;xtime.tv_sec = s3c2410_get_rtc_time();xtime.tv_sec = s3c2410_get_rtc_time(); / include/asm-arm/arch-s3c2410/time.hstatic inline void set
21、up_timer(void) /* set timer interrupt */TCFG0 = (TCFG0_DZONE(0)|TCFG0_PRE1(15)| TCFG0_PRE0(0); pclk = s3c2410_get_bus_clk(GET_PCLK)/1000;while (timer_count != 0) if (pclk = timer_count-freq) printk(DEBUG: timer count %dn, timer_count-count);TCNTB4 = timer_count-count;break;timer_count+;if (timer_cou
22、nt = 0) /* Error, assume that PCLK is 50 Mhz */TCNTB4 = 15626;/* down-counter, maximum value is 65535 (216) */TCON = (TCON_4_AUTO | TCON_4_UPDATE | COUNT_4_OFF);timer_irq.handler = s3c2410_timer_interrupt;setup_arm_irq(IRQ_TIMER4, &timer_irq);TCON = (TCON_4_AUTO | COUNT_4_ON);/ include/asm-arm/arch-
23、s3c2410/time.hstatic inline void setup_timer(void) /* set timer interrupt */TCFG0 = (TCFG0_DZONE(0)|TCFG0_PRE1(15)| TCFG0_PRE0(0); TCON = (TCON_4_AUTO | TCON_4_UPDATE | COUNT_4_OFF);timer_irq.handler = s3c2410_timer_interrupt;timer_irq.handler = s3c2410_timer_interrupt;setup_arm_irq(IRQ_TIMER4, &tim
24、er_irq);setup_arm_irq(IRQ_TIMER4, &timer_irq);TCON = (TCON_4_AUTO | COUNT_4_ON);PARTV定时器基本概念l大家都知道嵌入式系统大多是实时系统或准实时系统,为了能够准时调度任务,需要定时器来支持。从通常意义上来说,定时器是Linux系统提供的一种定时服务机制,它在某个特定的时间唤醒某个特定的进程完成某些工作。老版本定时器l在ARM-Linux系统中,包括两种类型的系统定时器,一种是较老的定时器,最多个,存放在一个静态数组中,有一个位的屏蔽码time_active,用于表明当前哪些定时器是活动的expires(*fn)
25、()structtimer_structexpires(*fn)()0131time_table include/linux/timer.hstruct timer_struct unsigned long expires;void (*fn)(void);所有的定时器被存放在全局数组timer_table中,定义如下:kernel/sched.cunsigned long timer_active = 0;struct timer_struct timer_table32;新版本定时器lLinux内核2.4版中去掉了老版本内核中的静态定时器机制,而只留下动态定时器。l动态定时器是指内核的定时
26、器队列是可以动态变化的,然而就定时器本身而言,二者并无本质的区别。新版本定时器数据结构 struct timer_list struct list_head list; unsigned long expires; unsigned long data; void (*function)(unsigned long); ;(1)双向链表元素list:用来将多个定时器连接成一条双向循环队列。 (2)expires:指定定时器到期的时间,这个时间被表示成自系统启动以来的时钟滴答计数(也即时钟节拍数)。当一个定时器的expires值小于或等于jiffies变量时,我们就说这个定时器已经超时或到期了。
27、在初始化一个定时器后,通常把它的expires域设置成当前expires变量的当前值加上某个时间间隔值(以时钟滴答次数计)。 (3)函数指针function:指向一个可执行函数。当定时器到期时,内核就执行function所指定的函数。而data域则被内核用作function函数的调用参数。 list.nextlist.prevexpiresdata(*function)()structtimer_listlist.nextlist.prevexpiresdata(*function)()structtimer_listlist.nextlist.prevexpiresdata(*functio
28、n)()structtimer_list定时器类型l在ARM-Linux系统中运行着很多定时器,这些定时器可以分为下面三种类型。lITIMER_REAL:这种定时器采用实时计数,也就是说不管进程处于用户模式下还是核心模式下运行,该定时器总是在计数。当定时到达时,会发送给进程一个SIGALRM信号。lITIMER_VIRTUAL:这种定时器在进程处于用户模式下执行的时候计数,当计数到达时,会给进程发送SIGVTALRM信号。lITIMER_PROF:这种定时器当进程在用户模式和核心模式的时候都计时,当定时达到时,会发送SIGPROF信号定时器组织l“定时器向量”就是指这样一条双向循环定时器队列(
29、对列中的每一个元素都是一个timer_list结构):对列中的所有定时器都在同一个时刻到期,也即对列中的每一个timer_list结构都具有相同的expires值。l定时器expires成员的值与jiffies变量的差值决定了一个定时器将在多长时间后到期。在32位系统中,这个时间差值的最大值应该是0xffffffff。因此如果是基于“定时器向量”基本定义,内核将至少要维护0xffffffff个timer_list结构类型的指针,这显然是不现实的l另一方面,从内核本身这个角度看,它所关心的定时器显然不是那些已经过期而被执行过的定时器(这些定时器完全可以被丢弃),也不是那些要经过很长时间才会到期的
30、定时器,而是那些当前已经到期或者马上就要到期的定时器(注意!时间间隔是以滴答次数为计数单位的)。l 基于上述考虑,并假定一个定时器要经过interval个时钟滴答后才到期(intervalexpiresjiffies),则Linux采用了下列思想来实现其动态内核定时器机制: 0interval255的定时器 :Linux严格按照定时器向量的基本语义来组织这些定时器,也即Linux内核最关心那些在接下来的255个时钟节拍内就要到期的定时器,因此将它们按照各自不同的expires值组织成256个定时器向量 256interval0xffffffff :以一种扩展的定时器向量语义(或称为“松散的定时
31、器向量语义”)进行组织。所谓“松散的定时器向量语义”就是指:各定时器的expires值可以互不相同的一个定时器队列 #define TVN_BITS 6 #define TVR_BITS 8 #define TVN_SIZE (1 TVN_BITS) #define TVR_SIZE (1 8)具有相同值的定时器都将被组织在同一个松散定时器向量中。因此,为组织所有满足条件0x100interval0x3fff的定时器,就需要2664个松散定时器向量。同样地,为方便起见,这64个松散定时器向量也放在一起形成数组,并作为数据结构timer_vec的一部分。基于数据结构timer_vec,Linux
32、定义了全局变量tv2,来表示这64条松散定时器向量l对于那些满足条件0x4000interval0xfffff的定时器,只要表达式(interval86)的值相同的定时器都将被放在同一个松散定时器向量中。同样,要组织所有满足条件0x4000interval0xfffff的定时器,也需要2664个松散定时器向量。类似地,这64个松散定时器向量也可以用一个timer_vec结构来描述,相应地Linux定义了tv3全局变量来表示这64个松散定时器向量。l于那些满足条件0x100000interval0x3ffffff的定时器,只要表达式(interval866)的值相同的定时器都将被放在同一个松散定
33、时器向量中。同样,要组织所有满足条件0x100000interval0x3ffffff的定时器,也需要2664个松散定时器向量。类似地,这64个松散定时器向量也可以用一个timer_vec结构来描述,相应地Linux定义了tv4全局变量来表示这64个松散定时器向量。 l对于那些满足条件0x4000000interval0xffffffff的定时器,只要表达式(interval8666)的值相同的定时器都将被放在同一个松散定时器向量中。同样,要组织所有满足条件0x4000000interval0xffffffff的定时器,也需要2664个松散定时器向量。类似地,这64个松散定时器向量也可以用一个
34、timer_vec结构来描述,相应地Linux定义了tv5全局变量来表示这64个松散定时器向量。PARTVI定时器操作定时器操作l(1)将一个定时器插入到它应该所处的定时器向量中。l(2)定时器的迁移,也即将一个定时器从它原来所处的定时器向量迁移到另一个定时器向量中。l(3)扫描并执行当前已经到期的定时器。动态定时器机制的初始化动态定时器机制的初始化 start_kernel()schedule_init()start_kernel()schedule_init()init_timervecs()init_timervecs()void init_timervecs (void) void i
35、nit_timervecs (void) int i; int i; for (i = 0; i TVN_SIZE; i+) for (i = 0; i TVN_SIZE; i+) INIT_LIST_HEAD(tv5.vec + i); INIT_LIST_HEAD(tv5.vec + i); INIT_LIST_HEAD(tv4.vec + i); INIT_LIST_HEAD(tv4.vec + i); INIT_LIST_HEAD(tv3.vec + i); INIT_LIST_HEAD(tv3.vec + i); INIT_LIST_HEAD(tv2.vec + i); INIT_L
36、IST_HEAD(tv2.vec + i); for (i = 0; i TVR_SIZE; i+) for (i = 0; i next=(ptr);(ptr)-prev=(ptr);while(0)动态定时器的时钟滴答基准动态定时器的时钟滴答基准timer_jiffiestimer_jiffies l由于动态定时器是在时钟中断的Bottom Half中被执行的,而从TIMER_BH向量被激活到其timer_bh()函数真正执行这段时间内可能会有几次时钟中断发生。因此内核必须记住上一次运行定时器机制是什么时候,也即内核必须保存上一次运行定时器机制时的jiffies值。为此,Linux在ker
37、nel/timer.c文件中定义了全局变量timer_jiffies来表示上一次运行定时器机制时的jiffies值。对内核动态定时器链表的保护对内核动态定时器链表的保护l由于内核动态定时器链表是一种系统全局共享资源,为了实现对它的互斥访问,Linux定义了专门的自旋锁timerlist_lock来保护。任何想要访问动态定时器链表的代码段都首先必须先持有该自旋锁,并且在访问结束后释放该自旋锁。将一个定时器插入到链表中将一个定时器插入到链表中l函数add_timer()用来将参数timer指针所指向的定时器插入到一个合适的定时器链表中。它首先调用timer_pending()函数判断所指定的定时器
38、是否已经位于在某个定时器向量中等待执行。如果是,则不进行任何操作,只是打印一条内核告警信息就返回了;如果不是,则调用internal_add_timer()函数完成实际的插入操作。void add_timer(struct timer_list *timer) unsigned long flags; spin_lock_irqsave(&timerlist_lock, flags);spin_lock_irqsave(&timerlist_lock, flags); if (timer_pending(timer) goto bug; internal_add_timer(timer)int
39、ernal_add_timer(timer); spin_unlock_irqrestore(&timerlist_lock, flags);spin_unlock_irqrestore(&timerlist_lock, flags); return; bug: spin_unlock_irqrestore(&timerlist_lock, flags); printk(bug: kernel timer added twice at %p.n, _builtin_return_address(0); 加锁判断所指定的定时器是否已经位于在某个定时器向量中等待执行。如果是,则不进行任何操作,只是
40、打印一条内核告警信息就返回了;如果不是,则调用internal_add_timer()函数完成实际的插入操作。解锁static inline void internal_add_timer(struct timer_list *timer) unsigned long expires = timer-expires; unsigned long idx = expires - timer_jiffies; struct list_head * vec; if (idx TVR_SIZE) int i = expires & TVR_MASK; vec = tv1.vec + i; else i
41、f (idx 1 TVR_BITS) & TVN_MASK; vec = tv2.vec + i; else if (idx 1 (TVR_BITS + TVN_BITS) & TVN_MASK; vec = tv3.vec + i; else if (idx 1 (TVR_BITS + 2 * TVN_BITS) & TVN_MASK; vec = tv4.vec + i; else if (signed long) idx 0) vec = tv1.vec + tv1.index; else if (idx (TVR_BITS + 3 * TVN_BITS) & TVN_MASK; vec
42、 = tv5.vec + i; else INIT_LIST_HEAD(&timer-list); return; list_add(&timer-list, vec-prev); 1)首先,计算定时器的expires值与timer_jiffies的插值(注意!这里应该使用动态定时器自己的时间基准),这个差值就表示这个定时器相对于上一次运行定时器机制的那个时刻还需要多长时间间隔才到期。局部变量idx保存这个差值。 (2)根据idx的值确定这个定时器应被插入到哪一个定时器向量中。其具体的确定方法察看前面PPT。(3)最后,调用list_add()函数将定时器插入到vec指针所指向的定时器队列的尾
43、部。修改一个定时器的修改一个定时器的expiresexpires值值 当一个定时器已经被插入到内核动态定时器链表中后,我们还可以修改该定时器的expires值。函数mod_timer()实现这一点。如下所示(kernel/timer.c): int mod_timer(struct timer_list *timer, unsigned long expires) int ret; unsigned long flags; spin_lock_irqsave(&timerlist_lock, flags); timer-expires = expires; ret = detach_timer
44、(timer); ret = detach_timer(timer); internal_add_timer(timer);internal_add_timer(timer); spin_unlock_irqrestore(&timerlist_lock, flags); return ret; 函数detach_timer()首先调用timer_pending()来判断指定的定时器是否已经处于某个链表中,如果定时器原来就不处于任何链表中,则detach_timer()函数什么也不做,直接返回0值,表示失败。否则,就调用list_del()函数将定时器从它原来所处的链表中摘除。删除一个定时器删
45、除一个定时器 函数del_timer()用来将一个定时器从相应的内核定时器队列中删除。该函数实际上是对detach_timer()函数的高层封装。如下所示(kernel/timer.c): int del_timer(struct timer_list * timer) int ret; unsigned long flags; spin_lock_irqsave(&timerlist_lock, flags); ret = detach_timer(timer); timer-list.next = timer-list.prev = NULL; spin_unlock_irqrestore
46、(&timerlist_lock, flags); return ret; 定时器迁移操作定时器迁移操作 由于一个定时器的interval值会随着时间的不断流逝(即jiffies值的不断增大)而不断变小,因此那些原本到期紧迫程度较低的定时器会随着jiffies值的不断增大而成为既将马上到期的定时器。比如定时器向量tv2.vec0中的定时器在经过256个时钟滴答后会成为未来256个时钟滴答内会到期的定时器。因此,定时器在内核动态定时器链表中的位置也应相应地随着改变。改变的规则是:当tv1.index重新变为0时(意味着tv1中的256个定时器向量都已被内核扫描一遍了,从而使tv1中 的 256个
47、 定 时 器 向 量 变 为 空 ) , 则 用tv2.vecindex定时器向量中的定时器去填充tv1,同时使tv2.index加1(它以64为模)。当tv2.index重新变为0(意味着tv2中的64个定时器向量都已经被全部填充到tv1中去了,从而使得tv2变为空),则用tv3.vecindex定时器向量中的定时器去填充tv2。如此一直类推下去,直到tv5。 l函数cascade_timers()完成这种定时器迁移操作,该函数只有一个timer_vec结构类型指针的参数tv。这个函数将把定时器向量tv-vectv-index中的所有定时器重新填充到上一层定时器向量中去。如下所示(kerne
48、l/timer.c):扫描并执行当前已经到期的定时器扫描并执行当前已经到期的定时器l函数run_timer_list()完成这个功能。如前所述,该函数是被timer_bh()函数所调用的,因此内核定时器是在时钟中断的Bottom Half中被执行的。记住这一点非常重要。全局变量timer_jiffies表示了内核上一次执行run_timer_list()函数的时间,因此jiffies与timer_jiffies的差值就表示了自从上一次处理定时器以来,期间一共发生了多少次时钟中断,显然run_timer_list()函数必须为期间所发生的每一次时钟中断补上定时器服务static inline v
49、oid run_timer_list(void) spin_lock_irq(&timerlist_lock); while (long)(jiffies - timer_jiffies) = 0) struct list_head *head, *curr; if (!tv1.index) int n = 1; do cascade_timers(tvecsn); while (tvecsn-index = 1 & +n next; if (curr != head) struct timer_list *timer; void (*fn)(unsigned long); unsigned
50、long data; timer = list_entry(curr, struct timer_list, list); fn = timer-function; data= timer-data; detach_timer(timer); timer-list.next = timer-list.prev = NULL; timer_enter(timer); spin_unlock_irq(&timerlist_lock); fn(data); spin_lock_irq(&timerlist_lock); timer_exit(); goto repeat; 函数run_timer_l
51、ist()的执行过程主要就是用一个大while循环来为时钟中断执行定时器服务,每一次循环服务一次时钟中断。因此一共要执行(jiffiestimer_jiffies1)次循环。循环体所执行的服务步骤如下:(1)首先,判断tv1.index是否为0,如果为0则需要从tv2中补充定时器到tv1中来。但tv2也可能为空而需要从tv3中补充定时器,因此用一个dowhile循环来调用cascade_timer()函数来依次视需要从tv2中补充tv1,从tv3中补充tv2、从tv5中补充tv4。显然如果tvi.index=0(2i5),则对于tvi执行cascade_timers()函数后,tvi.inde
52、x肯定为1。反过来讲,如果对tvi执行过cascade_timers()函数后tvi.index不等于1,那么可以肯定在未对tvi执行cascade_timers()函数之前,tvi.index值肯定不为0,因此这时tvi不需要从tv(i+1)中补充定时器,这时就可以终止dowhile循环。PARTSEVENLinux下定时器操作structtimer_listjiq_timer;voidjiq_timedout(unsignedlongptr)intjiq_read_run_timer(char*buf,char*start,off_toffset,intlen,intunused)init
53、_timer(&jiq_timer);/*初始化定时器结构*/jiq_timer.function=jiq_timedout;jiq_timer.data=0;jiq_timer.expires=jiffies+HZ;/*1秒*/.add_timer(&jiq_timer);.Linux下的定时器有两种lalarmlsetitimerALARMl如果不要求很精确的话,用alarm()和signal()就够了.l格式unsignedintalarm(unsignedintseconds)l专门为SIGALRM信号而设,在指定的时间seconds秒后,将向进程本身发送SIGALRM信号,又称为闹钟
54、时间。进程调用alarm后,任何以前的alarm()调用都将无效。如果参数seconds为零,那么进程内将不再包含任何闹钟时间。如果调用alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。 #include #include #include void sigalrm_fn(int sig) /* Do something */ printf(alarm!n); return; int main(void) signal(SIGALRM, sigalrm_fn); alarm(2); /* Do someting */ while(1) pause(); s
55、etitimerint setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);setitimer()第一个参数which指定定时器类型ITIMER_REAL : 以系统真实的时间来计算,它送出SIGALRM信号。ITIMER_VIRTUAL : 以该行程真正有执行的时间来计算,它送出SIGVTALRM信号。ITIMER_PROF : 以行程真正有执行及在核心中所费的时间来计算,它送出SIGPROF信号第二个参数是结构itimerval的一个实例;第三个参数可不做处理。#include #inc
56、lude #include #include #include #include int sec;void sigroutine(int signo) switch (signo) case SIGALRM: printf(Catch a signal - SIGALRM n); signal(SIGALRM, sigroutine); break; case SIGVTALRM: printf(Catch a signal - SIGVTALRM n); signal(SIGVTALRM, sigroutine); break; return; intmain()structitimerva
57、lvalue,ovalue,value2;sec=5;printf(processidis%d,getpid();signal(SIGALRM,sigroutine);signal(SIGVTALRM,sigroutine);=1;=0;=1;=0;setitimer(ITIMER_REAL,&value,&ovalue);value2.it_value.tv_sec=0;value2.it_value.tv_usec=500000;value2.it_interval.tv_sec=0;value2.it_interval.tv_usec=500000;setitimer(ITIMER_VIRTUAL,&value2,&ovalue);for(;);