【嵌入式硬件】Linux在x86与ARM架构上的设备树需求差异:技术原理与历史演进

Linux在x86与ARM架构上的设备树需求差异:技术原理与历史演进

引言:

在Linux内核开发和嵌入式系统领域,设备树(Device Tree)是一个经常被讨论的话题。有趣的是,Linux在不同硬件架构上对设备树的依赖程度存在显著差异:在ARM架构上,设备树已成为标准配置方法,而在x86架构上,系统却能够在没有设备树的情况下正常工作。这种差异不仅反映了两种架构的技术特点,也体现了计算机体系结构发展的历史轨迹和设计哲学。

本文将深入探讨为什么Linux在ARM平台上需要设备树,而在x86平台上却不需要,分析背后的技术原理、历史演变以及未来发展趋势。通过这种分析,我们可以更好地理解不同计算机架构的设计理念,以及Linux内核如何适应多样化的硬件平台。

一句话总结,:x86有一个叫acpi的高级设备树,同时pcie总线还有自枚举功能,多数x86外设都挂在pcie总线上

设备树的基本概念:

设备树的定义与结构

设备树是一种描述硬件的数据结构,它以树状结构表示计算机系统中的硬件设备及其属性。在Linux中,设备树通常以文本形式(DTS,Device Tree Source)编写,经过设备树编译器(Device Tree Compiler,DTC)编译后生成二进制形式(DTB,Device Tree Blob),然后加载到内核中,为内核提供必要的硬件信息。

设备树的基本结构包括节点(node)和属性(property)。节点代表系统中的设备或总线,可以包含子节点,形成树状结构;属性则是节点的特性描述,如地址范围、中断号等。一个简化的设备树示例如下:

/dts-v1/;

/ {
    compatible = "vendor,example-board";
    model = "Vendor Example Board";
    #address-cells = <1>;
    #size-cells = <1>;
    
    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>; /* 512MB RAM */
    };
    
    soc {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;
        
        uart@10000000 {
            compatible = "vendor,example-uart";
            reg = <0x10000000 0x1000>;
            interrupts = <0 42 4>;
            clocks = <&clk_uart>;
            status = "okay";
        };
        
        i2c@10001000 {
            compatible = "vendor,example-i2c";
            reg = <0x10001000 0x1000>;
            interrupts = <0 43 4>;
            clock-frequency = <100000>;
            status = "okay";
            
            eeprom@50 {
                compatible = "at24,24c256";
                reg = <0x50>;
                pagesize = <64>;
            };
        };
    };
    
    clocks {
        clk_uart: uart_clk {
            compatible = "fixed-clock";
            #clock-cells = <0>;
            clock-frequency = <24000000>;
        };
    };
};

设备树的作用

设备树的主要作用是解耦硬件描述和内核代码。在没有设备树的情况下,硬件信息通常硬编码在内核代码中,这导致内核与特定硬件平台紧密耦合,难以移植和维护。设备树的引入,使得内核可以动态地获取硬件信息,从而支持更广泛的硬件平台,并简化了内核的维护工作。

具体来说,设备树具有以下几个方面的作用:

  1. 硬件描述:设备树描述了系统中各个设备的类型、地址、中断号、时钟频率等信息,使得内核能够正确地识别和配置这些设备。

  2. 驱动匹配:设备树中的设备节点包含了设备的兼容性信息(compatible),内核可以根据这些信息找到对应的驱动程序,实现驱动的自动加载和绑定。

  3. 平台配置:设备树可以配置系统的启动参数、内存布局、中断路由等,使得内核能够根据不同的硬件平台进行定制。

  4. 分离硬件描述与内核代码:设备树将硬件描述从内核代码中分离出来,使得添加新板卡支持不再需要修改内核源码。只需为新板卡创建相应的设备树文件,编译成DTB格式,并在启动时加载即可。

x86架构的设备发现机制:

要理解为什么x86不需要设备树,我们需要先了解x86架构的设备发现机制。x86架构有着长达数十年的发展历史,在这个过程中形成了一套完整的硬件自描述机制。

BIOS/UEFI与硬件抽象层

x86平台上的BIOS(Basic Input/Output System)或其现代替代品UEFI(Unified Extensible Firmware Interface)提供了硬件抽象层,它们在操作系统启动前初始化硬件,并提供标准接口供操作系统访问硬件资源。

BIOS通过中断向量表(Interrupt Vector Table,IVT)提供了一系列硬件访问接口,操作系统可以通过这些接口来访问硬件。例如,INT 10h用于访问视频服务,INT 13h用于访问磁盘服务等。

UEFI则提供了更现代化的接口,它使用可扩展固件接口(Extensible Firmware Interface,EFI)来描述硬件信息,操作系统可以通过EFI接口来获取硬件配置。UEFI还提供了运行时服务(Runtime Services)和启动时服务(Boot Services),为操作系统提供更强大的硬件访问能力。

ACPI(高级配置与电源接口)

ACPI(Advanced Configuration and Power Interface)是x86平台上最重要的硬件描述机制之一。它提供了一套标准化的接口,用于描述系统硬件配置、电源管理功能以及设备枚举。

ACPI使用AML(ACPI Machine Language)描述硬件特性和行为,这些描述存储在ACPI表中,由固件提供给操作系统。主要的ACPI表包括:

  • RSDT(Root System Description Table):根系统描述表,包含指向其他ACPI表的指针。
  • DSDT(Differentiated System Description Table):差分系统描述表,包含大部分设备的描述信息。
  • SSDT(Secondary System Description Table):次级系统描述表,包含补充的设备描述信息。
  • FADT(Fixed ACPI Description Table):固定ACPI描述表,包含系统硬件的固定配置信息。

Linux内核解析这些表,获取设备信息,并据此加载相应的驱动程序。ACPI还提供了一系列方法(如_STA、_CRS、_PRS等),用于查询设备状态、资源需求等信息。

PCI总线的自发现机制

PCI(Peripheral Component Interconnect)总线是x86架构中广泛使用的外设总线标准。PCI设计了完善的设备枚举和自发现机制。每个PCI设备都有标准化的配置空间,包含厂商ID、设备ID、类别码等信息。

操作系统可以通过扫描PCI总线,读取每个设备的配置空间,从而发现并识别系统中的PCI设备。以下是PCI设备枚举的简化代码示例:

void pci_scan_bus(void)
{
    for (int bus = 0; bus < 256; bus++) {
        for (int dev = 0; dev < 32; dev++) {
            for (int func = 0; func < 8; func++) {
                u16 vendor_id = pci_read_config_word(bus, dev, func, PCI_VENDOR_ID);
                if (vendor_id != 0xFFFF) {
                    /* 找到一个PCI设备 */
                    u16 device_id = pci_read_config_word(bus, dev, func, PCI_DEVICE_ID);
                    u8 class_code = pci_read_config_byte(bus, dev, func, PCI_CLASS_CODE);
                    /* 根据设备信息加载相应驱动 */
                }
            }
        }
    }
}

在x86平台上,PCI配置空间的访问是通过标准的I/O端口或内存映射方式实现的:

u32 pci_read_config_dword(u8 bus, u8 dev, u8 func, u8 offset)
{
    u32 address = (1 << 31) | (bus << 16) | (dev << 11) | (func << 8) | offset;
    outl(address, 0xCF8);
    return inl(0xCFC);
}

USB的自描述能力

类似于PCI,USB(Universal Serial Bus)也具有强大的自描述能力。USB设备通过描述符(Descriptor)提供其功能和特性信息。当USB设备连接到系统时,主机控制器会读取设备的描述符,获取设备类型、厂商、产品等信息,然后操作系统根据这些信息加载适当的驱动程序。

主要的USB描述符包括:

  • 设备描述符(Device Descriptor):包含设备的基本信息,如厂商ID、产品ID、设备类别等。
  • 配置描述符(Configuration Descriptor):描述设备的配置信息,如电源需求、接口数量等。
  • 接口描述符(Interface Descriptor):描述设备提供的功能接口,如HID(Human Interface Device)、存储、音频等。
  • 端点描述符(Endpoint Descriptor):描述数据传输的端点,如传输类型、方向、最大包大小等。

SMBios/DMI

SMBios(System Management BIOS)或DMI(Desktop Management Interface)提供了关于系统硬件组件的详细信息,如主板、处理器、内存等。Linux通过解析SMBios表获取这些信息,用于系统识别和硬件管理。

这些自发现机制共同构成了x86平台的硬件描述框架,使得操作系统能够自动识别和配置硬件,而无需额外的设备树。

ARM架构的设备发现挑战:

相比于x86平台,ARM架构面临着截然不同的设备发现挑战:

ARM架构的多样性和碎片化

ARM不是单一的硬件平台,而是一种CPU架构,被广泛应用于各种嵌入式系统、移动设备和服务器。不同厂商可以基于ARM架构设计各种SoC(System on Chip),集成不同的外设和功能模块。这种多样性导致没有统一的硬件标准,每个ARM平台可能有完全不同的外设配置。

ARM架构的碎片化主要体现在以下几个方面:

  1. SoC厂商众多:不同的SoC厂商(如高通、联发科、三星、华为等)设计了各种各样的ARM处理器,这些处理器在架构、外设接口、中断控制器等方面存在差异。

  2. 外设接口多样:ARM平台的外设接口种类繁多,如UART、SPI、I2C、USB、Ethernet等。不同的平台可能使用不同的外设接口,或者使用相同的外设接口但配置不同。

  3. 中断控制器各异:ARM平台的中断控制器种类繁多,如GIC(Generic Interrupt Controller)、VIC(Vectored Interrupt Controller)等。不同的平台可能使用不同的中断控制器,或者使用相同的中断控制器但配置不同。

缺乏标准化的自发现机制

与x86不同,ARM平台传统上缺乏像ACPI或PCI那样的标准化自发现机制。早期的ARM系统通常是为特定应用定制的,硬件配置相对固定,因此没有强烈的需求开发通用的设备发现标准。

在ARM平台上,访问设备寄存器通常需要知道其物理地址,这些信息在没有设备树的情况下,需要硬编码在内核中:

#define UART_BASE_ADDR 0x10000000
#define UART_REG_DATA   0x00
#define UART_REG_STATUS 0x04

void uart_send_char(char c)
{
    volatile unsigned int *uart_data = (unsigned int *)(UART_BASE_ADDR + UART_REG_DATA);
    volatile unsigned int *uart_status = (unsigned int *)(UART_BASE_ADDR + UART_REG_STATUS);
    
    /* 等待发送缓冲区为空 */
    while (*uart_status & (1 << 5))
        ;
    
    /* 发送字符 */
    *uart_data = c;
}

板级支持包(BSP)的问题

在设备树出现之前,Linux对ARM平台的支持主要依赖于板级支持包(Board Support Package,BSP)。每种ARM板卡都需要在内核中添加专门的代码,描述其硬件配置。这种方法导致内核代码膨胀,且每添加一种新板卡都需要修改内核源码,不利于维护和扩展。

以下是设备树出现前ARM平台上硬件描述的简化示例:

/* 针对特定板卡的硬件描述代码 */
static struct resource example_board_uart_resources[] = {
    {
        .start = 0x10000000,
        .end   = 0x10000FFF,
        .flags = IORESOURCE_MEM,
    },
    {
        .start = 42,
        .end   = 42,
        .flags = IORESOURCE_IRQ,
    },
};

static struct platform_device example_board_uart = {
    .name = "vendor-uart",
    .id = 0,
    .resource = example_board_uart_resources,
    .num_resources = ARRAY_SIZE(example_board_uart_resources),
};

static void __init example_board_init(void)
{
    platform_device_register(&example_board_uart);
    /* 注册其他设备... */
}

MACHINE_START(EXAMPLE_BOARD, "Example Board")
    .init_machine = example_board_init,
    /* 其他初始化函数... */
MACHINE_END

这种方法的缺点显而易见:每添加一种新的板卡,就需要在内核中添加类似上面的代码,导致内核代码膨胀,且维护困难。Linux内核中曾经有数百个这样的板级支持文件,使得内核代码变得臃肿不堪。

设备树解决方案的引入:

为了解决ARM平台上的设备发现问题,Linux社区引入了设备树机制。

设备树的历史起源

设备树最初源于OpenFirmware,这是一种由Sun Microsystems开发的固件标准,后来被IEEE 1275标准采纳。PowerPC架构的计算机(如早期的苹果Mac电脑)使用OpenFirmware作为启动固件,它使用设备树来描述硬件配置。

随着PowerPC架构在Linux中的发展,设备树机制被引入到Linux内核中。最终,在Linux 3.x内核版本中,设备树成为ARM架构的标准配置方法。这一变化被称为"ARM平台的大规模重构"(ARM Platform Consolidation),它显著减少了内核代码量,并提高了ARM平台的可维护性。

设备树在ARM Linux中的实现

在ARM Linux中,设备树的实现涉及多个层面:

  1. 设备树源文件(DTS):设备树源文件是人类可读的文本文件,使用特定语法描述硬件配置。

  2. 设备树编译器(DTC):设备树编译器将DTS文件编译成二进制格式的DTB文件,供内核加载使用。编译命令示例:

dtc -I dts -O dtb -o example-board.dtb example-board.dts
  1. 设备树在引导过程中的加载:在ARM系统启动时,bootloader(如U-Boot)负责加载内核镜像和DTB文件到内存,并将DTB的内存地址传递给内核。内核启动后,解析DTB获取硬件信息,并据此初始化设备驱动。

  2. 内核中的设备树解析:Linux内核包含设备树解析代码,负责读取DTB并构建内部设备模型。以下是设备树解析的简化流程:

void __init setup_arch(char **cmdline_p)
{
    /* ... */
    
    /* 获取设备树地址 */
    extern char __dtb_start[];
    extern char __dtb_end[];
    
    void *dtb = __dtb_start;
    size_t dtb_size = __dtb_end - __dtb_start;
    
    /* 解析设备树 */
    early_init_dt_scan(dtb);
    
    /* ... */
}

static void __init early_init_dt_scan(void *dtb)
{
    /* ... */
    
    /* 遍历设备树节点 */
    of_scan_flat_dt(early_init_dt_scan_root, NULL);
    
    /* ... */
}

static void early_init_dt_scan_root(const struct of_flat_dt_entry *node, void *data)
{
    /* 处理根节点 */
    if (of_flat_dt_is_compatible(node, "vendor,example-board")) {
        /* 初始化板级硬件 */
    }
    
    /* 扫描子节点 */
    of_scan_flat_dt(early_init_dt_scan_memory, NULL);
    of_scan_flat_dt(early_init_dt_scan_devices, NULL);
}
  1. 设备驱动的匹配:内核根据设备树中的兼容性信息(compatible属性)将设备与驱动程序匹配。驱动程序通过of_match_table声明其支持的设备类型:
static const struct of_device_id example_uart_dt_ids[] = {
    { .compatible = "vendor,example-uart" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, example_uart_dt_ids);

static struct platform_driver example_uart_driver = {
    .probe = example_uart_probe,
    .remove = example_uart_remove,
    .driver = {
        .name = "example-uart",
        .of_match_table = example_uart_dt_ids,
    },
};

设备树解决的问题

设备树为ARM平台带来了以下几个方面的改进:

  1. 减少内核代码量:设备树将硬件描述从内核代码中分离出来,显著减少了内核中针对特定板卡的代码量。

  2. 提高可维护性:添加新板卡支持不再需要修改内核源码,只需创建相应的设备树文件即可,简化了维护工作。

  3. 统一硬件描述方法:设备树为ARM平台提供了统一的硬件描述方法,使得不同厂商的ARM平台可以使用相同的机制描述硬件。

  4. 支持动态配置:设备树支持运行时修改和覆盖,使得同一内核镜像可以支持多种硬件配置,提高了灵活性。

x86与ARM架构差异的深层次分析:

历史发展路径差异

x86和ARM的历史发展路径有着根本性的差异,这直接影响了它们的设备发现机制:

  1. x86的标准化历程:x86架构源于个人计算机领域,经历了数十年的发展和标准化过程。从最初的IBM PC到现代的个人电脑和服务器,x86平台形成了一套完整的硬件标准,包括BIOS/UEFI、ACPI、PCI等。这些标准保证了不同厂商的x86设备可以互操作,也为操作系统提供了一致的硬件抽象层。

  2. ARM的嵌入式起源:ARM架构起源于嵌入式系统领域,最初设计用于特定应用,如早期的Acorn计算机。在嵌入式领域,系统往往是为特定应用定制的,硬件配置相对固定,因此没有强烈的需求开发通用的设备发现标准。随着ARM架构进入移动设备和服务器领域,硬件配置变得更加复杂和多样化,但缺乏像x86那样的统一标准。

设计哲学差异

x86和ARM在设计哲学上也存在显著差异:

  1. x86的向后兼容性:x86架构高度重视向后兼容性,新的x86处理器必须能够运行为旧处理器设计的软件。这种兼容性要求导致x86架构保留了许多历史特性,如实模式、BIOS中断等。这些特性构成了x86平台的硬件自描述机制的基础。

  2. ARM的简洁设计:ARM架构强调简洁和效率,设计理念是"做得更少,但做得更好"(Do less, but do it better)。ARM处理器通常只包含必要的功能,减少了不必要的复杂性。这种设计理念使得ARM处理器功耗低、效率高,但也导致缺乏像x86那样丰富的硬件自描述机制。

硬件集成度的差异

x86和ARM在硬件集成度上也存在显著差异:

  1. x86的分立架构:传统的x86系统采用分立架构,处理器、芯片组、外设等组件由不同厂商生产,通过标准接口(如PCI)连接。这种架构需要标准化的设备发现机制,以确保不同厂商的组件可以互操作。

  2. ARM的SoC架构:ARM系统通常采用SoC架构,将处理器、内存控制器、外设等集成在单一芯片上。SoC由单一厂商设计和生产,因此不需要像x86那样的标准化设备发现机制。不同厂

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值