Enterprise Just Builder

Solution for Enterprise Software Architecture

Linux Kernel

X64 寄存器、栈帧和汇编

多年以来人们一直使用 X86 汇编编写性能关键代码。随着 32位 CPU 逐渐被 64 位处理器取代,底层汇编代码也随之发生变化。本文将简单介绍 X64 汇编一些相关知识。了解 X86 汇编虽然有助于了解这种过渡,不过不是必要前提。http://www.ejb.cc/wp-admin/profile.php

X64 泛指 Intel/AMD 的 32 位 X86 指令集架构 (ISA) 的 64 位扩展。AMD 率先推出了第一代 X64 指令集。即 X86-64,后来被命名为 AMD64。Intel 的实现称为 IA-32e ,后来又改为 EMT64。这两个版本大致相同,只在一些细微处略有不兼容。详细可以参考 《Intel® 64 and IA-32 Architectures Software Developer’s Manuals》 和 《AMD64 Architecture Tech Docs》。本文中将其统称为 X64 ,以区分 Intel 64 位安腾架构 IA-64。

本文将不涉及缓存、分支预测等等与硬件相关的内容,如果读者感兴趣,可以参考本文列出的一些参考文章。1

汇编常被用来编写程序性能关键部分,虽然大部分程序员还是宁可选择好的 C++ 编译器。掌握汇编对代码调试十分有用,有时候编译器会产生错误的汇编代码,通过调试器单步执行代码可以有助于了解错误的原因;代码优化有时也会产生错误。如果手边没有源代码,你还可以通过汇编/反汇编来了解或修正手边的程序;此外如果你想深入了解语言的内部机制,比如方法 A为什么比方法 B 速度快,这时候汇编就十分必要;最后,在诊断恶意代码时,汇编也不可或缺。

架构
要学习平台汇编,首先就得了解寄存器集。

>>> 阅读全文

 

, ,

Linux和Windows设备驱动架构比较

本文将考查最为广泛使用的 Windows 和 Linux 操作系统的设备驱动架构。我们首先将展示并比较在这些操作系统中实现设备驱动所需的组件,然后讨论实现驱动程序的过程,包括对内核缓冲区执行 I/O 操作。在文章的末尾我们还将了解这些操作系统所提供的开发环境和基础工具。1

1.引言
现代操作系统一般都包含多个模块,如内存管理器、进程调度器、硬件抽象层和安全管理器等等。想进一步了解这两种操作系统的内核,这里有一些参考资料:关于 Windows 内核的细节,可翻阅 Russinovich 著的《Windows Internal》一书;而 Linux 内核则可以参考 Rusling 的《The Linux Kernel》或者 Beck et al 等合著的《Linux Kernel Internals》。对和各类型硬件设备交互来说,内核可被视为一个黑盒。那么能否将和所有已知设备交互的功能内建到内核中呢?有可能,但没有实际意义,因为这会无谓地消耗太多系统资源。

内核模块化
对于各种新设备,内核无法在创建时就预知如何与之交互。现代操作系统的内核允许通过运行时添加设备驱动模块来扩展系统功能,该模块实现的功能使得内核可以与某种特定的新设备交互。这些模块通常都会实现一个例程供内核在模块被加载时调用,此外还有例程则在模块被移除时调用。模块还会实现各种不同的例程以便实现 I/O 功能,如将数据传送到设备或从设备接收数据;同时还有例程以便向设备发送 I/O 控制指令。以上所述对 Linux 和 Windows 两种驱动架构均适用。

本文的组织结构
本论文分成下面几节:

  • 对两种驱动架构的一般介绍 (第二节)
  • 详细论述两种驱动架构的各个组件 (第三节)
  • 驱动程序范例,包括如何对内核缓冲区执行 I/O 操作(第四节)
  • 相关驱动开发环境和基础工具(第五节)

相关著作
Windows 设备驱动架构相关文档可在 Windows 驱动开发包(DDK)中找到. 此外 Walter Oney 和Chris Cant 在 《Writing Windows WDM Device Drivers》一书中对 Windows 驱动架构进行了详细介绍。Linux 设备驱动架构可以参考 Rubini 的《Linux Device Drivers》,该书可免费获取。

>>> 阅读全文

 

, , , , ,

理解 ACPI (一) ACPI 简介及加电过程

在引入高级配置和电源接口 (ACPI — Advanced Configuration and Power Interface) 之前, 高级电源管理 (APM) BIOS 被广泛用于电源管理。在 APM 中,大部分的电源管理控制逻辑都驻留在 APM BIOS 代码中。支持 APM 的操作系统通过固定的 BIOS API 与 APM BIOS 进行通信,这些 API 提供了 BIOS 功能的基本访问。支持 APM 的操作系统必须定期轮询 APM 以处理 APM 相关事件。

APM 存在着以下三个缺陷: 首先,除非供应商提供专有程序,否则很多 APM 功能都要在操作系统启动前,进入 BIOS 菜单来配置,比如设置显示器关闭前的空闲时间。而且 APM 电源管理配置的具体策略是由 BIOS 供应商设定的,比如某 APM BIOS 的策略是在关闭显示器的同时降低 CPU 时钟频率或休眠网卡等设备,如果不想这么做,就必须修改 BIOS。 其次,APM 是 BIOS 级别的代码,运行在操作系统之外,因此开发和调试 APM 代码都十分困难,更糟糕的是用户只能通过重刷 ROM 的方式来解决 APM 的错误,而重刷 BIOS 相当危险,一旦发生错误,系统就再无法启动了。最后,由于 APM 是各个供应商专有的,为相同的功能而进行的重复开发投入是种巨大浪费。

不同于 APM 直接基于 BIOS 的管理方式,ACPI 中不再由 BIOS 代码来作出决策,而是通过操作系统内核来统一管理所有设备,从而解决了 APM 配置机制的局限性,换句话说,ACPI 允许操作系统直接对电源和散热进行管理,还允许系统硬件产生的 Hot-Plug 事件,让操作系统从用户的角度上直接支配即插即用设备。因而 ACPI 比其他机制更加灵活。

ACPI 主要提供下列功能:1

  • 系统电源管理(System power management):ACPI 提供了让系统作为一个整体进入或离开睡眠状态的机制,让设备唤醒系统。
  • 设备电源管理(Device power management):ACPI 表描述了电源信息(包括主板设备、设备电源状态以及设备所连接的电源层– Power Planes) 和让设备进入低功耗状态的控制信息,这样操作系统就可根据资源占用来控制设备进入低功耗状态。
  • 处理器电源管理(Processor power management):当操作系统处于空闲且非睡眠状态,可以通过 ACPI 提供的命令让处理器进入低功耗状态。
  • 设备和处理器性能管理(Device and processor performance management):操作系统在工作时可以根据 ACPI 的定义,让设备和处理器进入不同的性能状态
  • 配置/即插即用(Configuration/Plug and Play):ACPI 指定了用来枚举和配置主板设备的信息,这些信息按层次结构排列,当发生诸如 docking/undocking 之类的事件时,操作系统就可准确知道该事件影响了哪个设备。
  • 系统事件(System Event): ACPI 提供了通用的事件机制操作用于处理如散热、电源管理、坞站或设备的插拔等事件。
  • 电池管理(Battery management):兼容 ACPI 的电池设备需要具有智能电池子系统接口(Smart Battery subsystem interface)或者控制型电池接口(Control Method Battery interface),前者通过嵌入式控制器的接口控制,后者则完全由 AML 控制方法进行操控。
  • 温度管理(Thermal management):ACPI 允许 OEM 自由定义散热温度区间。
  • 嵌入式控制器(Embedded Controller):ACPI 提供了标准接口让操作系统和嵌入式控制器进行通讯。
  • SMBus 控制器(SMBus Controller): ACPI 提供了标准接口让操作系统和 SMBus 进行通讯。

当然为此操作系统内核中要添加额外的 ACPI 支持。支持 ACPI 的操作系统就可以叫做 OSPM (Operating System-directed configuration and Power Management) 。 支持 OSPM 的至少要求实现:

>>> 阅读全文

 

, , , ,

USB 协议详解之一 基础知识

在 USB 出现前,计算机上就已经存在众多接口标准,比如串口、并口、PS2 等等,各接口从硬件形状和软件设置都不兼容,因此 USB 的设计目标之一,就是希望通过它方便地实现设备之间的互联。从而解决接口标准太多的弊端,这也是 USB 中的 Universal 一词的含义。

USB 使用一个 4 针的标准插头,采用菊花瓣形式把所有外设连接起来,它使用串行方式传输数据,支持多数据流和多个设备并行操作并允许外设热插拔。

基础知识
从物理结构上,USB 系统是一个分层的星形结构,它包含三类硬件设备:主机(HOST)、 设备(DEVICE)和集线器(HUB)。处于每个星形拓扑中央的是集线器,而在主机与集线器之间,集线器与集线器之间,集线器与设备之间都是点对点连接。1

各个设备在系统中的作用如下:

  • USB 主机 的主要作用包括: 提供电源,同时进行节电管理;检测设备,包括设备的接入与断开;管理数据传输,包括控制流和数据流;发送配置请求对 USB 设备进行配置操作;对总线上的错误进行管理和恢复等等。
  • USB 设备:在一个 USB 系统中,设备和集线器的总数不能超过 127 个。USB 设备接收 USB 总线上所有数据包,通过数据包的地址域来判断是不是发给自己的数据包:若地址不符,则简单地丢弃该数据包;若地址相符,则通过响应主机的数据包来和 USB 主机进行数据传输。
  • USB 集线器:集线器用于设备扩展连接,所有 USB 设备都连接在 USB 集线器的端口上。一个 USB 主机总与一个根集线器 (USB ROOT HUB)相连。USB 集线器为其每个端口提供 100mA 电流供设备使用。同时,USB 集线器可以通过端口的电气变化来判断出设备的插拔操作,并通过响应 USB 主机的数据包把端口状态汇报给主机。一般来说,USB 设备与USB 集线器间的连线长度不能超过 5米,USB 系统的级联不能超过5层(包括ROOT HUB)。

USB 功能
USB 是一种串行接口协议,它依靠 D+/D- 两条数据线构成的差分线来进行数据传输,这和我们熟悉两线 RS232/485 协议有何区别呢?了解这种区别有助于深入理解 USB 。所以首先让我们回想一下 RS232 的数据是如何传送的,如图1-2:

>>> 阅读全文

 

, ,

Linux环境下的图形系统和AMD R600显卡编程(7)——AMD显卡的软件中断

CPU上处理的中断可以分成“硬件中断”和“软件中断”两类,比如网卡产生的中断称为硬件中断,而如果是软件使用诸如”int 0x10″(X86平台上)这样的指令产生中断称为软件中断,硬件中断是异步的,其发生的时机是不可知的,但是软件中断是同步的,CPU是“确切”知道其发生的时机的。

  同样的,在GPU看来,中断也可以分成“硬件中断”和“软件中断”两类,比如热插拔事件或者vblank事件都会产生“硬件中断”,这些事件在GPU看来是异步的,GPU不知道这些事情何时发生。GPU也可以使用类似CPU的int指令那样产生中断,考虑这样一种情形:驱动向硬件发送了绘图命令后必须等到硬件执行完了这些命令后才能进行后续的操作,否则硬件的上一次命令没有执行完就继续执行下一次命令会导致错误。前面介绍scratch寄存器的时候提及过可以在命令末尾添加一条写scratch寄存器的命令,发送命令之后驱动使用轮询的方式轮询scratch寄存器,当然这种场合使用轮询肯定是不合适的,实际上显卡可以采用软中断机制,在完成绘图命令后执行一个类似“int xx”的命令产生中断,这里GPU是“确切”知道中断发生的时机的—-即在绘图命令完成的时候。

  前面提到的fence就是这种“软件中断”的具体应用。

  在上一篇blog中看到,fence是按照下面的步骤使用的:

  radeon_fence_create->radeon_fence_emit->radeon_fence_wait

>>> 阅读全文

 

Linux环境下的图形系统和AMD R600显卡编程(5)——AMD显卡显命令处理机制

  通常通过读写设备寄存器对设备进行编程,在X86系统上,有专门的IO指令进行编程,在其他诸如MIPS、SPARC这类系统上,通过将设备的寄存器映射到内存地址空间直接使用读写内存的方式对设备进行编程。

  Radeon显卡提供两种方式对硬件进行编程,一种称为“推模式”(push mode)即直接写寄存器的方式,另一种称为拉模式,这篇blog讨论拉模式,这也是驱动中使用的模式。

  在拉模式下,驱动使用命令流(Command Stream)的形式进行对显卡编程:驱动程序将需要对显卡进行配置的一连串命令写入命令缓冲区,写完之后进入让出处理器,显卡按照命令写入的顺序执行这些命令,执行完成后触发中断通知驱动。CPU将这些命令放入一个称为命令环的环形缓冲区中,命令环是GTT内存中分出来的一片内存,驱动程序往命令环中填充命令,填充完后通知GPU命令已经写入命令,GPU的命令处理器CP(Command Processor)。上一篇博客即是通过ring环内存的使用来说明如何在系统中分配内存以及建立映射关系的。

  驱动写入的命令流由命令处理器CP进行解析,具体来说,CP完成以下工作:
•接收驱动程序的命令流。驱动程序将命令流先写入系统内存,然后由CP通过总线主设备访问方式进行获取,当前支持三种命令流,除了前面说的环形缓冲命令流,还有间接缓冲1命令流和间接缓冲2命令流;
•解析命令流,将解析后的数据传输给图形控制器的其他模块,包括3D图形处理器、2D图形处理器、视频处理器。

命令环缓冲区

>>> 阅读全文

 

Linux 图形系统和 AMD R600 显卡编程(4)——AMD显卡显存管理机制

显存可以分为两部分,一部分是显卡自带的内存称为 VRAM 内存,另外一部分则是系统主存称为 GTT 内存(Graphics Translation Table,GTT 和后面的 GART 含义相同,都是指显卡的页表,GTT 内存可以就理解为需要建立 GPU 页表的显存)。在嵌入式系统或者集成显卡上,显卡通常是不自带显存的,而是完全使用系统内存。通常显卡上的显存访存速度数倍于系统内存,因而许多数据如果是放在显卡自带显存上,其速度将明显高于使用系统内存的情况(比如纹理,OpenGL 中分普通纹理和常驻纹理)。

某些内容是必须放在 VRAM 中的,比如最终用于显示的“帧缓存”,以及后面说的页表 GART (Graphics Addres Remapping Table),另外有一些比如后面将介绍的命令环缓冲区(Ring Buffer)是要放在 GTT 内存中的。另一方面,VRAM 内存是有限的,如果 VRAM 内存使用完了,则必须将一些数据放入 GTT 内存中。

通常 GTT 内存是按需分配的,而且是给设备使用的,比如 radeon r600 显卡最多可以使用 512M 系统内存( Linux 内核中是这样设置的),一次性分配 512M 连续的给设备用的内存在 linux 系统中是不可能成功的,而且即使可以成功,有相当多的内存是会被浪费掉的。按照按需分配的原则,使用多少就从系统内存中分配多少,这样得到的 GTT 内存在内存中肯定是不连续的。GPU 同时需要使用 VRAM 内存和 GTT 内存,最简单的方法就是将这两片内存统一编址(这类似 RISC 机器上 IO 和 MEM 统一编址),VRAM 是显卡自带的内存,其地址一定是连续的,但是不连续的 GTT 内存如果要统一编址,就必须通过页表建立映射关系了,这个页表被称为 GTT 或者 GART,这也是这些内存被称为 GTT 内存的原因。

和 CPU 端地址类似,我们将 GPU 使用的地址称为“GPU虚拟地址”,经过查页表之后的地址称为“GPU物理地址”,这些地址是 GPU 最终用于访存的地址,由于 GPU 挂接在设备总线上,因此这里的“GPU物理地址”就是“总线地址”,当然落在 VRAM 区域的内存是不用建页表的,这一片内存区域的地址我们只关心其 “GPU 虚拟地址”。

R600 显卡核心存管理有关的寄存器如表4-1示,目前并没有找到完整的描述这些寄存器的手册,表中的数据根据阅读代码获取到。

>>> 阅读全文

 

Linux 图形系统和AMD R600显卡编程(3)——AMD显卡简介

早期的显卡仅用于显示,后来显卡中加入了 2D 加速部件,这些部件用于做拷屏,画点,画线等操作。随着游戏、三维模拟以及科学计算可视化等需要,对 3D 的需求逐渐增加,早期图形绘制工作由 CPU 来完成,要达到真实感和实时效果,只能绘制一些简单的线框模型,上世纪80年代,斯坦福大学的 Jim Clark 教授率先提出用专用集成电路技术实现一个专用的 3D 图形处理器的设想,于 1984 年推出了世界上第一个通用图形工作站 IRIS1400。

AMD 最早的显卡从 R100 开始,一直到 R900(R600 以后也使用HD xxxx作为代号),R900(HD 6xxx)之后是HD 7xxx系列,目前最新的显卡使用 Rx 2xx代号,最新的是 Radeon R9 2xx 系列。这里只描述 R600 显卡的编程,因此只讨论 R600 之前和之后不久的显卡。

AMD 显卡的演变过程如下(参考wiki页和 Plan9 操作系统开发人员的PPT):

R100 是一款固定渲染管线的显卡,R200 采用了可编程处理器,R300 在 R200 的基础上发生了比较大的变化,此后的 R400 和 R300 差别不大。到 R500 的时候 GPU 除了有 vbios 外,还引入了 atombios,atombios 是一段比较简单的脚本,和具体平台无关,解释器解释执行(代码集成在内核驱动里面),然而 3D 核较 R300 变化不大,寄存器变化也比较少(主要变化在像素着色器部分)。

R600 在 R500 的基础上发生很大的变化,2D 部件被废除,3D 部件改成了统一着色器架构,GPU 的寄存器和原来完全不同,由于体系结构的变化,对硬件编程也和原来有了很大变化。R700 GPU 是 R600 的优化版,在驱动层面上,R700 的编程和 R600 的编程基本上是一样的,后续的 Evergreen、Southern Island 和 Northern Island 都是这种统一着色器架构的延续,因此理解 R600 的编程后将比较容易理解后续GPU核的编程。

>>> 阅读全文

 

Linux 图形系统和AMD R600显卡编程(2)——Framebuffer、DRM、EXA和Mesa简介

原文

Framebuffer 在 Linux 中是作为设备来实现的,它是对图形硬件的一种抽象,代表着显卡中的帧缓冲区(Framebuffer)。通过Framebuffer 设备,上层软件可以通过一个良好定义的软件接口访问图形硬件,而不需要关心底层图形硬件是如何工作的,比如,上层软件不用关心应该如何读写显卡寄存器,也不需要知道显卡中的帧缓冲区从什么地址开始,所有这些工作都由 Framebuffer去 处理,上层软件只需要集中精力在自己要做的事情上就是了。

Framebuffer 的优点在于它是一种低级的通用设备,而且能够跨平台工作,比如 Framebuffer 既可以工作在 x86 平台上,也能工作在 PPC 平台上,甚至也能工作在 m68k 和 SPARC 等平台上,在很多嵌入式设备上 Framebuffer 也能正常工作。诸如 Minigui 之类的 GUI 软件包也倾向于采用 Framebuffer 作为硬件抽象层(HAL)。

从用户的角度来看,Framebuffer 设备与其它设备并没有什么不同。Framebuffer 设备位于/dev下,通常设备名为fb*,这里*的取值从0到31。最常用到的 Framebuffer 设备是 /dev/fb0。通常,使用 Framebuffer 的程序通过环境变量 FRAMEBUFFER 来取得要使用的Framebuffer 设备,环境变量 FRAMEBUFFER 通常被设置为”/dev/fb0”。

从程序员的角度来看,Framebuffer 设备其实就是一个文件而已,可以像对待普通文件那样读写 Framebuffer 设备文件,可以通过 mmap() 将其映射到内存中,也可以通过 ioctl() 读取或者设置其参数,等等。最常见的用法是将 Framebuffer 设备通过 mmap() 映射到内存中,这样可以大大提高IO效率。

>>> 阅读全文

 

,

Linux 图形系统和 AMD R600 显卡编程(1) —— Linux环境下的图形系统简介

Linux/Unix 环境下最早的图形系统是 Xorg 图形系统,Xorg 图形系统通过扩展的方式以适应显卡和桌面图形发展的需要,然而随着软硬件的发展,特别是嵌入式系统的发展,Xorg 显得庞大而落后。开源社区开发开发了一些新的图形系统,比如 Wayland 图形系统。

由于图形系统、3D 图形本身的复杂性以及历史原因,Linux 图形系统相关的源码庞大而且复杂,而且缺少学习的资料(所有源代码分析或者驱动编程的书籍都很少介绍显卡驱动)。在后续一系列文章中,笔者将从对 AMD 硬件编程的角度出发对部分问题做一个简单的介绍,当然,这种介绍是很初级的,旨在希望能够对初学着有所帮助。

内核 DRM、Xorg 以及 Mesa 三部分加起来的代码和整个 Linux 内核的体量是差不多大的。而且现代的显卡上的 GPU 的复杂程度在一定程度上可以和 CPU 相聘美,从程序员的角度看,操作系统(CPU 的驱动)包含的许多功能在 GPU 驱动上都能够找到。比如说,GPU 有自己的指令系统,着色器程序(GLSL、HLSL、Cg 这类着色语言)需要编译成 GPU 的指令集,然后才能够在 GPU 上运行,符合着色语言规范的 3D 驱动都包含这样的一个编译器。3D 应用程序需要使用大量内存,GPU 在进行运算时需要访问这些内存,所以 GPU 访问内存时也使用一套和 CPU 页表一样的机制。另外,在中断系统上,GPU 和 CPU 也有相似之处。后面的一些内容将会陆续对这些问题做一个简单的介绍。

传统上 Linux 是一个宏内核,设备驱动大部分都是包含在内核里面的,内核代码最庞大的部分就是 drivers 目录,如果你从 kernel.org 上面下载一个内核源码,你会发现编译时间大部分都耗在编译设备驱动上。

对于微内核操作系统,设备驱动不是内核的部分。不过出于调试方便以及其他一些原因,Linux 操作系统上面的一些驱动是放在核外的。比如说打印机、扫描仪这类设备,当前的打印机扫描仪通常都是通过 USB 接口连接到计算机上的,对于这些设备的 Linux 驱动,除了 USB 核心部分在内核,这些打印机扫描仪本身的驱动都是在核外的。Linux 上面的打印机使用 CUPS 系统,CUPS 运行在核外,其驱动是按照 CUPS 的接口来开发的。Linux上面的扫描仪使用的则是运行在核外的 sane 系统,其驱动是以动态链接库的形式存在的。另外一类核外的驱动是图形系统的驱动,由于图形系统、显卡本身比较复杂,加上一些历史原因,图形系统的驱动在核内核外都有,且显卡驱动最主要的部分在核外。

>>> 阅读全文

 

, , , , , , , ,

X86/x64 架构的系统地址表初始化(2): 基于 PCIe 的系统

如果读者对 PCI 总线协议还不太了解,建议您先阅读上一篇文章:《X86/x64 架构的系统地址表初始化(1): 基于 PCI 的系统》,以获得足够的背景知识方便理解本文。1

我们在第一篇文章中讨论了基于 PCI 的 X86/X64 系统的系统地址表的初始化。本文我们将关注更现代的系统,即基于 PCI Express 的 X86/X64 系统。后文 PCI Express 统一简称为 PCIe,以和 PCI Express 规格书保持一致。

我们将要研究基于 PCIe 的 X86/X64 系统的系统地址表初始化过程。和上一篇文章一样,重点在于理解 PCIe 总线协议的地址映射机制。要理解在 PCIe 系统上如何访问 PCI 扩展 ROM, 了解地址映射十分重要。

在物理层上 PCIe 完全不同于 PCI。但在逻辑层上,PCIe 只是 PCI 的扩展。事实上,在 PCIe 平台上可以引导专为 PCI 总线设计的操作系统,只要该 OS 支持 PCI 总线规范,就不会有问题。PCIe 是 PCI 扩展也意味着要理解 PCIe 协议,就要先熟悉 PCI 总线协议。这就是我建议您先阅读第一篇文章的原因。

约定
本文使用以下约定:

>>> 阅读全文

 

, , , , , ,

X86/X64 架构的系统地址表初始化 (1):PCI 总线系统

去年我在《信息安全杂志》上发表了“恶意的 PCI 扩展 ROM”一文,文中并未对 PCI 扩展 ROM 1的地址映射展开讨论,这篇文章2在此作出补充。系统程序员常对设备内存(如 PCI 设备内存)是如何映射到系统地址空间感到疑惑不已。本文详细论述了系统地址表的初始化过程,尤其是那些控制着设备内存映射到系统地址表的 PCI 芯片寄存器的初始化。注意:只有带内存的 PCI 设备才有地址映射的要求,比如显卡、带有板载缓冲区或支持 PCI 扩展 ROM 的网卡等。

由于 X86/X64 架构的总线协议必须保持向后兼容,因此其系统地址表十分复杂。系统所使用的总线协议规定了总线设备的内存到系统地址表的地址映射关系。因此,要了解系统地址表的初始化,就必须了解特定的总线协议的地址映射机制。本文重点关注基于 PCI 总线协议的系统。以当今的的标准来衡量,PCI 总线协议略显老旧了。但是,弄清楚它在软件/固件的最底层中所起到的作用仍非常重要。不了解 PCI 总线协议,就无法理解后续的 PCI Express (PCIe) 总线协议。而 PCIe 现在基本成为 X86/X64 系统的主流总线协议。 本系列文章的第 2 部分将讨论基于 PCIe 的系统。

约定
本文常常可见“内存”或“存储器”这个词,且含义不尽相同。为了避免困惑,本文约定如下:

  • “主存储器” 或者 “主存” 指的是安装在主板上的 RAM 模块。
  • “内存控制器” 指的是 CPU 或者芯片组中用以控制和存取 RAM 模块的部分
  • “可刷新存储器” 指的是主板上存储 BIOS/UEFI 的芯片或者存储 PCI 扩展 ROM 内容的芯片。
  • “内存范围” 或 “内存地址范围” 指的是设备在 CPU 内存空间所占用的范围,即从基地址(起始地址)到结束地址的范围(基地址+内存大小)
  • “内存空间”指的是 CPU 所能访问的内存地址集,即 CPU 所能寻址的内存。这里的内存包括 RAM、ROM 以及其它 CPU 所能寻址的存储器。
  • “PCI 扩展 ROM” 除非另有定义,否则指的是 PCI 设备上的 ROM 芯片。

启动过程概述
本节将详细解释系统启动过程,这有助于后续了解系统地址表及其它与总线协议相关的内容。在涉及这些内容之前,你需要对启动过程有清晰的认识。

X86/X64 的启动从执行平台固件(BIOS/UEFI)开始。执行平台固定比启动操作系统要早,甚至是在 “启动加载器” 加载并执行操作系统之前。平台固件的执行过程可以简单概括如下:

>>> 阅读全文

 

, ,

Linux 设备驱动(一) PCI 设备枚举

本文将讨论 Linux 内核如何初始化系统的 PCI 总线和 PCI 设备。所谓 PCI — Peripheral Component Interconnect 即外部设备互联,PCI 标准是描述如何将系统中的设备以一种结构化、可控制的方式连接在一起的规范,包括系统外部设备的连接方式以及符合标准的设备组件应具备的行为规范。PCI 总线作为处理器系统的局部总线,主要目的是为了连接外部设备。较之前的存在其他总线如 ISA、EISA 和 MCA 总线相比,PCI 具有许多突出的优点:

  • PCI 总线空间与处理器空间隔离。PCI 设备具有独立的地址空间,该空间与存储器地址空间通过 HOST 主桥隔离。
  • 可扩展性。在 PCI 总线中,HOST 主桥可以通过 PCI 桥(PCI Bridge)扩展出一系列 PCI 总线,形成以 HOST 主桥为根节点的 PCI 总线树。该总线树上最多能挂接 256 个 PCI 设备(包括 PCI 桥)。
  • 动态配置机制。每个 PCI 设备都有独立的配置空间,包含设备在 PCI 总线中使用的基地址。基地址由系统软件动态配置并保证唯一性,从而解决了地址冲突的问题,实现了“即插即用” 的功能。PCI 桥的配置空间中则含有其 PCI 子树所能使用的地址范围。
  • 总线带宽。与之前的局部总线相比,PCI 总线极大提高了数据传送带宽,如 32位/33MHz 的 PCI 总线可以提供 132MB/s 的峰值带宽,而 64位/66MHz 的 PCI 总线可提供的峰值带宽为 532MB/s。
  • 共享总线的仲裁机制。PCI 设备通过仲裁获得 PCI 总线的使用权后才能进行数据传送;在 PCI 总线上进行数据传送,并不需要处理器进行干预。
  • 中断机制。PCI 总线上的设备可以通过四根中断请求信号线 INTA~D# 向处理器提交中断请求。PCI 总线上的设备可以共享这些中断请求信号。

一、PCI 总线组成结构
PCI 总线作为处理器系统的局部总线,是处理器系统的一个组成部件。讲述 PCI 总线的组成结构不能离开处理器系统这个大环境。在一个处理器系统中,与 PCI 总线相关的模块如图1‑1所示。

在一个处理器系统中,与 PCI 总线相关的模块包括:HOST 主桥、PCI 总线、PCI 桥接器和 PCI 设备。对于一些简单的系统,可能不含有 PCI 桥接器,这时所有设备都连接在 HOST 主桥引出的 PCI 总线上;而有些处理器系统中可能有多个 HOST 主桥,比如图1‑1中的处理器系统中就含有 HOST 主桥 X 和 HOST 主桥 Y。

PCI 总线由 HOST 主桥和 PCI 桥接器引出,HOST 主桥与主内存控制器在同一级总线上,PCI 设备可以方便地通过 HOST 主桥访问主内存,即进行 DMA 操作。

值得注意的是,PCI 设备的 DMA 操作需要与处理器系统的 Cache 进行一致性操作,当 PCI 设备通过 HOST 主桥访问主内存时,Cache 一致性模块将进行地址监听,并根据监听结果改变 Cache 的状态。

>>> 阅读全文

 

, ,

Linux 内核简介之虚拟文件系统

虚拟文件系统又称虚拟文件系统交换器(Virual Filesystem Switch ,简称 VFS)。之所以说是“虚拟”,是因为VFS是一个抽象层,其所有数据结构都是在运行以后才建立,并在卸载时删除,磁盘上并不实际存储这些数据结构。VFS 提供了操作文件 目录和对象的统一方法,即通用文件模型。但显然如果只有 VFS,系统是无法工作的,因为这些数据结构只有和实际的文件系统,如 Ext2、Minix、VFAT 等相结合才有意义。相对的, Ext2、Minix、VFAT 等则称为“具体文件系统”。具体文件系统必须提供与 VFS 定义的结构适配的例程。所以对于基于完全不同概念的文件系统如XFS或Reiser,适配会相当困难,而如 EXT2 则会有性能提升。

Linux 内核可以支持40多种文件系统,主要分为三大类 磁盘文件系统,虚拟文件系统 如 proc 文件系统 及网络文件系统。

VFS 是 Linux 内核的一个子系统,内核中的其它子系统只与 VFS 打交道,而并不与具体文件系统发生联系。对具体文件系统来说,VFS 是一个管理者,而对内核的其它子系统来说,VFS 是它们与具体文件系统的接口,整个 Linux 中文件系统的逻辑关系如图1-1所示:

VFS 提供了一个统一的接口(实际上就是 file_operatoin 数据结构,稍后介绍)。一个具体文件系统要想为 Linux 所支持,就必须按该接口编写自己的操作函数,从而将细节对内核其它子系统隐藏起来。因此对内核其它子系统以及运行在操作系统之上的用户程序而言,所有的文件系统都是透明的。实际上,要支持一种新的文件系统,主要任务就是实现这些接口函数。

总的来看 VFS 主要作用有:

>>> 阅读全文

 

, ,

FreeBSD 的 ACPI 实现

几乎所有现代计算机系统的硬件都允许对电源使用进行管理、对系统温度进行监测,并将之维持在适当水平。由此一定水平的电源消耗就能带来最佳的性能。这对于使用电池的移动平台尤为重要,因为不必要的电源消耗会减少电池续航时间,在结束工作前,你就不得不重新充电。节电对桌面系统也同样重要,在系统空闲时关闭显示器、磁盘驱动器等设备显著可以降低能耗。1

不幸的是,在传统 PC 中, 用于电源及散热配置管理的大多数软件都与 BIOS 紧密相关。而且,除非受管的设备所连接的是即插即用总线(如 PCI),否则操作系统就很难便捷地检测、配置或管理设备。例如 ISA PnP 机制适用于枚举外置 ISA 卡设备而不是板载设备。而 PnP BIOS 则适用于枚举板载设备,但也很难扩展成通用方法。另外 PnP BIOS 是 16 位的,所以操作系统必须通过 16 位虚拟环境才能调用 PnP BIOS 的函数。

在引入高级配置和电源管理接口 (ACPI — Advanced Configuration and Power Management Interface) 前, 高级电源管理 (APM) BIOS 被广泛用于电源管理。在 APM 中,大部分的电源管理控制逻辑都驻留在 APM BIOS 代码中。支持 APM 的操作系统通过固定的 BIOS API 与 APM BIOS 进行通信,这些 API 提供了 BIOS 功能的基本访问。支持 APM 的操作系统必须定期对 APM 进行轮询以处理 APM 相关事件。APM BIOS 还可使用特殊系统管理中断,该中断对操作系统是不可见的。APM 提供了四种状态:运行、暂停、休眠和软关闭。

有三大因素制约着 APM 的使用。首先,除非供应商提供特殊的程序,否则很多 APM 功能都要在操作系统加载前通过供应商专有的 BIOS 菜单进行配置,比如设置关闭显示器前的系统空闲时间。此外,实施 APM 电源管理配置的具体策略是由 BIOS 供应商指定的。例如,某 APM BIOS 的策略是在关闭显示器的同时也降低 CPU 时钟频率或关闭网卡等设备,如果不修改 BIOS ,你就无法修改这一策略。

其次,APM 是 BIOS 级别代码,运行在操作系统的范围外。这使得开发和调试 APM 代码极其困难。而用户也只能通过烧写 ROM 的方式来解决 APM 的错误。重刷 BIOS 相当危险,因为一旦发生错误,系统就再无法使用了。

>>> 阅读全文

 

, , , , ,

Previous Posts