《自己动手写操作系统第六章》引入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

相关文章

gitlab安装及邮箱配置和常用使用方式

《gitlab安装及邮箱配置和常用使用方式》:本文主要介绍gitlab安装及邮箱配置和常用使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1.安装GitLab2.配置GitLab邮件服务3.GitLab的账号注册邮箱验证及其分组4.gitlab分支和标签的

电脑提示xlstat4.dll丢失怎么修复? xlstat4.dll文件丢失处理办法

《电脑提示xlstat4.dll丢失怎么修复?xlstat4.dll文件丢失处理办法》长时间使用电脑,大家多少都会遇到类似dll文件丢失的情况,不过,解决这一问题其实并不复杂,下面我们就来看看xls... 在Windows操作系统中,xlstat4.dll是一个重要的动态链接库文件,通常用于支持各种应用程序

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

SQL Server数据库死锁处理超详细攻略

《SQLServer数据库死锁处理超详细攻略》SQLServer作为主流数据库管理系统,在高并发场景下可能面临死锁问题,影响系统性能和稳定性,这篇文章主要给大家介绍了关于SQLServer数据库死... 目录一、引言二、查询 Sqlserver 中造成死锁的 SPID三、用内置函数查询执行信息1. sp_w

Java对异常的认识与异常的处理小结

《Java对异常的认识与异常的处理小结》Java程序在运行时可能出现的错误或非正常情况称为异常,下面给大家介绍Java对异常的认识与异常的处理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参... 目录一、认识异常与异常类型。二、异常的处理三、总结 一、认识异常与异常类型。(1)简单定义-什么是

Linux脚本(shell)的使用方式

《Linux脚本(shell)的使用方式》:本文主要介绍Linux脚本(shell)的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述语法详解数学运算表达式Shell变量变量分类环境变量Shell内部变量自定义变量:定义、赋值自定义变量:引用、修改、删

python判断文件是否存在常用的几种方式

《python判断文件是否存在常用的几种方式》在Python中我们在读写文件之前,首先要做的事情就是判断文件是否存在,否则很容易发生错误的情况,:本文主要介绍python判断文件是否存在常用的几种... 目录1. 使用 os.path.exists()2. 使用 os.path.isfile()3. 使用

Mybatis的分页实现方式

《Mybatis的分页实现方式》MyBatis的分页实现方式主要有以下几种,每种方式适用于不同的场景,且在性能、灵活性和代码侵入性上有所差异,对Mybatis的分页实现方式感兴趣的朋友一起看看吧... 目录​1. 原生 SQL 分页(物理分页)​​2. RowBounds 分页(逻辑分页)​​3. Page

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流