《自己动手写操作系统第六章》引入minix中断处理方式

摘要:回过头来看,我们发现我们的中断处理程序写的并不够优雅。中断被响应需要三个条件:Eflags中的中断标记是打开状态;中断屏蔽寄存器没有屏蔽对应中断,设置了EOI标志。

1.修改一下时钟中断处理程序:g/kernel/kernel.asm


174     inc dword [k_reenter]
175     cmp dword [k_reenter], 0
176     jne .1 ;重入进入.1
177 
178     mov esp, StackTop       ; 切到内核栈
179 	   
	push	.restart_v2
	jmp	.2
.1:
	push	.restart_reenter_v2
.2	
180     sti
181 
182     push    0
183     call    clock_handler
184     add esp, 4
185 
186     cli
	ret;对应前面的push语句

.restart_v2: 
188     mov esp, [p_proc_ready] ; 离开内核栈;
189     lldt    [esp + P_LDT_SEL]
190     lea eax, [esp + P_STACKTOP]
191     mov dword [tss + TSS3_S_SP0], eax
192 
193 .restart_reenter_v2:  ; 如果(k_reenter != 0),会跳转到这里
194     dec dword [k_reenter]   ; k_reenter--;
195     pop gs  ; ┓
196     pop fs  ; ┃
197     pop es  ; ┣ 恢复原寄存器值
198     pop ds  ; ┃
199     popad       ; ┛
200     add esp, 4
201 

注意:上面的代码从逻辑上也发生了改变:在发生了中断重入的时候,仍然会执行中断服务程序,不过仅仅会打印"!"就返回;没有发生中断切换的时候就会执行进程切换。
    我们来看看对应的中断处理程序:
 21 PUBLIC void clock_handler(int irq)
 22 {
 23     disp_str("#");
 24     ticks++;
 25 
 26     if (k_reenter != 0) {
 27         disp_str("!");
 28         return;
 29     }
 30 
 31     p_proc_ready++;
 32 
 33     if (p_proc_ready >= proc_table + NR_TASKS) {
 34         p_proc_ready = proc_table;
 35     }
 36 }

我们更改代码以后,再来运行一下程序:
思考:上面的代码是否会产生中断重入导致的堆栈溢出问题?

2.修改restart程序

restart是将睡眠的程序重新运行的一段程序,是用汇编在kernel.asm中定义的,由main.c中的tnix_main()调用,然后进入死循环:
 54     k_reenter = -1;
 55 
 56     p_proc_ready    = proc_table;
 57 
 58     restart();
 59 
 60 
 61     while(1){}

我们来修改restart的代码:
353 restart:
354     mov esp, [p_proc_ready]
355     lldt    [esp + P_LDT_SEL]
356     lea eax, [esp + P_STACKTOP]
357     mov dword [tss + TSS3_S_SP0], eax
358 restart_reenter:
 dec dword [k_reenter];由于这一句,我们需要将k_reenter的初始值从-1改成0
359     pop gs
360     pop fs
361     pop es
362     pop ds
363     popad
364     add esp, 4
365     iretd

由于时钟中断的后部分和restart是相同的,我们可以将这时钟中断中重合的部分省略。


3修改中断处理程序——save

对比原来的中断处理程序:我们总结一下时钟中断程序的内容:
1)保护上下文
2)判断中断重入与堆栈切换
3)开中断与中断处理核心程序
4)恢复上下文(已经并在了restart过程当中)
现在,我们将1)和2)也独立出来:
193 save:
194     sub esp, 4
195     pushad      ; ┓
196     push    ds  ; ┃
197     push    es  ; ┣ 保存原寄存器值
198     push    fs  ; ┃
199     push    gs  ; ┛
200     mov dx, ss
201     mov ds, dx
202     mov es, dx
203 
204     mov eax,esp
205 
206     inc dword [k_reenter]
207     cmp dword [k_reenter], 0
208     jne .1
209 
210     mov esp, StackTop       ; 切到内核栈
211     push    restart
212     jmp .2
213 .1:
214     push    restart_reenter
215 .2:
216     jmp     [eax+RETADR-P_STACKBASE]
     中断处理程序的部分内容:
158 hwint00:        ; Interrupt routine for irq 0 (the clock).
159 
160     ;inc    byte [gs:0] ; 改变屏幕第 0 行, 第 0 列的字符
161     call save
162     mov al, EOI     ; ┓reenable master 8259
163     out INT_M_CTL, al   ; ┛
      这里,我们注意到一个很奇怪的call指令——没有以ret结尾。为什么呢?想一想,ret从堆栈中弹出返回地址赋值给eip;但是由于这里,esp在call指令中发生了变化,所以没法用ret进行返回,需要使用jmp,就是call指令的下一条指令的地址作为call过程的返回地址。

4.修改中断处理程序——时钟中断的可重入问题

        我们在本文第一部分已经分析过,这种情况下的代码仍然可能导致时钟中断的重入问题:所以,我们有必要在重新打开中断之前关闭时钟中断;在关闭中断之后再打开时钟中断。
看代码:
160     call save
161     
162     in  al,INT_M_CTLMASK
163     or  al,1
164     out INT_M_CTLMASK,al
165     
166     mov al, EOI     ; ┓reenable master 8259
167     out INT_M_CTL, al   ; ┛
168     
169     sti
170     push    0
171     call    clock_handler
172     add esp, 4
173     
174     cli
175     in  al,INT_M_CTLMASK
176     and al,0xFE
177     out INT_M_CTLMASK
178 
179     ret

5.修改中断处理程序——代码模块化:统一的中断处理例程

     在上面的这段代码中,我们注意到,真正与时钟相关的部分是有限的,这很容易扩展到其他中断类型。我们来定义一个宏hwint_master 1来处理硬件中断。
150 ; 中断和异常 -- 硬件中断
151 ; ---------------------------------
152 %macro  hwint_master    1
153     call    save
154     in  al, INT_M_CTLMASK   ; ┓
155     or  al, (1 << %1)       ; ┣ 屏蔽当前中断
156     out INT_M_CTLMASK, al   ; ┛
157     mov al, EOI         ; ┓置EOI位
158     out INT_M_CTL, al       ; ┛
159     sti ; CPU在响应中断的过程中会自动关中断,这句之后就允许响应新的中断
160     push    %1          ; ┓
161     call    [irq_table + 4 * %1]    ; ┣ 中断处理程序;很显然,irq_table是一个函数指针组成的数组,我们在global.c中对他加以定义
162     pop ecx         ; ┛
163     cli
164     in  al, INT_M_CTLMASK   ; ┓
165     and al, ~(1 << %1)      ; ┣ 恢复接受当前中断
166     out INT_M_CTLMASK, al   ; ┛
167     ret
168 %endmacro

     相关:
irq_table在global.c中进行定义:
PUBLIC irq_hander irq_tabel[NR_IRQ];
申明:extern irq_hander irq_table[];在global.h
在type.h中增加对irq_handler的定义:typedef void (*irq_handler) (int irq)
const.h :#define NR_IRQ 16

    接下来,我们来初始化irq_table,先都初始化成spurious_irp:
在init_8259A函数中:
int i;
for(i=0;i<NR_IRQ;i++LL)
	irq_table[i]=surious_irq;

    现在,我们需要一个函数对irq_table[0]进行赋值:
 
44 PUBLIC void put_irq_handler(int irq, t_pf_irq_handler handler)
 45 {
 46     disable_irq(irq);这一句是必须的——进行中断处理之前,先关对应中断
 47     irq_table[irq] = handler;
 48 }

    这里,你看到,我们使用了一个中断使能函数disable_irq,我们接下来看它和另外一个函数enable_irq的结构体。

123 ; ========================================================================
124 ;                  void disable_irq(int irq);
125 ; ========================================================================
126 ; Disable an interrupt request line by setting an 8259 bit.
127 ; Equivalent code for irq < 8:
128 ;       out_byte(INT_CTLMASK, in_byte(INT_CTLMASK) | (1 << irq));
129 ; Returns true iff the interrupt was not already disabled.
130 ;
131 disable_irq:
132     mov ecx, [esp + 4]      ; irq
133     pushf
134     cli
135     mov ah, 1
136     rol ah, cl          ; ah = (1 << (irq % 8))
137     cmp cl, 8
138     jae disable_8       ; disable irq >= 8 at the slave 8259
139 disable_0:
140     in  al, INT_M_CTLMASK
141     test    al, ah
142     jnz dis_already     ; already disabled?
143     or  al, ah
144     out INT_M_CTLMASK, al   ; set bit at master 8259
145     popf
146     mov eax, 1          ; disabled by this function
147     ret
148 disable_8:
149     in  al, INT_S_CTLMASK
150     test    al, ah
151     jnz dis_already     ; already disabled?
152     or  al, ah
153     out INT_S_CTLMASK, al   ; set bit at slave 8259
154     popf
155     mov eax, 1          ; disabled by this function
156     ret
157 dis_already:
158     popf
159     xor eax, eax        ; already disabled
160     ret
161 
   相应的enable的代码我们省略,请读者自行看书讲解。

6.应用统一的中断处理范式


    在main.c中,绑定中断处理程序到固定的引脚。
 57     p_proc_ready    = proc_table;
 58 
 59     put_irq_handler(CLOCK_IRQ, clock_handler);  /* 设定时钟中断处理程序 */
 60     enable_irq(CLOCK_IRQ);              /* 让8259A可以接收时钟中断 */
 61 
 62     restart();
在init_8259A中屏蔽所有的中断:out_byte(INT_M_CTLMASK,0xFF).

        回过头来总结一下,我们是如何将中断处理流程范式化的:我们将寄存器恢复、堆栈切换ldt等操作提取出来,归到restart部分;将中断处理核心部分:打印相关信息和进程切换放到时钟中断处理句柄中;将上下文的保存、判断中断重入等放到save函数之中;为了防止时钟中断重入,我们在中断处理流程中加入中断使能控制语句;接下来,将时钟中断处理,推广到一般化的中断处理,将上述中断处理过程 封装成宏定义hwint_master。过程大致如此,可以看出,这是一个从特殊化到一般化的过程,也是我们避免代码臃肿和出错的良好方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值