码迷,mamicode.com
首页 > 系统相关 > 详细

linux0.00 head.s详细注解

时间:2015-09-03 23:04:55      阅读:429      评论:0      收藏:0      [点我收藏+]

标签:

  1 # head.s包含32位保护模式初始化设置代码、时钟中断代码、系统调用中断代码和两个任务的代码。
  2 # 在初始化完成之后程序移动到任务0开始执行,并在时钟中断控制下进行任务0和1之间的切换操作。
  3 LATCH        = 11930              # 定时器初始计数值,即每隔10毫秒发送一次中断请求。     问:为何是这个值?
  4 SCRN_SEL    = 0x18                # 屏幕显示内存段选择符。                   问:以下这些选择符是怎么定的值?
  5 TSS0_SEL    = 0x20                # 任务0的TSS段选择符。
  6 LDT0_SEL    = 0x28                # 任务0的LDT段选择符。
  7 TSS1_SEL    = 0x30                # 任务1的TSS段选择符。
  8 LDT1_SEL    = 0x38                # 任务1的LDT段选择符。
  9 .global    startup_32    # 作用?    
 10 .text                    # 表示可执行代码段(问:实际在编译时有什么影响吗?)
 11 startup_32:
 12 # 首先加载数据段寄存器DS、堆栈段寄存器SS和堆栈指针ESP。所有段的线性基地址都是0.
 13     movl    $0x10, %eax        # 0x10是GDT中数据段选择符。
 14 # 解释一下以上的数据段选择符为什么是0x10:
 15 # 首先,要知道段选择符的格式为:15-3:描述符索引;2:TI(Table index表指示标志);1-0:RPL(Requested Privilege Level请求特权级)
 16 # 这里,实际上数据段应该是第二个段,故索引的二进制为10,而TI和RPL都是0,所以,后面添三位0,乘以8,故选择符即为:0x10(=10 0 00)
 17     mov        %ax, %ds            # ds就是存放数据段选择符的段寄存器,这条指令将ax的值0x0010传递给ds寄存器
 18     lss        init_stack, %esp    # LSS:加载堆栈段(问:标号init_stack默认是一个多少位的地址?)
 19 # lss mem, reg: mem低字->reg,mem高字->ss (问:1.低字和高字分别占多少位?这样说来,该堆栈段的长度即使2^(mem低字的位数喽?) 2.指令有什么影响? 3.堆栈段描述符为      什么不在GDT中?)
 20 # 在新的位置重新设置IDT和GDT表。
 21     call    setup_idt
 22     call    setup_gdt
 23     movl    $0x10, %eax
 24     mov    %ax, %ds            # ds没有变,这句可省略
 25     mov    %ax, %es
 26     mov    %ax, %fs
 27     mov    %ax, %gs    
 28     lss    init_stack, %esp    # ss和esp也都没有变,这句可省略
 29 # 设置8253定时芯片。把计数器通道0设置成每隔10毫秒向中断控制器发送一个中断请求信号。
 30 # 下面介绍一下8253定时芯片:
 31 # 8253具有3个独立的计数通道,采用减1计数方式。在门控信号有效时,每输入1个计数脉冲,通道作1次计数操作。当计数脉冲是已知周期的时钟信号时,计数就成为定时。
 32 # 方式3为:方波发生器,最适合计算机。        
 33     movb    $0x36, %al                # 控制字:设置通道0工作在方式3、计数器初值采用二进制。
 34     movl    $0x43, %edx               # 8253芯片控制字寄存器写端口。
 35     outb    %al, %dx
 36     movl    $LATCH, %eax              # 初始计数值设置为LATCH(1193180/100),即频率100HZ。(问:这里是什么意思?1193180是怎么出来的?)
 37     movl    $0x40, %edx               # 通道0的端口。
 38     outb    %al, %dx                  # 分两次把初始计数值写入通道0.
 39     movb    %ah, %al 
 40     outb    %al, %dx
 41 # 在IDT表第8和第128(0x80)项处分别设置定时中断门描述符和系统调用陷阱门描述符。
 42 # 这里先解释一下int $0x80:
 43 # int $0x80是一条AT&T语法的中断指令,用于Linux的系统调用。
 44 # Linux系统下的汇编语言比较喜欢用AT&T的语法,如果翻译成Intel的语法就是int 80h,就像我们在Intel的语法下的DOS汇编中经常用的int 21h调用DOS中断,同样如果换成AT&T语      法就是int $0x80 45 # 不过无论使用那一种语法,int $0x80或者int 80h都是针对Linux的,在DOS或者Windows下不起相应作用。反之亦然。        
 46     movl    $0x00080000, %eax          # 中断程序属内核,即EAX高字是内核代码段选择符0x0008(即索引为1,TI=0,RPL=00)
 47     movw    $timer_interrupt, %ax      # 设置定时中断门描述符。取定时中断处理程序地址。
 48     movw    $0x8E00, %dx               # 中断门类型是14(屏蔽中断),特权级0或硬件使用。
 49     movl    $0x08, %ecx                # 开机时BIOS设置的时钟中断向量号8.这里直接使用它。
 50     lea        idt(, %ecx, 8), %esi    # 把IDT描述符0x08地址放入ESI中,然后设置该描述符
 51     movl    %eax, (%esi)
 52     movl    %edx, 4(%esi)
 53     movw    $system_interrupt, %ax     # 设置系统调用陷阱门描述符。取系统通调用处理程序地址。
 54     movw    $0xef00, %dx               # 陷阱门类型是15,特权级3的程序可执行。
 55     movl    $0x80, %ecx                # 系统调用向量号是0x80。
 56     lea        idt(, %ecx, 8), %esi    # 把IDT描述符项0x80地址放入ESI中,然后设置该描述符。
 57     movl    %eax, (%esi)
 58     movl    %edx, 4(%esi)
 59 # 好了,现在我们为移动到任务0(任务A)中执行来操作堆栈内容,在堆栈中人工建立中断返回时的场景。
 60 # 注: 由于处于特权级0的代码不能直接把控制权转移到特权级3的代码中执行,但中断返回操作是可以的,因此当初始化GDT、IDT和定时芯片结束后,我们就利用中断返回指令IRET来启动      运行第1个任务。
 61 #     具体实现方法是在初始堆栈init_stack中人工设置一个返回环境。即把任务0的TSS段选择符加载到任务寄存器LTR中、LDT段选择符加载到LDTR中以后,
 62 #     把任务0的用户栈指针(0x17:init_stack)和代码指针(0x0f:task0)以及标志寄存器压入栈中,然后执行中断返回指令IRET。
 63 #     该指令会弹出堆栈上的堆栈指针作为任务0的用户栈指针,恢复假设的任务0的标志寄存器内容,并且弹出栈中代码指针放入CS:EIP寄存器中,从而开始执行任务0的代码,
 64 #     完成了从特权级0到特权级3的控制转移。        
 65     
 66     pushfl                            # 复位标志寄存器EFLAGS中的嵌套任务标志。
 67     andl    $0xffffbfff, (%esp)
 68 # 解释一下EFLAGS寄存器中的NT标志:
 69 # 位14是嵌套任务标志(Nested Task)。它控制这被中断任务和调用任务之间的链接关系。在使用CALL指令、中断或异常执行任务调用时,处理器会设置该标志。在通过使用IRET指令从       一个任务返回时,处理器会检查并修改这个NT标志。
 70 # 使用POPF/POPFD指令也可以修改这个标志,但是在应用程序中改变这个标志的状态会产生不可意料的异常。
 71 # 嵌套任务标志NT用来控制中断返回指令IRET的执行。具体规定如下:
 72 # (1) 当NT=0,用堆栈中保存的值恢复EFLAGS、CS和EIP,执行常规的中断返回操作;
 73 # (2) 当NT=1,通过任务转换实现中断返回。        
 74     popfl
 75     movl    $TSS0_SEL, %eax            # 把任务0的TSS段选择符加载到任务寄存器TR。
 76     ltr        %ax
 77     movl    $LDT0_SEL, %eax            # 把任务0的LDT段选择符加载到局部描述符表寄存器LDTR。
 78     lldt    %ax                        # TR和LDTR只需人工加载一次,以后CPU会自动处理。
 79     movl    $0, current                # 把当前任务号0保存在current变量中。
 80     sti                                # 现在开启中断,并在栈中营造中断返回时的场景。
 81     pushl    $0x17                     # 把任务0当前局部空间数据段(堆栈段)选择符如栈。 
 82  # 问:0x17是怎么来的?
 83  # 答:0x17是任务0的数据段选择符,由下面设置的ldt0可知,数据段Index=2,TI=1(表示在LDT中),RPL=3(处理器的保护机制可识别4个特权级,0级到3级,详见4.5.1 段级保护)        ,故得0x17
 84     pushl    $init_stack                # 把堆栈指针入栈(也可以直接把ESP入栈)。
 85     pushfl                              # 把标志寄存器入栈。
 86     pushl    $0x0f                      # 把当前局部空间代码段选择符入栈。
 87     pushl    $task0                     # 把代码指针入栈。注意!pushl和push也是有区别的,我之前写成了push,运行就出错了!
 88     iret                                # 执行中断返回指令,从而切换到特权级3的任务0中执行。
 89 
 90 # 以下是设置GDT和IDT中描述符项的子程序。
 91 setup_gdt:                              # 使用6字节操作数lgdt_opcode设置GDT表位置和长度。
 92     lgdt    lgdt_opcode                 # lgdt指令加载GDT的入口地址(这里由lgdt_opcode指出)到GDTR中
 93     ret
 94 setup_idt:
 95     lea        ignore_int, %edx         # 设置方法与设置定时中断门描述符的方法一样。
 96     movl    $0x00080000, %eax           # 选择符位0x0008。
 97     movw    %dx, %ax                    # (注:ax为eax的低16位)
 98     movw    $0x8E00, %dx
 99     lea        idt, %edi
100     mov        $256, %ecx               # 循环设置所有256个门描述符项。
101 rp_idt:    movl    %eax, (%edi)
102     movl    %edx, 4(%edi)
103     addl    $8, %edi
104     dec        %ecx
105     jne        rp_idt
106     lidt    lidt_opcode
107     ret
108 
109 # 显示字符子程序。取当前光标位置并把AL中的字符显示在屏幕上。整屏可显示80X25个字符。
110 write_char:
111     push %gs
112     pushl %ebx
113 #    pushl %eax
114     mov $SCRN_SEL, %ebx
115     mov %bx, %gs
116     movl scr_loc, %ebx
117     shl $1, %ebx
118     movb %al, %gs:(%ebx)
119     shr $1, %ebx
120     incl %ebx
121     cmpl $2000, %ebx
122     jb 1f
123     movl $0, %ebx
124 1:    movl %ebx, scr_loc    
125     popl %ebx
126     pop %gs
127     ret
128 
129 # 以下是3个中断处理程序:默认中断、定时中断和系统调用中断。
130 # ignore_int是默认的中断处理程序,若系统产生了其他中断,则会载屏幕显示一个字符‘C’。
131 .align    2    # align是对齐的指令    (注意:之后来好好研究一下关于对齐这个问题)        
132 ignore_int:
133     push    %ds
134     pushl    %eax
135     movl    $0x10, %eax        # 首先让DS指向内核数据段,因为中断程序属于内核。
136     mov        %ax, %ds
137     movl    $67, %eax          # 在AL中存放字符C的代码,调用显示程序显示在屏幕上。
138     call    write_char
139     popl    %eax
140     pop        %ds
141     iret
142     
143 # 这是定时中断处理程序。其中主要执行任务切换操作。    
144 .align    2
145 timer_interrupt:
146     push    %ds
147     pushl    %eax
148     movl    $0x10, %eax          # 首先让DS指向内核数据段。这两句不要不影响。
149     mov        %ax, %ds
150     movb    $0x20, %al           # 然后立刻允许其他硬件中断,则向8253发送EOI命令。 这两句必须要!
151     outb    %al, $0x20
152     movl    $1, %eax
153     cmpl    %eax, current
154     je 1f
155     movl    %eax, current        # 若当前任务是0,则把1存入current,并跳转到任务1
156     ljmp    $TSS1_SEL, $0        # 去执行。注意跳转的偏移值无用,但需要写上。
157     jmp        2f
158 1:    movl    $0, current        # 若当前任务是1,则把0存入current,并跳转到任务0
159     ljmp    $TSS0_SEL, $0
160 2:    popl    %eax
161     pop        %ds
162     iret
163     
164 # 系统调用中断int0x80处理程序。该示例只有一个显示字符功能。
165 # 说明:system_interrup这个中断处理程序将由两个任务来调用。    
166 .align    2
167 system_interrupt:
168     push    %ds
169     pushl    %edx
170     push    %ecx
171     pushl    %ebx
172     pushl    %eax
173     movl    $0x10, %edx        # 首先让DS指向内核数据段
174     mov    %dx, %ds
175     call    write_char         # 然后调用显示字符子程序write_char, 显示AL中的字符
176     popl    %eax
177     popl    %ebx
178     popl    %ecx
179     popl    %edx
180     pop    %ds
181     iret
182     
183 /*****************************************************************/
184 current:    .long 0             # 当前任务号(0或1)。
185 scr_loc:    .long 0             # 屏幕显示位置。按从左上角到右下角顺序显示。
186 
187 .align    2
188 lidt_opcode:
189     .word    256*8-1            # 加载IDTR寄存器的6字节操作数:表长度和基地址。
190     .long    idt
191 lgdt_opcode:
192     .word    (end_gdt-gdt)-1    # 这个16位数表示GDT的段限长(注意:书P88:限长为0表示有1个有效字节。因为段描述符总是8字节长,因此GDT的限长值应该设置成总是8的倍     数减1(即8N-1))    问:1.N在哪里?
193     .long    gdt                # 这个32位数表示GDT的基地址
194     
195 .align    8 
196 idt:    .fill    256,8,0        # IDT表空间。每个门描述符8字节,共占用2KB字节。(注:.fill伪指令???)
197 
198 gdt:    .quad    0x0000000000000000    # GDT表。第1个描述符不用。
199     .quad    0x00c09a00000007ff        # 第2个是内核代码段描述符。其选择符是0x08。
200     .quad    0x00c09200000007ff        # 第3个是内核数据段描述符。其选择符是0x10。
201     .quad    0x00c0920b80000002        # 第4个是显示内存段描述符。其选择符是0x18。
202     .word    0x68, tss0, 0xe900, 0x0   # 第5个是TSS0段的描述符。其选择符是0x20
203     .word    0x40, ldt0, 0xe200, 0x0   # 第6个是LDT0段的描述符。其选择符是0x28
204     .word    0x68, tss1, 0xe900, 0x0   # 第7个是TSS1段的描述符。其选择符是0x30
205     .word    0x40, ldt1, 0xe200, 0x0   # 第8个是LDT1段的描述符。其选择符是0x38
206 end_gdt:
207     .fill    128,4,0                    # 初始内核堆栈空间(问:.fill是什么意思?)
208 init_stack:
209     .long    init_stack                 # 堆栈段偏移位置。
210     .word    0x10                       # 堆栈段同内核数据段
211 # 下面是任务0的LDT表段中的局部段描述符。
212 .align 8
213 ldt0:    .quad    0x0000000000000000        # 第1个描述符,不用。
214     .quad    0x00c0fa00000003ff             # 第2个局部代码段描述符,对应选择符是0x0f
215     .quad    0x00c0f200000003ff             # 第3个局部数据段描述符,对应选择符是0x17
216 # 下面是任务0的TSS段的内容。注意其中标号等字段在任务切换时不会改变。
217 tss0:    .long    0                        /* back link */
218     .long    krn_stk0, 0x10            /* esp0, ss0 */
219     .long    0, 0, 0, 0, 0            /* esp1, ss1, esp2, ss2, cr3 */
220     .long    0, 0, 0, 0, 0            /* eip,    eflags, eax, ecx, edx */
221     .long    0, 0, 0, 0, 0            /* ebx, esp, ebp, esi, edi */
222     .long    0, 0, 0, 0, 0, 0        /* es, cs, ss, ds, fs, gs */
223     .long    LDT0_SEL, 0x8000000        /* ldt, trace bitmap */
224         
225     .fill    128, 4, 0                # 这是任务0的内核栈空间。
226 krn_stk0:
227     
228 # 下面是任务1的LDT表段内容和TSS段内容
229 .align 8
230 ldt1:    .quad    0x0000000000000000        # 第1个描述符,不用
231     .quad    0x00c0fa00000003ff             # 选择符是0x0f,基地址=0x00000232     .quad    0x00c0f200000003ff             # 选择符是0x17,基地址=0x00000233 
234 tss1:    .long    0                        /* back link */
235     .long    krn_stk1, 0x10            /* esp0, ss0 */
236     .long    0, 0, 0, 0, 0            /* esp1, ss1, esp2, ss2, cr3 */
237     .long    task1, 0x200            /* eip,    eflags */
238     .long    0, 0, 0, 0                /* eax, ecx, edx, ebx */
239     .long    usr_stk1, 0, 0, 0        /* esp, ebp, esi, edi */
240     .long    0x17, 0x0f, 0x17, 0x17, 0x17, 0x17    /* es, cs, ss, ds, fs, gs */
241     .long    LDT1_SEL, 0x8000000        /* ldt, trace bitmap */
242         
243     .fill    128, 4, 0           # 这是任务1的内核栈空间。其用户栈直接使用初始栈空间。
244 krn_stk1:
245 
246 # 下面是任务0和任务1的程序,他们分别循环显示字符AB247 task0:
248     movl    $0x17, %eax         # 首先让DS指向任务的局部数据段。
249     movw    %ax, %ds            # 因为任务没有使用局部数据,所以这两句可省略。
250     movb    $65, %al            # 把需要显示的字符A放入寄存器中。
251     int    $0x80                # 执行系统调用,显示字符。
252     movl    $0xfff, %ecx        # 执行循环,起延时作用。
253 1:    loop    1b
254     jmp    task0                # 跳转到任务代码开始处继续显示字符。
255 task1:
256     movb    $66, %al            # 把需要显示的字符B放入寄存器中。
257     int    $0x80                # 执行系统调用,显示字符。
258     movl    $0xfff, %ecx        # 执行循环,起延时作用。
259 1:    loop    1b
260     jmp    task1                # 跳转到任务代码开始处继续显示字符。
261 
262     .fill    128,4,0            # 这是任务1的用户栈空间。
263 usr_stk1:

 

linux0.00 head.s详细注解

标签:

原文地址:http://www.cnblogs.com/zhaohuaipeng/p/4780757.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!