查看“APIC timer”的源代码
←
APIC timer
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
本地[[APIC]]定时器的最大好处是,它与每个CPU核心都是硬连线的,而[[PIT|PIT(Programmable Interval Timer - 可编程间隔定时器]]是一个单独的电路。 因此,不需要任何资源管理,这使得事情变得更容易。 缺点是它以CPU的频率之一振荡,该频率因机器而异,而PIT使用标准频率 (1,193,182Hz)。 要利用它,你必须知道它每秒能中断多少次。 ==APIC定时器模式== 定时器有2或3种模式。 前两种模式(周期性periodic和一次性one-shot)由所有本地APIC支持。 第三种模式(TSC-Deadline模式)是仅在最近的CPU上支持的扩展。 === 周期模式 === 对于周期模式,软件设置“初始计数”,本地APIC将其用于“当前计数”。 本地APIC递减当前计数直到其达到零,然后生成定时器IRQ并将当前计数重置为初始计数,并再次开始递减当前计数。 这样,本地APIC根据初始计数以固定速率生成IRQs。 当前计数的递减率取决于CPU的外部频率(“总线频率”)除以本地APIC的“除配置寄存器(Divide Configuration Register)”中的值。 例如,对于外部/总线频率为800兆赫兹的2.4GHzCPU,如果将分频配置寄存器设置为“除以4”并且将初始计数设置为123456; 然后,本地APIC定时器将以200 MHz的速率递减计数,并每617.28产生一个定时器IRQ,给出1620.01Hz的IRQs速率。 ==One-Shot模式=== 对于One-Shot单触发模式,本地APIC以与周期模式相同的方式递减当前计数(并在计数达到零时生成定时器IRQ); 但是,当当前计数达到零时,它不会将当前计数重置为初始计数。 相反,如果软件想要更多的定时器IRQ,它必须每次设置一个新的计数。 这种模式的优点是,软件可以精确控制定时器IRQ何时发生。 例如,在任务切换期间,操作系统可以将计数设置为取决于新任务优先级的值 (以便某些任务运行少量时间而其他任务运行较长时间), 而且不会有任何不必要的IRQ。 一些操作系统使用这种方法来实现通用的高精度定时器服务,其中本地APIC计数被设置为取决于哪个事件将最快发生的值。 例如,如果当前运行的任务开关应在1234纳秒内被抢先,则休眠任务需要在333纳秒内唤醒,并且必须在44444纳秒内发送警报信号, 然后定时器的计数将设置为333纳秒(所需的最早延迟) 当定时器IRQ发生时,OS知道在当前任务应该被抢占之前还有901纳秒,在需要发送报警信号之前还有441111纳秒 (并将下一个定时器IRQ的计数设置为901纳秒)。 缺点是,使用一次性模式更难实时跟踪,需要特别注意避免竞争条件;尤其是如果在旧计数到期之前设置了新计数。 === TSC-Deadline 模式= TSC-Deadline模式与其他2种模式有很大不同。 当CPU的时间戳计数器的值大于或等于截止日期时,软件设置“截止日期”,本地APIC生成定时器IRQ,而不是使用CPU的外部/总线频率来减少计数。 尽管有这些不同之处,但软件将/可以以与使用一次模式相同的方式使用它。 优点 (与 one-shot模式相比) 是你获得更高的精度 (因为CPU的时间戳计数器以CPU的(标称)内部频率而不是CPU的外部/总线频率运行), 而且更容易避免/处理竞争条件。 == 启用APIC定时器 == 在启用本地APIC定时器之前,应该设置本地APIC的其余部分。这包括: * 确定本地APIC的物理地址(通过ACPI表或多处理器规格表) * 指定一个虚拟中断和软件启用APIC * 确保TPR(任务优先级寄存器)已设置(因此它不会阻止/延迟低优先级IRQ) 一旦完成此操作: * 设置本地APIC定时器的除法配置寄存器 * 配置本地APIC定时器的中断向量,并取消定时器的IRQ掩码 * 设置本地APIC定时器的初始计数 注意: 建议遵循上面给出的顺序 (尤其是最后设置本地APIC定时器的初始计数)。 以不同的顺序进行操作(例如设置初始计数,然后启用定时器)可能会导致某些(真实或虚拟)机器出现问题 (例如,一切似乎都正确,计数器正在减少,但IRQ从不发送)。 == 初始化 == “请注意,这是确定APIC定时器频率的推荐方法。” '''注意:''' 根据英特尔IA-32 (x86) 和英特尔64 (x86_64) 的文档,APIC定时器的频率等于总线的频率 '''或'''核心晶振的频率除以所选的分频器。 总线和核心晶体的频率可以在[[CPUID|CPUID]]函数中找到分别为[https://sandpile.org/x86/cpuid.htm#level_0000_0015h 0x15]和[https://sandpile.org/x86/cpuid.htm#level_0000_0016h 0x16]。 通过CPUID.0x15还可以确定TSC频率。 APIC定时器的频率取决于系统是使用本地APIC还是 “discrete” APIC ([https://en.wikipedia.org/wiki/advanced_programmale_interrupt_controller 82489DX])。 当本地APIC内置在核心晶振中时,APIC定时器使用核心的频率。 否则,它使用的是总线频率。 有几种方法可以做到这一点,但它们都使用不同的、独立于CPU总线频率的时钟源来做到这一点。 例如:[[RTC|实时时钟]],[[TSC|时间戳计数器(TimeStamp Counter)]],PIT或甚至轮询[[CMOS#从RTC获得当前日期时间|CMOS寄存器]]获取。 在本教程中,我们将使用又老又好的PIT,因为它是最简单的。 需要完成的步骤: * 将APIC重置为必要状态 * 启用APIC定时器 * 重置APIC定时器计数器 * 等待不同时钟测量的特定时间 * 从APIC定时器计数器获取滴答数 * 调整到一秒钟 * 除以你选择的数量(结果X) * 使APIC定时器在每X个节拍时触发一个中断 APIC定时器可以设置为在给定频率下进行tick (减小计数器),称为 “分值”。 这意味着你必须将APIC定时器计数器tick数乘以这个除法值,才能得到真正的CPU总线频率。 你可以使用值1(每个总线周期上的tick)到128(每128个周期上的tick)。 有关详细信息,请参阅Intel手册vol3A第9.5.4章。 请注意,根据我的测试,Bochs似乎无法正确处理1的除除数,因此我将使用16。 ===先决条件=== 在开始之前,让我们定义一些常量和函数。 <source lang="asm"> APIC=映射APIC寄存器的线性地址 APIC_APICID = 20h APIC_APICVER = 30h APIC_TASKPRIOR = 80h APIC_EOI = 0B0h APIC_LDR = 0D0h APIC_DFR = 0E0h APIC_SPURIOUS = 0F0h APIC_ESR = 280h APIC_ICRL = 300h APIC_ICRH = 310h APIC_LVT_TMR = 320h APIC_LVT_PERF = 340h APIC_LVT_LINT0 = 350h APIC_LVT_LINT1 = 360h APIC_LVT_ERR = 370h APIC_TMRINITCNT = 380h APIC_TMRCURRCNT = 390h APIC_TMRDIV = 3E0h APIC_LAST = 38Fh APIC_DISABLE = 10000h APIC_SW_ENABLE = 100h APIC_CPUFOCUS = 200h APIC_NMI = (4<<8) TMR_PERIODIC = 20000h TMR_BASEDIV = (1<<20) ;Interrupt Service Routines isr_dummytmr: mov dword [apic+APIC_EOI], 0 iret isr_spurious: iret ;function to set a specific interrupt gate in IDT ;al=interrupt ;ebx=isr entry point writegate: ... ret </source> 我还将假设你有一个工作的 [[IDT]],并且你有一个函数可以为特定的中断编写一个门: writegate(intnumber,israddress)。 此外,为了简单起见,我假设你没有更改几乎每个教程中的默认中断映射: *中断0-31:异常 * 中断32: 定时器,IRQ0 *中断39:虚拟irq,IRQ7 如果你已经对此进行了更改,请进行相应的修改。 === ASM中的示例代码 === 以下是在fasm语法程序集中初始化APIC定时器的一种可能方法: <source lang="asm"> ;你应该读取MSR,获得APIC基址,并映射到“APIC” ;you should have used lidt properly ;set up isrs mov al, 32 mov ebx, isr_dummytmr call writegate mov al, 39 mov ebx, isr_spurious call writegate ; 将LAPIC初始化为合适的状态 mov dword [apic+APIC_DFR], 0FFFFFFFFh mov eax, dword [apic+APIC_LDR] and eax, 00FFFFFFh or al, 1 mov dword [apic+APIC_LDR], eax mov dword [apic+APIC_LVT_TMR], APIC_DISABLE mov dword [apic+APIC_LVT_PERF], APIC_NMI mov dword [apic+APIC_LVT_LINT0], APIC_DISABLE mov dword [apic+APIC_LVT_LINT1], APIC_DISABLE mov dword [apic+APIC_TASKPRIOR], 0 ;okay, now we can enable APIC ;global enable mov ecx, 1bh rdmsr bts eax, 11 wrmsr ;软件启用,将虚拟中断映射到虚拟isr mov dword [apic+APIC_SPURIOUS], 39+APIC_SW_ENABLE ; 将APIC定时器映射到中断,并通过该中断在one-shot模式中启用它 mov dword [apic+APIC_LVT_TMR], 32 ; 将除数设置为16 mov dword [apic+APIC_TMRDIV], 03h ;ebx=0xFFFFFFFF; xor ebx, ebx dec ebx ; 在one-shot模式下初始化PIT Ch 2 ; 等待1秒可能会显著减慢引导时间, ; 所以我们将等待1/100秒,并乘以计数的ticks mov dx, 61h in al, dx and al, 0fdh or al, 1 out dx, al mov al, 10110010b out 43h, al ;1193180/100 Hz = 11931 = 2e9bh mov al, 9bh ;LSB out 42h, al in al, 60h ;short delay mov al, 2eh ;MSB out 42h, al ;reset PIT one-shot counter (start counting) in al, dx and al, 0feh out dx, al ;gate low or al, 1 out dx, al ;gate high ;reset APIC timer (set counter to -1) mov dword [apic+APIC_TMRINITCNT], ebx ;now wait until PIT counter reaches zero @@: in al, dx and al, 20h jz @b ;stop APIC timer mov dword [apic+APIC_LVT_TMR], APIC_DISABLE ;now do the math... xor eax, eax xor ebx, ebx dec eax ;get current counter value mov ebx, dword [apic+APIC_TMRCURRCNT] ;it is counted down from -1, make it positive sub eax, ebx inc eax ;we used divide value different than 1, so now we have to multiply the result by 16 shl eax, 4 ;*16 xor edx, edx ;moreover, PIT did not wait a whole sec, only a fraction, so multiply by that too mov ebx, 100 ;*PITHz mul ebx ;-----edx:eax now holds the CPU bus frequency----- ;now calculate timer counter value of your choice ;this means that tasks will be preempted 1000 times in a second. 100 is popular too. mov ebx, 1000 xor edx, edx div ebx ;again, we did not use divide value of 1 shr eax, 4 ;/16 ;sanity check, min 16 cmp eax, 010h jae @f mov eax, 010h ;now eax holds appropriate number of ticks, use it as APIC timer counter initializer @@: mov dword [apic+APIC_TMRINITCNT], eax ;finally re-enable timer in periodic mode mov dword [apic+APIC_LVT_TMR], 32 or TMR_PERIODIC ;setting divide value register again not needed by the manuals ;although I have found buggy hardware that required it mov dword [apic+APIC_TMRDIV], 03h </source> === C语言中的示例代码 === 此代码是如何初始化APIC定时器以使其每10毫秒计时一次的示例。 这是通过让APIC定时器运行,使用PIT等待10毫秒,然后从APIC定时器获取滴答次数来完成的。 它假设你拥有“写入”/“读取”APIC寄存器和“pit_prepare_sleep”/“pit_perform_sleep”功能,以尽可能准确地测量定时器的频率。 <source lang="c"> void apic_start_timer() { //告诉APIC定时器使用divider16 write(APIC_REGISTER_TIMER_DIV, 0x3); // 准备PIT休眠10ms (10000 µ s) pit_prepare_sleep(10000); // 将APIC init计数器设置为 -1 write(APIC_REGISTER_TIMER_INITCNT, 0xFFFFFFFF); // 执行PIT支持的休眠 pit_perform_sleep(); // 停止APIC定时器 write(APIC_REGISTER_LVT_TIMER, APIC_LVT_INT_MASKED); // 现在我们知道APIC计时器在10毫秒内ticked的频率 uint32_t ticksIn10ms = 0xFFFFFFFF - read(APIC_REGISTER_TIMER_CURRCNT); // 在IRQ 0的divider16上启动计时器为周期定时器,计时时间为我们计算的时钟周期数 write(APIC_REGISTER_LVT_TIMER, 32 | APIC_LVT_TIMER_MODE_PERIODIC); write(APIC_REGISTER_TIMER_DIV, 0x3); write(APIC_REGISTER_TIMER_INITCNT, ticksIn10ms); } </source> ==另见== ===文章=== * [[APIC]] === 论坛主题 === * [http://www.osdev.org/phpBB2/viewtopic.php?t=10686 APIC timer] ===外部链接=== * [http://www.intel.com/products/processor/manuals/ Volume 3A:System Programming Guide, Part 1,manuals has a chapter on the APIC] * [http://www.osdever.net/tutorials/view/advanced-programming-interrupt-controller Advanced Programmable Interrupt Controller by Mike Rieker] [[Category:Interrupts]] [[Category:Time]]
返回至“
APIC timer
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
变体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息