为系统内核建立中断机制

本章代码有一定难度,请参看视频获得更详细的代码开发流程:
Linux kernel Hacker, 从零构建自己的内核

上一节,我们绘制了鼠标图案,遗憾的是,鼠标箭头是死的,动不了,要想让鼠标移动,我们需要为内核建立中断机制。当我们移动鼠标时,鼠标会给CPU发送信号,CPU接收到信号后,终止当前的运算,执行内核给定的代码以处理鼠标发送的信号,在这段代码中,内核根据鼠标发送过来的相关信息,重新绘制鼠标图像,那么,屏幕中的鼠标就可以根据鼠标硬件的挪动而发送改变了。

这样,我们就面临两个问题,一是鼠标如何给CPU发送信号,二是CPU在接收信号后,怎么去执行内核提供的一段代码。我们先看第一个问题的处理,外设硬件要给CPU发送信号,需要通过专门的处理芯片,这个芯片叫可编程控制器,俗称8259A:

####****中断信号的发送机制

这里写图片描述

从上图可以得知,每一个8259A控制器有8根中断信号线,总共可以接入15个外设硬件,一般情况下,鼠标接入的是从8259A所对应的IRQ4这根信号线,鼠标发送信号时,先通过管线IRQ4将信号传递到从8259A,然后通过管线IRQ2传递到主8259A,最后信号再传递给CPU,键盘产生的中断通过主8259A的IRQ1管线向CPU发送信号.

既然有硬件,那就需要对其初始化后才能使用,对硬件的控制我们前面已经说过,需要通过端口发送命令来完成,要配置这两个控制器,我们需要对指定端口发送1字节的数据,这个一字节(8 bit)的数据,我们称之为ICW(initialization control word).主8259A对应的端口地址是20h,21h, 从8259A对应的端口是A0h和A1h. 对端口发送数据时,顺序是定死的,不能违背:

  1. 往端口20h(主片)或A0h(从片)发送ICW1
  2. 往端口21h(主片)或A1h(从片)发送ICW2
  3. 往端口21h(主片)或A1h(从片)发送ICW3
  4. 往端口20h(主片)或A0h(从片)发送ICW4

接下来我们可以看看每个ICW的结构和意义:
ICW1[0...7]:
ICW1[0] 设置为1表示需要发送ICW4,0表示不需要发送ICW4.
ICW1[1] 设置为1表示单个8259, 0表示级联8259
ICW1[2] 设置为1表示4字节中断向量,0表示8字节中断向量
ICW1[3] 设置为1表示中断形式是水平触发,0表示边沿触发
ICW1[4] 必须设置为1
ICW1[5,6,7] 必须设置为0

ICW2[0...7]:
ICW2[0,1,2] 对于80X86架构必须设置为0
ICW2[3...7]: 80X86中断向量

ICW3[0...7](主片):
ICW3[0] 设置为1, IR0级联从片,0无从片
ICW3[1] 设置为1, IR1级联从片,0无从片
ICW3[2] 设置为1, IR2级联从片,0无从片
ICW3[3] 设置为1, IR3级联从片,0无从片
ICW3[4] 设置为1, IR4级联从片,0无从片
ICW3[5] 设置为1, IR5级联从片,0无从片
ICW3[6] 设置为1, IR6级联从片,0无从片
ICW3[7] 设置为1, IR7级联从片,0无从片

ICW3[0...7](从片):
ICW3[0,1,2] 从片连接主片的IR号
ICw3[3...7] 必须是0

ICW4[0...7]:
ICW4[0] 设置为1,表示x86模式,0表示MCS 80/85模式
ICW4[1] 设置为1,自动EOI;0 正常EOI
ICW4[2,3] 表示主从缓冲模式
ICW4[4] 1表示SFNM模式; 0 sequential 模式
ICW4[5,6,7] 设置为0

上面一些概念大家可能不明白,不用担心,继续往下走,后面我再对应解释。下面我们通过代码配置两个中断控制器:
1: 先向主8259A发生ICW1:
mov al, 011h
out 02h, al

011h 对应的二进制是00010001,对应ICW1的说明,由于ICW1[0]=1表示需要发送ICW4, ICW1[1] = 0,说明有级联8259A(我们买来的电脑都是级联的),
ICW1[2] =0 表示用8字节来设置中断向量号,ICW1[3]=0表示中断形式是边沿触发,ICW[4]必须设置为1,ICW[5,6,7]必须是0.

2: 向从8259A发送ICW1:
out A0h, al
3: 向主8259A发送ICW2:
mov al, 20h
out 021h, al

20h 分解成ICW2 是, ICW2[0,1,2] = 0, 这是强制要求的,也就是ICW2的值不能是0x21,0x22之类,只要前三位不是0就不行,整个ICW2 = 0x20,这样的话,当主8259A对应的IRQ0管线向CPU发送信号时,CPU根据0x20这个值去查找要执行的代码,IRQ1管线向CPU发送信号时,CPU根据0x21这个值去查找要执行的代码,依次类推。

4: 向从8259A发送ICW2:
mov al, 028h
out A1h, al

028h分解成ICW2是ICW[0,1,2]=0,这是强制要求,整个ICW2为0x28,表示当从8259A的IRQ0管线发送信号时,CPU根据数据0x28去查找要执行的代码,IRQ1管线发送信号时,CPU根据数据0x29去查询要执行的代码,以此类推。

5: 向主8259A发送ICW3
mov al, 04h
out 21h, al

04h 分解成ICW3 相当于ICW[2] = 1, 这表示从8259A通过主IRQ2管线连接到
主8259A控制器,如上图所示
6: 向从8259A 发送 ICW3
mov al, 02h
out Alh , al
根据从片的ICW3, 将02h对应过来是, ICW[0,1,2] = 2, 表示当前从片是从IRQ2管线接入主8259A芯片的,如上图。

7: 向主8259A发送ICW4:
mov al, 002h
out 021h, al

001h 对应的ICW4为,ICW4[0]=1表示当前CPU架构师80X86,ICW4[1]=1表示自动EOI, 如果这位设置成0的话,那么中断响应后,代码要想继续处理中断,就得主动给CPU发送一个信号,如果设置成1,那么代码不用主动给CPU发送信号就可以再次处理中断。

8: 向从8259A发送ICW4,原理同上:
out 0A1h, al

当上面的配置完成后,我们还需要再向两个芯片分别发送一个字节,叫OCW(operation control word), 一个OCW是一字节数据,也就是8bit,每一bit设置作用是,当OCW[i] = 1 时,屏蔽对应的IRQ(i)管线的信号,例如OCW[0]=1, 那么IRQ0管线的信号将不会被CPU接收,以此类推。配置代码如下:

mov al, 11111101b
out 21h, al

表示CPU只接收主8259A, IRQ1管线发送的信号,其他管线发送信号一概忽略,IRQ1对应的是键盘产生的中断。

mov al, 11111111b
out 0A1h, al

上面代码使得CPU忽略所有来自从8259A芯片的信号。
当我们移动鼠标时,鼠标是通过从8259A的IRQ4管线向CPU发送信号。
综合以上,我们得到的初始化代码如下:

init8259A:
 init8259A:
     mov  al, 011h
     out  02h, al
     call io_delay
  
     out 0A0h, al
     call io_delay

     mov al, 020h
     out 021h, al
     call io_delay

     mov  al, 028h
     out  0A1h, al
     call io_delay

     mov  al, 004h
     out  021h, al
     call io_delay

     mov  al, 002h
     out  0A1h, al
     call io_delay

     mov  al, 002h
     out  021h, al
     call io_delay

     out  0A1h, al
     call io_delay

     mov  al, 11111101b;允许接收键盘中断
     out  021h, al
     call io_delay

     mov  al, 11111111b
     out  0A1h, al
     call io_delay

     ret

io_delay:
     nop
     nop
     nop
     nop
     ret
  

####****中断代码的执行机制
前面我们处理了硬件如何发送信号的问题,接下来,我们看看,当CPU接收到信号后,如何执行内核指定的代码。要执行相应代码,CPU必须知道代码所在的内存位置,这个信息是通过中断描述符表来实现的,我们看看中断描述符的数据结构:

struct GATE_DESCRIPTOR {
short offset_low;
short selector;
char dw_count;
char attribute;
short offset_high;
};

中断描述符跟前面说到的全局描述符类似,也是用于描述内存性质的,只不过它专门用于描述可执行代码所在的内存, offset_low 和 offset_high 合在一起作为中断函数在代码执行段中的偏移,selector 用来指向全局描述符表中的某个描述符,中断函数的代码就处于该描述符所指向的段中,dw_count设置为0,attribute设置为08Eh,我们看看如何在内核中加载中断描述符表:

;Gate selecotor, offset, DCount, Attr
%macro Gate 4
  dw  (%2 & 0FFFFh)
  dw  %1
  dw  (%3 & 1Fh) | ((%4 << 8) & 0FF00h)
  dw  ((%2>>16) & 0FFFFh)
%endmacro

上面汇编代码中,%2对应的是4字节的地址偏移,把地址偏移的低2字节放到中断门的前两字节,接下来的一字节是宏定义的第一个参数,是中断代码所在的代码段的全局描述符,第三行设置中断描述符的属性,当前写死为08Eh,最后一行设置中断代码偏移的高二字节。

在内核代码里,当全局描述符表加载到CPU后,就是我们加载中断描述符表的时机了,首先我们要初始化一个中断描述符:

LABEL_IDT:
%rep  255
    Gate  SelectorCode32, SpuriousHandler,0, DA_386IGate
%endrep

IdtLen  equ $ - LABEL_IDT
IdtPtr  dw  IdtLen - 1
        dd  0

上面代码中,我们通过指令%rep 255 重复定义255个中断描述符,这么说来,CPU其实可以支持255种中断,其中两个8259A芯片的15个中断信号就包含在255个中断中,SpuriousHandler是中断代码的入口,我们把255个中断的处理代码都设置成SpuriousHandler,也就是无论哪个中断发生,都调用这个函数来处理:

     xor   eax, eax
     mov   ax,  ds
     shl   eax, 4
     add   eax, LABEL_IDT
     mov   dword [IdtPtr + 2], eax
     lidt  [IdtPtr]

上面代码跟以前我们加载全局描述符表是一样的,由于加载全局描述符时我们使用指令cli关闭了中断功能,因此我们需要回复中断功能,CPU才能相应来自8259A芯片的信号:

  [SECTION .s32]
     [BITS  32]
     LABEL_SEG_CODE32:
     ;initialize stack for c code
     mov  ax, SelectorStack
     mov  ss, ax
     mov  esp, TopOfStack

     mov  ax, SelectorVram
     mov  ds,  ax

     mov  ax, SelectorVideo
     mov  gs, ax

     sti
     %include "write_vga_desktop.asm"
    
     jmp  $

上面的代码通过运行指令sti 恢复中断功能。最后再看看SpuriousHandler的实现:

_SpuriousHandler:
SpuriousHandler  equ _SpuriousHandler - $$
call intHandlerFromC
iretd

当点击键盘,引发中断时,_SpuriousHandler的代码被调用,它又调用了C模块实现的函数intHandlerFromC。我们看看C语言怎么实现intHandlerFromC的:

void intHandlerFromC(char* esp) {
    char*vram = bootInfo.vgaRam;
    int xsize = bootInfo.screenX, ysize = bootInfo.screenY;
    boxfill8(vram, xsize, COL8_000000, 0,0,32*8 -1, 15);
    showString(vram, xsize, 0, 0, COL8_FFFFFF, "PS/2 keyboard"); 
    for (;;) {
        io_hlt();
    }
    show_char();
}

上面函数先绘制一个背景为黑色的矩形,在矩形里用白色的字体显示字符串"PS/2 keyboard"。当上面的代码编译后,启动虚拟机加载内核,初始画面如下:


这里写图片描述

然后随便点击键盘一个按钮,结果如下:


这里写图片描述

可见,我们中断机制的设置完全正确,CPU能够接收8259A芯片,同时CPU能够正确的执行内核提交的中断处理函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 8086汇编 本笔记是笔者观看小甲鱼老师(鱼C论坛)《零基础入门学习汇编语言》系列视频的笔记,在此感谢他和像他一样...
    Gibbs基阅读 37,106评论 8 114
  • 1 中断介绍 1.1 简介 中断控制是计算机发展中一种重要的技术。最初它是为克服对I/O接口控制采用程序查询所带来...
    疯狂小王子阅读 8,058评论 0 9
  • 从本质上讲,中断(硬)是一种电信号,当设备有某种事情发生的时候,他就会产生中断,通过总线把电信号发送给中断控制器。...
    Joe_HUST阅读 22,655评论 0 10
  • “改天请你吃饭!” “改天再说!” “改天我们好好聚一聚!” “改天我一定去!” 很多事情被安排在了改天里,一个改...
    魔线科技阅读 583评论 0 2
  • 可以说是面向切面编程的典范了。1 aspect修改原来的类通过消息转发实现回调2 这三个哥们是集合,但是无论怎么搞...
    事件_666阅读 322评论 0 0