boot.ld
/*
** 链接脚本
*/
OUTPUT_FORMAT(elf32-i386)
OUTPUT_ARCH(i386)
ENTRY(start)
/*
* ld有多种方法设置进程入口地址, 按以下顺序: (编号越前, 优先级越高)
* 1, ld命令行的-e选项
* 2, 连接脚本的ENTRY(SYMBOL)命令
* 3, 如果定义了start 符号, 使用start符号值
* 4, 如果存在 .text section , 使用.text section的第一字节的位置值
* 5, 使用值0
*
*
*/
SECTIONS
{
/* 将定位器符号置为0x7c00 */
. = 0x7C00;
/*
将所有(*符号代表任意输入文件)输入文件bootsector.S的.start section合并
成一个.start section, 该section的地址由定位器符号的值
指定, 即0x7c00.
bootsector.o整体作为一个start节
*/
.start : {
*bootsector.o(.text)
}
/*
将所有(*符号代表任意输入文件)输入文件的.text section合并
成一个.text section, 该section的地址紧接.start节.
bootmain.o中的text作为一个text节
*/
.text : { *(.text) }
/*
将所有(*符号代表任意输入文件)输入文件的.data section合并
成一个.data section, 该section的地址紧接.text节.
bootmain.o中的data作为一个data节
*/
.data : { *(.data .rodata) }
/DISCARD/ : { *(.eh_*) }
}
bootsector.S
/*
* 主要功能:关中断、内存探测、80x86 CPU从实模式变成保护模式
* 跳转到加载内核的32位代码段
* 注意:本文件不是MBR(512B),而是和bootmain.c链接成MBR
*/
.text
.code16 #.code16表示16位代码段
.global start
/*
*将ds、es和ss段寄存器均设置成cs段寄存器的值,并将栈顶指针esp指向
*0x7c00,栈向低地址增长。这步操作其实也可省略,因为在16位代码段中
*还用不到其他段寄存器,在需要使用的时候再初始化不迟
*/
start:
movw %cs,%ax
movw %ax,%ds # ->Data Segment
movw %ax,%es # ->Extra Segment
movw %ax,%ss # ->Stack Segment
movl $0x7C00,%esp
/*
*关中断,在后面我们在内存中会建立中断向量表,所以事先关好中断,
*防止在建表过程中来了中断,所以事先屏蔽,防止这种情况产生。
*/
cli
/* 内存探测,内存地址0x8000作为内存探测段数的存储地址,
方便后面调用 */
movw $0,0x8000
movw $0x8004,%di
xor %ebx,%ebx
mm_probe:
movl $0xe820,%eax
movl $20,%ecx
movl $0x534D4150,%edx
int $0x15
#产生进位则跳转
jnc cont
jmp probe_end
cont:
incl 0x8000
addw $20,%di
cmpl $0,%ebx
jnz mm_probe
probe_end:
/*
*打开地址线A20。实际上若我们使用qemu跑这个程序时,A20默认打开,
*但为了兼容性,最好还是手动将A20地址线打开.读者可以试一试将打开
*A20代码删去后,在保护模式(32位代码段#)下用回滚机制测试时是否
*仍然显示字符
*
*8042(键盘控制器)端口的P21和A20相连,置1则打开
*0x64端口 读:位1=1 输入缓冲器满(0x60/0x64口有给8042的数据)
*0x64端口 写: 0xd1->写8042的端口P2,比如位2控制P21
*当写时,0x60端口提供数据,比如0xdf,这里面把P2置1
*
*由于MacOS下编译器的版本原因,若加上下面代码会超出512B,故舍去
*/
/*waitforbuffernull1:
#先确定8042是不是为空,如果不为空,则一直等待
xorl %eax,%eax
inb $0x64,%al
testb $0x2,%al
jnz waitforbuffernull1
#8042中没有命令,则开始向0x64端口发出写P2端口的命令
movb $0xd1,%al
outb %al,$0x64
waitforbuffernull2:
#再确定8042是不是为空,如果不为空,则一直等待
xorl %eax,%eax
inb $0x64,%al
testb $0x2,%al
jnz waitforbuffernull2
#向0x60端口发送数据,即把P2端口设置为0xdf
movb $0xdf,%al
outb %al,$0x60*/
/* 加载gdt表,即将内存中的gdt基址和表长写入GDTR寄存器 */
lgdt gdt_48
/* 打开保护模式,将cr0的位0置为1,一般而言BIOS中断
只在实模式下进行调用 */
movl %cr0,%eax
orl $0x1,%eax
movl %eax,%cr0
/*
*进入到32位代码段。0x8代表段选择子(16位)——0000000000001000
*其中最后2为代表特权级.linux内核只使用了2个特权级(0和3),00代表
*0特权级(内核级),倒数第3位的代表是gdt(全局描述符表)还是
*idt(局部描述符表),0代表全局描述符表,前13位代表gdt的项数(第1项),
*属于代码段。所以0x8代表特权级为0(内核级)的全局代码段,promode代表
*偏移地址。
*/
ljmp $0x8,$promode
/* 保护模式下的32位代码段 */
promode:
.code32
movw $0x10,%ax
movw %ax,%ds #->Data Segment
movw %ax,%es #->Extra Segment
movw %ax,%ss #-