feat(kernel): add PIT timer interrupt foundation

This commit is contained in:
Microindole 2026-05-09 16:49:10 +08:00
parent a2e6889932
commit 9e610bdbaa
No known key found for this signature in database
GPG Key ID: 22FB34CD2133ACA1
18 changed files with 227 additions and 20 deletions

View File

@ -0,0 +1,24 @@
#ifndef ARCH_X86_IO_H
#define ARCH_X86_IO_H
#include <stdint.h>
static inline void outb(uint16_t port, uint8_t value)
{
__asm__ volatile("outb %0, %1" : : "a"(value), "Nd"(port));
}
static inline uint8_t inb(uint16_t port)
{
uint8_t value;
__asm__ volatile("inb %1, %0" : "=a"(value) : "Nd"(port));
return value;
}
static inline void io_wait(void)
{
outb(0x80, 0);
}
#endif

View File

@ -27,6 +27,7 @@ struct trap_frame {
};
void trap_dispatch(struct trap_frame *frame);
void handle_irq(struct trap_frame *frame);
void handle_page_fault(struct trap_frame *frame);
#endif

View File

@ -2,6 +2,7 @@ ARCH_KERNEL_SRCS := \
$(ARCH_DIR)/kernel/early_log.c \
$(ARCH_DIR)/kernel/gdt.c \
$(ARCH_DIR)/kernel/idt.c \
$(ARCH_DIR)/kernel/irq.c \
$(ARCH_DIR)/kernel/traps.c
ARCH_KERNEL_OBJS := \

View File

@ -45,5 +45,6 @@ void exception_28(void);
void exception_29(void);
void exception_30(void);
void exception_31(void);
void irq_32(void);
#endif

View File

@ -1,4 +1,7 @@
#include <stdint.h>
#include <arch/io.h>
#include <tianole/arch.h>
#define X86_QEMU_DEBUG_PORT 0xe9
@ -15,19 +18,6 @@
#define X86_COM_LCR_8N1 0x03
#define X86_COM_LSR_THRE 0x20
static inline void outb(uint16_t port, uint8_t value)
{
__asm__ volatile("outb %0, %1" : : "a"(value), "Nd"(port));
}
static inline uint8_t inb(uint16_t port)
{
uint8_t value;
__asm__ volatile("inb %1, %0" : "=a"(value) : "Nd"(port));
return value;
}
static void debug_port_putc(char ch)
{
outb(X86_QEMU_DEBUG_PORT, (uint8_t)ch);

View File

@ -15,6 +15,15 @@ exception_\vector:
jmp exception_common
.endm
.macro IRQ vector
.global irq_\vector
.type irq_\vector, @function
irq_\vector:
pushq $0
pushq $\vector
jmp exception_common
.endm
.section .text
EXCEPTION_NO_ERROR 0
@ -50,6 +59,8 @@ EXCEPTION_ERROR 29
EXCEPTION_ERROR 30
EXCEPTION_NO_ERROR 31
IRQ 32
.type exception_common, @function
exception_common:
cld

View File

@ -80,6 +80,7 @@ void idt_init(void)
idt_set_gate(29, exception_29);
idt_set_gate(30, exception_30);
idt_set_gate(31, exception_31);
idt_set_gate(32, irq_32);
load_idt(&idtr);
}

108
arch/x86/kernel/irq.c Normal file
View File

@ -0,0 +1,108 @@
#include <stdint.h>
#include <arch/io.h>
#include <arch/traps.h>
#include <tianole/early_log.h>
#include <tianole/timer.h>
#define PIC1_COMMAND 0x20
#define PIC1_DATA 0x21
#define PIC2_COMMAND 0xa0
#define PIC2_DATA 0xa1
#define PIC_EOI 0x20
#define ICW1_INIT 0x10
#define ICW1_ICW4 0x01
#define ICW4_8086 0x01
#define PIT_FREQUENCY 1193182u
#define PIT_TARGET_HZ 100u
#define PIT_COMMAND 0x43
#define PIT_CHANNEL0 0x40
#define PIT_MODE_RATE_GENERATOR 0x36
#define IRQ_BASE 32u
#define IRQ_TIMER 0u
static void enable_interrupts(void)
{
__asm__ volatile("sti");
}
static void pic_remap(void)
{
outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4);
io_wait();
outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4);
io_wait();
outb(PIC1_DATA, IRQ_BASE);
io_wait();
outb(PIC2_DATA, IRQ_BASE + 8);
io_wait();
outb(PIC1_DATA, 4);
io_wait();
outb(PIC2_DATA, 2);
io_wait();
outb(PIC1_DATA, ICW4_8086);
io_wait();
outb(PIC2_DATA, ICW4_8086);
io_wait();
}
static void pic_mask_all_except_timer(void)
{
outb(PIC1_DATA, 0xfe);
outb(PIC2_DATA, 0xff);
}
static void pic_send_eoi(uint8_t irq)
{
if (irq >= 8) {
outb(PIC2_COMMAND, PIC_EOI);
}
outb(PIC1_COMMAND, PIC_EOI);
}
static void pit_init(void)
{
uint16_t divisor = (uint16_t)(PIT_FREQUENCY / PIT_TARGET_HZ);
outb(PIT_COMMAND, PIT_MODE_RATE_GENERATOR);
outb(PIT_CHANNEL0, (uint8_t)(divisor & 0xff));
outb(PIT_CHANNEL0, (uint8_t)(divisor >> 8));
}
static void handle_timer_irq(void)
{
timer_tick();
pic_send_eoi(IRQ_TIMER);
}
void handle_irq(struct trap_frame *frame)
{
uint64_t irq = frame->vector - IRQ_BASE;
if (irq == IRQ_TIMER) {
handle_timer_irq();
return;
}
early_log_puts("unexpected irq=");
early_log_u64_decimal(irq);
early_log_puts("\n");
pic_send_eoi((uint8_t)irq);
}
void arch_timer_init(void)
{
pic_remap();
pic_mask_all_except_timer();
pit_init();
early_log_puts("timer initialized\n");
enable_interrupts();
}

View File

@ -57,6 +57,11 @@ void trap_dispatch(struct trap_frame *frame)
uint64_t vector = frame->vector;
const char *name = "unknown exception";
if (vector >= 32 && vector < 48) {
handle_irq(frame);
return;
}
if (vector < 32) {
name = exception_names[vector];
}

View File

@ -41,3 +41,26 @@
- 线程可以 sleep 并被 timer 唤醒。
- 调度现场保存和恢复稳定。
- 基础对象生命周期规则有文档和调用约束。
## 当前状态
基础完成:
- 已建立 x86 PIC remap外部 IRQ 使用 vector 32-47不再和 CPU exception 混用。
- 已接入 PIT periodic timer当前频率为 100Hz。
- 已建立通用 `timer_tick()` 入口和 `timer_ticks()` 计数接口。
- 已在 trap dispatch 中区分 CPU exception 和外部 IRQ。
- 已对 timer IRQ0 发送 EOI避免中断只触发一次。
- `scripts/check.sh` 已验证 `timer initialized``timer tick=1/2/3`
后续扩展:
- 把 IRQ 分发扩展为可注册 handler 的表,而不是只处理 timer。
- 建立 kernel thread 对象和内核栈。
- 建立上下文切换入口。
- 建立 run queue 和最小 round-robin scheduler。
- 建立 `yield()`、`sleep()`、timer wakeup 和 wait queue。
下一阶段:
- 继续在 `04-time-scheduler.md` 内推进最小 kernel thread。

View File

@ -28,7 +28,7 @@
| `01-early-debug.md` | 基础完成 | 已有 early log 前端、QEMU debug port、COM1 串口和最小 `panic()`。 |
| `02-cpu-interrupts.md` | 基础完成 | 已有 GDT/TSS/IDT、exception vector 0-31、`trap_frame` 和 invalid opcode 回归测试。 |
| `03-memory.md` | 基础完成 | 已有最小物理页 allocator、页表 map/unmap/query、page fault 诊断和内核堆。 |
| `04-time-scheduler.md` | 未开始 | 需要 timer、kernel thread、调度和等待队列。 |
| `04-time-scheduler.md` | 进行中 | 已有 PIT timer interrupt 和通用 tick 计数kernel thread、调度和等待队列未完成。 |
| `05-input-events.md` | 未开始 | 需要输入事件模型和键盘接入。 |
| `06-storage-vfs.md` | 未开始 | 需要块层、缓存、VFS 和基础文件系统。 |
| `07-user-mode.md` | 未开始 | 需要 syscall、进程、用户地址空间和 ELF 加载。 |
@ -36,7 +36,7 @@
| `09-driver-expansion.md` | 未开始 | 需要设备模型、PCI/ACPI、存储、网络等驱动扩展。 |
| `10-real-machine.md` | 未开始 | 需要 U 盘真机启动验证和硬件差异记录。 |
当前最合适的下一步`04-time-scheduler.md`:先建立 timer interrupt再引入最小 kernel thread 和调度循环。
当前最合适的下一步仍在 `04-time-scheduler.md` 内:引入最小 kernel thread 和内核栈,再做调度循环。
## Linux 级能力缺口路由

View File

@ -23,6 +23,7 @@
- 页表验证:页表 map/unmap/query selftest 已接入启动检查。
- page fault 基础:已能输出 fault address、错误码、访问类型和权限来源。
- 内核堆基础:已提供 `kmalloc/kfree`,并接入启动 selftest。
- timer 基础:已接入 x86 PIC/PITtimer IRQ 能进入通用 tick 入口。
- 构建系统已拆成根 Makefile、`arch/x86/Makefile` 和目录 Makefile。
- `scripts/check.sh` 已验证启动日志、串口日志和 invalid opcode 异常路径GitHub Actions 已接入。
- `.clang-format` 已用于强制当前 C 代码风格。
@ -30,7 +31,7 @@
后续未完成:
- 完整日志体系printk、log level、ring buffer、oops、符号化、crash dump。
- 完整中断体系:PIC/APIC、外部 IRQ、timer interrupt
- 完整中断体系:IRQ handler 注册、APIC、外部设备 IRQ、优先级和屏蔽策略
- 内存管理扩展长期内存区域模型、page metadata、buddy/slab、后续完整缺页策略。
- 后续主线:调度、进程、文件系统、用户态和 shell。
@ -42,7 +43,6 @@
目标:
- 建立 timer interrupt 基础。
- 建立最小 kernel thread 和调度循环。
## 任务路由

View File

@ -8,5 +8,6 @@ void arch_early_log_putc(char ch);
void arch_halt_forever(void) __attribute__((noreturn));
int arch_page_table_uses_page(uint64_t page);
void arch_traps_init(void);
void arch_timer_init(void);
#endif

9
include/tianole/timer.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef TIANOLE_TIMER_H
#define TIANOLE_TIMER_H
#include <stdint.h>
void timer_tick(void);
uint64_t timer_ticks(void);
#endif

View File

@ -1,7 +1,8 @@
KERNEL_SRCS := \
kernel/main.c \
kernel/boot_report.c \
kernel/early_log.c
kernel/early_log.c \
kernel/time/timer.c
KERNEL_OBJS := \
$(ARCH_KERNEL_OBJS) \

View File

@ -12,6 +12,7 @@ void kernel_main(const boot_info_t *boot_info)
arch_traps_init();
kernel_report_boot_state(boot_info);
mm_init(boot_info);
arch_timer_init();
#if KERNEL_TEST_TRAP
__asm__ volatile("ud2");

22
kernel/time/timer.c Normal file
View File

@ -0,0 +1,22 @@
#include <stdint.h>
#include <tianole/early_log.h>
#include <tianole/timer.h>
static uint64_t tick_count;
void timer_tick(void)
{
tick_count++;
if (tick_count <= 3) {
early_log_puts("timer tick=");
early_log_u64_decimal(tick_count);
early_log_puts("\n");
}
}
uint64_t timer_ticks(void)
{
return tick_count;
}

View File

@ -22,7 +22,11 @@ check_lines build/debug.log \
"kernel page table root active" \
"page table selftest ok" \
"kernel heap initialized" \
"kernel heap selftest ok"
"kernel heap selftest ok" \
"timer initialized" \
"timer tick=1" \
"timer tick=2" \
"timer tick=3"
check_lines build/serial.log \
"kernel_main entered" \
@ -36,6 +40,10 @@ check_lines build/serial.log \
"kernel page table root active" \
"page table selftest ok" \
"kernel heap initialized" \
"kernel heap selftest ok"
"kernel heap selftest ok" \
"timer initialized" \
"timer tick=1" \
"timer tick=2" \
"timer tick=3"
cat build/debug.log