软件世界网 购物 网址 三丰软件 | 小说 美女秀 图库大全 游戏 笑话 | 下载 开发知识库 新闻 开发 图片素材
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
移动开发 架构设计 编程语言 Web前端 互联网
开发杂谈 系统运维 研发管理 数据库 云计算 Android开发资料
  软件世界网 -> 开发杂谈 -> APICTimer -> 正文阅读

[开发杂谈]APICTimer


前言


(由于之前的blog已经关闭了,所以将此文章迁移至这里,并非转载)
之前已经大体的写过APIC的一些内容,这次是写一些APIC定时器的内容,当然,也是翻译了一些来自OSDev的资料(不要问我为什么不翻译Intel手册,其实都一样,Intel手册里面写的太长了,有时候不一定要把所有东西看完才能使用里面的内容)。如果喜欢看原文,可以点APIC timer – OSDev Wiki.链接。有任何疑问,可以e-mail联系,xuezhe.liu@hotmail.com。(其实msn也行)
使用APIC Timer的最大好处是每个cpu内核都有一个定时器(避免了定时器资源的抢占问题,你要是抢走了一个cpu核心,你就肯定抢走了一个定时器),这里的cpu内核是指和核心数,把超线程技术的那个内核也算进去了。相反的我们之前使用的PIT(Programmable Interval Timer)就不这样,PIT是共用的一个。因为这样,我们不需要考虑任何资源管理问题,我们能够很轻松的使用它(老外说话有时候很..)。但是APIC有一个比较麻烦的是,他的精度和CPU的频率有关(这里的频率指的是CPU的外频,现在主流的就是200MHz,但是有点飘,这个我在最后解释)。而PIT使用的是一个标准的频率(1193182Hz)。如果要使用APICTimer,那么你必须自己知道每秒钟能触发多少个中断。

APIC Timer的模式


APIC定时器一般包含2-3种定时器模式,前两种(周期触发periodic和一次性触发one-shot)一般被现在所有的Local APIC所支持。但是第三种(TSC-Deadline模式)目前只在最新的CPU里面支持(话说我手上拿的CPU貌似都支持)。

周期触发模式(Periodic Mode)


周期触发模式中,程序设置一个”初始计数“寄存器(Initial Count),同时Local APIC会将这个数复制到”当前计数“寄存器(Current Count)。Local APIC会将这个数(当前计数)递减,直到减到0为止,这时候将触发一个IRQ(可以理解为触发一次中断),与此同时将当前计数恢复到初始计数寄存器的值,然后周而复始的执行上述逻辑。可以使用这种方法通过Local APIC实现定时按照一定时间间隔触发中断的功能。”当前计数“寄存器中的值的递减速度依赖于当前CPU的主频除以一个叫做”分频寄存器“(“Divide Configuration Register”)的值(换个思路来说就是,多少个tick减少1)。
举例来说,对于一个2.4GHz的CPU使用的CPU总线频率是800MHz(大家说的外频800MHz),”分频寄存器“(“Divide Configuration Register”)设置的是“除四”(”divide by 4“),并且初始计数(initial count)设置到123456。那么结果就是当前计数(current count)每200M分之1秒减1,大约617.28us触发一个irq(一次中断),也就是1620.01Hz。

一次性触发模式(One-Shot Mode)


对于一次性触发模式,Local APIC中的“当前计数”寄存器的减少方式和周期触发模式一样,也是当“当前计数“寄存器的值到0的时候触发一次定时器IRQ(中断)。但是它不会自动恢复到初始计数。这样,软件必须每次都要写“初始计数”寄存器一个值来让其再一次的计时并触发IRQ。这种模式的有点事,软件可以精确地控制定时器的IRQ的发生时间。例如,系统可以根据进程的优先级来设置任务切换(一些任务使用较短的CPU时间,一些任务使用较长的CPU时间),并且不会产生任何不必要的IRQs(这句话我也不太清楚什么意思,不过大约就是可以精确地控制切换进程的时间到时IRQ产生,因为进程切换也耽误时间)。一些系统通过为计数器付值的方法去实现通用的高精度计时器服务。换个例子来说就是,有3个任务需要分别在1234ns、333ns、4444ns的时候去做,这样的话,就设定定时器显示333ns,到时中断发生时执行任务二,同时设定定时器901ns,到时中断发生时执行任务一,同时在设定定时器441111ns,定时后执行任务三(原文的英语的例子我是不理解为什么要写那么折腾了,我就简单的按上面举例了)。
缺点是,一次性触发可能会影响实时性(因为在设置的时候会耽误一些,导致的不确定性),并且还需要为了避免竞争,维护新的计数值和老的计数值。

TSC-Deadline Mode(不是我不翻译,我是真不会翻译)


TSC-Deadline模式同上述两种模式有很大的区别。触发方式发生了区别,上述两种方式是基于当前计数减少到0触发的,而TSC-Deadline模式是软件设置了一个”deadline“,然后当CPU的时间戳大于或等于”deadline“的时候触发一次IRQ。
尽管存在如上的差异,软件可能会将它用于替代一次性触发模式,相比于一次性触发模式,这样可以得到更高的精度(因为时间戳的生成时CPU的时钟,而不是CPU总线频率),并且它更容易处理资源竞争(最后这句话我真不太理解)。

使用APIC Timer

使能APIC Timer


首先,应该通过写MSR寄存器使能Local APIC硬件。
其次,配置一个用于定时器的中断,并且软件开启APIC。
最后,配置APIC Timer的中断向量以及操作模式。
具体操作方法,参考Inter开发手册Vol3A Chapter 9。

初始化步骤


这里使用的方式是使用CPU总线频率作为基准源, 有很多种方法能够完成这部分所讲的内容,并且每种方法都不一样。例如:Real Time Clock,TimeStamp Counter,PIT or even polling CMOS registers。这里要讲的内容仍然要用到PIT,因为他很简单。按照如下步骤执行:
  1. 重置APIC到一个已知的状态
  2. 使能APIC Timer
  3. 重置APIC Timer的计数器
  4. 设置另外的一个已知时钟源
  5. 获取APIC TImer的计数器的当前值
  6. 以秒为基准校准一下
  7. 设置APIC Timer计数器的分频
  8. 设置接受APIC Timer中断触发

APIC Timer可以被设置为经过若干个tick以后触发一次中断,这里设置的地方叫做”分频数“(divide value)。这意味着可以通过将这一数值乘以APIC Timer的计数来获得当前CPU总线的频率。这里的分频数可以设置最小值是1最大值是128,具体的请参考Intel编程手册Vol3A Chapter9.5.4。(注,网上有人说在Bochs上设置分频数为1不好用,于是他设置了16)。

实现


在开始前,我们先来定义一系列常量和函数。
apic        = the linear address where you have mapped the APIC registers

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

同样的也需要配置一些IDT项,并且需要设置一个用于处理中断的中断门和处理函数。这里是:

interrupt 32:timer, IRQ0
interrupt 39 : spurious irq, IRQ7

如果有需要,那就直接改代码就好了。

ASM代码示例


提供一种asm的代码示例,大家看看就好了,汇编都差不多也就两种语法,intel还有at&a我是都被祸害习惯了。
        ;you should read MSR, get APIC base and map to "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

        ;initialize LAPIC to a well known state
        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
        ;software enable, map spurious interrupt to dummy isr
        mov         dword [apic+APIC_SPURIOUS], 39+APIC_SW_ENABLE
        ;map APIC timer to an interrupt, and by that enable it in one-shot mode
        mov         dword [apic+APIC_LVT_TMR], 32
        ;set up divide value to 16
        mov         dword [apic+APIC_TMRDIV], 03h

        ;ebx=0xFFFFFFFF;
        xor         ebx, ebx
        dec         ebx

        ;initialize PIT Ch 2 in one-shot mode
        ;waiting 1 sec could slow down boot time considerably,
        ;so we'll wait 1/100 sec, and multiply the counted 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

C语言代码示例

void apic_timer_init(uint32 quantum){
    uint32 tmp, cpubusfreq;

    //set up isrs
    writegate(32,isr_dummytmr);
    writegate(39,isr_spurious);

    //initialize LAPIC to a well known state
    (uint32*)(apic+APIC_DFR)=0xFFFFFFFF;
    (uint32*)(apic+APIC_LDR)=((uint32*)(apic+APIC_LDR)&0x00FFFFFF)|1);
    (uint32*)(apic+APIC_LVT_TMR)=APIC_DISABLE;
    (uint32*)(apic+APIC_LVT_PERF)=APIC_NMI;
    (uint32*)(apic+APIC_LVT_LINT0)=APIC_DISABLE;
    (uint32*)(apic+APIC_LVT_LINT1)=APIC_DISABLE;
    (uint32*)(apic+APIC_TASKPRIOR)=0;

    //okay, now we can enable APIC
    //global enable
    cpuSetAPICBase(cpuGetAPICBase());
    //software enable, map spurious interrupt to dummy isr
    (uint32*)(apic+APIC_SPURIOUS)=39|APIV_SW_ENABLE;
    //map APIC timer to an interrupt, and by that enable it in one-shot mode
    (uint32*)(apic+APIC_LVT_TMR)=32;
    //set up divide value to 16
    (uint32*)(apic+APIC_TMRDIV)=0x03;

    //initialize PIT Ch 2 in one-shot mode
    //waiting 1 sec could slow down boot time considerably,
    //so we'll wait 1/100 sec, and multiply the counted ticks
    outb(0x61,inb(0x61)&0xFD)|1);
    outb(0x43,0xB2);
    //1193180/100 Hz = 11931 = 2e9bh
    outb(0x42,0x9B);    //LSB
    in(0x60);   //short delay
    outb(0x42,0x2E);    //MSB

    //reset PIT one-shot counter (start counting)
    (uint8)tmp=inb(0x61)&0xFE;
    outb(0x61,(uint8)tmp);      //gate low
    outb(0x61,(uint8)tmp|1);        //gate high
    //reset APIC timer (set counter to -1)
    (uint32*)(apic+APIC_TMRINITCNT)=0xFFFFFFFF;

    //now wait until PIT counter reaches zero
    while(!(inb(0x61)&0x20));

    //stop APIC timer
    (uint32*)(apic+APIC_LVT_TMR)=APIC_DISABLE;

    //now do the math...
    cpubusfreq=((0xFFFFFFFF-(uint32*)(apic+APIC_TMRINITCNT))+1)*16*100;
    tmp=cpubusfreq/quantum/16;

    //sanity check, now tmp holds appropriate number of ticks, use it as APIC timer counter initializer
    (uint32*)(apic+APIC_TMRINITCNT)=(tmp<16?16:tmp);
    //finally re-enable timer in periodic mode
    (uint32*)(apic+APIC_LVT_TMR)=32|TMR_PERIODIC;
    //setting divide value register again not needed by the manuals
    //although I have found buggy hardware that required it
    (uint32*)(apic+APIC_TMRDIV)=0x03;
}

其他已知问题


在最开始的时候我说过CPU总线频率不固定的这件事情,主要是在实测过程中会发生一些偏差,应该不算是漂移。大约在199.90~200.10MHz之间飘。这点同硬件的哥们研究过,后来得出的结果是一个叫做”自动跳频”技术造成的,好像可以关了这个就好了,具体我也没试验。只是希望别给大家带来不必要的麻烦。
......显示全文...
    点击查看全文


上一篇文章      下一篇文章      查看所有文章
2016-04-03 20:46:48  
开发杂谈 最新文章
BloomFilter
大学四年编程之历程
内核分析
造人论坛——意识的本质和一个人工脑模型
OFDM信号[matlab描述]
人类还会进化吗?
HDUACM1035RobotMotion简单模拟题
树、二叉树(二)
iisphpweb.config处理404,500等,跳转友好
DatabaseAsaFortress
360图书馆 软件开发资料 文字转语音 购物精选 软件下载 美食菜谱 新闻资讯 电影视频 小游戏 Chinese Culture 股票 租车
生肖星座 三丰软件 视频 开发 短信 中国文化 网文精选 搜图网 美图 阅读网 多播 租车 短信 看图 日历 万年历 2018年1日历
2018-1-20 7:31:13
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  软件世界网 --