1.UIO簡介
1.1 什么是UIO技術
UIO(Userspace I/O)是運行在用戶空間的I/O技術,Linux 系統中一般的驅動設備都是運行在內核空間,應用程序在用戶空間調用即可。UIO 則是將驅動的小部分運行在內核空間,在用戶空間實現驅動的絕大多數功能,使用 UIO 可以避免設備的驅動程序需要隨著內核的更新而更新的問題。
1.2 UIO主要任務
設備驅動的主要任務為存取設備的內存、處理設備產生的中斷。對于第一個任務,UIO 核心實現了 mmap() 可以處理物理內存 (physicalmemory),邏輯內存(logicalmemory),虛擬內存(virtualmemory)。UIO 驅動的編寫是就不需要再考慮這些繁瑣的細節。
對于第二個任務,設備中斷的應答必須在內核空間進行。所以在內核空間有一小部分代碼用來應答中斷和禁止中斷,其余的工作全部留給用戶空間處理。如果用戶空間要等待一個設備中斷,它只需要阻塞在對 /dev/uioX 的 read() 操作上,當設備產生中斷時,read() 操作立即返回。UIO 也實現了 poll() 系統調用,可以使用 select() 來等待中斷的發生,select() 有一個超時參數可以用來實現有限時間內等待中斷。對設備的控制還可以通過 /sys/class/uio 下的各個文件的讀寫來完成。注冊的 uio 設備將會出現在該目錄下,假設 uio 設備是 uio0,那么映射的設備內存文件出現在 /sys/class/uio/uio0/maps/mapX,對該文件的讀寫就是對設備內存的讀寫。
1.3 UIO 框架
uio 代碼可以分為三個部分:內核 uio 框架及內核內部函數,uio 內核驅動部分,uio 用戶驅動部分。
內核 uio 框架通過配置內核選項 CONFIG_UIO=y 使能 Userspace I/O drivers,在內核初始化時會調用 uio_init 創建 uio_class。
igb_uio 內核驅動通過編譯運行 igb_uio.ko 加載并注冊一個 pci 設備,但是igbuio_pci_driver 對應保存 pci 設備信息的 id_table 指針為空,這樣在內核注冊此 pci 設備時,會找不到匹配的設備,就不會調用 igb_uio 驅動中的探測 probe 函數 uio 用戶態驅動,運行 dpdk 提供的 Python 腳本 dpdk-devbind.py 綁定網卡設備后才會執行其 probe 函數。
uio 用戶態驅動則是在 dpdk 實例程序初始化 EAL 環境抽象層時才會進行驅動與設備匹配加載。
上圖為 UIO 框架,運行在內核空間的驅動程序功能為:分配、記錄設備需要的資源,注冊 uio 設備,中斷應答處理。
2.內核UIO框架
2.1 UIO 的內核使能及初始化
uio 的支持需要進行內核配置,配置路徑為 Device Drivers ---> Userspace I/O drivers,具體配置如下圖所示。
2.2 UIO 重要數據結構
內核態 uio 驅動有兩個重要的數據結構,分別為 uio_device 和 uio_info,該結構定義如下所示。
struct uio_device {
struct module *owner;
struct device dev;
int minor; /* 次設備號 */
atomic_t event; /* 中斷事件計數 */
struct fasync_struct *async_queue; /* 異步等待隊列 */
wait_queue_head_t wait; /* 等待隊列 */
struct uio_info *info; /* uio設備信息 */
struct mutex info_lock;
struct kobject *map_dir;
struct kobject *portio_dir;
};
struct uio_info {
struct uio_device *uio_dev; /* uio_info所屬的uio設備 */
const char *name; /* 設備名稱 */
const char *version; /* 設備驅動版本 */
struct uio_mem mem[MAX_UIO_MAPS]; /* 可映射內存區域列表 */
struct uio_port port[MAX_UIO_PORT_REGIONS]; /* 端口區域列表 */
long irq; /* 中斷號 */
unsigned long irq_flags; /* 中斷請求標志 */
void *priv; /* 可選的私有數據 */
/* 設備中斷處理程序 */
irqreturn_t (*handler)(int irq, struct uio_info *dev_info);
/* 此uio設備的mmap操作 */
int (*mmap)(struct uio_info *info, struct vm_area_struct *vma);
/* 此uio設備的open操作 */
int (*open)(struct uio_info *info, struct inode *inode);
/* 此uio設備的release操作 */
int (*release)(struct uio_info *info, struct inode *inode);
/* 禁止/使能操作,向/dev/uioX寫入0/1 */
int (*irqcontrol)(struct uio_info *info, s32 irq_on);
};
uio 核心是名為 "uio" 的字符設備;用戶驅動的內核部分(igb_uio)使用 uio_register_device 向 uio 核心部分注冊 uio 設備,uio 核心的任務就是管理好注冊的 uio 設備,uio 設備使用的數據結構是 uio_devic;設備屬性,比如 name,open(),release()等操作都放在了 uio_info 結構中,用戶使用 uio_register_device 注冊這些驅動之前要設置好 uio_info。
static const struct file_operations uio_fops = {
.owner = THIS_MODULE,
.open = uio_open,
.release = uio_release,
.read = uio_read,
.write = uio_write,
.mmap = uio_mmap,
.poll = uio_poll,
.fasync = uio_fasync,
.llseek = noop_llseek,
};
uio 核心字符設備注冊 uio_open ,uio_release,uio_read ,uio_write 中除了完成相關的維護工作外,還調用了注冊在 uio_info 中的相關方法。比如,在 uio_open 中調用了 uio_info 中注冊的 open 方法。
2.3 UIO 設備注冊接口
在用戶驅動的內核部分 igbuio_pci_probe() 調用 uio_register_device((宏定義為__uio_register_device)注冊 uio 設備時,在 __uio_register_device 中調用 uio_get_minor 函數,在 uio_get_minor 函數中,利用 idr 機制建立了次設備號(整數ID)和 uio_device 類型指針之間的聯系,uio_device 指針指向了代表注冊的 uio 設備的內核結構,device_add()調用完畢后在 /sys/class/uio/ 下就會出現代表 uio 設備的 uioX 文件夾,其中 X 為 uio 設備的次設備號,執行流程如下。
/* 用戶驅動的內核部分igb_uio */
igbuio_pci_probe(); /* 設備與驅動匹配后執行 */
uio_register_device(); /* 設備注冊 */
/* 內核部分uio核心 */
__uio_register_device();
kzalloc(); /* 為uio設備申請空間 */
init_waitqueue_head(&idev->wait); /* 初始化等待隊列 */
atomic_set(&idev->event, 0); /* 清空中斷事件計數器 */
uio_get_minor(idev); /* 獲取次設備號 */
device_initialize(&idev->dev); /* 設備初始化 */
dev_set_name(&idev->dev, "uio%d", idev->minor); /* 設置設備名稱 */
device_add(&idev->dev); /* 設備添加 */
3.UIO內核驅動
3.1 驅動模塊編譯
DPDK 在編譯生成 igb_uio.ko 時需要依賴運行系統的內核,具體編譯方法可參考DPDK官方文檔或者本專欄的《DPDK交叉編譯》。
3.2 驅動模塊加載
在模塊編譯成功后,需要在 DPDK 運行環境中插入 igb_uio 模塊,igb_uio 驅動主要做的就是注冊一個 pci 設備,但是 igbuio_pci_driver 對應保存 pci 設備信息的 id_table 指針為空,這樣在內核注冊此 pci 設備時,會找不到匹配的設備,就不會調用 igb_uio 驅動中的探測 probe 函數(對應igb_uio的igbuio_pci_probe()不會被調用到),只會在 /sys/bus/pci/drivers/ 目錄下創建 igb_uio 相應的目錄。
3.3 綁定網卡
DPDK 工程中提供了綁定網卡的 Python 腳本(dpdk-devbind.py),使用腳本或命令將指定的網卡綁定到igb_uio模塊后,igb_uio 的 probe 函數會執行(id_table非空),這是因為掃描到了匹配的設備,同時生成 /dev/uioX 設備(X為次設備號),/sys/class/uio 目錄下也產生與 /dev/uioX 設備對應的內容。
3.4 probe 函數執行
UIO 內核驅動注冊機制與其他驅動類似,通過調用 linux 提供的 uio API 接口進行注冊,在注冊之前所做的主要工作如下。
a. 分配一個封裝的 UIO 設備數據結構,包括了 uio_info。
/* A structure describing the private information for a uio device. */
struct rte_uio_pci_dev {
struct uio_info info;
struct pci_dev *pdev;
enum rte_intr_mode mode;
atomic_t refcnt;
};
igbuio_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
struct rte_uio_pci_dev *udev;
udev = kzalloc(sizeof(struct rte_uio_pci_dev), GFP_KERNEL);
/* 省略無關代碼 */
}
b. 使能 PCI 設備。
c. 填充 uio_info 結構體的信息。
/* fill uio infos */
udev->info.name = "igb_uio";
udev->info.version = "0.1";
udev->info.irqcontrol = igbuio_pci_irqcontrol;
udev->info.open = igbuio_pci_open;
udev->info.release = igbuio_pci_release;
udev->info.priv = udev;
udev->pdev = dev;
d. 映射 UIO 設備 PCI 資源空間(PCI 設備的物理地址及大小)。
調用函數為 igbuio_setup_bars()--->igbuio_pci_setup_iomem(),并且填充 uio_info 結構體的內存信息,映射 UIO 設備的內存空間后,就可以在用戶態直接對設備內存進行操作。
e. 根據 uio_info 的信息注冊 UIO 設備。
f. 中斷注冊。
初始化中斷的中斷號、中斷模式、中斷標志等,并初始化 uio_info 的 handler 字段,在產生中斷時,中斷處理函數將會被調用。如果有實際的硬件設備,那么 irq 應該是硬件設備實際使用的中斷號。
4.用戶驅動加載
網卡驅動模型一般包含三層,即 PCI 總線設備、網卡設備以及網卡設備的私有數據結構,即將設備的共性一層層的抽象,PCI 總線設備包含網卡設備,網卡設備又包含其私有數據結構。在 DPDK 中,首先會注冊設備驅動,然后查找當前系統有哪些 PCI 設備,并通過 PCI_ID 為 PCI 設備找到對應的驅動,最后調用驅動初始化設備。
4.1 網卡驅動注冊
網卡驅動的注冊使用了 GCC attribute 擴展屬性的 constructor 屬性,使得網卡驅動的注冊在程序 MAIN 函數之前就執行了,以 e1000 網卡驅動為例。
/* 驅動注冊調用的宏 */
RTE_PMD_REGISTER_PCI(net_e1000_igb, rte_igb_pmd);
RTE_PMD_REGISTER_PCI(net_e1000_igb_vf, rte_igbvf_pmd);
/* 對RTE_PMD_REGISTER_PCI宏進行展開 */
#define RTE_PMD_REGISTER_PCI(nm, pci_drv) \\
RTE_INIT(pciinitfn_ ##nm) \\
{\\
(pci_drv).driver.name = RTE_STR(nm);\\
rte_pci_register(&pci_drv); \\
} \\
RTE_PMD_EXPORT_NAME(nm, __COUNTER__)
#define RTE_INIT(func) RTE_INIT_PRIO(func, LAST)
#ifndef RTE_INIT_PRIO /* Allow to override from EAL */
#define RTE_INIT_PRIO(func, prio) \\
static void __attribute__((constructor(RTE_PRIO(prio)), used)) func(void)
#endif
使用 attribute 的 constructor 屬性,在 main 函數執行前執行 rte_pci_register 函數,將net_e1000_igb驅動掛載到全局rte_pci_bus.driver_list 鏈表上,其他的網卡驅動也是使用相同的方式掛載到該鏈表上。
4.2 掃描當前系統 PCI 設備
DPDK 例程中,main 函數調用rte_eal_init()—>rte_bus_scan()—>rte_pci_scan() 函數,查找當前系統中有哪些網卡,分別是什么類型,并將它們掛到全局鏈表 rte_pci_bus.device_list 上。
rte_pci_scan() 通過讀取 "/sys/bus/pci/devices/" 目錄下的信息,掃描當前系統的PCI設備,并按照PCI地址從大到小的順序將設備掛載到 rte_pci_bus.device_list 鏈表上。
4.3 PCI 驅動注冊
在 4.1 節的分析中,網卡的驅動在 main 函數執行之前已經注冊了,掛載到全局rte_pci_bus.driver_list 鏈表上,4.2 節流程中也將設備保存在 rte_pci_bus.device_list 鏈表上,接下來分析設備與驅動是如何進行匹配的,匹配的流程如下。
rte_eal_init
rte_bus_probe
pci_probe
pci_probe_all_drivers
rte_pci_probe_one_drive
當設備與驅動的 vendor/device ID 等信息匹配時,就會執行驅動的 probe 函數。
-
內核
+關注
關注
3文章
1366瀏覽量
40236 -
Linux
+關注
關注
87文章
11232瀏覽量
208958 -
內存
+關注
關注
8文章
3004瀏覽量
73900 -
函數
+關注
關注
3文章
4308瀏覽量
62445
發布評論請先 登錄
相關推薦
評論