草庐IT

x86中断基础

tq1086 2023-09-12 原文

x86中断基础

原文:Basic x86 interrupts 

作者:Alex Dzyoba

原文发表日期:2016年4月2日

在我的关于多重引导内核的文章中,我们看到了如何加载内核、打印文本,然后停止。然而要让操作系统可用,需要支持键盘输入,将敲击的按键打印在屏幕上。

所需的工作会超出你最初的估计,因为需要初始化x86中断。x86中断是一种古怪而繁琐的机制,拥有超过40年的历史。

1. x86中断

中断是设备发送给CPU的事件,用以通知CPU设备有新的消息:比如用户在键盘上输入,或者网络报文到达。如果没有中断,系统需要轮询所有外部设备,这会浪费CPU时间,增加延迟。

中断按照来源可以分为三种类型:

  1. 硬件中断。来自硬件设备,比如键盘或网卡。
  2. 软件中断。由软件通过int指令产生。在引入sysenter和sysexit指令之前,Linux的系统调用就是通过中断0x80实现的。
  3. 异常。在遇到诸如“除零”或“页错误”的异常时,由CPU产生。

x86中断系统由三个部分共同构成,初始化操作也分为三步:

  1. 配置可编程中断控制器(PIC)从设备接收中断请求(IRQs),发送给CPU。
  2. 配置CPU从PIC接收中断请求,通过中断描述符表(IDT)中定义的调用门(gate),调用相应的中断处理程序。
  3. 操作系统需要提供中断服务程序(ISRs)来处理中断。操作系统要支持被中断抢占,并配置PIC和CPU启用中断。

请在阅读文章时回顾下面的参考图。

 

在配置中断之前,我们需要像之前那样设置好GDT。

2. 可编程中断控制器(PIC)

PIC是替代CPU连接各种外部设备的硬件,本质上它是一个多路复用器或代理。PIC可以减少CPU引脚,提供多项功能:

  • 通过级联支持更多的中断线。2个级联的PIC可以支持15个中断线。
  • 可以屏蔽特定中断,无需屏蔽全部中断(cli)。
  • 中断排队,即对传递给CPU的中断排序。当某些中断被屏蔽时,PIC将中断加入队列延迟发送,不会直接丢弃中断。

最初的IBM PC有独立的8259 PIC芯片。后来它被集成为南桥/ICH/PCH的一部分。现代PC系统使用高级可编程中断控制器(APIC)来解决多处理器机器上的中断路由问题。但为了向后兼容,APIC可以模拟8259 PIC。只要不使用旧硬件,你实际上使用的是手动配置或由BIOS自动配置的APIC。在本文中,我将使用BIOS自动配置的APIC,不会手动配置PIC。原因有两个:首先PIC有很多常人难以理解的怪异之处。其次,后续我们将为SMP配置APIC。BIOS自动按照IBM PC AT机器的模式配置APIC,即2个级联的PCI和15个中断线。

除了连接引发CPU中断的中断线,PIC还连接CPU数据总线。数据总线用于将IRQ号从PIC发送到CPU,并将配置命令从CPU发送到PIC。配置命令包括PIC初始化、IRQ屏蔽、中断结束(EOI)命令等。

3. 中断描述符表(IDT)

中断描述符表(IDT)是一个系统表,保存了中断服务程序(ISR)或简单中断处理程序的描述符。

实模式下有一个中断向量表(IVT)。中断向量表固定在0x0位置,包含以CS和IP描述的中断处理函数指针。这确实不是一种灵活的方式,并且依赖于分段内存管理。自从80286以来,保护模式使用中断描述符表(IDT)。

IDT是内存中的表,由操作系统建立和维护。IDT地址保存在idtr寄存器中。使用lidt指令可以将IDT地址加载到idtr寄存器。IDT只能在保护模式下使用。IDT条目包含门描述符,其中有32位的中断处理程序(ISR)地址、标志位和特权等级。IDT条目是中断门的描述符,类似于GDT中的段描述符。

描述符的主要部分是偏移量。偏移量实质上是由段选择子指定的代码段中ISR的指针。段选择子包含GDT表索引、表指示器(指定GDT或LDT)和特权级别(RPL)。对于中断门,选择子指定的是GDT中的内核代码段。

调用门描述符中的类型(type)域定义了调用门类型:任务、陷阱或中断。对于中断处理函数,需要使用中断调用门。调用中断门时,CPU会清除IF标志位。基本上在设置IDT时,各描述符只有偏移量,即ISR函数地址是不同的。

4. 中断服务程序(ISR)

IDT的主要目的是存储指向ISR的指针,CPU在收到中断后自动调用ISR。你不能手动调用中断处理程序。一旦配置好了IDT并打开中断(sti),在发生中断时,在完成一些幕后工作后,CPU将控制传递给中断处理函数。了解这些幕后工作的内容是很重要的。

如果中断发生在用户空间(即中断发生在不同特权级别的代码段),CPU执行下列操作:

  1. 在内部临时保存SS、ESP、EFLAGS、CS和EIP的值。
  2. 从TSS为新栈(即中断处理函数栈)加载段选择子和栈指针到SS和ESP,切换到新栈。
  3. 将临时保存的SS、ESP、EFLAGS、CS和EIP压栈。
  4. 如果有错误代码,将错误码压栈。
  5. 从中断门或陷阱门加载新代码段的段寄存器和指令指针到CS和EIP寄存器。
  6. 如果调用通过中断门进行,清除EFLAGS寄存器的IF标志位。
  7. 以新的特权等级开始执行中断处理函数。

如果中断发生在内核空间,CPU不会切换堆栈,这意味着内核空间的中断没有自己的堆栈,而是使用被中断函数的堆栈。在x64系统上,由于red zone的存在,内核中断可能会导致堆栈损坏。这就是内核代码需要使用参数-mno-red-zone编译的原因。关于这点我有一个有趣的故事

当内核模式下发生中断时,CPU将:

  1. 将EFLAGS、CS和EIP的值压栈。
  2. 如果有错误码,将其压栈。
  3. 从中断门或陷阱门中加载新代码段的段选择子和指令指针到CS和EIP寄存器。
  4. 如果调用的是中断门,清除EFLAGS寄存器的IF标志位。
  5. 开始执行中断处理程序。

请注意这两种情况的区别在于压栈数据不同。EFLAGS、CS和EIP一定会压栈。当用户空间中断时,还会将SS和ESP压栈。

这意味着当中断处理程序开始执行时,它拥有下面的堆栈:

 

那么,当控制传递给中断处理程序时,它应该做什么呢?

记住,中断发生在用户空间或内核空间某些代码执行过程的中间,因此首先要做的是在处理中断之前保存被中断过程的状态。过程状态由寄存器的值定义。指令pusha可以将通用寄存器的值保存到栈中。

下一步是修改段寄存器,完全切换到中断处理程序的运行环境。CPU会自动切换CS,因此中断处理程序需要加载4个段寄存器DS、FS、ES和GS。不要忘记保存这些寄存器的原值,并在将来恢复。

当状态已保存、执行环境就绪后,中断处理程序可以开始工作。不过首要的是向PIC发送命令EOI来确认中断。

最后,在完成所有工作之后,中断处理程序要干净的退出。恢复被中断过程的状态(恢复数据段寄存器,执行popa),开启在CPU进入ISR时关闭的中断(sti),并调用iret将控制权返回被中断过程。

下面是基本的ISR处理过程:

  1. 保存被中断过程的状态。
  2. 保存数据段寄存器。
  3. 使用内核数据描述符加载数据段寄存器。
  4. 向PIC发送EOI命令确认中断。
  5. 处理中断。
  6. 恢复数据段寄存器。
  7. 恢复被中断过程的状态。
  8. 启用中断。
  9. 调用iret退出中断处理程序。

5. 汇总

现在可以完成全景图,让我们看看键盘事件是如何被处理的:

  1. 设置中断:
    1. 创建IDT表。
    2. 设置编号为9的IDT条目,将中断门指向键盘ISR。
    3. 使用lidt加载IDT地址。
    4. 发送中断屏蔽码0xfd(11111101)到主PIC,恢复IRQ1。
    5. 使用sti启用中断。
  2. 用户点击键盘按钮。
  3. 键盘控制器在主PIC中引发中断IRQ1。
  4. PIC确认中断IRQ1没有被屏蔽,向CPU发送中断号9。
  5. CPU检查EFLAGS的IF标志位,确认中断未被屏蔽。
  6. (假设当前正在内核模式下运行)
  7. CPU将EFLAGS、CS和EIP压栈。
  8. 如果PIC有错误码,将其压栈。
  9. 查询idtr寄存器指向的IDT,从描述符9中获得段选择子。
  10. 检查特权级别,将段选择器和ISR地址加载到CS:EIP。
  11. 因为IDT条目是中断门,CPU清除IF标志。
  12. 将控制权传递给ISR。
  13. ISR接收中断:
    1. 使用cli屏蔽中断。
    2. 使用pusha保存被中断过程的状态。
    3. 将DS压栈。
    4. 从内核数据段加载DS、ES、FS、GS。
  14. 发送命令EOI(0x20)到主PIC(I/O端口0x20),确认中断。
  15. 从键盘控制器(I/O端口0x64)读取键盘状态。
  16. 如果状态是1,从键盘控制器(I/O端口0x60)读取键码。
  17. 最后,写VGA缓冲区或发送字符到TTY。
  18. 从中断处理程序返回:
    1. 从堆栈中弹出并恢复DS。
    2. 使用popa恢复被中断过程状态。
    3. 使用sti启用中断。
    4. 调用iret退出中断处理程序。

请注意每次敲击按键时,上述步骤都会发生。也别忘了有几十个其他类型的中断,如时钟、网络数据包等。有些中断是悄悄处理的,你可能都没有注意到。你能想象硬件有多快吗?你能想象你使用的操作开发得有多好吗?现在考虑一下,给操作系统作者和硬件设计师一个大大的表扬。

有关x86中断基础的更多相关文章

  1. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  2. 软件测试基础 - 2

    Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功

  3. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  4. ruby - ruby 乘法语句中星号中断语法前的空格 - 2

    在添加一些空格以使代码更具可读性时(与上面的代码对齐),我遇到了这个:classCdefx42endendm=C.new现在这将给出“错误数量的参数”:m.x*m.x这将给出“语法错误,意外的tSTAR,期待$end”:2/m.x*m.x这里的解析器到底发生了什么?我使用Ruby1.9.2和2.1.5进行了测试。 最佳答案 *用于运算符(42*42)和参数解包(myfun*[42,42])。当你这样做时:m.x*m.x2/m.x*m.xRuby将此解释为参数解包,而不是*运算符(即乘法)。如果您不熟悉它,参数解包(有时也称为“spl

  5. ruby - 在 ASP 页面上 Mechanize 中断 - 2

    require'mechanize'agent=Mechanize.newlogin=agent.get('http://www.schoolnet.ch/DE/HomeDE.htm')agent.clicklogin.link_withtext:/Login/然后我得到Mechanize::UnsupportedSchemeError。 最佳答案 Mechanize不支持javascript但您可以将搜索字段添加到表单并为其分配搜索词并使用mechanize提交表单form=page.forms.firstform.add_fie

  6. ruby - 可以正常中断的来自 Rake 的长时间运行的 shell 命令? - 2

    在几个项目中,我希望有一个类似rakeserver的rake任务,它将通过任何需要的方式开始为该应用程序提供服务。这是一个示例:task:serverdo%x{bundleexecrackup-p1234}end这行得通,但是当我准备停止它时,按Ctrl+c并没有正常关闭;它中断了Rake任务本身,它说rakeaborted!并给出堆栈跟踪。在某些情况下,我必须执行Ctrl+c两次。我可能可以用Signal.trap写一些东西来更优雅地中断它。有没有更简单的方法? 最佳答案 trap('SIGINT'){puts"Yourmessa

  7. ruby-on-rails - prawnto 显示新页面时不会中断的表格 - 2

    我有可变数量的表格和可变数量的行,我想让它们一个接一个地显示,但如果表格不适合当前页面,请将其放在下一页,然后继续。我已将表格放入事务中,以便我可以回滚然后打印它(如果高度适合当前页面),但我如何获得表格高度?我现在有这段代码pdf.transactiondopdf.table@data,:font_size=>12,:border_style=>:grid,:horizontal_padding=>10,:vertical_padding=>3,:border_width=>2,:position=>:left,:row_colors=>["FFFFFF","DDDDDD"]pdf.

  8. ruby-on-rails -/usr/local/lib/libz.1.dylib,文件是为 i386 构建的,它不是被链接的体系结构 (x86_64) - 2

    在我的mac上安装几个东西时遇到这个问题,我认为这个问题来自将我的豹子升级到雪豹。我认为这个问题也与macports有关。/usr/local/lib/libz.1.dylib,filewasbuiltfori386whichisnotthearchitecturebeinglinked(x86_64)有什么想法吗?更新更具体地说,这发生在安装nokogirigem时日志看起来像:xslt_stylesheet.c:127:warning:passingargument1of‘Nokogiri_wrap_xml_document’withdifferentwidthduetoproto

  9. ruby - libxml-ruby 无法在 x86_64 上加载 - 2

    我们在服务器端遇到libxml-rubygem的问题可能是因为它使用x86_64架构:$uname-aLinuxip-10-228-171-642.6.21.7-2.fc8xen-ec2-v1.0#1SMPTueSep110:25:30EDT2009x86_64GNU/Linuxrequire'libxml'LoadError:/usr/local/ruby-enterprise/lib/ruby/gems/1.8/gems/libxml-ruby-1.1.4/lib/libxml_ruby.so:invalidELFheader-/usr/local/ruby-enterprise/

  10. 【网络】-- 网络基础 - 2

    (本文是网络的宏观的概念铺垫)目录计算机网络背景网络发展认识"协议"网络协议初识协议分层OSI七层模型TCP/IP五层(或四层)模型报头以太网碰撞路由器IP地址和MAC地址IP地址与MAC地址总结IP地址MAC地址计算机网络背景网络发展        是最开始先有的计算机,计算机后来因为多项技术的水平升高,逐渐的计算机变的小型化、高效化。后来因为计算机其本身的计算能力比较的快速:独立模式:计算机之间相互独立。    如:有三个人,每个人做的不同的事物,但是是需要协作的完成。    而这三个人所做的事是需要进行协作的,然而刚开始因为每一台计算机之间都是互相独立的。所以前面的人处理完了就需要将数据

随机推荐