1、導論
在當今數據以前所未有的速度和規模產生、傳輸和處理的時代,系統性能的每一個環節都面臨着極致的考驗。從高性能計算集羣到大規模分佈式存儲,從實時金融交易到低延遲的雲服務,對高效數據交換的需求日益迫切。然而,傳統的網絡通信方式,儘管成熟穩定,其固有的處理開銷和多次數據拷貝,在這些追求極致性能的場景下,往往成為難以逾越的瓶頸。
1.1 什麼是 RDMA?為什麼需要它?
RDMA (Remote Direct Memory Access) 是一種允許一台計算機的內存直接被另一台計算機訪問的技術,整個過程無需兩端操作系統的CPU介入,也無需經過複雜的網絡協議棧處理。應用程序可以直接將數據從本地內存寫入到遠程服務器的內存中,或者從遠程服務器內存讀取數據到本地,讓 CPU 專注於核心的計算任務。
為什麼需要 RDMA?
傳統網絡通信(如基於 TCP/IP 的 Socket 編程)通常涉及以下步驟:
- 數據拷貝: 數據從用户空間緩衝區拷貝到內核空間緩衝區。
- 協議處理: 內核協議棧(TCP/IP)對數據進行分段、添加頭部、計算校驗和等操作。
- 再次拷貝: 數據從內核空間緩衝區拷貝到網絡接口卡 (NIC) 的緩衝區。
- 接收端進行類似的反向操作。
- 這些步驟,尤其是多次數據拷貝和CPU密集的協議處理,帶來了顯著的延遲 (Latency)和CPU 開銷 (CPU Overhead),並可能限制吞吐量 (Throughput)。
RDMA 通過以下關鍵特性解決了這些問題:
- 內核旁路 (Kernel Bypass): 用户空間應用程序可以直接向 RDMA 網卡 (稱為 HCA - Host Channel Adapter) 提交工作請求,數據傳輸路徑繞過了內核網絡協議棧。
- 零拷貝 (Zero-Copy): 數據可以直接從應用程序的內存發送到遠程應用程序的內存,避免了中間的多次內存拷貝。
- CPU卸載 (CPU Offload): 許多數據傳輸的控制邏輯,包括連接管理、可靠性保證、數據校驗等,都由HCA硬件來完成,從而將 CPU 資源釋放給應用程序。
這些特性共同賦予了RDMA無與倫比的性能優勢:
- 極低的延遲: 通常可以達到微秒級,遠低於傳統網絡的毫秒級。
- 極高的吞吐量: 能夠充分利用現代高速網絡鏈路的帶寬(如100Gbps、200Gbps甚至更高)。
- 顯著降低的CPU使用率: 使得CPU能夠更高效地執行計算密集型任務。
憑藉這些優勢,RDMA技術已廣泛應用於眾多領域: - 高性能計算 (HPC): 作為集羣節點間消息傳遞接口 (MPI) 的高速通道。
- 大語言模型 (LLMs): 在大規模 GPU 集羣中,RDMA (如 GPUDirect RDMA) 用於高效地在不同節點的 GPU 顯存之間直接傳輸海量訓練數據和模型參數,顯著縮短模型訓練時間,是支撐千億甚至萬億參數大模型訓練的關鍵網絡技術。"
- 分佈式存儲系統: 例如NVMe-oF (NVMe over Fabrics)、Ceph、Lustre等,用於實現存儲節點間的高效數據共享和訪問。
- 分佈式數據庫與內存緩存: 加速數據同步、複製和遠程查詢。
- 虛擬化與雲計算: 提升虛擬機之間的網絡I/O性能和存儲訪問速度。
1.2 Linux 中的 RDMA 支持:內核子系統與 rdma-core
Linux 內核提供了對 RDMA 技術的強大且成熟的支持,主要通過其 RDMA 子系統實現。這個子系統是內核的一部分,為各種 RDMA 硬件(如 InfiniBand HCAs, RoCE NICs, iWARP NICs)提供了一個統一的、硬件無關的編程接口。
Linux RDMA 架構主要包含:
內核 RDMA 子系統 :
- 核心模塊 (ib_core): 提供核心的數據結構、API 實現和設備管理框架。
- 硬件驅動 (Hardware Drivers): 針對特定 HCA 硬件的驅動程序(例如 mlx5_core for Mellanox, i40iw for Intel iWARP)。這些驅動實現了 Verbs API 中與硬件相關的部分,並向核心層註冊其設備。
- 用户空間接口 (ib_uverbs): 提供了一個字符設備接口(通常是 /dev/infiniband/uverbsX),允許用户空間應用程序或者 rdma-core 庫通過 ioctl 調用來訪問內核 RDMA 資源和執行操作。
- 連接管理模塊 (e.g., ib_cm, rdma_cm): 幫助建立和管理 RDMA 連接。
rdma-core 用户空間包:
這是一個重要的用户空間軟件包,提供了與內核 RDMA 子系統交互所需的庫和工具。
- libibverbs:最核心的用户空間庫,它封裝了通過 uverbs 接口與內核驅動通信的細節,為應用程序提供了標準的 Verbs API。
- Provider 庫 (e.g., libmlx5.so, librxe.so): 這些是 libibverbs 根據檢測到的硬件動態加載的庫,它們包含了針對特定硬件或軟件 provider 的特定邏輯,用於將通用的 libibverbs 調用轉換為特定於該 provider 的 uverbs 命令。
- librdmacm:一個用户空間庫,用於簡化 RDMA 連接的建立和管理。
- 實用工具: 如 ibv_devices (列出可用的 RDMA 設備), ibv_devinfo (顯示設備屬性) 等。
應用程序通常鏈接 libibverbs 和 librdmacm,通過這些庫間接與內核中的 RDMA 驅動進行交互。
1.3 本系列目標:從零開始編寫一個最簡單的 RDMA 內核驅動
本系列的目標是帶領讀者踏上一段實踐之旅:從零開始編寫一個基礎的、功能極簡的 RDMA 內核驅動程序,並將其成功接入到用户空間的 rdma-core生態系統中。這意味着,當我們的驅動加載後,用户空間工具(如 ibv_devices)應該能夠識別出我們“虛擬”的 RDMA 設備,並且我們能夠通過 libibverbs 嘗試與這個設備進行最基本的交互。
在libibverbs的基礎上,我們甚至可以通過操作系統提供的接口,在用户態實現 RDMA 的主要驅動功能。
我們將不會涉及實際的硬件控制,而是專注於:
- 理解 Linux 內核模塊開發的基礎。
- 學習 Linux RDMA 子系統的核心架構和關鍵數據結構。
- 掌握如何實現一個最小化的 ib_device,讓內核 RDMA 子系統能夠識別我們的驅動。
- 瞭解如何暴露必要的 uverbs 接口,以便 rdma-core 中的 libibverbs 能夠與我們的驅動通信。
通過這個循序漸進的過程,希望讀者能學習到 RDMA 驅動開發的基本流程,也能深入理解 Linux 內核與用户空間 RDMA 組件是如何協同工作的。這對於希望深入研究 RDMA、調試現有 RDMA 問題,或者為新型 RDMA 硬件/協議開發驅動的工程師來説,將是一個寶貴的起點。
1.4 讀者前提
為了更好地跟上本系列的步伐,建議讀者具備以下基礎:
- C 語言編程經驗: 內核模塊主要使用 C 語言編寫。
- 基本的 Linux 命令行操作: 包括編譯程序、加載/卸載內核模塊等。
- (建議) 對操作系統基本概念的理解: 如內核空間與用户空間、系統調用、設備驅動程序等。
- (可選) 內核模塊開發經驗: 如果你之前接觸過內核模塊開發,那會很有幫助,但不是強制性的,我們會從一個簡單的 "Hello, World!" 內核模塊開始。
- 你不需要預先具備 RDMA 知識,本系列會逐步介紹相關的概念。
2、環境配置 (Environment Setup)
進行 Linux 內核模塊開發,尤其是涉及 RDMA 這樣複雜子系統的模塊,一個穩定且隔離的開發環境至關重要。直接在主力工作機器上進行內核開發存在使系統不穩定的風險。因此,強烈推薦使用虛擬機 (VM) 進行開發與測試。本章將通過 QEMU/KVM 和 Ubuntu Cloud Image,快速創建一個預配置的 Linux 虛擬機,專門用於 RDMA 內核驅動開發。
本章核心步驟:
- 在宿主機上安裝必要的虛擬化軟件包。
- 下載 Ubuntu Cloud Image 並創建差分鏡像。
- 使用 cloud-init 準備用户數據和元數據以自動化虛擬機配置。
- 通過 QEMU 命令行啓動和配置虛擬機。
- 在虛擬機內部署開發工具、內核頭文件、額外的內核模塊 (包含 ib_uverbs) 及 rdma-core。
2.1 宿主機:安裝虛擬化及管理工具
首先,在您的 Linux 宿主機上安裝 QEMU、KVM 以及用於生成 cloud-init 數據源的工具。
2.1.1 安裝軟件包
對於基於 Debian/Ubuntu 的宿主機:
sudo apt update
qemu-system, qemu-utils: QEMU 模擬器和相關工具。
cloud-image-utils: 包含 cloud-localds 工具,用於創建 cloud-init 數據源。
cpu-checker: 包含 kvm-ok 工具。
2.1.2 驗證 KVM 可用性
kvm-ok
期望輸出包含 "KVM acceleration can be used"。
2.1.3 (推薦) 將用户添加到 kvm 組
這樣可以無需 sudo 運行某些 QEMU 操作。
sudo usermod -aG kvm $(whoami)
重要提示: 可能需要註銷並重新登錄或者重新連接ssh,用户組的更改才能生效。
2.2 使用 Cloud Image 和 cloud-init 創建虛擬機
Cloud images 是預裝好的輕量級操作系統鏡像,通常與 cloud-init 配合使用,可以在首次啓動時自動完成用户配置、網絡設置、軟件包安裝等任務。
2.2.1 下載 Ubuntu Cloud Image
這裏將使用 Ubuntu 24.04 LTS 的 cloud image 作為示例。可以從官方源下載最新的鏡像。
# 創建工作目錄
mkdir -p ~/rdma_vm_setup && cd ~/rdma_vm_setup
# 下載 cloud image (qcow2 格式)
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
2.2.2 創建差分磁盤鏡像 (Backing File)
為了不直接修改原始的 cloud image(便於複用),我們創建一個基於它的差分鏡像。對虛擬機的所有磁盤寫入將保存在這個新鏡像中。
qemu-img create -f qcow2 -F qcow2 -b noble-server-cloudimg-amd64.img my-ubuntu.qcow2 40G
- -f qcow2: 新鏡像的格式。
- -F qcow2: 後備鏡像 (backing file) 的格式。
- -b noble-server-cloudimg-amd64.img: 指定後備鏡像。
- my-ubuntu.qcow2: 新創建的差分鏡像文件名。
- 40G: 差分鏡像的最大可增長大小。
2.2.3 準備 cloud-init 用户數據和元數據
cloud-init 使用 user-data 和 meta-data 文件來配置虛擬機。
- meta-data.yaml (元數據):
cat > meta-data.yaml <<EOF
instance-id: ubuntu-$(date +%s)
local-hostname: ubuntu-vm
EOF
這定義了實例ID和虛擬機在網絡中可見的主機名。
- user-data.yaml (用户數據):
# 如果 ~/.ssh/id_rsa.pub 不存在,請先通過 ssh-keygen 生成
if [ ! -f ~/.ssh/id_rsa.pub ]; then ssh-keygen; fi
cat > user-data.yaml <<EOF
#cloud-config
password: ubuntu # 設置用户密碼 (可選)
chpasswd: { expire: False } # 密碼永不過期
ssh_pwauth: true # 允許密碼認證 (可選,推薦使用公鑰認證)
ssh_authorized_keys:
- $(cat ~/.ssh/id_rsa.pub)
EOF
2.2.4 創建 cloud-init 數據源鏡像 (seed.img)
cloud-localds seed.img user-data.yaml meta-data.yaml
這將創建一個名為 seed.img 的小鏡像,QEMU 會將其作為 CD-ROM 提供給虛擬機,cloud-init 會從中讀取配置。
2.3 使用 QEMU 啓動和訪問虛擬機
現在,我們可以使用 QEMU 啓動配置好的虛擬機。
2.3.1 通過 9P (Plan 9 Filesystem Protocol) 配置共享文件夾
QEMU 支持通過 9P 協議在宿主機和虛擬機之間共享文件夾。這對於在宿主機上編輯代碼,然後在虛擬機中編譯測試非常方便。
# 在宿主機上創建一個用於共享的目錄
mkdir -p ~/shared_with_vm
2.3.2 啓動 QEMU 虛擬機
qemu-system-x86_64 \
-enable-kvm \
-cpu host \
-smp $(nproc) \
-m 4G \
-drive file=my-ubuntu.qcow2,format=qcow2,if=virtio \
-drive file=seed.img,format=raw,if=virtio \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=net0 \
-virtfs local,path=$HOME/shared_with_vm,mount_tag=host_share,security_model=passthrough,id=fsdev0 \
-device virtio-9p-pci,fsdev=fsdev0,mount_tag=host_share \
-nographic -serial mon:stdio
參數解釋:
- -enable-kvm: 啓用 KVM 硬件加速。
- -cpu host: 將宿主機的 CPU 透傳給虛擬機,通常能獲得更好的性能。
- -smp $(nproc): 使用的處理器核心數,這裏將宿主機的CPU全部用上。
- -m 4G: 分配 4GB 內存給虛擬機。
- -drive file=my-ubuntu.qcow2,...: 主磁盤鏡像。
- -drive file=seed.img,...: cloud-init 數據源鏡像,作為 virtio 塊設備。
- -netdev user,id=net0,hostfwd=tcp::2222-:22: 用户模式網絡,宿主機 2222 端口轉發到虛擬機 22 端口。
- -virtfs ... 和 -device virtio-9p-pci...: 配置 9P 文件共享。宿主機的 ~/shared_with_vm 目錄將以 host_share 標籤共享給虛擬機。
- -nographic -serial mon:stdio: 無圖形界面啓動,並將串行控制枱重定向到標準輸入/輸出。這允許您直接在啓動 QEMU 的終端看到內核啓動信息和登錄提示。
2.3.3 通過 SSH 訪問虛擬機
等待 cloud-init 完成後,由於啓動 QEMU 時配置了端口轉發 (hostfwd=tcp::2222-:22),現在可以從宿主機通過 SSH 連接到虛擬機的 22 端口,對應宿主機的 2222 端口。並且 user-data.yaml 中配置了 SSH 公鑰,可以從宿主機直接免密登錄:
# 在宿主機上執行 (注意端口是 2222)
ssh ubuntu@localhost -p 2222
通過 SSH 操作通常比直接在 QEMU 窗口中輸入更方便,尤其是在複製粘貼命令和代碼時。
2.4 虛擬機內部:安裝開發環境
成功 SSH 登錄到虛擬機後,現在我們手動安裝 RDMA 開發所需的軟件包。
2.4.1 更新系統並安裝基礎開發工具
sudo apt update
sudo apt install build-essential gcc make git linux-headers-$(uname -r) linux-modules-extra-$(uname -r) rdma-core ibverbs-utils
- build-essential, gcc, make: C 編譯環境。
- git: 版本控制工具。
- linux-headers-$(uname -r): 當前運行內核的頭文件,用於編譯內核模塊。
- linux-modules-extra-$(uname -r): 當前運行內核的缺失的部分內核模塊,如ib_uverbs, rdma_rxe.
- rdma-core: 包含 libibverbs 和 librdmacm 等核心庫。
- ibverbs-utils: 提供 ibv_devices, ibv_devinfo 等命令行工具。
2.4.2 驗證 RDMA 環境
安裝完成後,我們可以加載 SoftRoCE (RXE) 模塊來創建一個軟件模擬的 RDMA 設備,並用 ibv_devinfo 檢查,以確認整個 RDMA 軟件棧(內核模塊、rdma-core庫、工具)都已正確安裝。
# 獲取網卡名稱
NETDEV=$(ip -o -4 route show to default | awk '{print $5}')
# 加載 RXE 模塊並創建一個 rxe 設備
sudo modprobe rdma_rxe
sudo rdma link add rxe0 type rxe netdev $NETDEV
# 查看設備信息
ibv_devinfo
如果一切正常,應該能看到類似下面的輸出,表明 RDMA 環境正常。
hca_id: rxe0
transport: InfiniBand (0)
fw_ver: 0.0.0
node_guid: 5054:00ff:fe12:3456
sys_image_guid: 5054:00ff:fe12:3456
vendor_id: 0xffffff
vendor_part_id: 0
hw_ver: 0x0
phys_port_cnt: 1
port: 1
state: PORT_ACTIVE (4)
max_mtu: 4096 (5)
active_mtu: 1024 (3)
sm_lid: 0
port_lid: 0
port_lmc: 0x00
link_layer: Ethernet
2.4.3 掛載和使用 9P 共享目錄
在 QEMU 啓動命令中我們已經定義了 9P 共享,現在在虛擬機中掛載它:
# 在虛擬機內執行
sudo mkdir -p /mnt/host_share
sudo mount -t 9p -o trans=virtio,version=9p2000.L,rw host_share /mnt/host_share
host_share 是在 QEMU 命令中指定的 mount_tag。
現在,宿主機的 ~/shared_with_vm 目錄的內容應該可以在虛擬機的 /mnt/host_share 下訪問了。
(可選) 設置開機自動掛載:
向虛擬機的 /etc/fstab 文件添加一行:
echo 'host_share /mnt/host_share 9p trans=virtio,version=9p2000.L,rw,_netdev,nofail 0 0' | sudo tee -a /etc/fstab
_netdev 選項建議在網絡可用後再掛載,nofail 選項防止因掛載失敗導致啓動卡住。
至此,我們已經擁有一個健壯的開發環境,為內核模塊和 RDMA 驅動開發做好了準備。在下一章中,我們將邁出第一步,編寫一個簡單的“Hello, World!”內核模塊,以熟悉基本機制。
3、Linux 內核模塊入門
在我們深入研究 RDMA 驅動的複雜性之前,首先需要理解 Linux 內核模塊是什麼,以及它們是如何工作的。內核模塊是允許我們在不重新編譯整個內核的情況下,動態地向運行中的內核添加或移除代碼片段的機制。這對於設備驅動程序、文件系統、網絡協議等功能的開發和維護至關重要。
3.1 什麼是內核模塊?為什麼使用它們?
Linux 內核模塊是指能夠獨立編譯,並在運行時動態加載到內核或從內核卸載的代碼單元。 它們允許在不重新啓動系統或重新編譯內核映像的情況下,擴展內核的功能。當系統需要特定功能時(例如,對新硬件的支持或實現新的文件系統),相應的內核模塊可以被加載,其代碼和數據結構將成為運行中內核的一部分。功能不再需要時,模塊也可以被卸載,釋放其佔用的系統資源。
內核模塊的主要特點和優勢:
- 動態擴展性 : 內核模塊最核心的特性是其動態加載和卸載的能力。這使得系統管理員和開發者可以在系統運行時添加、移除或更新內核功能,而無需中斷服務。
- 模塊化設計 : 該機制促進了內核設計的模塊化。複雜的功能被分解為獨立的、功能明確的模塊,降低了內核的整體複雜性,使得開發、測試和維護更為高效。每個模塊可以專注於其特定領域,例如,一個網絡接口卡驅動負責控制該硬件,而不必關心其他子系統的實現細節。
- 保持內核映像精簡 : 由於大量非核心功能(如特定於硬件的驅動程序)可以作為模塊存在,核心內核鏡像(即 /boot/vmlinuz)可以保持相對較小。僅在實際需要時,才會加載特定功能的模塊代碼。
- 獨立的開發與分發 : 驅動程序和其他內核功能的開發者可以獨立於主線內核的發佈週期進行開發和分發他們的模塊。用户也可以根據自己的需求選擇性地安裝和加載模塊。
- 潛在的故障隔離 : 模塊化的結構有助於問題的定位。不過由於內核模塊運行在內核中,一個有 Bug 的模塊仍可能導致系統不穩定甚至崩潰。
常見的內核模塊類型:
- 設備驅動程序: 這是內核模塊最主要的用途。幾乎所有外部硬件設備(如網絡適配器、存儲控制器、USB 設備、聲卡、顯卡等)的控制邏輯都是通過內核模塊實現的。本系列旨在開發的虛擬 RDMA 驅動,就屬於設備驅動程序的一種。
- 文件系統模塊: Linux 支持多種文件系統(如 ext4, XFS, Btrfs 等),它們中的許多是以內核模塊的形式提供的。
- 網絡協議模塊: 除了核心的網絡協議(如 TCP/IP),其他網絡協議通常也可以作為模塊實現。
3.2 編寫第一個內核模塊:"Hello, World!"
Linux 內核模塊是實現操作系統功能動態擴展、保持內核設計清晰以及簡化驅動程序開發和管理的關鍵機制。對於任何希望深入理解和定製 Linux 內核的開發者而言,熟悉內核模塊的開發流程是不可或缺的一環。接下來,我們將通過編寫一個標準的 out-of-tree "Hello, World!" 示例,它會在加載時向內核日誌打印 "Hello, World!",並在卸載時打印 "Goodbye, World!"。通過這個例子詳細介紹內核模塊的編寫、編譯、加載和卸載過程。
3.2.1 源代碼
創建一個名為 hello.c 的文件,內容如下:
#include <linux/module.h> // 包含了加載模塊所需要的所有頭文件
#include <linux/kernel.h> // 包含了內核相關的宏定義,如 KERN_INFO
// 模塊加載時調用的函數
static int __init hello_init(void)
{
// printk 是內核的打印函數,類似於用户空間的 printf
// KERN_INFO 是日誌級別,表示“信息性”消息
printk(KERN_INFO "Hello, World! from the kernel space.\n");
return 0; // 返回 0 表示模塊加載成功
}
// 模塊卸載時調用的函數
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye, World! from the kernel space.\n");
}
// 註冊模塊的入口和出口函數
module_init(hello_init);
module_exit(hello_exit);
// 模塊許可證聲明 (非常重要)
// GPL (GNU General Public License) 是最常見的許可證
// 如果沒有正確的許可證,模塊無法加載或某些內核函數可能無法使用
MODULE_LICENSE("GPL");
// 可選的模塊描述
MODULE_DESCRIPTION("A simple Hello World kernel module");
// 可選的模塊作者
MODULE_AUTHOR("Your Name");
代碼解釋:
- include <linux/module.h> :所有內核模塊都必須包含此頭文件。它定義了 module_init、module_exit 等宏,以及與模塊相關的各種結構和函數。
- include <linux/kernel.h> :包含了內核常用的函數和宏,例如 printk 及其日誌級別(如 KERN_INFO, KERN_ALERT 等)。
- hello_init(void):這是模塊的入口函數。當模塊被加載到內核時(例如通過 insmod 命令),這個函數會被調用。__init 宏告訴內核這個函數只在初始化時使用,一旦初始化完成,內核可以釋放這段代碼所佔用的內存(如果模塊是靜態編譯進內核的;對於可加載模塊,這個標記主要用於優化)。
- hello_exit(void):這是模塊的出口函數。當模塊從內核中卸載時(例如通過 rmmod 命令),這個函數會被調用。__exit 宏告訴內核這個函數只在模塊退出時使用。
- module_init(hello_init);:這個宏用於註冊 hello_init 函數作為模塊的初始化入口點。
- module_exit(hello_exit);:這個宏用於註冊 hello_exit 函數作為模塊的卸載出口點。
- printk(KERN_INFO "...");:printk 是內核中用於打印消息的函數。它的用法與用户空間的 printf 類似,但輸出到內核日誌緩衝區。KERN_INFO 是一個日誌級別宏,表示這是一條信息性消息。其他級別還有 KERN_ERR (錯誤), KERN_WARNING (警告), KERN_DEBUG (調試) 等。
- MODULE_LICENSE("GPL");:這是模塊的許可證聲明。Linux 內核本身是 GPL 許可證的,因此大多數內核模塊也使用 GPL 兼容的許可證。這是一個強制性聲明,缺少它或者使用不兼容的許可證可能會導致內核在加載模塊時發出警告,甚至某些內核符號(函數或變量)可能對非 GPL 模塊不可用。
- MODULE_DESCRIPTION(...) 和 MODULE_AUTHOR(...):這些是可選的宏,用於提供模塊的描述信息和作者信息。可以使用 modinfo 命令查看這些信息。
3.2.2 Makefile
為了編譯內核模塊,我們需要一個特殊的 Makefile。它利用內核的構建系統來正確編譯模塊。在 hello.c 相同的目錄下,創建一個名為 Makefile 的文件,內容如下:
# obj-m 表示我們要編譯一個可加載的模塊(module)
# 如果是 obj-y,則表示將代碼編譯進內核本身(靜態鏈接)
obj-m += hello.o
# KDIR 指向你的內核源碼樹或已安裝的內核頭文件目錄
# 通常,內核頭文件位於 /lib/modules/$(shell uname -r)/build
# $(shell uname -r) 會獲取當前正在運行的內核版本
KDIR := /lib/modules/$(shell uname -r)/build
# PWD 表示當前目錄
PWD := $(shell pwd)
# 默認目標
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
# 清理目標
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
# 可選:安裝目標 (通常不需要,我們手動 insmod)
# install:
# $(MAKE) -C $(KDIR) M=$(PWD) modules_install
Makefile 解釋:
- obj-m += hello.o:這行告訴內核構建系統,我們要將 hello.c 編譯成一個名為 hello.ko (.ko 是內核對象文件的擴展名) 的模塊。如果你的模塊由多個源文件組成(例如 file1.c 和 file2.c 編譯成 mymodule.ko),你可以這樣寫:obj-m += mymodule.o 和 mymodule-objs := file1.o file2.o。
- KDIR := /lib/modules/$(shell uname -r)/build:這行指定了內核構建系統所需文件的路徑。通常,這是指向你當前運行內核的已編譯頭文件和構建腳本的符號鏈接。
- all::執行命令調用內核的構建系統來編譯內核模塊。
- $(MAKE) -C $(KDIR) M=$(PWD) modules:
- -C $(KDIR):指示 make 在執行命令前先切換到 $(KDIR) 目錄。內核的頂層 Makefile 在那裏。
- M=$(PWD):將變量 M 設置為當前目錄的路徑。內核構建系統會查找這個目錄下的 Makefile 和源文件。
- modules:這是傳遞給內核構建系統的一個目標,指示它編譯模塊。
- clean::這個目標用於清除編譯過程中生成的中間文件和最終的模塊文件。
3.2.3 編譯、加載、查看輸出、卸載
現在,我們可以在包含 hello.c 和 Makefile 的目錄中執行以下命令:
- 編譯模塊:
在終端中運行 make 命令:
make
如果一切順利,你會看到一些編譯輸出,並且當前目錄下會生成一個 hello.ko 文件,以及一些中間文件(如 .o, .mod.c 等)。
- 加載模塊:
使用 insmod 命令加載模塊。需要有 root 權限:
sudo insmod hello.ko
如果沒有錯誤消息,模塊應該已經加載到內核中了。
- 查看內核日誌輸出:
模塊加載時,hello_init 函數中的 printk 會將消息輸出到內核日誌緩衝區。我們可以使用 dmesg 命令查看這些消息:
sudo dmesg -T | tail
應該能看到"Hello, World! from the kernel space." 的輸出。
- 查看已加載模塊:
使用 lsmod 命令可以列出當前加載到內核中的所有模塊。可以用 grep 篩選:
lsmod | grep hello
應該能看到 hello 模塊的信息。
- 查看模塊信息:
使用 modinfo 命令可以查看模塊的詳細信息,包括我們之前設置的許可證、描述和作者:
modinfo hello.ko
- 卸載模塊:
使用 rmmod 命令卸載模塊。同樣需要 root 權限:
sudo rmmod hello
注意,rmmod 後面跟的是模塊名(通常是 .ko 文件去掉擴展名),而不是文件名。
- 再次查看內核日誌輸出:
模塊卸載時,hello_exit 函數中的 printk 也會輸出消息:
sudo dmesg -T | tail
應該能看到類似 "Goodbye, World! from the kernel space." 的輸出。
恭喜!你已經成功編寫、編譯、加載和卸載了你的第一個 Linux 內核模塊。
3.3 內核模塊基本要素
除了 module_init 和 module_exit,內核模塊還有一些其他重要的組成部分:
- MODULE_LICENSE():如前所述,這是必需的,用於聲明模塊的許可證。常見的有 "GPL", "GPL v2","Dual MIT/GPL", "Dual BSD/GPL" 等。內核非常看重許可證,某些核心功能(導出符號)只對 GPL 兼容模塊開放。
- MODULE_AUTHOR():聲明模塊的作者。
- MODULE_DESCRIPTION():提供模塊的簡短描述。
- MODULE_VERSION():聲明模塊的版本號。
- MODULE_DEVICE_TABLE():對於設備驅動程序,這個宏用於聲明該驅動支持哪些設備。這使得內核的熱插拔系統(如 udev)能夠自動為匹配的設備加載驅動。
- 導出符號 (EXPORT_SYMBOL(), EXPORT_SYMBOL_GPL()):
如果你的模塊實現了一些函數或變量,希望被其他內核模塊使用,你需要使用這些宏將它們導出。EXPORT_SYMBOL_GPL() 導出的符號只能被聲明為 GPL 兼容許可證的模塊使用。
4、總結
至此,本系列關於 RDMA 驅動開發的第一階段——基礎構建與環境準備——已圓滿結束。回顧本部分內容,我們重點覆蓋了:
- RDMA 技術概覽: 對 RDMA 的基本概念、實現原理及其在高性能網絡通信中的關鍵作用進行了闡述。
- 虛擬化開發環境的標準化配置: 詳細介紹瞭如何利用 QEMU 和 cloud-init 自動化部署一個用於內核開發的 Ubuntu 虛擬機環境。
- 內核模塊開發基礎實踐: 通過一個具體的 "Hello, World!" 示例,完成了從源代碼編寫到模塊加載、驗證及卸載的完整流程。
掌握這些內容,意味着你已經具備了從用户空間視角向內核空間探索的技術儲備。我們不僅理解了為何要使用 RDMA,也掌握瞭如何在內核中運行自定義代碼的基礎方法。
在之後的文章中,我們將從通用的內核模塊知識過渡到 RDMA 驅動的特定領域。我們將瞭解 Linux RDMA 子系統的內部架構並將前面瞭解的通用內核知識應用於 RDMA 子系統的框架上,再通過實際編碼,讓一個我們自己定義的虛擬 RDMA 設備成功註冊到內核中。有興趣的讀者可以繼續關注之後的文章。
達坦科技始終致力於打造高性能AI+Cloud基礎設施平台,積極推動AI應用的落地。達坦科技通過軟硬件深度融合的方式,提供AI推理引擎和高性能網絡,為AI應用提供彈性、便利、經濟的基礎設施服務,以此滿足不同行業客户對AI+Cloud的需求。
公眾號:達坦科技DatenLord
DatenLord官網:
https://datenlord.github.io/zh-cn/
知乎賬號:
https://www.zhihu.com/org/da-tan-ke-ji
B站:
https://space.bilibili.com/2017027518
郵箱:info@datenlord.com
如果您有興趣加入達坦科技Rust前沿技術交流羣、硬件敏捷開發和驗證方法學討論羣或blue-rdma交流羣,請添加小助手微信:DatenLord_Tech