当前位置: 首页 > news >正文

10.从开始写LINUX内核——时钟中断

Linux 0.12 内核时钟中断实现:从初始化到中断响应

时钟中断是操作系统中最基础且最重要的中断之一,它为系统提供时间基准,支持进程调度、定时器等核心功能。本文将基于 Linux 0.12 内核的 setup 程序框架,详细介绍时钟中断的完整实现,包括 8253 定时器初始化、中断向量绑定及中断处理程序编写,确保代码可直接用于实验验证。

一、时钟中断实现基础

1. 硬件基础

  • 8253 可编程定时器:产生周期性时钟信号,默认频率 18.2Hz(约 55ms 一次中断)
  • 8259A 中断控制器:时钟中断默认映射到 IRQ0,对应中断向量 0x20
  • 中断描述符表(IDT):需在向量 0x20 处注册时钟中断处理程序

2. 开发环境与工具链

沿用 Linux 0.12 开发环境,核心工具包括:

  • as 2.34:汇编器(支持 AT&T 语法)
  • ld 2.34:链接器(生成二进制镜像)
  • qemu-system-i386:模拟器(验证中断响应)

二、完整代码实现

以下是基于 setup 程序扩展的时钟中断实现代码,包含定时器初始化、中断处理程序及 IDT 配置:

asm

/* setup.s —— 扩展时钟中断支持(2048字节) */
.code16
.text
.global _start_setup/* 段地址定义 */
INITSEG  = 0x9000      /* 硬件信息存储段 */
SETUPSEG = 0x9020      /* setup程序段地址 */
IDT_BASE  = 0x0000      /* 中断描述符表基地址 */
IDT_LIMIT = 0x7FFF      /* IDT长度(8192字节) */_start_setup:/* 初始化段寄存器 */movw    %cs, %axmovw    %ax, %dsmovw    %ax, %es/* 收集硬件信息(光标、内存等) */movb    $0x03, %alxor     %bh, %bhint     $0x10movw    %dx, (0)              /* 存储光标位置到INITSEG:0000 */movb    $0x88, %ahint     $0x15movw    %ax, (2)              /* 存储内存大小到INITSEG:0002 *//* 显示启动信息 */movw    $setup_msg, %axmovw    %ax, %bpmovw    $0x1301, %ax          /* BIOS 10h/13h:显示字符串 */movw    $0x000C, %bx          /* 亮红色文字 */movw    $16, %cx              /* 字符串长度 */movb    $3, %dh               /* 行3 */movb    $0, %dl               /* 列0 */int     $0x10/* 初始化8259A中断控制器(允许IRQ0时钟中断) */call    init_8259A/* 初始化8253定时器(产生时钟中断) */call    init_8253/* 准备进入保护模式 */cli                           /* 关闭中断 */movw    $0x0000, %axcld                           /* 清除方向标志 */
do_move:movw    %ax, %esaddw    $0x1000, %axcmpw    $0x9000, %axjz      end_movemovw    %ax, %dsxorw    %di, %dixorw    %si, %simovw    $0x8000, %cx          /* 复制64KB数据 */repmovswjmp     do_move
end_move:/* 加载GDT并切换到保护模式 */movw    $SETUPSEG, %axmovw    %ax, %dslgdt    gdt_48                /* 加载全局描述符表 *//* 加载IDT(包含时钟中断描述符) */call    setup_idt             /* 初始化中断描述符表 */lidt    idt_48                /* 加载IDT寄存器 *//* 切换到保护模式 */movl    %cr0, %eaxorl     $1, %eaxmovl    %eax, %cr0.byte   0x66, 0xea            /* 远跳转到32位代码 */.long   protected_mode.word   0x0008                /* 代码段选择子 *//* 32位保护模式代码 */
.code32
protected_mode:/* 初始化数据段寄存器 */movl    $0x10, %eaxmovw    %ax, %dsmovw    %ax, %esmovw    %ax, %fsmovw    %ax, %gsmovw    %ax, %ssmovl    $0x90000, %esp        /* 设置栈指针 *//* 开启中断 */sti/* 显示时钟中断就绪标志 */movl    $0xb8000 + 2*80, %edi  /* 第2行起始位置 */movb    $'C', %al             /* 'C'表示时钟就绪 */movb    $0x0A, %ah            /* 绿底黑字 */movw    %ax, (%edi)loop:jmp     loop                  /* 等待时钟中断 *//* 初始化8259A中断控制器:允许IRQ0时钟中断 */
init_8259A:/* 主8259A初始化 */movb    $0x11, %al            /* ICW1:边沿触发,多片 */outb    %al, $0x20.word   0x00eb, 0x00eb        /* 短延迟 */movb    $0x20, %al            /* ICW2:IRQ0映射到向量0x20 */outb    %al, $0x21.word   0x00eb, 0x00ebmovb    $0x04, %al            /* ICW3:主片级联 */outb    %al, $0x21.word   0x00eb, 0x00ebmovb    $0x01, %al            /* ICW4:8086模式 */outb    %al, $0x21.word   0x00eb, 0x00ebmovb    $0xFE, %al            /* OCW1:仅允许IRQ0(时钟)中断 */outb    %al, $0x21ret/* 初始化8253定时器:产生18.2Hz时钟信号 */
init_8253:movb    $0x36, %al            /* 控制字:计数器0,模式3,二进制 */outb    %al, $0x43.word   0x00eb, 0x00ebmovb    $0x00, %al            /* 计数器0低8位(初值0xFFFF) */outb    %al, $0x40.word   0x00eb, 0x00ebmovb    $0xFF, %al            /* 计数器0高8位 */outb    %al, $0x40.word   0x00eb, 0x00ebret/* 初始化IDT:注册时钟中断处理程序(向量0x20) */
setup_idt:leal    idt, %edi             /* EDI = IDT基地址 */movl    $256, %ecx            /* 初始化256个中断描述符 */movl    $ignore_int, %edx     /* 默认处理程序地址 */movl    $0x00080000, %eax     /* 高16位=0,低16位=处理程序偏移 */movw    %dx, %ax              /* AX = 处理程序偏移低16位 */movw    $0x8E00, %dx          /* 中断门属性(P=1,DPL=0,32位) */rp_idt:movl    %eax, (%edi)          /* 偏移低32位 */movl    %edx, 4(%edi)         /* 选择子+属性 */addl    $8, %edi              /* 下一个描述符 */decl    %ecxjne     rp_idt/* 单独设置时钟中断描述符(向量0x20) */leal    0x20*8(%edi - 256*8), %edi  /* 定位到向量0x20 */leal    clock_int, %edx       /* 时钟处理程序地址 */movw    %dx, %ax              /* 更新偏移低16位 */movl    %eax, (%edi)movl    $0x8E00 + 0x0008, 4(%edi)  /* 选择子=0x08(内核代码段) */ret/* 时钟中断处理程序 */
clock_int:pushal                       /* 保存所有通用寄存器 *//* 更新屏幕显示(第3行显示中断计数) */movl    $0xb8000 + 3*80*2, %edi  /* 显示位置:第3行第0列 */incl    (%edi)                /* 计数+1(初始值0) */movb    $0x0C, %ah            /* 红底黑字 */movb    (%edi), %al           /* 计数数值 */addb    $'0', %al             /* 转换为ASCII */movw    %ax, (%edi)/* 发送EOI信号给8259A */movb    $0x20, %aloutb    %al, $0x20            /* 主控制器EOI */popal                        /* 恢复寄存器 */iret                         /* 中断返回 *//* 默认中断处理程序 */
ignore_int:pushalmovl    $0xb8000 + 4*80*2, %edi  /* 第4行显示错误 */movb    $'!', %almovb    $0x0F, %ah            /* 白字黑底 */movw    %ax, (%edi)movb    $0x20, %aloutb    %al, $0x20popaliret/* 全局描述符表(GDT) */
gdt:.word   0, 0, 0, 0            /* 空描述符 */.word   0x07ff, 0x0000, 0x9A00, 0x00C0  /* 代码段:0-32MB */.word   0x07ff, 0x0000, 0x9200, 0x00C0  /* 数据段:0-32MB */.word   0xffff, 0x8000, 0x920b, 0x00C0  /* 视频段:0xB8000 */gdt_48:.word   0x800                 /* GDT长度 */.word   512 + gdt, 0x9        /* GDT基地址(0x9xxxx) *//* 中断描述符表(IDT) */
idt:.fill   256, 8, 0             /* 256个中断描述符 */idt_48:.word   IDT_LIMIT             /* IDT长度 */.word   IDT_BASE + idt, 0x0   /* IDT基地址 *//* 字符串与填充 */
setup_msg:.ascii  "setup is running".fill   2048 - (.-_start_setup), 1, 0  /* 填充到2048字节 */

三、编译与实验验证

1. 编译命令

bash

# 汇编生成目标文件
as -32 -o setup.o setup.s# 链接生成2048字节二进制
ld -m elf_i386 -Ttext 0x0 -s --oformat binary -e _start_setup -o setup setup.o# 验证文件大小
ls -l setup | awk '{print $5 " 字节(预期2048字节)"}'

2. 制作镜像与运行

bash

# 拼接引导扇区和setup程序(假设引导扇区为bootsect)
cat bootsect setup > linux.img# 使用QEMU运行
qemu-system-i386 -fda linux.img -boot a -vga std -no-reboot

3. 预期实验现象

  • QEMU 窗口第 2 行显示 C(时钟就绪标志)
  • 第 3 行字符随时间递增(每 55ms+1),表明时钟中断正常响应
  • 无其他错误字符(如第 4 行无 !),说明中断向量配置正确

四、关键代码解析

1. 8253 定时器初始化

asm

movb $0x36, %al    ; 控制字:计数器0,模式3(方波)
outb %al, $0x43
movb $0x00, %al    ; 初值低8位(0xFFFF)
outb %al, $0x40
movb $0xFF, %al    ; 初值高8位
outb %al, $0x40

  • 定时器 0 工作在模式 3(方波输出),初值 0xFFFF,产生约 18.2Hz 的周期性中断

2. 时钟中断向量绑定

asm

leal 0x20*8(%edi), %edi  ; 定位到IDT的0x20号向量
leal clock_int, %edx     ; 绑定时钟处理程序
movw %dx, %ax            ; 存储处理程序偏移
movl %eax, (%edi)
movl $0x8E00 + 0x08, 4(%edi)  ; 内核代码段选择子(0x08)

  • 中断门属性 0x8E00 表示 32 位中断门,特权级 0
  • 选择子 0x08 对应 GDT 中的内核代码段

3. 中断处理程序

asm

clock_int:pushal                  ; 保存寄存器incl    (%edi)          ; 更新计数movb    $0x20, %aloutb    %al, $0x20      ; 发送EOIpopaliret                    ; 中断返回

  • 必须发送 EOI 信号(0x20),否则 8259A 会屏蔽后续中断
  • iret 指令自动恢复 CS、EIP、EFLAGS 寄存器

五、常见问题解决

  1. 时钟中断无响应

    • 检查 8259A 初始化:movb $0xFE, %al 确保仅开启 IRQ0
    • 验证 IDT 加载:lidt idt_48 指令是否正确执行
    • 确认 GDT 代码段选择子:中断门选择子必须为内核代码段(0x08)
  2. 中断后系统崩溃

    • 检查堆栈设置:保护模式下 %esp 需指向有效内存(如 0x90000)
    • 确保 pushal 与 popal 配对,避免寄存器状态混乱
  3. QEMU 显示异常

    • 验证 VGA 内存地址:文本模式内存基地址为 0xB8000
    • 检查字符 ASCII 转换:计数需加 '0' 才能正确显示数字

总结

本文实现了 Linux 0.12 内核时钟中断的完整流程,从 8253 定时器初始化到 IDT 向量绑定,再到中断处理程序编写,所有代码严格遵循 AT&T 语法及 as 汇编器规范。通过 QEMU 运行可观察到周期性的中断计数更新,直观验证时钟中断的响应机制。这一实现为后续进程调度、时间管理等内核功能奠定了基础。

http://www.dtcms.com/a/335346.html

相关文章:

  • 从零开始构建在线语言翻译网站:完整开发指南
  • 批次防混的“电子锁”:浪智WMS系统 如何用绑定技术终结出入库乱局
  • 深入理解 Python 元类中的 __prepare__ 方法:掌控类属性定义顺序的艺术
  • 【Html网页模板】赛博朋克数据分析大屏网页
  • 聊聊智慧这个东西之三:从食物的毒性、偏性聊起
  • 一种采用双PID串级控制的双轮自平衡车的研制-论文复现与分析
  • 使用影刀RPA实现快递信息抓取
  • XSS攻击:从原理入门到实战精通详解
  • Python代码规范与静态检查(ruff/black/mypy + pyproject.toml + Makefile)自动化工具链介绍
  • 8.从零开始写LINUX内核——初始化中断控制芯片
  • 实时计算 记录
  • 小杨的H字矩阵-洛谷B3924 [GESP202312 二级]
  • Python环境下载安装、以及环境配置教程(Windows版)
  • Vue组件基础解析
  • B+树索引分析:单表最大存储记录数
  • AI搜索:大模型商业落地的“第一束光”,照见了什么?
  • 车灯的技术和未来方向
  • Python列表与元组:数据存储的艺术
  • 【科研绘图系列】R语言在DOM再矿化数据分析与可视化中的应用
  • 力扣(接雨水)——基于最高柱分割的双指针
  • LLaVA
  • 胶质母细胞瘤对化疗的敏感性由磷脂酰肌醇3-激酶β选择性调控
  • MySQL 的 DDL / DML / DQL / DCL 做一次系统梳理:概念区别→常用语法→特点与注意点→实战小例子→常见面试/坑点速记
  • 解构下一-代 AI 智能体:超越 LLM,深度解析三大核心支柱——上下文、认知与行动
  • 基础数据结构
  • Linux——进程管理和计划任务管理
  • Python中*args和**kwargs
  • 基于springboot的在线视频教育管理系统设计与实现(源码+文档+部署讲解)
  • Flow-GRPO:通过在线 RL 训练 Flow matching 模型
  • 概率论基础教程第3章条件概率与独立性(二)