Linux PCI 子系統:工作原理與實現機制深度分析

Linux PCI 子系統:工作原理與實現機制深度分析

1. Linux PCI 子系統基礎概念

1.1 PCI/PCIe 基礎概念回顧
  • 總線拓撲: PCI/PCIe 系統是一個樹形結構。CPU 連接到 Root Complex (RC),RC 連接至 PCIe 交換機 (Switch)PCIe 端點設備 (Endpoint)。傳統 PCI 設備通過 PCI 橋連接。
  • 配置空間: 每個 PCI 設備都有一個 256 字節(PCIe 為 4KB)的配置空間,用于識別設備、配置資源和控制設備。前 64 字節是標準化的,包含了:
    • Vendor ID, Device ID: 標識設備廠商和型號。
    • Class Code: 標識設備類型(網卡、顯卡等)。
    • BARs (Base Address Registers): 6個BAR,用于定義設備需要的內存或I/O地址空間的大小和類型。
  • 枚舉 (Enumeration): 系統啟動時,BIOS/UEFI 或 OS 會遍歷 PCI 總線樹,發現所有設備,讀取其配置空間,并為每個設備的 BAR 分配唯一的物理地址,避免沖突。
1.2 Linux PCI 子系統架構與工作流程

Linux PCI 子系統采用分層架構,如下圖所示:

內核空間
用戶空間
ioctl/sysfs
直接訪問
依賴/調用
抽象/調用
依賴/調用
配置讀寫操作
IRQ/DMA訪問
硬件層
PCI/PCIe硬件
內核PCI驅動
e.g., igb, nvme
PCI核心層 (pci.c)
- 總線枚舉
- 資源分配
- sysfs 接口
- 公共函數
系統總線驅動
e.g., pci_host_generic
Host Bridge驅動
e.g., pcie-rcar, pci-thunder-ecam
應用程序
工具 lspci, setpci

各層職責

  1. PCI Host Bridge 驅動

    • 最底層驅動,與硬件架構緊密相關(如 x86, ARM, RISC-V)。
    • 實現 pci_ops 結構體,提供 read()write() 方法來訪問 CPU 特定域的 PCI 配置空間。這是操作系統與 PCI 硬件交互的基石。
  2. PCI 核心層 (drivers/pci/pci.c, probe.c, etc.)

    • 內核的核心基礎設施,與硬件平臺無關。
    • 功能
      • 總線枚舉和設備發現。
      • 資源管理和分配(內存、I/O、中斷)。
      • 提供 PCI 總線的抽象模型 (pci_bus)。
      • 實現 sysfsprocfs 接口,向用戶空間暴露設備信息。
      • 提供公共 API 供其他內核驅動調用(如 pci_read_config_byte, pci_enable_device, pci_request_regions)。
  3. 內核 PCI 設備驅動

    • 針對特定型號 PCI 設備的驅動(如 e1000 網卡驅動,nvme SSD 驅動)。
    • 通過 pci_driver 結構體向核心層注冊自己,聲明其支持的設備(Vendor/ID)。
    • probe() 函數中初始化設備,請求資源(內存區域、中斷),并使其可供系統使用。

設備枚舉與驅動匹配流程

BIOS/UEFIPCI CoreHost DriverPCI Driver系統啟動/總線掃描初始硬件配置(可選)pci_scan_root_bus()read_config_(word/dword)(VendorID)VendorID創建pci_dev結構體并填充信息alt[VendorID !=0xFFFF(設備存在)]loop[掃描每條總線,每個插槽,每個功能]分配資源(地址,IRQ)驅動注冊與設備探測pci_register_driver()比較設備的Vendor/Device ID與驅動支持的ID列表調用驅動的probe(dev, id)啟用設備,請求資源,初始化alt[匹配成功]loop[為每個設備檢查已注冊驅動]BIOS/UEFIPCI CoreHost DriverPCI Driver

2. 核心數據結構與代碼分析

2.1 核心數據結構
數據結構描述關鍵成員(簡化)
struct pci_dev代表一個PCI設備struct bus *bus (所屬總線)
unsigned int devfn (設備/功能號)
unsigned short vendor, device
struct resource resource[DEV_COUNT_RESOURCE] (BAR資源)
irq (分配的中斷號)
struct pci_bus代表一條PCI總線struct list_head node (總線列表)
struct pci_bus *parent (父總線,橋連接)
struct list_head devices (總線上的設備列表)
struct pci_ops *ops (配置空間訪問方法)
struct pci_driver代表一個PCI設備驅動const char *name
const struct pci_device_id *id_table (支持的設備ID表)
int (*probe)(struct pci_dev *dev, const struct pci_device_id *id)
void (*remove)(struct pci_dev *dev)
struct pci_opsHost Bridge驅動提供的
配置空間訪問方法
int (*read)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val)
int (*write)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val)
2.2 關鍵代碼片段分析

1. Host Bridge 驅動示例 (ECAM 方式):

ECAM (Enhanced Configuration Access Mechanism) 是 PCIe 的標準配置訪問方式。以下是一個簡化版的 Host 驅動,它實現了 pci_ops

/* 假設:ECAM 配置空間的物理地址為 0x30000000 */
#define PCI_ECAM_BUS_OFFSET    (0x1000) /* 每總線偏移 4KB */static void __iomem *config_base; /* 映射后的虛擬地址 */static int ecam_pci_read(struct pci_bus *bus, unsigned int devfn,int where, int size, u32 *val)
{void __iomem *addr;/* 計算配置空間中該設備的偏移地址 */addr = config_base + (bus->number << 20) + (devfn << 12) + where;switch (size) {case 1:*val = readb(addr);break;case 2:*val = readw(addr);break;case 4:*val = readl(addr);break;default:return PCIBIOS_Failed;}return PCIBIOS_SUCCESSFUL;
}/* write() 函數類似,使用 writeb/writew/writel */struct pci_ops ecam_pci_ops = {.read = ecam_pci_read,.write = ecam_pci_write,
};/* 在驅動 probe 中: */
config_base = ioremap(0x30000000, 256 * PCI_ECAM_BUS_OFFSET); /* 映射物理地址到虛擬地址 */

2. PCI 設備驅動框架示例:

#include <linux/pci.h>
#include <linux/module.h>#define MY_VENDOR_ID 0x1234
#define MY_DEVICE_ID 0x5678static int my_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{int ret;void __iomem *bar0;/* 1. 啟用設備 */ret = pci_enable_device(dev);if (ret) {dev_err(&dev->dev, "Enable failed\n");return ret;}/* 2. 請求設備占用的內存區域(BAR0) */ret = pci_request_region(dev, 0, "my_device");if (ret) {dev_err(&dev->dev, "Cannot request BAR0\n");goto err_disable;}/* 3. 將 BAR0 映射到內核虛擬地址空間 */bar0 = pci_iomap(dev, 0, 0);if (!bar0) {dev_err(&dev->dev, "Cannot map BAR0\n");goto err_release;}/* 4. 設置 DMA 掩碼(可選) */ret = pci_set_dma_mask(dev, DMA_BIT_MASK(64));if (ret) {ret = pci_set_dma_mask(dev, DMA_BIT_MASK(32));if (ret) {dev_err(&dev->dev, "No suitable DMA mask\n");goto err_iounmap;}}/* 5. 獲取中斷號并注冊中斷處理程序 */ret = pci_alloc_irq_vectors(dev, 1, 1, PCI_IRQ_MSI | PCI_IRQ_LEGACY);if (ret < 0) {dev_err(&dev->dev, "Cannot allocate IRQ\n");goto err_iounmap;}ret = request_irq(pci_irq_vector(dev, 0), my_irq_handler, IRQF_SHARED, "my_device", dev);if (ret) {dev_err(&dev->dev, "Cannot request IRQ\n");goto err_irq;}/* 6. 設備初始化操作,比如讀寫寄存器 */iowrite32(0xAA55, bar0 + MY_REG_OFFSET);/* 7. 將私有數據存儲到 pci_dev */pci_set_drvdata(dev, private_data);dev_info(&dev->dev, "Device initialized\n");return 0;/* 錯誤處理:按申請資源的相反順序釋放 */
err_irq:pci_free_irq_vectors(dev);
err_iounmap:pci_iounmap(dev, bar0);
err_release:pci_release_region(dev, 0);
err_disable:pci_disable_device(dev);return ret;
}static void my_pci_remove(struct pci_dev *dev)
{struct my_private_data *private = pci_get_drvdata(dev);free_irq(pci_irq_vector(dev, 0), dev);pci_free_irq_vectors(dev);pci_iounmap(dev, private->bar0);pci_release_region(dev, 0);pci_disable_device(dev);
}static const struct pci_device_id my_pci_ids[] = {{ PCI_DEVICE(MY_VENDOR_ID, MY_DEVICE_ID) }, /* 宏用于組合 Vendor 和 Device ID */{ 0, } /* 終止條目 */
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);static struct pci_driver my_pci_driver = {.name = "my_pci_drv",.id_table = my_pci_ids, /* 驅動支持的設備表 */.probe = my_pci_probe,.remove = my_pci_remove,
};module_pci_driver(my_pci_driver); /* 注冊驅動 */

3. 最簡單的用戶空間應用實例

用戶空間程序通常通過 sysfs/proc 來獲取 PCI 設備信息,或者通過 mmap() 將設備的 BAR 映射到用戶空間進行直接訪問(需要驅動支持)。

示例:讀取設備的 Vendor ID 和 Device ID (通過 sysfs)

/* read_pci_info.c */
#include <stdio.h>
#include <stdlib.h>int main(int argc, char *argv[]) {FILE *file;unsigned int vendor, device;char path[256];/* 假設總線:設備:功能號為 00:01:0 */sprintf(path, "/sys/bus/pci/devices/0000:00:01.0/vendor");file = fopen(path, "r");if (file) {fscanf(file, "%x", &vendor);fclose(file);printf("Vendor ID: 0x%04X\n", vendor);}sprintf(path, "/sys/bus/pci/devices/0000:00:01.0/device");file = fopen(path, "r");if (file) {fscanf(file, "%x", &device);fclose(file);printf("Device ID: 0x%04X\n", device);}return 0;
}

編譯與運行:

gcc read_pci_info.c -o read_pci_info
./read_pci_info

4. 常用工具命令和 Debug 手段

工具/命令描述示例
lspci列出所有 PCI 設備 (最常用)lspci (基本列表)
lspci -vvv (最詳細信息)
lspci -vvv -s 00:1f.2 (查看特定設備)
lspci -t (以樹形顯示拓撲)
lspci -n (顯示數字ID)
setpci直接讀寫配置空間setpci -s 00:1f.2 0xa.w=0x1000 (寫命令)
setpci -s 00:1f.2 0xa.l (讀長字)
cat /proc/iomem查看物理內存映射grep -i pci /proc/iomem (查看PCI設備占用的內存區域)
cat /proc/interrupts查看中斷信息grep -i pci /proc/interrupts (查看PCI設備的中斷)
dmesg \| grep -i pci查看內核啟動和運行中的PCI相關日志dmesg \| grep -i pci
sysfs/sys/bus/pci/ 下查看設備詳細信息ls /sys/bus/pci/devices/ (所有設備)
cat /sys/bus/pci/devices/0000:00:1c.0/resource (查看設備資源)
devmem2(危險!) 直接讀寫物理內存devmem2 0xfed10000 (讀取指定物理地址)
高級 Debug 手段
  1. 內核動態調試 (Dynamic Debug)

    • make menuconfig 中啟用 CONFIG_DYNAMIC_DEBUG
    • 可以動態開啟/關閉特定源文件、函數、行號的調試信息。
    • echo 'file drivers/pci/* +p' > /sys/kernel/debug/dynamic_debug/control (啟用所有PCI核心驅動的debug日志)
  2. 分析內核 Oops

    • 如果驅動崩潰,會產生 Oops 消息,包含調用棧 (Call Trace)。
    • 使用 gdbvmlinux 內核鏡像文件來解析地址,定位出錯代碼行。
  3. 硬件輔助

    • 使用 PCIe 協議分析儀進行硬件層面的抓包和分析,這是最底層的終極手段。

總結

Linux PCI 子系統通過精妙的分層設計,抽象了底層硬件差異,為上層驅動提供了統一的接口。其核心工作流程是 枚舉 -> 資源分配 -> 驅動匹配 -> 設備初始化。理解 pci_dev, pci_driver, pci_ops 這三個核心數據結構是編寫和調試 PCI 驅動的關鍵。用戶空間通過 sysfs 與 PCI 設備交互,而 lspcisetpci 等工具則是開發和運維過程中不可或缺的利器。Debug 時需要結合內核日志、sysfs 信息和各種工具進行綜合分析。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/96682.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/96682.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/96682.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

RabbitMQ 全面指南:架構解析與案例實戰

目錄一、RabbitMQ 簡介1.1 什么是 RabbitMQ1.2 RabbitMQ 的核心組件1.3 RabbitMQ 的應用場景二、環境搭建2.1 安裝 RabbitMQ2.2 安裝 Erlang2.3 配置 RabbitMQ三、RabbitMQ 核心概念與工作原理3.1 消息模型3.2 交換機類型3.3 隊列特性3.4 消息確認機制四、Spring Boot 集成 Rab…

6.2 el-menu

一、 <el-menu>: 菜單組件&#xff0c;定義了側邊欄內部的具體導航項、層級結構和交互行為。<el-container><!-- 側邊欄容器 --><el-aside width"200px"><!-- 菜單內容 --><el-menu default-active"1" class"el-men…

Windows 筆記本實現僅關屏仍工作:一種更便捷的 “偽熄屏” 方案

在使用 Windows 筆記本作為臨時服務器或需要后臺持續運行程序時&#xff0c;我們常面臨一個需求&#xff1a;關閉屏幕以節省電量或減少光污染&#xff0c;同時保持系統正常工作。然而&#xff0c;網絡上流傳的諸多方法往往存在局限&#xff0c;要么無法兼顧 “熄屏” 與 “工作…

Linux應急響應一般思路(二)

進程排查進程(Process)是計算機中的程序關于某數據集合上的一次運行活動&#xff0c;是系統進行資源分配和調度的基本單位&#xff0c;是操作系統結構的基礎無論是在Windows系統還是Linux系統中&#xff0c;主機在感染惡意程序后&#xff0c;惡意程序都會啟動相應的進程&#x…

基于 SkyWalking + Elasticsearch + Grafana 的可落地調用鏈監控方案

這個方案成熟穩定、社區活躍、部署相對簡單,非常適合中小型團隊作為第一代調用鏈系統落地。 一、核心組件選型與角色 組件 版本建議 角色 優點 Apache SkyWalking v9.x+ 核心平臺 (采集、分析、存儲、UI) 國產優秀,Java Agent無侵入接入,功能全面,性能損耗低 Elasticsearc…

APP逆向——某站device-id參數

免責聲明本博客所涉及的 爬蟲技術、逆向分析方法 僅用于 學習、研究和技術交流。文中所有示例代碼、工具和方法&#xff0c;均不得用于以下行為&#xff1a;未經授權的數據采集侵犯他人知識產權干擾或破壞正常業務系統任何違反國家法律法規的行為因讀者將本教程內容用于 非法用…

C/C++數據結構之循環鏈表

概述循環鏈表本質上也是一個單向或雙向鏈表&#xff0c;但其最后一個節點的指針并不指向NULL&#xff0c;而是指向鏈表的第一個節點&#xff0c;從而形成一個閉合的環。這種結構使得在遍歷鏈表時&#xff0c;可以從任意一個節點開始&#xff0c;并最終回到起始點。音樂播放軟件…

Mongodb的教程

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 前言 一、mongodb是什么&#xff1f; 二、mongodb的下載與安裝教程 三、mongodb的常見操作 總結 前言 在當今數據驅動的世界中&#xff0c;數據庫技術是構建高效…

MySQL視圖有什么用?一文讀懂虛擬表的六大核心價值

引言 在數據庫開發中&#xff0c;你是否遇到過這樣的困境&#xff1a;業務人員需要查看復雜關聯數據卻難以理解多表JOIN&#xff0c;或需要限制某些用戶只能訪問特定字段&#xff1f;MySQL視圖正是為此設計的"數據透視鏡"。本文將通過官方定義、典型場景和最佳實踐&a…

ubuntu24.04 frps服務器端自動啟動設置【2025-08-20】

Ubuntu 24.04采用systemd作為默認的init系統&#xff0c;我們可以通過創建systemd服務單元文件來實現開機自啟動。以下是具體實施步驟&#xff1a;創建服務文件使用文本編輯器創建服務配置文件&#xff1a;sudo nano /etc/systemd/system/frps.service編寫服務配置內容在文件中…

數據結構與算法-字符串、數組和廣義表(String Array List)

3 字符串、數組和廣義表&#xff08;String Array List&#xff09; 3.1 字符串&#xff08;String&#xff09; 3.1.1 串的順序存儲 a. 定長順序&#xff1a; #define MAXLEN 255 // 串的定長順序存儲結構 typedef struct {char ch[MAXLEN 1]; // 字符串數據&#xff0c;…

【網絡運維】Shell 腳本編程:if 條件語句

Shell 腳本編程&#xff1a;if 條件語句 if 條件語句概述 if 條件語句是 Linux Shell 腳本編程中最基礎且使用頻率最高的控制結構之一&#xff0c;其語義類似于自然語言中的“如果…那么…”。熟練掌握 if 語句的用法&#xff0c;是成為一名合格運維工程師的基本要求。 if 語句…

浮點型的位結構和表示的值

位結構float 各部分的含義 符號位&#xff1a; 為 0 表示正數&#xff0c;為 1 表示負數。 指數部分&#xff1a; 指數部分是一個移碼。指數部分有 8 位&#xff0c;首先當成無符號整型&#xff0c;則值域是 [0, 255] .因為是移碼&#xff0c;所以 移碼值 無符號整型值 - 127 …

39_基于深度學習的行人摔倒檢測識別系統(yolo11、yolov8、yolov5+UI界面+Python項目源碼+模型+標注好的數據集)

目錄 項目介紹&#x1f3af; 功能展示&#x1f31f; 一、環境安裝&#x1f386; 環境配置說明&#x1f4d8; 安裝指南說明&#x1f3a5; 環境安裝教學視頻 &#x1f31f; 二、數據集介紹&#x1f31f; 三、系統環境&#xff08;框架/依賴庫&#xff09;說明&#x1f9f1; 系統環…

【系統分析師】高分論文:論企業數據治理

【摘要】 2022年3月&#xff0c;我作為系統分析師及IT 負責人&#xff0c;參加了我司的企業級數據平臺建設項目&#xff0c;該項目作為我司在企業數字化轉型過程中重要的里程碑&#xff0c;在我司數字化運營中扮演著關鍵的角色。該項目主要包含企業級數據倉庫&#xff0c;數據治…

Seata原理分析

簡介Apache Seata? (incubating) 是什么&#xff1f;Seata 是一款開源的分布式事務解決方案&#xff0c;致力于在微服務架構下提供高性能和簡單易用的分布式事務服務。在 Seata 開源之前&#xff0c;其內部版本在阿里系內部一直扮演著應用架構層數據一致性的中間件角色&#x…

力扣 30 天 JavaScript 挑戰 第38天 (第九題)學習了 語句表達式的區別 高級函數 promise async await 節流

開始答題 版本一&#xff1a; /*** param {Function} fn* return {Function}*/ var once function(fn) {let runCount0return function(...args){runCountrunCount 1 ? return fn(...args) :return undefined} };/*** let fn (a,b,c) > (a b c)* let onceFn once(fn)…

25年八月份寧德時代社招部分崗位入職Verify測評演繹數字推理SHL題型變更、題庫使用說明

開始測評前&#xff0c;請注意:1、挑選一個安靜的環境&#xff0c;選擇一臺網速正常且無任何網絡端口限制的電腦進行測評;2、移動設備無法兼容遠程監考功能&#xff0c;請使用配備有可正常運作的攝像頭的臺式機或筆記本電腦&#xff0c;建議使用最新版本的Chrome&#xff0c;Fi…

【KO】前端面試四

以下是剩余題目的詳細解答,結合前端知識體系和實際應用場景展開: 91. JS 放在 head 里和放在 body 里有什么區別? 對比維度 放在 <head> 放在 <body> 加載阻塞性 會阻塞頁面渲染,需等待 JS 下載/執行完成后,才繼續渲染頁面 一般放在 </body> 前,頁面渲…

[Vid-LLM] 數據集 | 基準測試

第5章&#xff1a;數據集與基準測試 在前一章中&#xff0c;我們探討了**視頻大語言模型(Vid-LLMs)**能夠執行的各種"工作"或"功能"&#xff0c;從視頻總結到充當智能代理。 我們了解了它們的構建方式和扮演的角色。 但這里有個關鍵問題&#xff1a;這些驚…