linux0.11学习笔记-技术铺垫-简单AB任务切换程序(4)-向现存写数据并响应时钟中断

来源:互联网 发布:快手 知乎 编辑:程序博客网 时间:2024/06/07 16:51

转自:http://www.cnblogs.com/linucos/archive/2012/04/14/2447027.html

上几节的介绍中,我们能够用bootloader加载32位代码,进入保护模式,并且跳转到了保护模式下的程序里,本篇我们实现在32位模式下完成写显存输出字符,并且在时钟中断中完成显示字符的程序。

此后的代码,我们会略去bootloader不说,只说明32位程序head.s

1. 看代码head.s

SCRN_SEL = 0x18.globl startup_32.textstartup_32:    movl $0x10,%eax    mov %ax,%ds    mov %ax,%es    mov %ax,%gs    mov %ax,%fs    lss init_stack,%esp    call setup_idt    call setup_gdt    # init again    movl $0x10,%eax    mov %ax,%ds    mov %ax,%es    mov %ax,%gs    mov %ax,%fs    lss init_stack,%esp# setup timer & system call interrupt descriptors.    movl $0x00080000, %eax        movw $timer_interrupt, %ax    movw $0x8E00, %dx    movl $0x08, %ecx    lea idt(,%ecx,8), %esi    movl %eax,(%esi)     movl %edx,4(%esi)    stidie:    jmp die.align 2ignore_int:    push %ds    pushl %eax    movl $0x10, %eax    mov %ax, %ds    mov $0x0c98, %ax            /* print 'C' */    call write_char    popl %eax    pop %ds    iretwrite_char:    push %gs    pushl %ebx    mov $SCRN_SEL,%ebx    mov %bx,%gs    mov src_loc,%bx    shl $1,%ebx    mov %ax,%gs:(%ebx)    shr $1,%ebx    incl %ebx    cmpl $2000,%ebx    jb 1f    movl $0,%ebx1:    movl %ebx,src_loc    popl %ebx    pop %gs    ret.align 2timer_interrupt:    push %ds    pushl %edx    pushl %ecx    pushl %ebx    pushl %eax    movl $0x10, %eax    mov %ax, %ds    movb $0x20, %al    outb %al, $0x20    mov $0x0c61,%ax    call write_char    popl %eax    popl %ebx    popl %ecx    popl %edx    pop %ds    iretsetup_idt:    lea ignore_int,%edx    movl $0x00080000,%eax    movw %dx,%ax        /* selector = 0x0008 = cs */    movw $0x8E00,%dx    /* interrupt gate - dpl=0, present */    lea idt,%edi    mov $256,%ecxrp_sidt:    movl %eax,(%edi)    movl %edx,4(%edi)    addl $8,%edi    dec %ecx    jne rp_sidt    lidt lidt_opcode    retsetup_gdt:    lgdt lgdt_opcode    ret.align 2lidt_opcode:    .word 256*8-1    .long idtlgdt_opcode:    .word (end_gdt-gdt)-1    .long gdtsrc_loc:    .long 0.align 2idt:    .fill 256,8,0gdt:    .quad 0x0000000000000000    .quad 0x00c09a00000007ff    .quad 0x00c09200000007ff    .quad 0x00c0920b80000002end_gdt:    .fill 128,4,0init_stack:    .long init_stack    .word 0x10

2. 代码分析

SCRN_SEL = 0x18

.globl startup_32
.text
startup_32:
    movl $0x10,%eax
    mov %ax,%ds
    mov %ax,%es
    mov %ax,%gs
    mov %ax,%fs

    lss init_stack,%esp

上边这些没啥好说的,设置环境,段寄存器和esp,为了后边的call指令和压栈等做准备。

    call setup_idt
    call setup_gdt

上边调用函数,设置了gdt全局描述符和idt中断描述符,gdt是保护模式下必须的,因为cs存储的正是这个表中的描述符;

如果要使用中断的话,idt也是必须的,中断服务程序需要idt指明。下面分析子程序。

setup_idt:
    lea ignore_int,%edx
    movl $0x00080000,%eax
    movw %dx,%ax        /* selector = 0x0008 = cs */
    movw $0x8E00,%dx    /* interrupt gate - dpl=0, present */
    lea idt,%edi
    mov $256,%ecx
rp_sidt:
    movl %eax,(%edi)
    movl %edx,4(%edi)
    addl $8,%edi
    dec %ecx
    jne rp_sidt
    lidt lidt_opcode
    ret

以上代码大体意思是,把函数ignore_int这个默认中断服务的段和偏移信息,加载到256个中断描述符里,最后加载idt进寄存器。这样,系统所有的中断都会调用ignore_int这个函数来服务了。

setup_gdt:
    lgdt lgdt_opcode
    ret

直接加载gdt,没什么可说的。

.align 2
ignore_int:
    push %ds
    pushl %eax
    movl $0x10, %eax
    mov %ax, %ds
    mov $0x0c98, %ax            /* print 'C' */
    call write_char
    popl %eax
    pop %ds
    iret

ignore_int子程序默认的中断服务程序,首先保护了需要用到ds和eax寄存器,设置了ds段地址为系统数据段地址,调用了写字符子程序,显示字符C.

最后恢复现场,中断返回。

write_char:
    push %gs
    pushl %ebx

    mov $SCRN_SEL,%ebx
    mov %bx,%gs

    mov src_loc,%bx
    shl $1,%ebx
    mov %ax,%gs:(%ebx)
    shr $1,%ebx
    incl %ebx
    cmpl $2000,%ebx
    jb 1f
    movl $0,%ebx
1:
    movl %ebx,src_loc

    popl %ebx

    pop %gs
    ret

以上代码,首先保护现场,之后去视频显存段选择符,再取当前输出位置src_loc,因为一个字符需要显存两个字节来形容(属性,值),所以要左移一位,相当于乘以2(shl $1,%ebx),之后把ax内容写进显存相应位置mov %ax,%gs:(%ebx)。同时保存了屏幕位置信息(movl %ebx,src_loc)。本函数实现了把参数ax中的内容,写进显存输出的功能。

src_loc:
    .long 0

.align 2
idt:
    .fill 256,8,0

gdt:
    .quad 0x0000000000000000
    .quad 0x00c09a00000007ff
    .quad 0x00c09200000007ff
    .quad 0x00c0920b80000002
end_gdt:

    .fill 128,4,0
init_stack:
    .long init_stack
    .word 0x10

以上定义了系统用到的段和变量信息,不在详述。

    movl $0x00080000, %eax   
    movw $timer_interrupt, %ax
    movw $0x8E00, %dx
    movl $0x08, %ecx
    lea idt(,%ecx,8), %esi
    movl %eax,(%esi)
    movl %edx,4(%esi)

    sti

以上代码实现了设置0x08号中断,即时钟中断的服务程序设置,跟ignore_int设置类型,填充段和服务函数偏移到idt描述符。

最后一句打开了中断,系统可以响应中断了。

3. 编译执行

过程不再详述,可参考前边的文章。直接看结果截图:

 

2. 代码分析

SCRN_SEL = 0x18

.globl startup_32
.text
startup_32:
    movl $0x10,%eax
    mov %ax,%ds
    mov %ax,%es
    mov %ax,%gs
    mov %ax,%fs

    lss init_stack,%esp

上边这些没啥好说的,设置环境,段寄存器和esp,为了后边的call指令和压栈等做准备。

    call setup_idt
    call setup_gdt

上边调用函数,设置了gdt全局描述符和idt中断描述符,gdt是保护模式下必须的,因为cs存储的正是这个表中的描述符;

如果要使用中断的话,idt也是必须的,中断服务程序需要idt指明。下面分析子程序。

setup_idt:
    lea ignore_int,%edx
    movl $0x00080000,%eax
    movw %dx,%ax        /* selector = 0x0008 = cs */
    movw $0x8E00,%dx    /* interrupt gate - dpl=0, present */
    lea idt,%edi
    mov $256,%ecx
rp_sidt:
    movl %eax,(%edi)
    movl %edx,4(%edi)
    addl $8,%edi
    dec %ecx
    jne rp_sidt
    lidt lidt_opcode
    ret

以上代码大体意思是,把函数ignore_int这个默认中断服务的段和偏移信息,加载到256个中断描述符里,最后加载idt进寄存器。这样,系统所有的中断都会调用ignore_int这个函数来服务了。

setup_gdt:
    lgdt lgdt_opcode
    ret

直接加载gdt,没什么可说的。

.align 2
ignore_int:
    push %ds
    pushl %eax
    movl $0x10, %eax
    mov %ax, %ds
    mov $0x0c98, %ax            /* print 'C' */
    call write_char
    popl %eax
    pop %ds
    iret

ignore_int子程序默认的中断服务程序,首先保护了需要用到ds和eax寄存器,设置了ds段地址为系统数据段地址,调用了写字符子程序,显示字符C.

最后恢复现场,中断返回。

write_char:
    push %gs
    pushl %ebx

    mov $SCRN_SEL,%ebx
    mov %bx,%gs

    mov src_loc,%bx
    shl $1,%ebx
    mov %ax,%gs:(%ebx)
    shr $1,%ebx
    incl %ebx
    cmpl $2000,%ebx
    jb 1f
    movl $0,%ebx
1:
    movl %ebx,src_loc

    popl %ebx

    pop %gs
    ret

以上代码,首先保护现场,之后去视频显存段选择符,再取当前输出位置src_loc,因为一个字符需要显存两个字节来形容(属性,值),所以要左移一位,相当于乘以2(shl $1,%ebx),之后把ax内容写进显存相应位置mov %ax,%gs:(%ebx)。同时保存了屏幕位置信息(movl %ebx,src_loc)。本函数实现了把参数ax中的内容,写进显存输出的功能。

src_loc:
    .long 0

.align 2
idt:
    .fill 256,8,0

gdt:
    .quad 0x0000000000000000
    .quad 0x00c09a00000007ff
    .quad 0x00c09200000007ff
    .quad 0x00c0920b80000002
end_gdt:

    .fill 128,4,0
init_stack:
    .long init_stack
    .word 0x10

以上定义了系统用到的段和变量信息,不在详述。

    movl $0x00080000, %eax   
    movw $timer_interrupt, %ax
    movw $0x8E00, %dx
    movl $0x08, %ecx
    lea idt(,%ecx,8), %esi
    movl %eax,(%esi)
    movl %edx,4(%esi)

    sti

以上代码实现了设置0x08号中断,即时钟中断的服务程序设置,跟ignore_int设置类型,填充段和服务函数偏移到idt描述符。

最后一句打开了中断,系统可以响应中断了。

3. 编译执行

过程不再详述,可参考前边的文章。直接看结果截图: