02年世界杯韩国黑哨_曲棍球世界杯 - guanchafang.com

构建64位操作系统-高精度定时器与定时机制
2025-07-06 06:08:49

1.高精度定时器

1.1.论述

要实现高精度定时器,我们得依赖HPET芯片的高精度中断。所以,高精度定时器的实现,属于设备驱动的范畴。我们要驱动的设备是HPET芯片。

所谓设备驱动,就是处理器与设备的寄存器交互。实现设备行为控制,来达到我们预定的目标。

HPET芯片位于主板芯片组。时间精度高达69.84ns。

系统上电后,HPET默认处于禁用状态。

所以,HPET的设备驱动,包含设备的启用,启用后的交互(交互方法,交互效果)。

1.设备启用

HPTC是一个4B寄存器。可以实现HPET设备访问地址开启,选择HPET配置寄存器组物理基地址。

HPTC位于芯片组配置寄存器的0x3404偏移处。

芯片组配置寄存器的物理基地址由RCBA寄存器指定。

这样就相当于告诉了我们HPTC的访问方式。

下面就介绍HPTC的效果:

编号[0,1]区域存储 地址映射范围选择域

编号[7]区域存储 地址映射使能标志位

当HPTC[7]为1时,

地址映射范围选择域与映射地址范围的关系为:

数值范围0b00FED0,0000~FED0,03FF0b01FED0,1000~FED0,13FF0b10FED0,2000~FED0,23FF0b11FED0,3000~FED0,33FF

2.知道如何开启设备,确定设备寄存器映射范围后,介绍设备可编程寄存器

IndexNameDefault-Value0x000~0x007GCAP_ID0x0429,B17F,8086,A7010x010~0x017GEN_CONF0000,0000,0000,00000x020~0x027GINTR_STA0000,0000,0000,00000x0F0~0x0F7MAIN_CNT0x100~0x107TIM0_CONF(配置寄存器)0x108~0x10FTIM0_COMP(对比寄存器)0x120~0x127TIM1_CONF0x128~0x12FTIM1_COMP0x140~0x147TIM2_CONF0x148~0x14FTIM2_COMP0x160~0x167TIM3_CONF0x168~0x16FTIM3_COMP0x180~0x187TIM4_CONF0x188~0x18FTIM4_COMP0x1A0~0x1A7TIM5_CONF0x1A8~0x1AFTIM5_COMP0x1C0~0x1C7TIM6_CONF0x1C8~0x1CFTIM6_COMP0x1E0~0x1E7TIM7_CONF0x1E8~0x1EFTIM7_COMP

3.有了映射范围及上述的偏移,我们就知道了如何与上述寄存器交互。下面就需要知道寄存器交互的效果了。

3.1.GCAP_ID

编号[0,7]区域存储 修订版本号(默认0x01)

编号[8, 12]区域存储 定时器数

编号[13]区域存储 计数器位宽

编号[15]区域存储 旧设备中断路由兼容功能

编号[16, 31]区域存储 供应商ID

编号[32, 63]区域存储 主计数器时间精度(固定为0x0429,b17f)

主计数器时间精度固定为0x0429,b17f。表示每69.84ns计数一次。

旧设备中断路由兼容:置位表示支持兼容8259A的中断请求链路。

计数器位宽:1,表示64位。

3.2.GEN_CONF

编号[0]区域存储 定时器组使能标志位

编号[1]区域存储 旧设备中断路由兼容标志位

旧设备中断路由兼容:

置位时,

定时器0向8259A的IRQ0&I/O APIC的IRQ2发中断请求

定时器1向8259A的IRQ8&I/O APIC的IRQ8发中断请求

其他定时器按自身配置寄存器决定中断请求的接收引脚。

定时器组使能标志位:

置位时,HPET定时器才能产生中断。

3.3.GINTR_STA

编号[0]区域存储 定时器0的中断触发标志位

编号[1]区域存储 定时器1的中断触发标志位

编号[2]区域存储 定时器2的中断触发标志位

编号[3]区域存储 定时器3的中断触发标志位

编号[4]区域存储 定时器4的中断触发标志位

编号[5]区域存储 定时器5的中断触发标志位

编号[6]区域存储 定时器6的中断触发标志位

编号[7]区域存储 定时器7的中断触发标志位

定时器N的中断触发标志位:

如果定时器中断请求为电平触发,则硬件自动置位对应定时器位。

软件向中断触发标志位写入1才能将其复位。

如果定时器中断请求为边沿触发,触发标志位忽略即可。

3.4.MAIN_CNT

编号[0, 63]区域存储 主计数值

可读,可写

3.5.TIMn_CONF

编号[1]区域存储 中断触发模式

编号[2]区域存储 定时器中断使能标志位

编号[3]区域存储 定时器类型

编号[4]区域存储 周期定时标志位(只读)

编号[5]区域存储 定时器位宽标志位(只读)

编号[6]区域存储 定时器值设置标志位

编号[8]区域存储 计数器位宽模式

编号[9, 13]区域存储 定时器中断路由

编号[14]区域存储 定时器中断消息使能标志位

编号[15]区域存储 定时器中断消息投递标志位(只读)

编号[43, 44]区域存储 定时器中断路由

编号[52, 55]区域存储 定时器中断路由

Timer Nbit 43bit 44bit 52bit 53bit 54bit 550/1N/AN/AIRQ20IRQ21IRQ22IRQ232IRQ11N/AIRQ20IRQ21IRQ22IRQ233N/AIRQ11IRQ20IRQ21IRQ22IRQ23

定时器4/5/6/7,使用TIMERn_PROCMSG_ROUT来投递中断消息。

定时器中断消息投递标志位:

1,定时器中断消息不经8259A,I/O APIC直达cpu。

定时器中断消息使能标志位:

1,定时器可以产生中断消息

定时器中断路由:

对0/1/2/3定时器,用于设置使用的中断请求引脚。

定时器4/5/6/7不支持。

计数器位宽模式:

只对定时器0有效。其余定时器固定32位宽。

1,32位宽

0,64位宽。

定时值设置标志位:

只对定时器0在周期定时模式下有效,

置位时,使软件在定时器运行时修改定时值

定时器位宽标志:

1,64位

0,32位

周期定时功能:

1,支持周期定时。只有定时器0支持周期定时。

0,不支持

定时器类型:

1,周期性产生中断。只有定时器0支持周期性产生中断。

0,一次性产生中断

定时器中断使能标志位:

0,禁止中断

1,使能中断

中断触发模式:

0,边沿触发

1,电平触发

3.6.TIMn_COMP

只有当MAIN_CNT计数值与TIMn_COMP保存的定时值相等时,定时器才会产生中断。

1.2.实践

//get RCBA address

io_out32(0xcf8, 0x8000f8f0);

x = io_in32(0xcfc);

类似启用I/O APIC,这里我们要启用HPET。

所以,我们第一步是取得RCBA寄存器内容。

RCBA寄存器位于主板LPC桥控制器组,访问方法的查阅主板使用手册。对于IntelQM67,上述x就是RCBA寄存器值。

芯片组配置寄存器物理基地址是16KB对齐的。

x = x & 0xffffc000;

这样x就是芯片组配置寄存器物理基地址。

因为HPTC位于芯片组配置寄存器的0x3404偏移处。

unsigned int * p = NULL;

//get HPTC address

if(x > 0xfec00000 && x < 0xfee00000)

{

p = (unsigned int *)Phy_To_Virt(x + 0x3404UL);

}

这样p就是HPTC寄存器线性地址。

通过HPTC[7] = 1来允许通过地址映射访问HPET,也就是启用HPET。

//enable HPET

*p = 0x80;

io_mfence();

上述io_mfence()作用是,等待此cpu此前指令全部执行完毕。

// 这是HPET映射区域起始线性地址

unsigned char * HPET_addr = (unsigned char *)Phy_To_Virt(0xfed00000);

// 这样取得8字节是HPET的GCAP_ID寄存器内容

color_printk(RED,BLACK,"HPET - GCAP_ID:<%#018lx>\n", *(unsigned long *)HPET_addr);

// 这样是设置HPET的GEN_CONF寄存器为3

// 1.使得HPET的定时器可以产生中断。

// 2.使得定时器0可向8259A的IRQ 0&I/O APIC的IRQ2发中断请求。

// 3.使得定时器1可向8259A的IRQ 8&I/O APIC的IRQ8发中断请求。

*(unsigned long *)(HPET_addr + 0x10) = 3;

io_mfence();

// 这样是设置HPET的TIM0_CONF寄存器

// 0b0000 0000 0100 1100

// 中断触发模式:边沿触发

// 定时器中断使能标志位:使能中断

// 定时器类型:周期性产生中断--只有定时器0支持

// 定时器值设置标志位:软件在定时器运行时可修改定时值

// 计数器位宽模式:64位宽

// 这样定时器周期性产生中断信号,

// 结合GEN_CONF,定时器0的中断信号会传递到I/O APIC的IRQ2引脚

*(unsigned long *)(HPET_addr + 0x100) = 0x004c;

io_mfence();

// 这样是设置HPET的TIM0_COMP寄存器

// 这样在经过14*69.841279将收到一次来自HPET定时器0的信号中断。

// 这个耗时近似为1毫秒

*(unsigned long *)(HPET_addr + 0x108) = 14000;

io_mfence();

// init MAIN_CNT & get CMOS time

get_cmos_time(&time);

// 这样是设置HPET的MAIN_CNT寄存器

*(unsigned long *)(HPET_addr + 0xf0) = 0;

io_mfence();

color_printk(RED, BLACK,

"year:%#010x,month:%#010x,day:%#010x,hour:%#010x,mintue:%#010x,second:%#010x\n",

time.year, time.month, time.day, time.hour, time.minute, time.second);

启用HPET后,我们通过与其寄存器交互完成设置。达到近似1微妙在I/O APIC的IRQ2收到一次中断信号的效果。

struct IO_APIC_RET_entry entry;

entry.vector = 34;

entry.deliver_mode = APIC_ICR_IOAPIC_Fixed;

entry.dest_mode = ICR_IOAPIC_DELV_PHYSICAL;

entry.deliver_status = APIC_ICR_IOAPIC_Idle;

entry.polarity = APIC_IOAPIC_POLARITY_HIGH;

entry.irr = APIC_IOAPIC_IRR_RESET;

entry.trigger = APIC_ICR_IOAPIC_Edge;

entry.mask = APIC_ICR_IOAPIC_Masked;

entry.reserved = 0;

entry.destination.physical.reserved1 = 0;

entry.destination.physical.phy_dest = 0;

entry.destination.physical.reserved2 = 0;

register_irq(34, &entry , &HPET_handler, NULL, &HPET_int_controller, "HPET");

为了对I/O APIC的IRQ2收到的中断信号进行正确处理,我们需要做上述工作。上述工作的含义解释可以参考中断与异常-I/O APIC实践部分。

上述达到的效果是,I/O APIC的IRQ2引脚收到的中断信号将投递给APIC ID为0的cpu。

处理器收到向量号为34的中断,先是执行do_IRQ,然后进入

// timer.h

extern unsigned long volatile jiffies;

// timer.c

unsigned long volatile jiffies = 0;

// HPET.c

void HPET_handler(unsigned long nr, unsigned long parameter, struct pt_regs * regs)

{

jiffies++;

...

}

这样jiffies每隔1毫秒加1,利用这个特性构成了内核高精度定时器的基础。

2.软中断

2.1.论述

在我们每次处理外部中断时,我们需要执行中断处理完成指定的处理。但是,有些功能,我们虽然依赖中断机制,但是并不需要每次中断处理均触发,而是在中断处理中检测到满足指定条件时候,再触发。

上述这种,中断处理中满足指定条件才触发一次处理的机制称为软中断机制。

2.2.实践

// softirq.h

extern unsigned long softirq_status;

void set_softirq_status(unsigned long status);

unsigned long get_softirq_status();

// softirq.c

unsigned long softirq_status = 0;

void set_softirq_status(unsigned long status)

{

softirq_status |= status;

}

unsigned long get_softirq_status()

{

return softirq_status;

}

为了实现软中断,我们需要有一个上述的实例对象。该实例对象包含64个比特位。这样用每个比特位代表一个软中断类型。我们的系统就可以支持64种软中断类型。

// softirq.h

struct softirq

{

void (*action)(void * data);

void * data;

};

extern struct softirq softirq_vector[64];

void register_softirq(int nr, void (*action)(void * data),void * data);

void unregister_softirq(int nr);

// softirq.c

struct softirq softirq_vector[64] = {0};

void register_softirq(int nr, void (*action)(void * data), void * data)

{

softirq_vector[nr].action = action;

softirq_vector[nr].data = data;

}

void unregister_softirq(int nr)

{

softirq_vector[nr].action = NULL;

softirq_vector[nr].data = NULL;

}

为了知道,指定类型软中断被触发时,如何处理。我们需要softirq_vector[64]。每个索引对应一个软中断类型。索引里面元素存储了对应软中断类型被触发时的处理方法及其参数。

void softirq_init()

{

softirq_status = 0;

memset(softirq_vector, 0, sizeof(struct softirq) * 64);

}

系统启动时,执行上述初始化。

register_softirq(0, &do_timer, NULL);

void do_timer(void * data)

{

...

}

为了使得软中断机制发挥作用,首先的有地方去注册软中断。

上述注册了索引为0的软中断,指定了索引0软中断的处理函数。

#define TIMER_SIRQ (1 << 0)

void HPET_handler(unsigned long nr, unsigned long parameter, struct pt_regs * regs)

{

jiffies++;

if(jiffies % 1000 == 0)

{

// 表示刚好过了1秒

set_softirq_status(TIMER_SIRQ);

}

...

}

有了注册不够。还需要有触发的地方。

HPET_handler我们知道是HPET定时器0的中断处理函数。这个中断处理每隔1微妙被触发一次。这样,如果按上述的写法。我们就实现了每隔1秒设置一次TIMER_SIRQ软中断状态。

这里TIMER_SIQ对应的是索引0。

ret_from_exception:

ENTRY(ret_from_intr)

movq $-1, %rcx

testq softirq_status(%rip), %rcx check softirq

jnz softirq_handler

jmp RESTORE_ALL

softirq_handler:

callq do_softirq

jmp RESTORE_ALL

有了上述设置标志的地方。我们还需一个触发软中断处理的地方。

上述是中断和异常返回时的代码。

我们在中断和异常返回时,去检测softirq_status中是否有比特位被置位。

如果有被置位的,我们转而去执行softirq_handler,进而执行do_softirq。

void do_softirq()

{

int i;

// 软中断处理过程可以开放外部可屏蔽中断(外部可屏蔽中断进入处理时处理器会屏蔽外部可屏蔽中断)

sti();

for(i = 0; i < 64 && softirq_status; i++)

{

// 如果i对应比特位被设置了

if(softirq_status & (1 << i))

{

// 执行i位置的软中断处理函数

softirq_vector[i].action(softirq_vector[i].data);

// 复位i对应比特位

softirq_status &= ~(1 << i);

}

}

// 软中断结束禁止中断(达到和处理器默认执行外部中断一样的效果)

cli();

}

软中断处理中我们对置位的软中断类型,执行注册时提供的处理函数。然后将其复位。

软中断处理执行期间,我们是开放外部可屏蔽中断的。

这样,我们完整的讲述了软中断机制。并实际举了一个利用软中断实现间隔1秒定时器的例子。

值得注意的是,我们HPET定时器0设置为1微妙触发一次中断,虽然可以获得高精度定时。但代价时,cpu会频繁的被中断打断正常任务执行。任务执行切换到中断处理,再恢复。涉及执行现场保存和恢复,会一定程度降低实际用于处理任务的cpu时间。

另一个值得注意的是,我们是在中断处理中设置的软中断标志。在中断返回处检测的标志并转而执行的软中断处理。但软中断处理过程我们是开放可屏蔽外部中断的。

3.定时机制

3.1.论述

有了高精度定时器,有了软中断机制,这样我们可以借此实现内核级的定时任务机制。

3.2.实践

// timer.h

extern unsigned long volatile jiffies;

// timer.c

unsigned long volatile jiffies = 0;

为了实现定时机制,我们首先需要可以自系统启动依赖的时间消耗。就是上述的jiffies,该数值依赖HPET的定时器0的中断每隔1毫秒自增一次。

// timer.h

// 定时任务队列

struct timer_list

{

// 采用双向链表维护

struct List list;

unsigned long expire_jiffies;

void (* func)(void * data);

void *data;

};

extern struct timer_list timer_list_head;

// timer.c

struct timer_list timer_list_head;

// timer_list对象初始化

void init_timer(struct timer_list * timer, void (* func)(void * data), void *data, unsigned long expire_jiffies)

{

list_init(&timer->list);

timer->func = func;

timer->data = data;

timer->expire_jiffies = expire_jiffies + jiffies;// ms单位

}

// 加入链表

void add_timer(struct timer_list * timer)

{

// 链表首元素固定

struct timer_list * tmp = container_of(list_next(&timer_list_head.list), struct timer_list, list);

// 如果链表是空的--只有一个元素

if(list_is_empty(&timer_list_head.list))

{

}

else

{

while(tmp->expire_jiffies < timer->expire_jiffies)

{

// 1.取得链表下个元素--List对象指针

// 2.得到容纳此List对象的timer_list对象指针

tmp = container_of(list_next(&tmp->list), struct timer_list, list);

}

}

// 链表不空时,tmp指向元素此时的expire_jiffies必然大于等于timer指向元素的expire_jiffies

// 因为我们为链表首元素设置了超大expire_jiffies,所以,else中while循环必然经过有限次迭代可以找到这样的tmp

// 在tmp后插入timer

list_add_to_before(&tmp->list, &timer->list);

}

void del_timer(struct timer_list * timer)

{

list_del(&timer->list);

}

为了对系统内定时任务进行管理。我们需要 timer_list来描述定时任务信息,包括定时任务执行时机,处理函数,参数。

我们将所有注册的定时任务通过双向链表链接在一起,且链接时,按expire_jiffies有序存储。

这里的有序存储具体为除了哨兵节点timer_list_head,其余节点按expire_jiffies从小到达顺序链接。

void timer_init()

{

jiffies = 0;

struct timer_list *tmp = NULL;

// 链表首元素初始化-->expire_jiffies设置为超大值

init_timer(&timer_list_head, NULL, NULL, -1UL);

// 软中断注册

register_softirq(0, &do_timer, NULL);

int i = 0;

for(; i < 10; i++)

{

// 分配新的timer_list对象

tmp = (struct timer_list *)kmalloc(sizeof(struct timer_list), 0);

int *lpInt = (int*)kmalloc(sizeof(int), 0);

*lpInt = i + 1;

init_timer(tmp, &test_timer, lpInt, i + 1);

// 加入链表

add_timer(tmp);

}

}

为了对定时任务提供支持,我们利用软中断来实现定时驱动。

上述我们注册了10个定时任务,分别在1~10s后触发。

register_sofrirq注册了一个索引为0的软中断。结合软中断实践部分,这个软中断处理会每间隔1秒被触发一次。触发时,进入do_timer进入软中断处理。

// 软中断类型处理函数

void do_timer(void * data)

{

// 取得首元素下个元素

struct timer_list * tmp = container_of(list_next(&timer_list_head.list), struct timer_list, list);

// 只要链式结构尚有元素 & 当前元素的expire_jiffies小于或等于jiffies

// 一旦遇到首个元素的expire_jiffies大于jiffies,因为链式结构有序。可以认为后续元素必然也大于jiffies,所以可以直接结束

while((!list_is_empty(&timer_list_head.list)) && (tmp->expire_jiffies <= jiffies))

{

// 定时任务一次性

del_timer(tmp);

// 执行定时任务

tmp->func(tmp->data);

kfree(tmp);

kfree(tmp->data);

// 继续取下个元素

tmp = container_of(list_next(&timer_list_head.list), struct timer_list,list);

}

}

在do_timer中我们对注册的定时任务进行检查。如果注册的定时任务执行时机已经满足,则执行其处理函数,执行后将定时任务从链式结构移除,释放关联内存。

这样,我们就完整讲述了定时机制。

讲述了定时任务的注册,触发,销毁完整过程。

货车帮赚的是司机加入平台的一个年费,我们货主要用这个平台现在也是要交费的
wps或word怎么发送文件到微信,Word文档转发到微信的方法
最新文章