随笔-80  评论-24  文章-0  trackbacks-0
上一篇日志主要讲解了对8259A以及中断向量表的初始化。
下面的程序主要是时钟中断、硬盘中断以及系统调用入口函数的实现。

  1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  2 ; 每个进程的内核态堆栈顶部栈帧应该是这样的
  3 ; ss
  4 ; esp
  5 ; eflags
  6 ; cs
  7 ; eip
  8 ; eax
  9 ; ecx
 10 ; edx
 11 ; ebx
 12 ; ebp
 13 ; esi
 14 ; edi
 15 ; ds
 16 ; es
 17 ; fs
 18 ; gs
 19 ; 其中ss、esp、eflags、cs、eip是在发生中断时CPU自动压栈的
 20 ; 而其他的是由中断程序压栈的,这个顺序不能改变,否则后果自负
 21 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 22 
 23 CS_OFFSET    equ 0x30
 24 ESP_OFFSET    equ 0x38
 25 SS_OFFSET    equ 0x3c
 26 
 27 ; 一个宏,因为所有的irq中断函数都是先保存现场并将数据段等堆栈段
 28 ; 切换到内核态,因此,该操作所有的irq中断入口函数均相同
 29 ; 故写成宏节省空间^_!
 30 %macro save_all 0
 31     push eax
 32     push ecx
 33     push edx
 34     push ebx
 35     push ebp
 36     push esi
 37     push edi
 38     push ds
 39     push es
 40     push fs
 41     push gs
 42     mov si, ss
 43     mov ds, si
 44     mov es, si
 45     mov gs, si
 46     mov fs, si
 47 %endmacro
 48 
 49 ; 一个宏,恢复现场
 50 %macro recover_all 0
 51     pop gs
 52     pop fs
 53     pop es
 54     pop ds
 55     pop edi
 56     pop esi
 57     pop ebp
 58     pop ebx
 59     pop edx
 60     pop ecx
 61     pop eax
 62 %endmacro
 63 
 64 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 65 ; 时钟中断处理程序
 66 ; 这是整个系统中最要求“速度快”的程序,因为时钟中断没隔1/HZ(s)
 67 ; 就发生一次,大概它是整个系统调用最频繁的函数,所以需要该函数
 68 ; 尽量短,没有必要的函数调用尽量避免。
 69 ; 另外判断中断重入minix和linux采取的方法也是不一样的,minix采用
 70 ; 一个全局变量,类似于信号量的概念;而linux的方法则比较简单,它
 71 ; 直接获取存储在内核堆栈中的cs段寄存器的RPL值来判断被中断的程序
 72 ; 是内核态程序的还是用户态的进程;我们打算采用linux的办法,虽然
 73 ; minix方法更酷,但是linux的显然更加简单:)
 74 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 75 int_clock:
 76     save_all
 77     ; 增加心跳数
 78     inc dword [boot_heartbeat]
 79 
 80     ; 发送EOI指令结束本次中断
 81     mov ax, 0x20
 82     out 0x20, al
 83     sti
 84     
 85     mov eax, [esp + CS_OFFSET]
 86     and eax, 0x03
 87     cmp eax, 0x0 ; 如果CS段寄存器的RPL为0,则说明是由内核态进入时钟中断,则是中断重入
 88     je return
 89     call pre_schedule
 90 return:
 91     recover_all
 92     iretd
 93 
 94 int_keyboard:
 95     save_all
 96 
 97     recover_all
 98     iretd
 99 
100 int_serial_port2:
101     save_all
102 
103     recover_all
104     iretd
105 
106 int_serial_port1:
107     save_all
108 
109     recover_all
110     iretd
111 
112 int_lpt2:
113     save_all
114 
115     recover_all
116     iretd
117 
118 int_floppy:
119     save_all
120 
121     recover_all
122     iretd
123 
124 int_lpt1:
125     save_all
126 
127     recover_all
128     iretd
129 
130 int_rtc:
131     save_all
132 
133     recover_all
134     iretd
135 
136 int_ps_2_mouse:
137     save_all
138 
139     recover_all
140     iretd
141 
142 int_fpu_fault:
143     save_all
144 
145     recover_all
146     iretd
147 
148 ;硬盘中断处理程序
149 int_at_win:
150     save_all
151 
152     mov byte [gs:0xb8006], 'e'; 试验硬盘中断是否成功:)
153 
154     ; 发送EOI指令给从8259A结束本次中断
155     mov ax, 0x20
156     out 0xa0, al
157     nop
158     nop
159     ; 发送EOI指令给主8259A结束本次中断
160     out 0x20, al
161     nop
162     nop
163 
164     ; 调用该函数使buf_info缓冲区生效
165     call validate_buffer
166 
167     recover_all
168     iretd
169 
170 ; 默认的中断处理函数,所有的未定义中断都会调用此函数
171 int_default:
172     save_all
173     recover_all
174     iretd
175 
176 ; 注意从系统调用返回时不需要从栈中弹出eax的值,因为eax保存着调用
177 ; 对应系统调用之后的返回值
178 %macro recover_from_sys_call 0
179     pop gs
180     pop fs
181     pop es
182     pop ds
183     pop edi
184     pop esi
185     pop ebp
186     pop ebx
187     pop edx
188     pop ecx
189     add esp, 4 * 1
190 %endmacro
191 
192 ; 系统调用框架,系统调用采用0x30号中断向量,利用int 0x30指令产
193 ; 生一个软中断,之后便进入sys_call函数,该函数先调用save_all框
194 ; 架保存所有寄存器值,然后调用对应系统调用号的入口函数完成系统调用
195 ; 注意!!!!!系统调用默认有三个参数,分别利用ebx、ecx、edx来
196 ; 传递,其中eax保存系统调用号
197 sys_call:
198     save_all
199 
200     sti
201 
202     push ebx
203     push ecx
204     push edx
205     call [sys_call_table + eax * 4]
206     add esp, 4 * 3
207 
208     recover_from_sys_call
209 
210     cli
211 
212     iretd
213 

目前不打算对时钟中断处理函数、硬盘中断处理函数以及系统调用入口框架做解释,因为后序部分将会专门分章节进行讲解。这里只说在发生类似时钟中断、硬盘中断以及软中断int X的系统调用时,CPU如何处理的。
初始情况下假设CPU正在用户态执行某一个进程a,此时的CS、DS、ES、FS、SS均指向用户态进程a的段基地址。
当时钟中断等中断抑或int X的系统调用到来的时候,CPU会自动从TSS中寻找用户态进程a预先保存的ring0下的SS0和ESP0,然后将SS和ESP寄存器值转换成SS0和ESP0,即切换到核心态堆栈段(注意,每个进程都可能会有一个自己独立的ring0堆栈段,这样可以更好的实现进程切换时对内核态的保护,linux对此的做法是在创建一个进程的时候在进程页的末尾申请一块空间作为该进程对应的ring0堆栈段),然后将用户态下的SS、ESP、EFLAGS、CS、EIP的值保存在新的SS0:ESP0堆栈段中。注意以上过程都是CPU自动完成的,然后再通过save_all宏手工将eax、ecx、edx、ebx、ebp、esi、edi、ds、es、fs、gs压入堆栈,然后再执行相应的中断处理程序。完成之后会通过recover_all再按序恢复所有的常规寄存器。然后调用iretd命令从堆栈中弹出EIP、CS、EFLAGS、ESP、SS寄存器,然后再重新恢复进程a的运行。这一过程需要对GDT、IDT、TSS以及保护模式下的中断门、陷阱门有所了解才可以。
不过还有一种情况此处没有涉及:当发生进程切换的时候现场保护与恢复的过程如何呢?这一过程将在后面叙述。

  1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  2 ; 以下为库函数
  3 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  4 
  5 ; 对端口进行写操作
  6 void out_byte(unsigned short port, unsigned char value);
  7 out_byte:
  8     mov edx, [esp + 4 * 1]
  9     mov al, [esp + 4 * 2]
 10     out dx, al
 11     nop
 12     nop
 13     ret
 14 
 15 ; 对端口进行读操作
 16 ; uint8 in_byte(unsigned short port);
 17 in_byte:
 18     mov edx, [esp + 4 * 1]
 19     xor eax, eax
 20     in al, dx
 21     nop
 22     nop
 23     ret
 24 
 25 ; 对从指定端口进行读操作,读出的n个字节数据放入buf缓冲区中
 26 void read_port(uint16 port, void* buf, int n);
 27 read_port:
 28     mov    edx, [esp + 4 * 1]    ; port
 29     mov    edi, [esp + 4 * 2]    ; buf
 30     mov    ecx, [esp + 4 * 3]    ; n
 31     shr    ecx, 1
 32     cld
 33     rep    insw
 34     ret
 35 
 36 ; 对从指定端口进行写操作,数据源在buf缓冲区中,写n个字节
 37 void write_port(uint16 port, void* buf, int n);
 38 write_port:
 39     mov    edx, [esp + 4 * 1]    ; port
 40     mov    edi, [esp + 4 * 2]    ; buf
 41     mov    ecx, [esp + 4 * 3]    ; n
 42     shr    ecx, 1
 43     cld
 44     rep    outsw
 45     ret
 46 
 47 ; 安装指定中断号的中断处理程序
 48 extern int install_int_handler(uint8 INT_IV, void* handler);
 49 install_int_handler:
 50     mov eax, [esp + 4 * 1] ; 中断向量号
 51     mov ebx, [esp + 4 * 2] ; 中断程序入口
 52     cmp eax, 256
 53     jae failed
 54     cmp eax, 0
 55     jbe failed
 56     push PRIVILEGE_KERNEL
 57     push ebx
 58     push INT_GATE_386
 59     push eax
 60     call init_idt
 61     add esp, 4 * 4
 62 failed:
 63     ret
 64     
 65 ; 卸载指定中断号的中断处理程序
 66 extern int uninstall_int_handler(uint8 INT_IV);
 67 uninstall_int_handler:
 68     mov eax, [esp + 4 * 1] ; 中断向量号
 69     cmp eax, 256
 70     jae failed
 71     cmp eax, 0
 72     jbe failed
 73     push PRIVILEGE_KERNEL
 74     push int_default
 75     push INT_GATE_386
 76     push eax
 77     call init_idt
 78     add esp, 4 * 4
 79     ret
 80     
 81 ; 安装指定中断号的系统调用入口
 82 extern int install_sys_call_handler(uint8 INT_IV, void* handler);
 83 install_sys_call_handler:
 84     mov eax, [esp + 4 * 1] ; 中断向量号
 85     mov ebx, [esp + 4 * 2] ; 中断程序入口
 86     cmp eax, 256
 87     jae failed_inst_sys
 88     cmp eax, 0
 89     jbe failed_inst_sys
 90     push PRIVILEGE_USER
 91     push ebx
 92     push INT_TRAP_386
 93     push eax
 94     call init_idt
 95     add esp, 4 * 4
 96 failed_inst_sys:
 97     ret
 98 
 99 ; 打开对应向量号的硬件中断
100 ; 注意,这里传入的参数是硬件中断对应的中断向量号
101 ; 需要将该中断向量号转化为在8259A上的索引号
102 void enable_hwint(uint8 IV);
103 enable_hwint:
104     mov ecx, [esp + 4 * 1]
105     cmp cl, IRQ0_IV
106     jae master_1
107     jmp ret_1
108 master_1:
109     cmp cl, IRQ8_IV
110     jae slave_1
111     push MASTER_CTL_MASK_8259
112     call in_byte
113     add esp, 4 * 1
114     sub cl, IRQ0_IV
115     mov bl, 1
116     shl bl, cl
117     xor bl, 0xff
118     and al, bl
119     push eax
120     push MASTER_CTL_MASK_8259
121     call out_byte
122     add esp, 4 * 2
123     jmp ret_1
124 slave_1:
125     cmp cl, IRQ15_IV
126     ja ret_1
127     push SLAVE_CTL_MASK_8259
128     call in_byte
129     add esp, 4 * 1
130     sub cl, IRQ8_IV
131     mov bl, 1
132     shl bl, cl
133     xor bl, 0xff
134     and al, bl
135     push eax
136     push SLAVE_CTL_MASK_8259
137     call out_byte
138     add esp, 4 * 2
139 ret_1:
140     ret
141 
142 ; 关闭对应向量号的硬件中断
143 ; 注意,这里传入的参数是硬件中断对应的中断向量号
144 ; 需要将该中断向量号转化为在8259A上的索引号
145 void disable_hwint(uint8 IV);
146 disable_hwint:
147     mov ecx, [esp + 4 * 1]
148     cmp cl, IRQ0_IV
149     jae master_2
150     jmp ret_2
151 master_2:
152     cmp cl, IRQ8_IV
153     jae slave_2
154     push MASTER_CTL_MASK_8259
155     call in_byte
156     add esp, 4 * 1
157     sub cl, IRQ0_IV
158     mov bl, 1
159     shl bl, cl
160     or al, bl
161     push eax
162     push MASTER_CTL_MASK_8259
163     call out_byte
164     add esp, 4 * 2
165     jmp ret_2
166 slave_2:
167     cmp cl, IRQ15_IV
168     ja ret_2
169     push SLAVE_CTL_MASK_8259
170     call in_byte
171     add esp, 4 * 1
172     sub cl, IRQ8_IV
173     mov bl, 1
174     shl bl, cl
175     or al, bl
176     push eax
177     push SLAVE_CTL_MASK_8259
178     call out_byte
179     add esp, 4 * 2
180 ret_2:
181     ret
182 
183 [SECTION .data]
184 idt:
185         ; idt表共可存放256个中断门描述符
186         times 256 * 8 db 0
187 
188 idtr:    dw $ - idt - 1
189         dd idt
190 

上面这段函数比较简单,是一些库函数,主要包括对端口进行读、写操作;以及安装或者卸载中断处理程序,方法就是通过接受中断号,将IDT中对应的中断描述符置空或初始化;还有安装系统调用,安装系统调用和安装中断处理程序几乎相同,唯一的区别就是门描述符的类型以及门描述符的特权级不同,中断处理程序是中断门,对应的门描述符DPL为0,系统调用是陷阱门,对应的DPL为3,这是因为中断门只需要检查CPL>=处理程序的DPL,而陷阱门除了检查该条件以外还检查CPL<=陷阱门描述符的DPL。这样做的原因是陷阱门是由程序引起的,诸如系统调用之类的,需要从程序中跳入;而中断是硬件引起的。
再后面函数就是打开或关闭8259A上的硬件中断。

关于init.s文件的描述就到此为止。之后还会对进程切换做进一步的阐释。
posted on 2012-02-14 01:06 myjfm 阅读(506) 评论(0)  编辑 收藏 引用 所属分类: 操作系统

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理