请问如何实现纳秒级的sleep? rt 解决方案 » 免费领取超大流量手机卡,每月29元包185G流量+100分钟通话, 中国电信官方发货 Sleep(10 * 1000);相信你明摆了摆怎么做了吧? 自己写个函数,比如wait(int nTime){ start = 当前纳秒级的时间 while(1) { if (start - 当前纳秒级的时间 > nTime )break; }}; bossmao() 是答非所问。happy__888([顾问团]寻开心) : 这样会比较耗费CPU时间的吧? 楼主开玩笑吧, 纳秒级的延时恐怕只有FPGA才能做到,软件不可能 所的对,哪里有软件要求到纳秒级的延时的用FPGA做吧 楼上的能否说清楚些?我现在做一个实时性要求很高的,即使是1毫秒级别的SLEEP,都会带来延迟的。所以需要纳秒级别。 发信人: windcbf (编程浪子), 信区: VC标 题: [转载]使用CPU时间戳进行高精度计时发信站: 瀚海星云 (2002年11月17日20:07:01 星期天), 站内信件使用CPU时间戳进行高精度计时对关注性能的程序开发人员而言,一个好的计时部件既是益友,也是良师。计时器既可以作为程序组件帮助程序员精确的控制程序进程,又是一件有力的调试武器,在有经验的程序员手里可以尽快的确定程序的性能瓶颈,或者对不同的算法作出有说服力的性能比较。在Windows平台下,常用的计时器有两种,一种是timeGetTime多媒体计时器,它可以提供毫秒级的计时。但这个精度对很多应用场合而言还是太粗糙了。另一种是QueryPerformanceCount计数器,随系统的不同可以提供微秒级的计数。对于实时图形处理、多媒体数据流处理、或者实时系统构造的程序员,善用QueryPerformanceCount/QueryPerformanceFrequency是一项基本功。本文要介绍的,是另一种直接利用Pentium CPU内部时间戳进行计时的高精度计时手段。以下讨论主要得益于《Windows图形编程》一书,第15页-17页,有兴趣的读者可以直接参考该书。关于RDTSC指令的详细讨论,可以参考Intel产品手册。本文仅仅作抛砖之用。在Intel Pentium以上级别的CPU中,有一个称为“时间戳(Time Stamp)”的部件,它以64位无符号整型数的格式,记录了自CPU上电以来所经过的时钟周期数。由于目前的CPU主频都非常高,因此这个部件可以达到纳秒级的计时精度。这个精确性是上述两种方法所无法比拟的。在Pentium以上的CPU中,提供了一条机器指令RDTSC(Read Time Stamp Counter)来读取这个时间戳的数字,并将其保存在EDX:EAX寄存器对中。由于EDX:EAX寄存器对恰好是Win32平台下C++语言保存函数返回值的寄存器,所以我们可以把这条指令看成是一个普通的函数调用。像这样:inline unsigned __int64 GetCycleCount(){ __asm RDTSC}但是不行,因为RDTSC不被C++的内嵌汇编器直接支持,所以我们要用_emit伪指令直接嵌入该指令的机器码形式0X0F、0X31,如下:inline unsigned __int64 GetCycleCount(){ __asm _emit 0x0F __asm _emit 0x31}以后在需要计数器的场合,可以像使用普通的Win32 API一样,调用两次GetCycleCount函数,比较两个返回值的差,像这样:unsigned long t;t = (unsigned long)GetCycleCount();//Do Something time-intensive ...t -= (unsigned long)GetCycleCount();《Windows图形编程》第15页编写了一个类,把这个计数器封装起来。有兴趣的读者可以去参考那个类的代码。作者为了更精确的定时,做了一点小小的改进,把执行RDTSC指令的时间,通过连续两次调用GetCycleCount函数计算出来并保存了起来,以后每次计时结束后,都从实际得到的计数中减掉这一小段时间,以得到更准确的计时数字。但我个人觉得这一点点改进意义不大。在我的机器上实测,这条指令大概花掉了几十到100多个周期,在Celeron 800MHz的机器上,这不过是十分之一微秒的时间。对大多数应用来说,这点时间完全可以忽略不计;而对那些确实要精确到纳秒数量级的应用来说,这个补偿也过于粗糙了。这个方法的优点是:1.高精度。可以直接达到纳秒级的计时精度(在1GHz的CPU上每个时钟周期就是一纳秒),这是其他计时方法所难以企及的。2.成本低。timeGetTime 函数需要链接多媒体库winmm.lib,QueryPerformance* 函数根据MSDN的说明,需要硬件的支持(虽然我还没有见过不支持的机器)和KERNEL库的支持,所以二者都只能在Windows平台下使用(关于DOS平台下的高精度计时问题,可以参考《图形程序开发人员指南》,里面有关于控制定时器8253的详细说明)。但RDTSC指令是一条CPU指令,凡是i386平台下Pentium以上的机器均支持,甚至没有平台的限制(我相信i386版本UNIX和Linux下这个方法同样适用,但没有条件试验),而且函数调用的开销是最小的。3.具有和CPU主频直接对应的速率关系。一个计数相当于1/(CPU主频Hz数)秒,这样只要知道了CPU的主频,可以直接计算出时间。这和QueryPerformanceCount不同,后者需要通过QueryPerformanceFrequency获取当前计数器每秒的计数次数才能换算成时间。这个方法的缺点是:1.现有的C/C++编译器多数不直接支持使用RDTSC指令,需要用直接嵌入机器码的方式编程,比较麻烦。2.数据抖动比较厉害。其实对任何计量手段而言,精度和稳定性永远是一对矛盾。如果用低精度的timeGetTime来计时,基本上每次计时的结果都是相同的;而RDTSC指令每次结果都不一样,经常有几百甚至上千的差距。这是这种方法高精度本身固有的矛盾。关于这个方法计时的最大长度,我们可以简单的用下列公式计算:自CPU上电以来的秒数 = RDTSC读出的周期数 / CPU主频速率(Hz)64位无符号整数所能表达的最大数字是1.8×10^19,在我的Celeron 800上可以计时大约700年(书中说可以在200MHz的Pentium上计时117年,这个数字不知道是怎么得出来的,与我的计算有出入)。无论如何,我们大可不必关心溢出的问题。下面是几个小例子,简要比较了三种计时方法的用法与精度//Timer1.cpp 使用了RDTSC指令的Timer类//KTimer类的定义可以参见《Windows图形编程》P15//编译行:CL Timer1.cpp /link USER32.lib#include <stdio.h>#include "KTimer.h"main(){ unsigned t; KTimer timer; timer.Start(); Sleep(1000); t = timer.Stop(); printf("Lasting Time: %d\n",t);}//Timer2.cpp 使用了timeGetTime函数//需包含<mmsys.h>,但由于Windows头文件错综复杂的关系//简单包含<windows.h>比较偷懒:)//编译行:CL timer2.cpp /link winmm.lib #include <windows.h>#include <stdio.h>main(){ DWORD t1, t2; t1 = timeGetTime(); Sleep(1000); t2 = timeGetTime(); printf("Begin Time: %u\n", t1); printf("End Time: %u\n", t2); printf("Lasting Time: %u\n",(t2-t1));}//Timer3.cpp 使用了QueryPerformanceCounter函数//编译行:CL timer3.cpp /link KERNEl32.lib#include <windows.h>#include <stdio.h>main(){ LARGE_INTEGER t1, t2, tc; QueryPerformanceFrequency(&tc); printf("Frequency: %u\n", tc.QuadPart); QueryPerformanceCounter(&t1); Sleep(1000); QueryPerformanceCounter(&t2); printf("Begin Time: %u\n", t1.QuadPart); printf("End Time: %u\n", t2.QuadPart); printf("Lasting Time: %u\n",( t2.QuadPart- t1.QuadPart));}//////////////////////////////////////////////////以上三个示例程序都是测试1秒钟休眠所耗费的时间file://测试环境:Celeron 800MHz / 256M SDRAM // Windows 2000 Professional SP2// Microsoft Visual C++ 6.0 SP5////////////////////////////////////////////////以下是Timer1的运行结果,使用的是高精度的RDTSC指令Lasting Time: 804586872以下是Timer2的运行结果,使用的是最粗糙的timeGetTime APIBegin Time: 20254254End Time: 20255255Lasting Time: 1001以下是Timer3的运行结果,使用的是QueryPerformanceCount APIFrequency: 3579545Begin Time: 3804729124End Time: 3808298836Lasting Time: 3569712古人说,触类旁通。从一本介绍图形编程的书上得到一个如此有用的实时处理知识,我感到非常高兴。有美不敢自专,希望大家和我一样喜欢这个轻便有效的计时器。参考资料:[YUAN 2002]Feng Yuan 著,英宇工作室 译,Windows图形编程,机械工业出版社,2002.4.,P15-17 楼上的弟兄。那篇文章我看过很多次了,它只是个记数而已。又不能实现sleep。:( Sleep的话会把当前进程挂起,就不浪费CPU时间了,对吧?但知不知道,任务切换是非常费时的,用保护模式下的Task切换的话也要17微秒才能完成,所以... 不想"浪费"cpu时间又想高精度,很难地说~ 这不是动脑筋的问题,你说省下来的CPU时间用来干什么吧。 linux的定时器代码分析(转载的)时钟和定时器中断IRQ 0 [Timer]|\|/|IRQ0x00_interrupt // wrapper IRQ handler |SAVE_ALL --- |do_IRQ | wrapper routines |handle_IRQ_event --- |handler() -> timer_interrupt // registered IRQ 0 handler |do_timer_interrupt |do_timer |jiffies++; |update_process_times |if (--counter <= 0) { // if time slice ended then |counter = 0; // reset counter |need_resched = 1; // prepare to reschedule |} |do_softirq |while (need_resched) { // if necessary |schedule // reschedule |handle_softirq |} |RESTORE_ALL·IRQ0x00_interrupt, SAVE_ALL [include/asm/hw_irq.h]·do_IRQ, handle_IRQ_event [arch/i386/kernel/irq.c]·timer_interrupt, do_timer_interrupt [arch/i386/kernel/time.c]·do_timer, update_process_times [kernel/timer.c]·do_softirq [kernel/soft_irq.c]·RESTORE_ALL, while loop [arch/i386/kernel/entry.S] 系统启动核心时,调用start_kernal()继续各方面的初始化,在这之前,各种中断都被禁止,只有在完成必要的初始化后,直到执行完Kmalloc_init()后,才允许中断(init\main.c)。与时钟中断有关的部分初始化如下: 调用trap_init()设置各种trap入口,如system_call、GDT entry、LDT entry、call gate等。其中0~17为各种错误入口,18~47保留。 调用init_IRQ()函数设置核心系统的时钟周期为10ms,即100HZ,它是以后按照轮转法进行CPU调度时所依照的基准时钟周期。每10ms产生的时钟中断信号直接输入到第一块8259A的INT 0(即irq0)。初始化中断矢量表中从0x20起的17个中断矢量,用bad_IRQ#_interrupt函数的地址(#为中断号)填写。 调用sched_init()函数,设置启动第一个进程init_task。设置用于管理bottom_half机制的数据结构bh_base[],规定三类事件的中断处理函数,即时钟TIMER_BH、设备TQUEUE_BH和IMMEDIATE_BH。 调用time_init()函数,首先读取当时的CMOS时间,最后调用setup_x86_irq(0,&irq0)函数,把irq0挂到irq_action[0]队列的后面,并把中断矢量表中第0x20项,即timer中断对应的中断矢量改为IRQ0_interrupt函数的地址,在irq0中,指定时间中断服务程序是timer_interrupt, static struct irqaction irq0 = { timer_interrupt, 0, 0, "timer", NULL, NULL} 结构irqaction的定义如下: struct irqaction { void (*handler)(int, void *, struct pt_regs *); /* 中断服务函数入口 */ unsigned long flags; /* 服务允中与否标记 */ unsigned long mask; const char *name; void *dev_id; struct irqaction *next; }; 其中,若flag==SA_INTERRUPT,则中断矢量改为fast_IRQ#_interrupt,在执行中断服务的过程中不允许出现中断,若为其它标记,则中断矢量为IRQ#_interrupt,在执行中断服务的过程中,允许出现中断。Irq_action的定义与初始化如下: static void (*interrupt[17])(void) = {IRQ#_interrupt}; static void (*fast_interrupt[16])(void) = {fast_IRQ#_interrupt}; static void (*bad_interrupt[16])(void) = {bad_IRQ#_interrupt};(以上#为中断号) static struct irqaction *irq_action[16] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; irq_action是一个全局数组,每个元素指向一个irq队列,共16个irq队列,时钟中断请求队列在第一个队列,即irq_action[0]。当每个中断请求到来时,都调用setup_x86_irq把该请求挂到相应的队列的后面。 以后,系统每10ms产生一次时钟中断信号,该信号直接输入到第一块8259A的INT 0(即irq0)。CPU根据中断矢量表和中断源,找到中断矢量函数入口IRQ0_interrupt(程序运行过程中允许中断)或者fast_IRQ0_interrupt(程序运行过程中不允许中断)或者bad_IRQ0_interrupt(不执行任何动作,直接返回),这些函数由宏BUILD_TIMER_IRQ(chip, nr, mask)展开定义。宏BUILD_TIMER_IRQ(chip, nr, mask)的定义如下:#define BUILD_TIMER_IRQ(chip,nr,mask) asmlinkage void IRQ_NAME(nr); asmlinkage void FAST_IRQ_NAME(nr); asmlinkage void BAD_IRQ_NAME(nr); __asm__( "\n"__ALIGN_STR"\n" SYMBOL_NAME_STR(fast_IRQ) #nr "_interrupt:\n\t" SYMBOL_NAME_STR(bad_IRQ) #nr "_interrupt:\n\t" SYMBOL_NAME_STR(IRQ) #nr "_interrupt:\n\t" "pushl $-"#nr"-2\n\t" SAVE_ALL ENTER_KERNEL ACK_##chip(mask,(nr&7)) "incl "SYMBOL_NAME_STR(intr_count)"\n\t"\ /* intr_count为进入临界区的同步信号量 */ "movl %esp,%ebx\n\t" "pushl %ebx\n\t" "pushl $" #nr "\n\t" \ /* 把do_irq函数参数压进堆栈 */ "call "SYMBOL_NAME_STR(do_IRQ)"\n\t" "addl $8,%esp\n\t" "cli\n\t" UNBLK_##chip(mask) "decl "SYMBOL_NAME_STR(intr_count)"\n\t" "incl "SYMBOL_NAME_STR(syscall_count)"\n\t" "jmp ret_from_sys_call\n"); 其中nr为中断请求类型,取值0~15。在irq.c中通过语句BUILD_TIMER_IRQ(first, 0, 0x01)调用该宏,在执行宏的过程中处理时钟中断响应程序do_irq()。 函数do_irq()的第一个参数是中断请求队列序号,时钟中断请求传进来的该参数是0。于是程序根据参数0找到请求队列irq_action[0],逐个处理该队列上handler所指的时钟中断请求的服务函数。由于已经指定时钟中断请求的服务函数是timer_interrupt,在函数timer_interrupt中,立即调用do_timer()函数。 函数do_timer()把jiffies和lost_ticks加1,接着就执行_bh(TIMER_BH)函数,把bottom_half中时钟队列对应的位置位,表示该队列处于激活状态。在做完这些动作后,程序从函数do_irq()中返回,继续执行以后的汇编代码。于是,程序在执行语句jmp ret_from_sys_call后,跳到指定的位置处继续执行。代码段jmp ret_from_sys_call及其相关的代码段如下: ALIGN .globl ret_from_sys_callret_from_sys_call: cmpl $0,SYMBOL_NAME(intr_count) jne 2f9: movl SYMBOL_NAME(bh_mask),%eax andl SYMBOL_NAME(bh_active),%eax jne handle_bottom_half#ifdef __SMP__ cmpb $(NO_PROC_ID), SYMBOL_NAME(saved_active_kernel_processor) jne 2f#endif movl EFLAGS(%esp),%eax # check VM86 flag: CS/SS are testl $(VM_MASK),%eax # different then jne 1f cmpw $(KERNEL_CS),CS(%esp) # was old code segment supervisor ? je 2f1: sti orl $(IF_MASK),%eax # these just try to make sure andl $~NT_MASK,%eax # the program doesn't do anything movl %eax,EFLAGS(%esp) # stupid cmpl $0,SYMBOL_NAME(need_resched) jne reschedule#ifdef __SMP__ GET_PROCESSOR_OFFSET(%eax) movl SYMBOL_NAME(current_set)(,%eax), %eax#else movl SYMBOL_NAME(current_set),%eax#endif cmpl SYMBOL_NAME(task),%eax # task[0] cannot have signals je 2f movl blocked(%eax),%ecx movl %ecx,%ebx # save blocked in %ebx for signal handling notl %ecx andl signal(%eax),%ecx jne signal_return2: RESTORE_ALLALIGNsignal_return: movl %esp,%ecx pushl %ecx testl $(VM_MASK),EFLAGS(%ecx) jne v86_signal_return pushl %ebx call SYMBOL_NAME(do_signal) popl %ebx popl %ebx RESTORE_ALLALIGNv86_signal_return: call SYMBOL_NAME(save_v86_state) movl %eax,%esp pushl %eax pushl %ebx call SYMBOL_NAME(do_signal) popl %ebx popl %ebx RESTORE_ALL handle_bottom_half:incl SYMBOL_NAME(intr_count)call SYMBOL_NAME(do_bottom_half)decl SYMBOL_NAME(intr_count)jmp 9fALIGNreschedule:pushl $ret_from_sys_call jmp SYMBOL_NAME(schedule) # test 另外,一些与时钟中断及bottom half机制有关的数据结构介绍如下:#define HZ 100unsigned long volatile jiffies=0;系统每隔10ms自动把它加1,它是核心系统计时的单位。enum { TIMER_BH = 0, CONSOLE_BH, TQUEUE_BH, DIGI_BH, SERIAL_BH, RISCOM8_BH,SPECIALIX_BH, BAYCOM_BH, NET_BH, IMMEDIATE_BH, KEYBOARD_BH, CYCLADES_BH, CM206_BH};现在只定义了13个bottom half队列,将来可扩充到32个队列。unsigned long intr_count = 0;相当于信号量的作用。只有其等于0,才可以do_bottom_half。int bh_mask_count[32];用来计算bottom half队列被屏蔽的次数。只有某队列的bh_mask_count数为0,才能enable该队列。unsigned long bh_active = 0;bh_active是32位长整数,每一位表示一个bottom half队列,该位置1,表示该队列处于激活状态,随时准备在CPU认为合适的时候执行该队列的服务,置0则相反。unsigned long bh_mask = 0;bh_mask也是32位长整数,每一位对应一个bottom half队列,该位置1,表示该队列可用,并把处理函数的入口地址赋给bh_base,置0则相反。void (*bh_base[32])(void);bottom half服务函数入口地址数组。定时器处理函数拥有最高的优先级,它的地址存放在bh_base[0],总是最先执行它所指向的函数。我们注意到,在IRQ#_interrupt和fast_IRQ#_interrupt中断函数处理返回前,都通过语句jmp ret_from_sys_call,跳到系统调用的返回处(见irq.h),如果bottom half队列不为空,则在那里做类似: if (bh_active & bh_mask) { intr_count = 1; do_bottom_half(); intr_count = 0; }(该判断的汇编代码见Entry.S)的判断,调用do_bottom_half()函数。在CPU调度时,通过schedule函数执行上述的判断,再调用do_bottom_half()函数。总而言之,在下列三种时机:CPU调度时系统调用返回前中断处理返回前都会作判断调用do_bottom_half函数。Do_bottom_half函数依次扫描32个队列,找出需要服务的队列,执行服务后把对应该队列的bh_active的相应位置0。由于bh_active标志中TIMER_BH对应的bit为1,因而系统根据服务函数入口地址数组bh_base找到函数timer_bh()的入口地址,并马上执行该函数,在函数timer_bh中,调用函数run_timer_list()和函数run_old_timers()函数,定时执行服务。TVECS结构及其实现有关TVECS结构的一些数据结构定义如下:#define TVN_BITS 6#define TVR_BITS 8#define TVN_SIZE (1 << TVN_BITS)#define TVR_SIZE (1 << TVR_BITS)#define TVN_MASK (TVN_SIZE - 1)#define TVR_MASK (TVR_SIZE - 1)#define SLOW_BUT_DEBUGGING_TIMERS 0struct timer_vec { int index; struct timer_list *vec[TVN_SIZE];};struct timer_vec_root { int index; struct timer_list *vec[TVR_SIZE];};static struct timer_vec tv5 = { 0 };static struct timer_vec tv4 = { 0 };static struct timer_vec tv3 = { 0 };static struct timer_vec tv2 = { 0 };static struct timer_vec_root tv1 = { 0 };static struct timer_vec * const tvecs[] = { (struct timer_vec *)&tv1, &tv2, &tv3, &tv4, &tv5};#define NOOF_TVECS (sizeof(tvecs) / sizeof(tvecs[0]))static unsigned long timer_jiffies = 0;TVECS结构是一个元素个数为5的数组,分别指向tv1,tv2,tv3,tv4,tv5的地址。其中,tv1是结构timer_vec_root的变量,它有一个index域和有256个元素的指针数组,该数组的每个元素都是一条类型为timer_list的链表。其余四个元素都是结构timer_vec的变量,它们各有一个index域和64个元素的指针数组,这些数组的每个元素也都是一条链表。函数internal_add_timer(struct timer_list *timer)函数代码如下:static inline void internal_add_timer(struct timer_list *timer){ /* * must be cli-ed when calling this */ unsigned long expires = timer->expires; unsigned long idx = expires - timer_jiffies; if (idx < TVR_SIZE) { int i = expires & TVR_MASK; insert_timer(timer, tv1.vec, i); } else if (idx < 1 << (TVR_BITS + TVN_BITS)) { int i = (expires >> TVR_BITS) & TVN_MASK; insert_timer(timer, tv2.vec, i); } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) { int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK; insert_timer(timer, tv3.vec, i); } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) { int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK; insert_timer(timer, tv4.vec, i); } else if (expires < timer_jiffies) { /* can happen if you add a timer with expires == jiffies, * or you set a timer to go off in the past */ insert_timer(timer, tv1.vec, tv1.index); } else if (idx < 0xffffffffUL) { int i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK; insert_timer(timer, tv5.vec, i); } else { /* Can only get here on architectures with 64-bit jiffies */ timer->next = timer->prev = timer; }} expires在调用该函数之前,必须关中。对该函数的说明如下:取出要加进TVECS的timer的激发时间(expires),算出expires与timer_jiffies的差值idx,用来决定该插到哪个队列中去。若idx小于2^8,则取expires的第0位到第7位的值I,把timer加到tv1.vec中第I个链表的第一个表项之前。若idx小于2^14,则取expires的第8位到第13位的值I,把timer加到tv2.vec中第I个链表的第一个表项之前。若idx小于2^20,则取expires的第14位到第19位的值I,把timer加到tv3.vec中第I个链表的第一个表项之前。若idx小于2^26,则取expires的第20位到第25位的值I,把timer加到tv4.vec中第I个链表的第一个表项之前。若expires小于timer_jiffies,即idx小于0,则表明该timer到期,应该把timer放入tv1.vec中tv1.index指定的链表的第一个表项之前。若idx小于2^32,则取expires的第26位到第32位的值I,把timer加到tv5.vec中第I个链表的第一个表项之前。若idx大等于2^32,该情况只有在64位的机器上才有可能发生,在这种情况下,不把timer加入TVECS结构。函数cascade_timers(struct timer_vec *tv)该函数只是把tv->index指定的那条链表上的所有timer调用internal_add_timer()函数进行重新调整,这些timer将放入TVECS结构中比原来位置往前移一级,比如说,tv4上的timer将放到tv3上去,tv2上的timer将放到tv1上。这种前移是由run_timer_list函数里调用cascade_timers函数的时机来保证的。然后把该条链表置空,tv->index加1,若tv->index等于64,则重新置为0。函数run_timer_list()函数代码如下:static inline void run_timer_list(void){cli();while ((long)(jiffies - timer_jiffies) >= 0) { struct timer_list *timer; if (!tv1.index) { int n = 1; do { cascade_timers(tvecs[n]); } while (tvecs[n]->index == 1 && ++n < NOOF_TVECS); } while ((timer = tv1.vec[tv1.index])) { void (*fn)(unsigned long) = timer->function; unsigned long data = timer->data; detach_timer(timer); timer->next = timer->prev = NULL; sti(); fn(data); cli(); } ++timer_jiffies; tv1.index = (tv1.index + 1) & TVR_MASK;}sti();} 对run_timer_list函数的说明如下:关中。判断jiffies是否大等于timer_jiffies,若不是,goto 8。判断tv1.index是否为0(即此时系统已经扫描过整个tv1的256个timer_list链表,又回到的第一个链表处,此时需重整TVECS结构),若是,置n为1;若不是,goto 6。调用cascade_timers()函数把TVECS[n]中由其index指定的那条链表上的timer放到TVECS[n-1]中来。注意:调用cascade_timers()函数后,index已经加1。判断TVECS[n]->index是否为1,即原来为0。如果是(表明TVECS[n]上所有都已经扫描一遍,此时需对其后一级的TVECS[++n]调用cascade_timers()进行重整),把n加1,goto 4。执行tv1.vec上由tv1->index指定的那条链表上的所有timer的服务函数,并把该timer从链表中移走。在执行服务函数的过程中,允许中断。timer_jiffies加1,tv1->index加1,若tv1->index等于256,则重新置为0,goto 2。开中,返回。Linux提供了两种定时器服务。一种早期的由timer_struct等结构描述,由run_old_times函数处理。另一种“新”的服务由timer_list等结构描述,由add_timer、del_timer、cascade_time和run_timer_list等函数处理。早期的定时器服务利用如下数据结构:struct timer_struct { unsigned long expires; /*本定时器被唤醒的时刻 */ void (*fn)(void); /* 定时器唤醒后的处理函数 */}struct timer_struct timer_table[32]; /*最多可同时启用32个定时器 */unsigned long timer_active; /* 每位对应一定时器,置1表示启用 */新的定时器服务依靠链表结构突破了32个的限制,利用如下的数据结构:struct timer_list { struct timer_list *next; struct timer_list *prev; unsigned long expires; unsigned long data; /* 用来存放当前进程的PCB块的指针,可作为参数传 void (*function)(unsigned long); 给function */}表示上述数据结构的图示如下: 在这里,顺便简单介绍一下旧的timer机制的运作情况。 系统在每次调用函数do_bottom_half时,都会调用一次函数run_old_timers()。函数run_old_timers()该函数处理的很简单,只不过依次扫描timer_table中的32个定时器,若扫描到的定时器已经到期,并且已经被激活,则执行该timer的服务函数。间隔定时器itimer系统为每个进程提供了三个间隔定时器。当其中任意一个定时器到期时,就会发出一个信号给进程,同时,定时器重新开始运作。三种定时器描述如下:ITIMER_REAL 真实时钟,到期时送出SIGALRM信号。ITIMER_VIRTUAL 仅在进程运行时的计时,到期时送出SIGVTALRM信号。ITIMER_PROF 不仅在进程运行时计时,在系统为进程运作而运行时它也计时,与ITIMER_VIRTUAL对比,该定时器通常为那些在用户态和核心态空间运行的应用所花去的时间计时,到期时送出SIGPROF信号。与itimer有关的数据结构定义如下:struct timespec { long tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */};struct timeval { int tv_sec; /* seconds */ int tv_usec; /* microseconds */};struct itimerspec { struct timespec it_interval; /* timer period */ struct timespec it_value; /* timer expiration */};struct itimerval { struct timeval it_interval; /* timer interval */ struct timeval it_value; /* current value */};这三种定时器在task_struct中定义:struct task_struct { …… unsigned long timeout; unsigned long it_real_value,it_prof_value,it_virt_value; unsigned long it_real_incr,it_prof_incr,it_virt_incr; struct timer_list real_timer; ……}在进程创建时,系统把it_real_fn函数的入口地址赋给real_timer.function。(见sched.h)我们小组分析了三个系统调用:sys_getitimer,sys_setitimer,sys_alarm。在这三个系统调用中,需用到以下一些函数:函数static int _getitimer(int which, struct itimerval *value)该函数的运行过程大致如下:根据传进的参数which按三种itimer分别处理:若是ITIMER_REAL,则设置interval为current进程的it_real_incr,val设置为0;判断current进程的real_timer有否设置并挂入TVECS结构中,若有,设置val为current进程real_timer的expires,并把real_timer重新挂到TVECS结构中,接着把val与当前jiffies作比较,若小等于当前jiffies,则说明该real_timer已经到期,于是重新设置val为当前jiffies的值加1。最后把val减去当前jiffies的值,goto 2。若是ITIMER_VIRTUAL,则分别设置interval,val的值为current进程的it_virt_incr、it_virt_value,goto 2。若是ITIMER_PROF,则分别设置interval,val的值为current进程的it_prof_incr、it_prof_value,goto 2。 (2)调用函数jiffiestotv把val,interval的jiffies值转换为timeval,返回0。函数 int _setitimer(int which, struct itimerval *value, struct itimerval *ovalue)该函数的运行过程大致如下:调用函数tvtojiffies把value中的interval和value转换为jiffies i 和 j。判断指针ovalue是否为空,若空,goto ;若不空,则把由which指定类型的itimer存入ovalue中,若存放不成功,goto 4;根据which指定的itimer按三种类型分别处理:若是ITIMER_REAL,则从TVECS结构中取出current进程的real_timer,并重新设置current进程的it_real_value和it_real_incr为j和i。若j等于0,goto 4;若不等于0,则把当前jiffies的值加上定时器剩余时间j,得到触发时间。若i小于j,则表明I已经溢出,应该重新设为ULONG_MAX。最后把current进程的real_timer的expires设为i,把设置过的real_timer重新加入TVECS结构,goto 4。若是ITIMER_VIRTUAL,则设置current进程的it-_virt_value和it_virt_incr为j和i。若是ITIMER_PROF,则设置current进程的it-_prof_value和it_prof_incr为j和i。 (4)返回0。函数verify_area(int type, const void *addr, unsigned long size)该函数的主要功能是对以addr为始址的,长度为size的一块存储区是否有type类型的操作权利。函数memcpy_tofs(to, from, n)该函数的主要功能是从以from为始址的存储区中取出长度为n的一块数据放入以to为始址的存储区。函数memcpy_fromfs(from, to, n)该函数的主要功能是从以from为始址的存储区中取出长度为n的一块数据放入以to为始址的存储区。函数memset((char*)&set_buffer, 0, sizeof(set_buffer))该函数的主要功能是把set_buffer中的内容置为0,在这里,即把it_value和it_interval置为0。现在,我简单介绍一下这三个系统调用:系统调用sys_getitimer(int which, struct itimerval *value)首先,若value为NULL,则返回-EFAULT,说明这是一个bad address。其次,把which类型的itimer取出放入get_buffer。再次,若存放成功,再确认对value的写权利。最后,则把get_buffer中的itimer取出,拷入value。系统调用sys_setitimer(int which, struct itimerval *value,struct itimerval *ovalue)首先,判断value是否为NULL,若不是,则确认对value是否有读的权利,并把set_buffer中的数据拷入value;若value为NULL,则把set_buffer中的内容置为0,即把it_value和it_interval置为0。其次,判断ovalue是否为NULL,若不是,则确认对ovalue是否有写的权利。再次,调用函数_setitimer设置由which指定类型的itimer。最后,调用函数memcpy_tofs把get_buffer中的数据拷入ovalue,返回。系统调用sys_alarm(unsigned int seconds)该系统调用重新设置进程的real_itimer,若seconds为0,则把原先的alarm定时器删掉。并且设interval为0,故只触发一次,并把旧的real_timer存入oldalarm,并返回oldalarm。 楼上的几位都属高人啊,关注下。另外楼主你纳秒级的程序在意CPU么? Advanced Installer 制作的安装包在XP下不能安装 请教关于多线程传输文件的问题? 菜鸟提问:有关vc读取Access数据库的问题!急!!! 使用ctreectrl控件的createdragimage函数失败 哪里有windows程序调试这本书的下载? 求语音处理的代码资料 如何在COM接口中传递IDL基本类型的数组 一个ListBox的问题 有谁知道“虚拟硬盘”的原理? 如何在OnDraw()中途停止画图,等待按键后再继续画剩余的部分 求助:进程反注入或者说反进程入侵 time_t实际是一个整数,它是怎样表示年月日以及时间的?
相信你明摆了摆怎么做了吧?
wait(int nTime)
{
start = 当前纳秒级的时间
while(1)
{
if (start - 当前纳秒级的时间 > nTime )break;
}
};
用FPGA做吧
我现在做一个实时性要求很高的,即使是1毫秒级别的SLEEP,都会带来延迟的。所以需要纳秒级别。
标 题: [转载]使用CPU时间戳进行高精度计时
发信站: 瀚海星云 (2002年11月17日20:07:01 星期天), 站内信件使用CPU时间戳进行高精度计时对关注性能的程序开发人员而言,一个好的计时部件既是益友,也是良师。计时器既可
以作为程序组件帮助程序员精确的控制程序进程,又是一件有力的调试武器,在有经验
的程序员手里可以尽快的确定程序的性能瓶颈,或者对不同的算法作出有说服力的性能
比较。
在Windows平台下,常用的计时器有两种,一种是timeGetTime多媒体计时器,它可以提
供毫秒级的计时。但这个精度对很多应用场合而言还是太粗糙了。另一种是QueryPerfor
manceCount计数器,随系统的不同可以提供微秒级的计数。对于实时图形处理、多媒体
数据流处理、或者实时系统构造的程序员,善用QueryPerformanceCount/QueryPerforma
nceFrequency是一项基本功。
本文要介绍的,是另一种直接利用Pentium CPU内部时间戳进行计时的高精度计时手段。
以下讨论主要得益于《Windows图形编程》一书,第15页-17页,有兴趣的读者可以直接
参考该书。关于RDTSC指令的详细讨论,可以参考Intel产品手册。本文仅仅作抛砖之用
。在Intel Pentium以上级别的CPU中,有一个称为“时间戳(Time Stamp)”的部件,它
以64位无符号整型数的格式,记录了自CPU上电以来所经过的时钟周期数。由于目前的CP
U主频都非常高,因此这个部件可以达到纳秒级的计时精度。这个精确性是上述两种方法
所无法比拟的。
在Pentium以上的CPU中,提供了一条机器指令RDTSC(Read Time Stamp Counter)来读
取这个时间戳的数字,并将其保存在EDX:EAX寄存器对中。由于EDX:EAX寄存器对恰好是W
in32平台下C++语言保存函数返回值的寄存器,所以我们可以把这条指令看成是一个普通
的函数调用。像这样:inline unsigned __int64 GetCycleCount()
{
__asm RDTSC
}但是不行,因为RDTSC不被C++的内嵌汇编器直接支持,所以我们要用_emit伪指令直接嵌
入该指令的机器码形式0X0F、0X31,如下:inline unsigned __int64 GetCycleCount()
{
__asm _emit 0x0F
__asm _emit 0x31
}以后在需要计数器的场合,可以像使用普通的Win32 API一样,调用两次GetCycleCount
函数,比较两个返回值的差,像这样:unsigned long t;
t = (unsigned long)GetCycleCount();
//Do Something time-intensive ...
t -= (unsigned long)GetCycleCount();《Windows图形编程》第15页编写了一个类,把这个计数器封装起来。有兴趣的读者可以
去参考那个类的代码。作者为了更精确的定时,做了一点小小的改进,把执行RDTSC指令
的时间,通过连续两次调用GetCycleCount函数计算出来并保存了起来,以后每次计时结
束后,都从实际得到的计数中减掉这一小段时间,以得到更准确的计时数字。但我个人
觉得这一点点改进意义不大。在我的机器上实测,这条指令大概花掉了几十到100多个周
期,在Celeron 800MHz的机器上,这不过是十分之一微秒的时间。对大多数应用来说,
这点时间完全可以忽略不计;而对那些确实要精确到纳秒数量级的应用来说,这个补偿
也过于粗糙了。这个方法的优点是:
1.高精度。可以直接达到纳秒级的计时精度(在1GHz的CPU上每个时钟周期就是一纳秒)
,这是其他计时方法所难以企及的。
2.成本低。timeGetTime 函数需要链接多媒体库winmm.lib,QueryPerformance* 函数根
据MSDN的说明,需要硬件的支持(虽然我还没有见过不支持的机器)和KERNEL库的支持
,所以二者都只能在Windows平台下使用(关于DOS平台下的高精度计时问题,可以参考
《图形程序开发人员指南》,里面有关于控制定时器8253的详细说明)。但RDTSC指令是
一条CPU指令,凡是i386平台下Pentium以上的机器均支持,甚至没有平台的限制(我相
信i386版本UNIX和Linux下这个方法同样适用,但没有条件试验),而且函数调用的开销
是最小的。
3.具有和CPU主频直接对应的速率关系。一个计数相当于1/(CPU主频Hz数)秒,这样只要
知道了CPU的主频,可以直接计算出时间。这和QueryPerformanceCount不同,后者需要
通过QueryPerformanceFrequency获取当前计数器每秒的计数次数才能换算成时间。这个方法的缺点是:
1.现有的C/C++编译器多数不直接支持使用RDTSC指令,需要用直接嵌入机器码的方式编
程,比较麻烦。
2.数据抖动比较厉害。其实对任何计量手段而言,精度和稳定性永远是一对矛盾。如果
用低精度的timeGetTime来计时,基本上每次计时的结果都是相同的;而RDTSC指令每次
结果都不一样,经常有几百甚至上千的差距。这是这种方法高精度本身固有的矛盾。关于这个方法计时的最大长度,我们可以简单的用下列公式计算:自CPU上电以来的秒数 = RDTSC读出的周期数 / CPU主频速率(Hz)64位无符号整数所能表达的最大数字是1.8×10^19,在我的Celeron 800上可以计时大约
700年(书中说可以在200MHz的Pentium上计时117年,这个数字不知道是怎么得出来的,
与我的计算有出入)。无论如何,我们大可不必关心溢出的问题。下面是几个小例子,简要比较了三种计时方法的用法与精度
//Timer1.cpp 使用了RDTSC指令的Timer类//KTimer类的定义可以参见《Windows图形编
程》P15
//编译行:CL Timer1.cpp /link USER32.lib
#include <stdio.h>
#include "KTimer.h"
main()
{
unsigned t;
KTimer timer;
timer.Start();
Sleep(1000);
t = timer.Stop();
printf("Lasting Time: %d\n",t);
}//Timer2.cpp 使用了timeGetTime函数
//需包含<mmsys.h>,但由于Windows头文件错综复杂的关系
//简单包含<windows.h>比较偷懒:)
//编译行:CL timer2.cpp /link winmm.lib
#include <windows.h>
#include <stdio.h>main()
{
DWORD t1, t2;
t1 = timeGetTime();
Sleep(1000);
t2 = timeGetTime();
printf("Begin Time: %u\n", t1);
printf("End Time: %u\n", t2);
printf("Lasting Time: %u\n",(t2-t1));
}//Timer3.cpp 使用了QueryPerformanceCounter函数
//编译行:CL timer3.cpp /link KERNEl32.lib
#include <windows.h>
#include <stdio.h>main()
{
LARGE_INTEGER t1, t2, tc;
QueryPerformanceFrequency(&tc);
printf("Frequency: %u\n", tc.QuadPart);
QueryPerformanceCounter(&t1);
Sleep(1000);
QueryPerformanceCounter(&t2);
printf("Begin Time: %u\n", t1.QuadPart);
printf("End Time: %u\n", t2.QuadPart);
printf("Lasting Time: %u\n",( t2.QuadPart- t1.QuadPart));
}////////////////////////////////////////////////
//以上三个示例程序都是测试1秒钟休眠所耗费的时间
file://测试环境:Celeron 800MHz / 256M SDRAM
// Windows 2000 Professional SP2
// Microsoft Visual C++ 6.0 SP5
////////////////////////////////////////////////
以下是Timer1的运行结果,使用的是高精度的RDTSC指令
Lasting Time: 804586872以下是Timer2的运行结果,使用的是最粗糙的timeGetTime API
Begin Time: 20254254
End Time: 20255255
Lasting Time: 1001以下是Timer3的运行结果,使用的是QueryPerformanceCount API
Frequency: 3579545
Begin Time: 3804729124
End Time: 3808298836
Lasting Time: 3569712古人说,触类旁通。从一本介绍图形编程的书上得到一个如此有用的实时处理知识,我
感到非常高兴。有美不敢自专,希望大家和我一样喜欢这个轻便有效的计时器。参考资料:
[YUAN 2002]Feng Yuan 著,英宇工作室 译,Windows图形编程,机械工业出版社,2002
.4.,P15-17
定时器代码分析(转载的)时钟和定时器中断
IRQ 0 [Timer]
|
\|/
|IRQ0x00_interrupt // wrapper IRQ handler
|SAVE_ALL ---
|do_IRQ | wrapper routines
|handle_IRQ_event ---
|handler() -> timer_interrupt // registered IRQ 0 handler
|do_timer_interrupt
|do_timer
|jiffies++;
|update_process_times
|if (--counter <= 0) { // if time slice ended then
|counter = 0; // reset counter
|need_resched = 1; // prepare to reschedule
|}
|do_softirq
|while (need_resched) { // if necessary
|schedule // reschedule
|handle_softirq
|}
|RESTORE_ALL·IRQ0x00_interrupt, SAVE_ALL [include/asm/hw_irq.h]
·do_IRQ, handle_IRQ_event [arch/i386/kernel/irq.c]
·timer_interrupt, do_timer_interrupt [arch/i386/kernel/time.c]
·do_timer, update_process_times [kernel/timer.c]
·do_softirq [kernel/soft_irq.c]
·RESTORE_ALL, while loop [arch/i386/kernel/entry.S] 系统启动核心时,调用start_kernal()继续各方面的初始化,在这之前,各种中断都被禁止,只有在完成必要的初始化后,直到执行完Kmalloc_init()后,才允许中断(init\main.c)。与时钟中断有关的部分初始化如下: 调用trap_init()设置各种trap入口,如system_call、GDT entry、LDT entry、call gate等。其中0~17为各种错误入口,18~47保留。 调用init_IRQ()函数设置核心系统的时钟周期为10ms,即100HZ,它是以后按照轮转法进行CPU调度时所依照的基准时钟周期。每10ms产生的时钟中断信号直接输入到第一块8259A的INT 0(即irq0)。初始化中断矢量表中从0x20起的17个中断矢量,用bad_IRQ#_interrupt函数的地址(#为中断号)填写。 调用sched_init()函数,设置启动第一个进程init_task。设置用于管理bottom_half机制的数据结构bh_base[],规定三类事件的中断处理函数,即时钟TIMER_BH、设备TQUEUE_BH和IMMEDIATE_BH。 调用time_init()函数,首先读取当时的CMOS时间,最后调用setup_x86_irq(0,&irq0)函数,把irq0挂到irq_action[0]队列的后面,并把中断矢量表中第0x20项,即timer中断对应的中断矢量改为IRQ0_interrupt函数的地址,在irq0中,指定时间中断服务程序是timer_interrupt,
static struct irqaction irq0 = { timer_interrupt, 0, 0, "timer", NULL, NULL}
结构irqaction的定义如下:
struct irqaction {
void (*handler)(int, void *, struct pt_regs *); /* 中断服务函数入口 */
unsigned long flags; /* 服务允中与否标记 */
unsigned long mask;
const char *name;
void *dev_id;
struct irqaction *next;
};
其中,若flag==SA_INTERRUPT,则中断矢量改为fast_IRQ#_interrupt,在执行中断服务的过程中不允许出现中断,若为其它标记,则中断矢量为IRQ#_interrupt,在执行中断服务的过程中,允许出现中断。
Irq_action的定义与初始化如下:
static void (*interrupt[17])(void) = {IRQ#_interrupt};
static void (*fast_interrupt[16])(void) = {fast_IRQ#_interrupt};
static void (*bad_interrupt[16])(void) = {bad_IRQ#_interrupt};(以上#为中断号)
static struct irqaction *irq_action[16] = {
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL
}; irq_action是一个全局数组,每个元素指向一个irq队列,共16个irq队列,时钟中断请求队列在第一个队列,即irq_action[0]。当每个中断请求到来时,都调用setup_x86_irq把该请求挂到相应的队列的后面。 以后,系统每10ms产生一次时钟中断信号,该信号直接输入到第一块8259A的INT 0(即irq0)。CPU根据中断矢量表和中断源,找到中断矢量函数入口IRQ0_interrupt(程序运行过程中允许中断)或者fast_IRQ0_interrupt(程序运行过程中不允许中断)或者bad_IRQ0_interrupt(不执行任何动作,直接返回),这些函数由宏BUILD_TIMER_IRQ(chip, nr, mask)展开定义。
宏BUILD_TIMER_IRQ(chip, nr, mask)的定义如下:
#define BUILD_TIMER_IRQ(chip,nr,mask) asmlinkage void IRQ_NAME(nr); asmlinkage void FAST_IRQ_NAME(nr); asmlinkage void BAD_IRQ_NAME(nr); __asm__( "\n"__ALIGN_STR"\n" SYMBOL_NAME_STR(fast_IRQ) #nr "_interrupt:\n\t" SYMBOL_NAME_STR(bad_IRQ) #nr "_interrupt:\n\t" SYMBOL_NAME_STR(IRQ) #nr "_interrupt:\n\t" "pushl $-"#nr"-2\n\t" SAVE_ALL ENTER_KERNEL ACK_##chip(mask,(nr&7)) "incl "SYMBOL_NAME_STR(intr_count)"\n\t"\ /* intr_count为进入临界区的同步信号量 */
"movl %esp,%ebx\n\t" "pushl %ebx\n\t" "pushl $" #nr "\n\t" \ /* 把do_irq函数参数压进堆栈 */
"call "SYMBOL_NAME_STR(do_IRQ)"\n\t" "addl $8,%esp\n\t" "cli\n\t" UNBLK_##chip(mask) "decl "SYMBOL_NAME_STR(intr_count)"\n\t" "incl "SYMBOL_NAME_STR(syscall_count)"\n\t" "jmp ret_from_sys_call\n"); 其中nr为中断请求类型,取值0~15。在irq.c中通过语句BUILD_TIMER_IRQ(first, 0, 0x01)调用该宏,在执行宏的过程中处理时钟中断响应程序do_irq()。 函数do_irq()的第一个参数是中断请求队列序号,时钟中断请求传进来的该参数是0。于是程序根据参数0找到请求队列irq_action[0],逐个处理该队列上handler所指的时钟中断请求的服务函数。由于已经指定时钟中断请求的服务函数是timer_interrupt,在函数timer_interrupt中,立即调用do_timer()函数。 函数do_timer()把jiffies和lost_ticks加1,接着就执行_bh(TIMER_BH)函数,把bottom_half中时钟队列对应的位置位,表示该队列处于激活状态。在做完这些动作后,程序从函数do_irq()中返回,继续执行以后的汇编代码。于是,程序在执行语句jmp ret_from_sys_call后,跳到指定的位置处继续执行。代码段jmp ret_from_sys_call及其相关的代码段如下:
ALIGN
.globl ret_from_sys_call
ret_from_sys_call:
cmpl $0,SYMBOL_NAME(intr_count)
jne 2f
9: movl SYMBOL_NAME(bh_mask),%eax
andl SYMBOL_NAME(bh_active),%eax
jne handle_bottom_half
#ifdef __SMP__
cmpb $(NO_PROC_ID), SYMBOL_NAME(saved_active_kernel_processor)
jne 2f
#endif
movl EFLAGS(%esp),%eax # check VM86 flag: CS/SS are
testl $(VM_MASK),%eax # different then
jne 1f
cmpw $(KERNEL_CS),CS(%esp) # was old code segment supervisor ?
je 2f
1: sti
orl $(IF_MASK),%eax # these just try to make sure
andl $~NT_MASK,%eax # the program doesn't do anything
movl %eax,EFLAGS(%esp) # stupid
cmpl $0,SYMBOL_NAME(need_resched)
jne reschedule
#ifdef __SMP__
GET_PROCESSOR_OFFSET(%eax)
movl SYMBOL_NAME(current_set)(,%eax), %eax
#else
movl SYMBOL_NAME(current_set),%eax
#endif
cmpl SYMBOL_NAME(task),%eax # task[0] cannot have signals
je 2f
movl blocked(%eax),%ecx
movl %ecx,%ebx # save blocked in %ebx for signal handling
notl %ecx
andl signal(%eax),%ecx
jne signal_return
2: RESTORE_ALLALIGN
signal_return:
movl %esp,%ecx
pushl %ecx
testl $(VM_MASK),EFLAGS(%ecx)
jne v86_signal_return
pushl %ebx
call SYMBOL_NAME(do_signal)
popl %ebx
popl %ebx
RESTORE_ALLALIGN
v86_signal_return:
call SYMBOL_NAME(save_v86_state)
movl %eax,%esp
pushl %eax
pushl %ebx
call SYMBOL_NAME(do_signal)
popl %ebx
popl %ebx
RESTORE_ALL handle_bottom_half:
incl SYMBOL_NAME(intr_count)
call SYMBOL_NAME(do_bottom_half)
decl SYMBOL_NAME(intr_count)
jmp 9fALIGN
reschedule:
pushl $ret_from_sys_call
jmp SYMBOL_NAME(schedule) # test
#define HZ 100
unsigned long volatile jiffies=0;
系统每隔10ms自动把它加1,它是核心系统计时的单位。
enum {
TIMER_BH = 0,
CONSOLE_BH,
TQUEUE_BH,
DIGI_BH,
SERIAL_BH,
RISCOM8_BH,
SPECIALIX_BH,
BAYCOM_BH,
NET_BH,
IMMEDIATE_BH,
KEYBOARD_BH,
CYCLADES_BH,
CM206_BH
};
现在只定义了13个bottom half队列,将来可扩充到32个队列。
unsigned long intr_count = 0;
相当于信号量的作用。只有其等于0,才可以do_bottom_half。
int bh_mask_count[32];
用来计算bottom half队列被屏蔽的次数。只有某队列的bh_mask_count数为0,才能enable该队列。
unsigned long bh_active = 0;
bh_active是32位长整数,每一位表示一个bottom half队列,该位置1,表示该队列处于激活状态,随时准备在CPU认为合适的时候执行该队列的服务,置0则相反。
unsigned long bh_mask = 0;
bh_mask也是32位长整数,每一位对应一个bottom half队列,该位置1,表示该队列可用,并把处理函数的入口地址赋给bh_base,置0则相反。
void (*bh_base[32])(void);
bottom half服务函数入口地址数组。定时器处理函数拥有最高的优先级,它的地址存放在bh_base[0],总是最先执行它所指向的函数。我们注意到,在IRQ#_interrupt和fast_IRQ#_interrupt中断函数处理返回前,都通过语句jmp ret_from_sys_call,跳到系统调用的返回处(见irq.h),如果bottom half队列不为空,则在那里做类似:
if (bh_active & bh_mask) {
intr_count = 1;
do_bottom_half();
intr_count = 0;
}(该判断的汇编代码见Entry.S)
的判断,调用do_bottom_half()函数。
在CPU调度时,通过schedule函数执行上述的判断,再调用do_bottom_half()函数。
总而言之,在下列三种时机:
CPU调度时
系统调用返回前
中断处理返回前
都会作判断调用do_bottom_half函数。Do_bottom_half函数依次扫描32个队列,找出需要服务的队列,执行服务后把对应该队列的bh_active的相应位置0。由于bh_active标志中TIMER_BH对应的bit为1,因而系统根据服务函数入口地址数组bh_base找到函数timer_bh()的入口地址,并马上执行该函数,在函数timer_bh中,调用函数run_timer_list()和函数run_old_timers()函数,定时执行服务。TVECS结构及其实现
有关TVECS结构的一些数据结构定义如下:#define TVN_BITS 6
#define TVR_BITS 8
#define TVN_SIZE (1 << TVN_BITS)
#define TVR_SIZE (1 << TVR_BITS)
#define TVN_MASK (TVN_SIZE - 1)
#define TVR_MASK (TVR_SIZE - 1)#define SLOW_BUT_DEBUGGING_TIMERS 0struct timer_vec {
int index;
struct timer_list *vec[TVN_SIZE];
};
struct timer_vec_root {
int index;
struct timer_list *vec[TVR_SIZE];
};static struct timer_vec tv5 = { 0 };
static struct timer_vec tv4 = { 0 };
static struct timer_vec tv3 = { 0 };
static struct timer_vec tv2 = { 0 };
static struct timer_vec_root tv1 = { 0 };static struct timer_vec * const tvecs[] = {
(struct timer_vec *)&tv1, &tv2, &tv3, &tv4, &tv5
};
#define NOOF_TVECS (sizeof(tvecs) / sizeof(tvecs[0]))
static unsigned long timer_jiffies = 0;TVECS结构是一个元素个数为5的数组,分别指向tv1,tv2,tv3,tv4,tv5的地址。其中,tv1是结构timer_vec_root的变量,它有一个index域和有256个元素的指针数组,该数组的每个元素都是一条类型为timer_list的链表。其余四个元素都是结构timer_vec的变量,它们各有一个index域和64个元素的指针数组,这些数组的每个元素也都是一条链表。函数internal_add_timer(struct timer_list *timer)函数代码如下:
static inline void internal_add_timer(struct timer_list *timer)
{
/*
* must be cli-ed when calling this
*/
unsigned long expires = timer->expires;
unsigned long idx = expires - timer_jiffies; if (idx < TVR_SIZE) {
int i = expires & TVR_MASK;
insert_timer(timer, tv1.vec, i);
} else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
int i = (expires >> TVR_BITS) & TVN_MASK;
insert_timer(timer, tv2.vec, i);
} else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
insert_timer(timer, tv3.vec, i);
} else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
insert_timer(timer, tv4.vec, i);
} else if (expires < timer_jiffies) {
/* can happen if you add a timer with expires == jiffies,
* or you set a timer to go off in the past
*/
insert_timer(timer, tv1.vec, tv1.index);
} else if (idx < 0xffffffffUL) {
int i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
insert_timer(timer, tv5.vec, i);
} else {
/* Can only get here on architectures with 64-bit jiffies */
timer->next = timer->prev = timer;
}
} expires
在调用该函数之前,必须关中。对该函数的说明如下:
取出要加进TVECS的timer的激发时间(expires),算出expires与timer_jiffies的差值idx,用来决定该插到哪个队列中去。
若idx小于2^8,则取expires的第0位到第7位的值I,把timer加到tv1.vec中第I个链表的第一个表项之前。
若idx小于2^14,则取expires的第8位到第13位的值I,把timer加到tv2.vec中第I个链表的第一个表项之前。
若idx小于2^20,则取expires的第14位到第19位的值I,把timer加到tv3.vec中第I个链表的第一个表项之前。
若idx小于2^26,则取expires的第20位到第25位的值I,把timer加到tv4.vec中第I个链表的第一个表项之前。
若expires小于timer_jiffies,即idx小于0,则表明该timer到期,应该把timer放入tv1.vec中tv1.index指定的链表的第一个表项之前。
若idx小于2^32,则取expires的第26位到第32位的值I,把timer加到tv5.vec中第I个链表的第一个表项之前。
若idx大等于2^32,该情况只有在64位的机器上才有可能发生,在这种情况下,不把timer加入TVECS结构。函数cascade_timers(struct timer_vec *tv)该函数只是把tv->index指定的那条链表上的所有timer调用internal_add_timer()函数进行重新调整,这些timer将放入TVECS结构中比原来位置往前移一级,比如说,tv4上的timer将放到tv3上去,tv2上的timer将放到tv1上。这种前移是由run_timer_list函数里调用cascade_timers函数的时机来保证的。然后把该条链表置空,tv->index加1,若tv->index等于64,则重新置为0。函数run_timer_list()函数代码如下:
static inline void run_timer_list(void)
{
cli();
while ((long)(jiffies - timer_jiffies) >= 0) {
struct timer_list *timer;
if (!tv1.index) {
int n = 1;
do {
cascade_timers(tvecs[n]);
} while (tvecs[n]->index == 1 && ++n < NOOF_TVECS);
}
while ((timer = tv1.vec[tv1.index])) {
void (*fn)(unsigned long) = timer->function;
unsigned long data = timer->data;
detach_timer(timer);
timer->next = timer->prev = NULL;
sti();
fn(data);
cli();
}
++timer_jiffies;
tv1.index = (tv1.index + 1) & TVR_MASK;
}
sti();
}
关中。
判断jiffies是否大等于timer_jiffies,若不是,goto 8。
判断tv1.index是否为0(即此时系统已经扫描过整个tv1的256个timer_list链表,又回到的第一个链表处,此时需重整TVECS结构),若是,置n为1;若不是,goto 6。
调用cascade_timers()函数把TVECS[n]中由其index指定的那条链表上的timer放到TVECS[n-1]中来。注意:调用cascade_timers()函数后,index已经加1。
判断TVECS[n]->index是否为1,即原来为0。如果是(表明TVECS[n]上所有都已经扫描一遍,此时需对其后一级的TVECS[++n]调用cascade_timers()进行重整),把n加1,goto 4。
执行tv1.vec上由tv1->index指定的那条链表上的所有timer的服务函数,并把该timer从链表中移走。在执行服务函数的过程中,允许中断。
timer_jiffies加1,tv1->index加1,若tv1->index等于256,则重新置为0,goto 2。
开中,返回。Linux提供了两种定时器服务。一种早期的由timer_struct等结构描述,由run_old_times函数处理。另一种“新”的服务由timer_list等结构描述,由add_timer、del_timer、cascade_time和run_timer_list等函数处理。
早期的定时器服务利用如下数据结构:
struct timer_struct {
unsigned long expires; /*本定时器被唤醒的时刻 */
void (*fn)(void); /* 定时器唤醒后的处理函数 */
}
struct timer_struct timer_table[32]; /*最多可同时启用32个定时器 */
unsigned long timer_active; /* 每位对应一定时器,置1表示启用 */
新的定时器服务依靠链表结构突破了32个的限制,利用如下的数据结构:
struct timer_list {
struct timer_list *next;
struct timer_list *prev;
unsigned long expires;
unsigned long data; /* 用来存放当前进程的PCB块的指针,可作为参数传
void (*function)(unsigned long); 给function */
}
表示上述数据结构的图示如下:
在这里,顺便简单介绍一下旧的timer机制的运作情况。
系统在每次调用函数do_bottom_half时,都会调用一次函数run_old_timers()。
函数run_old_timers()
该函数处理的很简单,只不过依次扫描timer_table中的32个定时器,若扫描到的定时器已经到期,并且已经被激活,则执行该timer的服务函数。间隔定时器itimer
系统为每个进程提供了三个间隔定时器。当其中任意一个定时器到期时,就会发出一个信号给进程,同时,定时器重新开始运作。三种定时器描述如下:
ITIMER_REAL 真实时钟,到期时送出SIGALRM信号。
ITIMER_VIRTUAL 仅在进程运行时的计时,到期时送出SIGVTALRM信号。
ITIMER_PROF 不仅在进程运行时计时,在系统为进程运作而运行时它也计时,与ITIMER_VIRTUAL对比,该定时器通常为那些在用户态和核心态空间运行的应用所花去的时间计时,到期时送出SIGPROF信号。
与itimer有关的数据结构定义如下:
struct timespec {
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
struct timeval {
int tv_sec; /* seconds */
int tv_usec; /* microseconds */
};
struct itimerspec {
struct timespec it_interval; /* timer period */
struct timespec it_value; /* timer expiration */
};
struct itimerval {
struct timeval it_interval; /* timer interval */
struct timeval it_value; /* current value */
};这三种定时器在task_struct中定义:
struct task_struct {
……
unsigned long timeout;
unsigned long it_real_value,it_prof_value,it_virt_value;
unsigned long it_real_incr,it_prof_incr,it_virt_incr;
struct timer_list real_timer;
……
}
在进程创建时,系统把it_real_fn函数的入口地址赋给real_timer.function。(见sched.h)
我们小组分析了三个系统调用:sys_getitimer,sys_setitimer,sys_alarm。
在这三个系统调用中,需用到以下一些函数:
函数static int _getitimer(int which, struct itimerval *value)
该函数的运行过程大致如下:
根据传进的参数which按三种itimer分别处理:
若是ITIMER_REAL,则设置interval为current进程的it_real_incr,val设置为0;判断current进程的real_timer有否设置并挂入TVECS结构中,若有,设置val为current进程real_timer的expires,并把real_timer重新挂到TVECS结构中,接着把val与当前jiffies作比较,若小等于当前jiffies,则说明该real_timer已经到期,于是重新设置val为当前jiffies的值加1。最后把val减去当前jiffies的值,goto 2。
若是ITIMER_VIRTUAL,则分别设置interval,val的值为current进程的it_virt_incr、it_virt_value,goto 2。
若是ITIMER_PROF,则分别设置interval,val的值为current进程的it_prof_incr、it_prof_value,goto 2。
(2)调用函数jiffiestotv把val,interval的jiffies值转换为timeval,返回0。
函数 int _setitimer(int which, struct itimerval *value, struct itimerval *ovalue)
该函数的运行过程大致如下:
调用函数tvtojiffies把value中的interval和value转换为jiffies i 和 j。
判断指针ovalue是否为空,若空,goto ;若不空,则把由which指定类型的itimer存入ovalue中,若存放不成功,goto 4;
根据which指定的itimer按三种类型分别处理:
若是ITIMER_REAL,则从TVECS结构中取出current进程的real_timer,并重新设置current进程的it_real_value和it_real_incr为j和i。若j等于0,goto 4;若不等于0,则把当前jiffies的值加上定时器剩余时间j,得到触发时间。若i小于j,则表明I已经溢出,应该重新设为ULONG_MAX。最后把current进程的real_timer的expires设为i,把设置过的real_timer重新加入TVECS结构,goto 4。
若是ITIMER_VIRTUAL,则设置current进程的it-_virt_value和it_virt_incr为j和i。
若是ITIMER_PROF,则设置current进程的it-_prof_value和it_prof_incr为j和i。
(4)返回0。函数verify_area(int type, const void *addr, unsigned long size)
该函数的主要功能是对以addr为始址的,长度为size的一块存储区是否有type类型的操作权利。函数memcpy_tofs(to, from, n)
该函数的主要功能是从以from为始址的存储区中取出长度为n的一块数据放入以to为始址的存储区。函数memcpy_fromfs(from, to, n)
该函数的主要功能是从以from为始址的存储区中取出长度为n的一块数据放入以to为始址的存储区。函数memset((char*)&set_buffer, 0, sizeof(set_buffer))
该函数的主要功能是把set_buffer中的内容置为0,在这里,即把it_value和it_interval置为0。现在,我简单介绍一下这三个系统调用:
系统调用sys_getitimer(int which, struct itimerval *value)首先,若value为NULL,则返回-EFAULT,说明这是一个bad address。
其次,把which类型的itimer取出放入get_buffer。
再次,若存放成功,再确认对value的写权利。
最后,则把get_buffer中的itimer取出,拷入value。系统调用sys_setitimer(int which, struct itimerval *value,struct itimerval *ovalue)首先,判断value是否为NULL,若不是,则确认对value是否有读的权利,并把set_buffer中的数据拷入value;若value为NULL,则把set_buffer中的内容置为0,即把it_value和it_interval置为0。
其次,判断ovalue是否为NULL,若不是,则确认对ovalue是否有写的权利。
再次,调用函数_setitimer设置由which指定类型的itimer。
最后,调用函数memcpy_tofs把get_buffer中的数据拷入ovalue,返回。系统调用sys_alarm(unsigned int seconds)该系统调用重新设置进程的real_itimer,若seconds为0,则把原先的alarm定时器删掉。并且设interval为0,故只触发一次,并把旧的real_timer存入oldalarm,并返回oldalarm。
另外楼主你纳秒级的程序在意CPU么?