代码、内容参考来自于包括《操作系统真象还原》、《一个64位操作系统的设计与实现》以及《ORANGE’S:一个操作系统的实现》。
我们要完成的步骤简单:
- IRQ0引脚上的时钟中断信号频率是由8253的计数器0设置的,我们要使用计数器0
- 时钟发出的中断信号不能只发一次,必须是周期性发出的,也就是我们要采取循环计数的工作方式,可选的工作方式为方式2和方式3,这里咱们就选择方式2,这是标准的分频方式,这正是咱们所需要的。
- 计数器发出输出信号的频率是由计数初值决定的,所以我们要为计数器0赋予合适的计数初值。
这个初值的计算方式为1193180/中断信号的频率=计数器0的初始计数值
我们要设置的中断信号的频率为100Hz,直接代入公式即可,计数器0的初始计数值=1193180/100约等于 11932。11932 就是计数器 0 的计数初值。
device目录用来存储设备代码。
代码/device/timer.c
#include "timer.h"
#include "io.h"
#include "print.h"
#define IRQ0_FREQUENCY 100
#define INPUT_FREQUENCY 1193180
#define COUNTER0_VALUE INPUT_FREQUENCY/IRQ0_FREQUENCY
#define COUNTER0_PORT 0x40
#define COUNTER0_NO 0
#define COUNTER_MODE 2
#define READ_WRITE_LATCH 3
#define PIT_CONTROL_PORT 0x43
/*把操作的计数器counter_no、读写锁属性rw1、计数器模式counter_mode写入模式控制寄存器中并赋予初始值counter_value*/
static void frequency_set(uint8_t coutner_port,uint8_t counter_no,uint8_t rw1,uint8_t counter_mode,uint16_t counter_value){
/*向控制字寄存器端口0x43写入控制字*/
outb(PIT_CONTROL_PORT,(uint8_t)(counter_no << 6 | rw1 << 4 | counter_mode << 1));
/*先写入counter_value的低8位*/
outb(coutner_port,(uint8_t)counter_value);
/*再写入counter_value的高8位*/
outb(coutner_port,(uint8_t)counter_value >> 8);
}
/*初始化PIT8253*/
void timer_init(){
put_str("timer_init start\n");
/*设置8253的定时周期,也就是发中断的周期*/
frequency_set(COUNTER0_PORT,COUNTER0_NO,READ_WRITE_LATCH,COUNTER_MODE,COUNTER0_VALUE);
put_str("timer_init done\n");
}8253的设置在timer_init函数中完成,不过,最终做初始化工作的是frequency_set函数。
frequency_set函数定义了五个参数:
- counter_port是计数器的端口号,用来指定初值counter_value的目的端口号。
- counter_no 用来在控制字中指定所使用的计数器号码,对应于控制字中的 SC1 和 SC2 位。
- rwl用来设置计数器的读/写/锁存方式,对应于控制字中的RW1和RW0位。
- counter_mode用来设置计数器的工作方式,对应于控制字中的M2~M0位。
- counter_value用来设置计数器的计数初值,由于此值是16位,所以我们用了uint16_t来定义它。
此函数的功能是把操作的计数器counter_no、读写锁属性rwl、计数器工作模式counter_mode写入模式控制寄存器并赋予计数器的计数初值为 counter_value.
frequency_set使用的实参都是一些预先定义好的宏:
IRQ0_FREQUENCY是我们要设置的时钟中断的频率,我们要将它设为100Hz
INPUT_FREQUENCY是计数器0的工作脉冲信号频率。
COUNTERO_VALUE 是计数器 0 的计数初值,由之前的公式算出来的,也就是COUNTERO_VALUE 的值为INPUT_FREQUENCY/IRQO_FREQUENCY。
CONTRERO_PORT 是计数器 0 的端口号 0x40。
COUNTERO_NO 是用在控制字中选择计数器的号码,其值为 0,代表计数器 0,它将被赋值给函数的形参 counter_no。
COUNTER_MODE 是工作模式的代码,其值为 2,即方式 2,这是我们选择的工作方式:比率发生器。
READ_WRITE_LATCH是读写方式,其值为3,这表示先读写低8位,再读写高8位。我们要写入的初值是16位,按照8253的初始化步骤,必须先写低8位,后写高8位。
PIT_CONTROL_PORT 是控制字寄存器的端口。
frequency_set函数中初始化8253的步骤,先写入控制字,再将16位的计数初值的低8位和高8位分别写入计数器0的端口。
提供对外暴露timer.h
#ifndef __DEVICE_TIMER_H #define __DEVICE_TIMER_H #include "stdint.h" void timer_init(); #endif
然后修改/kernel/init.c
#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "../device/timer.h"
/*负责初始化所有模块*/
void init_all(){
put_str("init_all\n");
idt_init(); //初始化中断
timer_init(); //初始化PIT
}
然后编译
nasm -f elf -o build/print.o lib/kernel/print.S nasm -f elf -o build/kernel.o kernel/kernel.S gcc -m32 -I lib/kernel -c -o build/timer.o device/timer.c gcc -m32 -I lib/kernel -m32 -I kernel/ -c -fno-builtin -o build/main.o kernel/main.c gcc -m32 -I lib/kernel -m32 -I kernel/ -c -fno-stack-protector -fno-builtin -o build/interrupt.o kernel/interrupt.c gcc -m32 -I lib/kernel -m32 -I kernel/ -c -fno-builtin -o build/init.o kernel/init.c ld -m elf_i386 -Ttext 0xc0001500 -e main -o build/kernel.bin build/main.o build/init.o build/interrupt.o build/print.o build/kernel.o build/timer.o
写入硬盘
dd if=build/kernel.bin of=/bochs/bin/dreams.img bs=512 count=200 seek=9 conv=notrunc
运行
sudo /bochs/bin/bochs -f /bochs/bin/bochsrc.disk
这里与之前的变化仅仅变快了而已。

参考
郑钢著操作系统真象还原
田宇著一个64位操作系统的设计与实现
丁渊著ORANGE’S:一个操作系统的实现


