WinixJ---kernel/int.s文件详解(下)

WinixJ---kernel/int.s文件详解(下)
上一篇日志主要讲解了对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文件的描述就到此为止。之后还会对进程切换做进一步的阐释。

你可能感兴趣的:(WinixJ---kernel/int.s文件详解(下))