?21,
#  head.s contains the 32-bit startup code.
#  head.s 是32位的启动代码

#  Two L3 task multitasking. The code of tasks are in kernel area,
# 有两个L3(Level 3,即第三特权级,IA32提供给用户0-3,四个特权级,但是Linux0.11只使用了
# 0和3这两个特权级,表示用户态程序和内核态程序,内核-kernel)的多任务

#  just like the Linux. The kernel code is located at 0x10000.
# ……内核代码在0x10000,在boot.s中已经确定了呦~

#大家可能发现boot.s和head.s的代码有一点点不一样,这是因为boot.s使用的是as86汇编器
#而head.s使用的是GNU as汇编器,具体表现在
#    1~movl等操作的出现:当然还是mov的意思,l表示双字,w表示单字,b表示字节
#    2~movl等操作的源操作数和目的操作数位置,源在前而目的在后,与boot.s中的mov操作是相反的
#    3~立即数前必须加$,寄存器前必须加%
#    4~待续

.code32
# CODE32伪指令通知编译器,其后的指令序列为 32 位的 ARM 指令
SCRN_SEL    = 0x18
TSS0_SEL    = 0x20
LDT0_SEL    = 0x28
TSS1_SEL    = 0X30
LDT1_SEL    = 0x38

.global startup_32
.text
#.text表示文本段,通常包含可执行代码
startup_32:
    movl $0x10,%eax
    mov %ax,%ds
    # 将0x0010赋给ds
#    mov %ax,%es
    # 将0x0010赋给es,可是为啥被注释掉了呢……
    lss init_stack,%esp
    # lss mem,reg( mem低字->reg,mem高字->ss),这句的意思就是指让ss:esp指向init_stack
    # 我们不妨去看看init_stack到底存了一些什么东西:
    #    .long init_stack 表示了init_stack这个数据的地址
    #    .word 0x10 给ss赋值了
# 那么现在问题来了0x10是什么,是段选择符:0x10000,右移三位后正好选定gdt中的第2项。内核数据段。
# setup base fields of descriptors.
# 装载基本的域和描述符
    call setup_idt
        # 跳转到setup_idt去执行,请直接去setup_idt那里看后续注释
        # 普及一下call 和 jmp 两条指令的差别吧,call相当于这三条指令的连续作用“push cs”,“push eip” 和“jmp XXX”
        # 所以由call跳转过去执行的代码最后一定要用ret的方式回来
    call setup_gdt
        # 好的,我们回到这里了!
        # idt设置完了,我们去设置gdt,显然我们也要类比一下idt的设置方法来讨论gdt的设置方法
        # 我们猜测gdt需要做这几件事:1~ 填满gdt,2~ 构建gdtr应该有的值,然后一个lgdt指令,完事儿!
        # 好,我们进入setup_idt看看
    movl $0x10,%eax        # reload all the segment registers
        # 在改变gdt后,重新设置全部的段寄存器
    mov %ax,%ds        # after changing gdt.
    mov %ax,%es
    mov %ax,%fs
    mov %ax,%gs
    lss init_stack,%esp   
        # 把初始栈地址放给 ss:esp

# setup up timer 8253 chip.
    # 设置8253芯片,这个芯片具体的工作原理请看这里:http://baike.baidu.com/view/1684875.htm
    # 这个芯片就像我们小学期设计的CPU里的那个节拍发生器
    # 这一段的作用是让8253芯片每隔10ms就向CPU发送一个时钟中断请求
    movb $0x36, %al
    movl $0x43, %edx
    outb %al, %dx
    # 向%dx所示端口输出一个字节(b),值为%al中数据
    movl $11930, %eax        # timer frequency 100 HZ
    movl $0x40, %edx
    outb %al, %dx
    movb %ah, %al
    outb %al, %dx

# setup timer & system call interrupt descriptors.
    movl $0x00080000, %eax
    movw $timer_interrupt, %ax
        # 这两条指令就把 %eax设置成了 0x0008~timer_interrupt(请把“~”看成是地址的链接符……)的地址,
        # 还是代码段中偏移为timer_interrupt的地方
        # 但是究竟0x0008和0x0010有什么区别呢?
        # 显然我们知道0x0008和0x0010分别是代码段和数据段的段选择符,可是代码段和数据段分别在哪里?
        # 去gdt看看,跳到gdt去~
        # ok可以回来了!现在我们继续,去看看timer_interrupt是干什么的?
    movw $0x8E00, %dx
        # 将%dx置为0x8E00
    movl $0x08, %ecx              # The PC default timer int.
        # 将%ecx置为0x08,Linus注释说是PC的默认时钟中断
        # 还不清楚是啥意思……等一会再看……
    lea idt(,%ecx,8), %esi
        # 这条指令是什么意思呢,是将%ecx*8+idt的地址放入%esi中去
    movl %eax,(%esi)
        # 再把%eax的值放入%esi所指的内存区域
        # %eax里面是什么呢?是0x0008~timer_interrupt的地址
    movl %edx,4(%esi)
        # 把%edx放在这个中断描述符的高八位
    movw $system_interrupt, %ax
        # 设置完了时钟中断之后,我们再去设置系统中断
    movw $0xef00, %dx
    movl $0x80, %ecx
        # 0x80是什么!?系统调用的中断号……
    lea idt(,%ecx,8), %esi
    movl %eax,(%esi)
    movl %edx,4(%esi)
        # 上面的几个步骤跟设置时钟中断的方式是一样的

# unmask the timer interrupt.
#    movl $0x21, %edx
#    inb %dx, %al
#    andb $0xfe, %al
#    outb %al, %dx

# Move to user mode (task 0)
    # 然后就要跳转到用户态去执行了
    pushfl
        # pushfl是push flags long的简写,将标志寄存器压栈,双字四字节
    andl $0xffffbfff, (%esp)
        # 1111111111111111 1011 1111 1111 1111
        # andl指令专门用来清零特定的位
        # 这里它的用处我猜不透啊……在完全剖析那本书上也没写……
    popfl
        # 然后又pop了标志位,难道andl操作会改变标志位?
    movl $TSS0_SEL, %eax
        # $TSS0_SEL是在一开始就设定好的,它的值是0x20
    ltr %ax
        # ltr--load task register,装载任务寄存器
        # 那么现在是装载了任务0的,段选择符0x20,右移三位变成二进制的100,表示gdt表的第4项
    movl $LDT0_SEL, %eax
        # 想执行任务0,除了要设置tr以外还要设置ldt
    lldt %ax
        # 好的,这里就设置完成了。
    movl $0, current
        # 把一个叫做current的变量设置成0
    sti
        # 在head.s中关闭的中断终于可以打开了
        # 现在栈里有什么?什么都没有。
    pushl $0x17
    pushl $init_stack
    pushfl
    pushl $0x0f
    pushl $task0
        # 好,这里我们在栈中push了很多东西,分别来看一看
        # 栈顶项 $task0的地址
        # 第二项 0x0f(这是什么?)
        # 第三项 全部标志寄存器
        # 第四项 初始栈的地址
        # 第五项 0x17(这又是什么?)
    iret
        # 借鉴一个网上的博客对这个指令的解释,他先引用了IA32手册上对IRET指令的解释:
            # the IRET instruction pops the return instruction pointer, return code segment selector,
            # 译:IRET指令一一对应地弹出IP指令指针和CS代码段选择符
            # and EFLAGS image from the stack to the EIP, CS, and EFLAGS registers, respectively,
            # 译:以及EFLAGS的值到EIP,CS和EFLAGS寄存器中
            # and then resumes execution of the interrupted program or procedure.
            # 译:然后继续执行中断的程序
            # If the return is to another privilege level, the IRET instruction also pops the stack pointer and SS from the stack,
            # 译:如果返回到另一个特权级,那么这个指令再继续执行前还要弹出栈指针和SS寄存器
            # before resuming program execution.
        # 显然,这里要转换特权级,就要弹出五项,一一对应
        # EIP -> $task0的地址
        # CS -> 0x0f (00001111),特权级3的ldt的第一项,任务0
        # EFLAGS -> EFLAGS 这个不用变
        # ESP -> $init_stack的地址
        # SS -> 0x17 (00010111),特权级3的ldt的第二项,数据段,也做堆栈段的选择符
        # 到这里,就跳到任务0去执行了……我们直接去任务0!
/****************************************/
setup_gdt:
    # 惊呆了有木有,好短啊……然后我们看看这个lgdt_opcode可能会提供给我们什么信息!
    # 我们刚刚的猜测第二步已经有了,lgdt指令就在这里。我们还是先猜测lgdt_opcode里有啥?
    # 无非就是长度,基址呗~ 去看看。
    lgdt lgdt_opcode
    ret

setup_idt:
    lea ignore_int,%edx
        # lea:load effective address,将变量的地址从内存中取出并放入寄存器
    movl $0x00080000,%eax
    movw %dx,%ax        /* selector = 0x0008 = cs */
        #将%eax设置为0x0008-ignore_int地址
    movw $0x8E00,%dx    /* interrupt gate - dpl=0, present */
        #中断门类型,dpl设置为0
    lea idt,%edi
        # 将idt的地址存入%edi中去
    mov $256,%ecx
        # %ecx通常在程序中充当循环语句执行次数计数器的角色,
        # 比如 loop指令和rep指令每次执行时都要%ecx减一,%ecx为零时停止循环
        # 所以当遇到%ecx时,可以考虑它是不是又来当循环计数器了,结果在dec那一句可以看到
rp_sidt:
    movl %eax,(%edi)
        # 将%edi地址中的数据改为%eax中的数据,就是这个ignore_int代码标记的地址,
        # 也就是每一个idt项的低四位
    movl %edx,4(%edi)
        # 首先说一下4(%edi)的意思是[%edi+4]这个内存地址里的数据,
        # 其次,这表示与上面同一个idt描述项的高四位。
        # 这一通循环的目的是把256个idt描述项都设置成一样的,即都是由ignore_int来处理。
        # 剧透,怎么处理?看名字可以知道:ignore_int——通过ignore的方式来处理……
    addl $8,%edi
        # 然后%edi加8,进入下一个idt描述符表去修改
    dec %ecx
        # 每执行一次都要修改%ecx,dec的意思是%ecx减一
    jne rp_sidt
        # %ecx 若不为零,则跳回rp_sidt继续执行,rp_sidt(repeat setup idt)
    lidt lidt_opcode
        # 当256项都执行完了,所有中断处理程序全部指向ignore_int了之后,高高兴兴地把lidt_opcode置成idtr
        # lidt_opcode把长度啊,基地址啥的都设好了
    ret
        # 执行完了这一通,可以回去了到call setup_idt这条之后的那一条去执行了
        # 那么回去之前,我们考虑一下究竟这个setup_idt都做了些什么呢?
        # 1~ 256个entries的表统统都填写上了这个ignore_int的处理函数
        # 2~ 把lidt_opcode置给了idtr,然后高高兴兴地ret了……

# -----------------------------------
write_char:
    push %gs
    pushl %ebx
#    pushl %eax
    mov $SCRN_SEL, %ebx
    mov %bx, %gs
    movl scr_loc, %ebx
    shl $1, %ebx
    movb %al, %gs:(%ebx)
    shr $1, %ebx
    incl %ebx
    cmpl $2000, %ebx
    jb 1f
    movl $0, %ebx
1:    movl %ebx, scr_loc
#    popl %eax
    popl %ebx
    pop %gs
    ret

/***********************************************/
/* This is the default interrupt "handler" :-) */
.align 2
ignore_int:
    push %ds
    pushl %eax
    movl $0x10, %eax
    mov %ax, %ds
    movl $67, %eax            /* print 'C' */
    call write_char
    popl %eax
    pop %ds
    iret

/* Timer interrupt handler */
.align 2
timer_interrupt:
    push %ds
    pushl %eax
    movl $0x10, %eax
    mov %ax, %ds
        # 将%ds设置为0x10
    movb $0x20, %al
        # 将%al设置为0x20
    outb %al, $0x20
    movl $1, %eax
        # 将%eax置为1
    cmpl %eax, current
        # 将current与%eax进行比较,如果相等则跳转到1去执行
    je 1f
    movl %eax, current
        # 若不相等,证明目前current为0,那么就去跳转到TSS0那里去执行
    ljmp $TSS1_SEL, $0
    jmp 2f
1:    movl $0, current
        #在这里将current设置为0
    ljmp $TSS0_SEL, $0
        # ljmp的意思是跳转到段选择子,段内偏移
        # jmpi的意思是跳转到段内偏移,段选择子
        # 这里的意思是跳转到TSS0那里去执行
2:    popl %eax
    pop %ds
        # 最后无论如何都会pop,完事儿~
    iret
        # 这里才是真的完事儿了~
        # 用一个最可爱的IRET指令标志注释的结束,再见观众朋友们~

/* system call handler */
.align 2
system_interrupt:
# 系统中断
    push %ds
    pushl %edx
    pushl %ecx
    pushl %ebx
    pushl %eax
        # 保存寄存器
    movl $0x10, %edx
    mov %dx, %ds
        # 将%ds置0x10
    call write_char
        # 调用这个write_char函数,我们不去分析write_char是干啥的了,
        # 根据名称可知,是写一个字符
    popl %eax
    popl %ebx
    popl %ecx
    popl %edx
    pop %ds
        # 弹出各个寄存器
    iret
        # 中断返回
# 可见,这个系统中断做的事情就是把当前任务对应的字符打印出来,
# task0打印“A”,task1打印“B”
# 但是一开始我们看到的是task0是一个死循环,task1还没有执行过?
# 似乎……只有时钟中断可以让task1得到执行了!

/*********************************************/
current:.long 0
scr_loc:.long 0

.align 2
lidt_opcode:
    .word 256*8-1        # idt contains 256 entries
    .long idt        # This will be rewrite by code.
lgdt_opcode:
    # 长度16位,在这里,end_gdt标签位置减去gdt标签位置再加1。
    .word (end_gdt-gdt)-1    # so does gdt
    # 基址32位,gdt的基址就叫gdt。好,我们去看看写了什么……
    .long gdt        # This will be rewrite by code.

.align 8
    # 这个指令我们汇编课上学过,忘记的去百度一下哦~
idt:    .fill 256,8,0        # idt is uninitialized
# fill伪指令:.fill repeat,size,value 本命令生成size个字节的repeat个副本。
# 各个副本中的内容取自一个8字节长的数。最高4个字节为零,最低的4个字节是value,
# 那么这个指令的意思就是复制256个8字节大小的量,每项都填充成为0
# 看完后面那通代码可知,这里的fill指令纯粹就是为了占位置的,嗯对一定是这样。

gdt:    .quad 0x0000000000000000    /* NULL descriptor */
        # 出现了一个我们没有见过的伪指令,但是它的意思跟 .word是一类的,表示定义一个多长的空间
        # quad是多长呢?联想“quadra kill”……肯定跟四什么什么有关,
        # 再看后面串的长度,可见这个四是四个字的意思。.quad就是规定了后面的八个字节(四个字)的数据的伪指令
    .quad 0x00c09a00000007ff    /* 8Mb 0x08, base = 0x00000 */
        # 这个0x00c09a00000007ff是怎么组成的呢?
        # 显然从左到右是从高到低,我们按照每二位16进制数一拆分
        # 0x  00               c09a            00
        #     基地址的31-24位  一些状态标识位  基地址的23-16位
        #     0000               07ff
        #     基地址的15-0位   段限长,2047个字节
        # 可见这个基地址是0x00000000,与下面这个表项所示一样,说明代码段和数据段是重叠的~
    .quad 0x00c09200000007ff    /* 8Mb 0x10 */
    .quad 0x00c0920b80000002    /* screen 0x18 - for display */
        # 上面一共有四项,gdt第一项还是照例空缺
        # 第四项是显示内存段的描述符,段选择符是0x18……怎么算的就不讲了~
    .word 0x0068, tss0, 0xe900, 0x0    # TSS0 descr 0x20
        # 任务0的TSS段,段选择符0x20
    .word 0x0040, ldt0, 0xe200, 0x0    # LDT0 descr 0x28
        # 任务0的LDT段,段选择符0x28
    .word 0x0068, tss1, 0xe900, 0x0    # TSS1 descr 0x30
        # 任务1的TSS段,段选择符0x30
    .word 0x0040, ldt1, 0xe200, 0x0    # LDT1 descr 0x38
        # 任务1的LDT段,段选择符0x38
end_gdt:
    .fill 128,4,0
        # 哦最后这个是做啥的?
        # 猜~
    # 好的看到这里我们可以回去了,回到call setup_gdt那里!
init_stack:                          # Will be used as user stack for task0.
    .long init_stack
    .word 0x10

/*************************************/
.align 8
ldt0:    .quad 0x0000000000000000
    .quad 0x00c0fa00000003ff    # 0x0f, base = 0x00000
    .quad 0x00c0f200000003ff    # 0x17

tss0:    .long 0             /* back link */
    .long krn_stk0, 0x10        /* esp0, ss0 */
    .long 0, 0, 0, 0, 0        /* esp1, ss1, esp2, ss2, cr3 */
    .long 0, 0, 0, 0, 0        /* eip, eflags, eax, ecx, edx */
    .long 0, 0, 0, 0, 0        /* ebx esp, ebp, esi, edi */
    .long 0, 0, 0, 0, 0, 0         /* es, cs, ss, ds, fs, gs */
    .long LDT0_SEL, 0x8000000    /* ldt, trace bitmap */

.fill 128,4,0
krn_stk0:
#    .long 0

/************************************/
.align 8
ldt1:    .quad 0x0000000000000000
    .quad 0x00c0fa00000003ff    # 0x0f, base = 0x00000
    .quad 0x00c0f200000003ff    # 0x17

tss1:    .long 0             /* back link */
    .long krn_stk1, 0x10        /* esp0, ss0 */
    .long 0, 0, 0, 0, 0        /* esp1, ss1, esp2, ss2, cr3 */
    .long task1, 0x200        /* eip, eflags */
    .long 0, 0, 0, 0        /* eax, ecx, edx, ebx */
    .long usr_stk1, 0, 0, 0        /* esp, ebp, esi, edi */
    .long 0x17,0x0f,0x17,0x17,0x17,0x17 /* es, cs, ss, ds, fs, gs */
    .long LDT1_SEL, 0x8000000    /* ldt, trace bitmap */

.fill 128,4,0
krn_stk1:

/************************************/
task0:
    movl $0x17, %eax
    movw %ax, %ds
        # %ds置为0x17,
    movb $65, %al              /* print 'A' */
        # %al置为ASCII码的65号,“A”
    int $0x80
        # 调用int $0x80,这个中断在之前就已经写好了,
        # 我们可以去看看system_interrupt,这个就是0x80中断的处理程序,
        # 为什么?在上面写了,请好好的找一找~
        # 然后我们就去这个处理程序看看去,我们目前知道的是,ds为0x17,al为65
        #???
    movl $0xfff, %ecx
        # 将%ecx改为0xfff,意思是一直执行这个task0,因为loop没执行一次会减ecx的值
1:    loop 1b
        # b和f的意思分别是向前跳转和向后跳转的意思
    jmp task0
# 先在我们只看到task0的工作,那么task1何时工作呢?
# 在前面我们记得时钟中断在每10ms时8253芯片会发送一个时钟中断,
# 我们还没有分析这个时钟中断是做什么的。
task1:
    movl $0x17, %eax
    movw %ax, %ds
    movb $66, %al              /* print 'B' */
    int $0x80
    movl $0xfff, %ecx
1:    loop 1b
    jmp task1
    # 不解释……跟task0差不多……
    .fill 128,4,0
usr_stk1:

[Operating System Labs] 我对Linux0.00中 head.s 的理解和注释的更多相关文章

  1. [Operationg System Labs] 我对 Linux0.00 中 boot.s的理解和注释

    (如有错误请立即指正,么么哒!) !    boot.s!! It then loads the system at 0x10000, using BIOS interrupts. Thereafte ...

  2. 如何定位“Operating system error 32(failed to retrieve text for this error. Reason: 15105)”错误中被占用的文件

      之前在这篇"Operating system error 32(failed to retrieve text for this error. Reason: 15105)"博 ...

  3. VMware vSphere Client中启动虚拟机提示No boot filename received/Operating System not found解决方法

    昨天下载安装 .NET Framework 3.5 SP1解决了VMware vSphere Client安装问题后,今天需要远程连接服务器搭建一台虚拟机. 根据指引步骤进行下一步.下一步的操作完成后 ...

  4. IX-Protected Dataplane Operating System解读

    一.概述 商业操作系统在应用程序每秒钟需要数百万次操作时才能保持高吞吐量和低(尾)延迟,对于最慢的请求只需几百微秒.通常认为对于高性能网络(小信息的高包率.低延迟)的构建,最好都是在内核之外构建用户态 ...

  5. DBCC CHECKDB 遭遇Operating system error 112(failed to retrieve text for this error. Reason: 15105) encountered

    我们一个SQL Server服务器在执行YourSQLDBa的作业YourSQLDba_FullBackups_And_Maintenance时遇到了错误: Exec YourSQLDba.Maint ...

  6. Unable to open the physical file xxxx. Operating system error 2

    在新UAT服务器上,需要将tempdb放置在SSD(固态硬盘)上.由于SSD(固态硬盘)特性,所以tempdb的文件只能放置在D盘下面,而不能是D盘下的某一个目录下面. ALTER  DATABASE ...

  7. CREATE FILE encountered operating system error 5(Access is denied.)

    这篇博文主要演示"CREATE FILE encountered operating system error 5(Access is denied.)"错误如出现的原因(当然只是 ...

  8. Linux启动报错missing operating system

    用UltraISO制作了一个Red Hat Enterprise Linux Server release 5.7系统的U盘启动盘,然后在一台PC上安装,由于安装过程中在干别的事情,有些选项没有细看. ...

  9. u盘安装CENTOS后,启动missing operating system ,只能用U盘才能启动系统

    好久之前就想把家里闲置的那台老的不能再老的笔记本换成linux的,用来学习 从N久之前用光盘安装的时候发现光驱坏掉了之后就没有再装过,最近又想安装于是就试了U盘安装 U盘安装过程也很简单,只需要制作一 ...

随机推荐

  1. Linux内核-内核线程

    线程分类:内核线程.用户线程(指不需要内核支持而完全建立在用户空间的线程库,这种线程效率高,由于Linux内核没有轻量级进程(线程)的概念,因此不能独立的对用户线程进行调度,而是由一个线程运行库来组织 ...

  2. android Json解析详解

    JSON的定义: 一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性.业内主流技术为其提供了完整的解决方案(有点类似于正则表达式 ,获得了当今大部分语 言的支持),从而可以在不同平台间进行数 ...

  3. poj 1012 Joseph (约瑟夫问题)

    Joseph Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 47657   Accepted: 17949 Descript ...

  4. 如何完全退出android应用程序

    当一个android应用程序包含多个activity时,要完全退出android应用程序,便要销毁掉所有的activity,下面是一种网上流传的比较经典完美的方法: 首先要定义一个继承Applicat ...

  5. iTerm2和oh-my-zsh的个性化定制

    终于在某东做活动新入手了一台air,看身边小伙伴的mac的终端感觉好炫酷,于是乎准备自己也捯饬捯饬,google了一下,发现了osx平台上的终端神器iTerm2和用来代替bash的oh-my-zsh, ...

  6. exploit writing tutorial 阅读笔记总结

    近日阅读Corelan Team编写的exploit writing tutorial系列,大致了解了一下原理,记了一些笔记.此系列文章有中文翻译版,在看雪论坛上发表. 英文版地址:https://w ...

  7. spring04 spel注入

    1.创建需要的实体类对象 public class Student { //学生实体类 private String name; //姓名 private Integer age; //年龄 priv ...

  8. 自己动手写easyui的checkbox

    最近项目中用到了easyui这个框架,找了一圈也没有找到checkbox list控件,被迫只能自己实现了,为了便于复用,自己封装了下,有需要的,直接拿去用吧.有意见或建议的,欢迎指教啊. 调用示例 ...

  9. java的@see注释

    @see注释用法 @see 类名 @see #方法名或属性名 @see 类名#方法名或属性名

  10. [php基础]PHP环境变量$_SERVER和系统常量详细说明

    在PHP网站开发中,为了满足网站的需要,时常需要对PHP环境变量进行设置和应用,在虚拟主机环境下,有时我们更需要通过PHP环境变量操作函数来对PHP环境变量值进行设置.为此我们有必要对PHP环境变量先 ...