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

2024-04-22 14:08

本文主要是介绍《自己动手写操作系统第六章》引入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_v2jmp	.2
.1:push	.restart_reenter_v2
.2	
180     sti
181 
182     push    0
183     call    clock_handler
184     add esp, 4
185 
186     cliret;对应前面的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。过程大致如此,可以看出,这是一个从特殊化到一般化的过程,也是我们避免代码臃肿和出错的良好方式。

这篇关于《自己动手写操作系统第六章》引入minix中断处理方式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/925987

相关文章

Debezium 与 Apache Kafka 的集成方式步骤详解

《Debezium与ApacheKafka的集成方式步骤详解》本文详细介绍了如何将Debezium与ApacheKafka集成,包括集成概述、步骤、注意事项等,通过KafkaConnect,D... 目录一、集成概述二、集成步骤1. 准备 Kafka 环境2. 配置 Kafka Connect3. 安装 D

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

SQL 中多表查询的常见连接方式详解

《SQL中多表查询的常见连接方式详解》本文介绍SQL中多表查询的常见连接方式,包括内连接(INNERJOIN)、左连接(LEFTJOIN)、右连接(RIGHTJOIN)、全外连接(FULLOUTER... 目录一、连接类型图表(ASCII 形式)二、前置代码(创建示例表)三、连接方式代码示例1. 内连接(I

Android里面的Service种类以及启动方式

《Android里面的Service种类以及启动方式》Android中的Service分为前台服务和后台服务,前台服务需要亮身份牌并显示通知,后台服务则有启动方式选择,包括startService和b... 目录一句话总结:一、Service 的两种类型:1. 前台服务(必须亮身份牌)2. 后台服务(偷偷干

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

JS 实现复制到剪贴板的几种方式小结

《JS实现复制到剪贴板的几种方式小结》本文主要介绍了JS实现复制到剪贴板的几种方式小结,包括ClipboardAPI和document.execCommand这两种方法,具有一定的参考价值,感兴趣的... 目录一、Clipboard API相关属性方法二、document.execCommand优点:缺点:

Python创建Excel的4种方式小结

《Python创建Excel的4种方式小结》这篇文章主要为大家详细介绍了Python中创建Excel的4种常见方式,文中的示例代码简洁易懂,具有一定的参考价值,感兴趣的小伙伴可以学习一下... 目录库的安装代码1——pandas代码2——openpyxl代码3——xlsxwriterwww.cppcns.c

Deepseek使用指南与提问优化策略方式

《Deepseek使用指南与提问优化策略方式》本文介绍了DeepSeek语义搜索引擎的核心功能、集成方法及优化提问策略,通过自然语言处理和机器学习提供精准搜索结果,适用于智能客服、知识库检索等领域... 目录序言1. DeepSeek 概述2. DeepSeek 的集成与使用2.1 DeepSeek API

CSS弹性布局常用设置方式

《CSS弹性布局常用设置方式》文章总结了CSS布局与样式的常用属性和技巧,包括视口单位、弹性盒子布局、浮动元素、背景和边框样式、文本和阴影效果、溢出隐藏、定位以及背景渐变等,通过这些技巧,可以实现复杂... 一、单位元素vm 1vm 为视口的1%vh 视口高的1%vmin 参照长边vmax 参照长边re