Linux内核完全注释:基于0.11内核(修正版V3.0) 的第四章,最后一节的实验,多任务内核程序head.s 源码详解
# 多任务内核程序 [32] 位的启动代码
# 包含32位模式下的初始化设置代码,时钟中断代码,系统调用中断代码和两个任务代码
LATCH = 11930
SCRN_SEL=0x18 # 屏幕显示内存段选择符。
# 问:以下这些选择符是怎么定的值?根据段选择符的定义:位bit[15-3]为段索引,位bit[2]为0表示GDT,1表示LDT,bit[1-0]表示RPL。所以0x18二进制为[00011 0 00]表示GDT表中的第三个描述符。
TSS0_SEL=0X20 # 任务0的TSS段选择符。 0x20二进制为[00100 0 00],表示选择GDT表中的第四个描述符
LDT0_SEL=0X28 # 任务0的LDT段选择符。 0x28二进制为[00101 0 00],表示选择GDT表中的第五个描述符
TSS1_SEL=0X30 # 任务1的TSS段选择符。 0x30二进制为[00110 0 00],表示选择GDT表中的第六个描述符
LDT1_SEL=0X38 # 任务1的LDT段选择符。 0x38二进制为[00111 0 00],表示选择GDT表中的第七个描述符
.global startup_32
.text # 表示可执行代码段(问:实际在编译时有什么影响吗?.text,.data,.bss,用于分别定义当前代码段,数据段和未初始化数据段,
# 在链接多个目标模块时,链接程序会根据它们的类别把各个目标模块中的相应段分别组合在一起。)
startup_32: # 首先加载 数据段寄存器DS、堆栈段寄存器SS和堆栈指针ESP。所有段的线性基地址都是0.
movl $0x10, %eax # 段选择子 0x10 = 00010 0 00 根据段选择符的定义(从左向右分析:00010 表示段索引,0 表示在GDT表查找, 00 表示后三位表示RPL(Ring0-3),在GDT表中查找 第00010个的段描述符 ) # ???上面的赋值,感觉没啥卵用, 当然 0x10, 在下面的 gdt定义里面有, 是 可读写的数据段???
mov %ax, %ds # 将eax的低16位(ax)赋给ds寄存器。当指令中没有指定所操作数据的段时,那么DS将是数据段寄存器
lss init_stack, %esp # init_stack地址值的低16位传入esp,高16位传入ss.
# ???lss mem, reg = mem的低字-reg, mem的高位-段,这里是 ss
# 并且, 所以这里是加载了 16+32个字节一共, lss是把前4个字节加载到esp, 后2个字节加载到ss = 0x10 , 堆栈段必须可读写,所以这里用了内核空间的数据段0x10,???
# esp->init_stack的地址, 它的上面有 128 * 4 字节的零值,作为栈空间来使用.
call setup_idt # 设置中断向量表
call setup_gdt # 设置全局描述符表
movl $0x10, %eax # 重新设置GDT表后,虽然段选择符没变,但是实际的描述表位置已经改变 (感觉boot.s里的idt和gdt没啥用啊)
# (setup_gdt子程序中用lgdt指令更新了GDTR寄存器中gdt表的位置和长度),所以要重新加载段寄存器
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
# 上面所有的段寄存器, 存的都是 内核数据段选择子 0x10; (当然除了CS,这个不能直接设置的)
lss init_stack, %esp # 重新加载ss esp
# ################################## 设置 时钟芯片 10ms 一次中断信号 ############################
# 设置8253定时芯片。把计数器通道0,设置成每隔10毫秒向中断控制器发送一个中断信息号
# 下面介绍一下8253定时芯片:
# 计数器的工作原理是这样的:它有一个输入频率,在PC上是1193180HZ , 所以把 1193180 /100 = 11931.8 ≈ 11930 赋值给计数器, 收到11931.8 次信号,记作一个周期。也就是周期 0.01s = 10ms
# 8253具有3个独立的计数通道,采用减1计数方式。在门控信号有效时,每输入1个计数脉冲,通道做1次计数操作。当计数脉冲是已知周期的时钟信号时,计数就成为定时。
# 方式3为:方波发生器,最适合计算机。
movb $0x36, %al # 控制字:设置通道0工作在方式3、计数器初值采用二进制。
movl $0x43, %edx # 8253芯片控制字寄存器写端口。
outb %al, %dx # 向I/O端口写入一个字节,这里是向端口0x43写入0x36
movl $LATCH, %eax # 初始计数值设置为LATCH(1193180/100),即频率100HZ
movl $0x40, %edx # 通道0的端口。
outb %al, %dx # 分两次把初始计数值写入通道0.
movb %ah, %al
outb %al, %dx # 上面的这个, 了解下就可以,就是为了设置 8253的工作方式而已
# #####
# 在IDT表第8和第128(0x80)项处分别设置定时中断门描述符和系统调用陷阱门描述符。
# ##### 设置定时中断门描述符 #####
# 为IDT表(中断描述符表)的第8项,设置中断门描述符(目的是发生该中断,可以调用到设置好的处理程序
# 这里先解释一下int $0x80:
# int $0x80是一条AT&T语法的中断指令,用于Linux的系统调用。
# Linux系统下的汇编语言用AT&T的语法,如果翻译成Intel的语法就是int 80h,就像我们在Intel的语法下的DOS汇编中经常用的int 21h调用DOS中断,同样如果换成AT&T语法就是int $0x80。
# 不过无论使用那一种语法,int $0x80或者int 80h都是针对Linux的,在DOS或者Windows下不起相应作用。反之亦然。
movl $0x00080000, %eax # eax的值将被用于设置到门描述符的低32位。根据门描述符的格式,0x0008 对应的是段选择符。
# 根据 段选择符的格式,0x0008 = 0b1 0 00, 表示gdt表中第二个描述符, 内核的代码段
movw $timer_interrupt, %ax # 设置eax的低16位,也就是设置 定时中断门描述符的段中偏移值,取定时中断处理程序地址。
movw $0x8e00, %dx # edx的值将被用于设置到门描述符的高32位。edx 被设置为0x0000 8e00。