0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

一文詳解Linux UIO技術(shù)

CHANBAEK ? 來源:話說Linux ? 作者:話說Linux ? 2023-01-19 16:35 ? 次閱讀

1.UIO簡介

1.1 什么是UIO技術(shù)

UIO(Userspace I/O)是運(yùn)行在用戶空間的I/O技術(shù),Linux 系統(tǒng)中一般的驅(qū)動(dòng)設(shè)備都是運(yùn)行在內(nèi)核空間,應(yīng)用程序在用戶空間調(diào)用即可。UIO 則是將驅(qū)動(dòng)的小部分運(yùn)行在內(nèi)核空間,在用戶空間實(shí)現(xiàn)驅(qū)動(dòng)的絕大多數(shù)功能,使用 UIO 可以避免設(shè)備的驅(qū)動(dòng)程序需要隨著內(nèi)核的更新而更新的問題。

1.2 UIO主要任務(wù)

設(shè)備驅(qū)動(dòng)的主要任務(wù)為存取設(shè)備的內(nèi)存、處理設(shè)備產(chǎn)生的中斷。對(duì)于第一個(gè)任務(wù),UIO 核心實(shí)現(xiàn)了 mmap() 可以處理物理內(nèi)存 (physicalmemory),邏輯內(nèi)存(logicalmemory),虛擬內(nèi)存(virtualmemory)。UIO 驅(qū)動(dòng)的編寫是就不需要再考慮這些繁瑣的細(xì)節(jié)。

對(duì)于第二個(gè)任務(wù),設(shè)備中斷的應(yīng)答必須在內(nèi)核空間進(jìn)行。所以在內(nèi)核空間有一小部分代碼用來應(yīng)答中斷和禁止中斷,其余的工作全部留給用戶空間處理。如果用戶空間要等待一個(gè)設(shè)備中斷,它只需要阻塞在對(duì) /dev/uioX 的 read() 操作上,當(dāng)設(shè)備產(chǎn)生中斷時(shí),read() 操作立即返回。UIO 也實(shí)現(xiàn)了 poll() 系統(tǒng)調(diào)用,可以使用 select() 來等待中斷的發(fā)生,select() 有一個(gè)超時(shí)參數(shù)可以用來實(shí)現(xiàn)有限時(shí)間內(nèi)等待中斷。對(duì)設(shè)備的控制還可以通過 /sys/class/uio 下的各個(gè)文件的讀寫來完成。注冊的 uio 設(shè)備將會(huì)出現(xiàn)在該目錄下,假設(shè) uio 設(shè)備是 uio0,那么映射的設(shè)備內(nèi)存文件出現(xiàn)在 /sys/class/uio/uio0/maps/mapX,對(duì)該文件的讀寫就是對(duì)設(shè)備內(nèi)存的讀寫。

1.3 UIO 框架

uio 代碼可以分為三個(gè)部分:內(nèi)核 uio 框架及內(nèi)核內(nèi)部函數(shù),uio 內(nèi)核驅(qū)動(dòng)部分,uio 用戶驅(qū)動(dòng)部分。

內(nèi)核 uio 框架通過配置內(nèi)核選項(xiàng) CONFIG_UIO=y 使能 Userspace I/O drivers,在內(nèi)核初始化時(shí)會(huì)調(diào)用 uio_init 創(chuàng)建 uio_class。

igb_uio 內(nèi)核驅(qū)動(dòng)通過編譯運(yùn)行 igb_uio.ko 加載并注冊一個(gè) pci 設(shè)備,但是igbuio_pci_driver 對(duì)應(yīng)保存 pci 設(shè)備信息的 id_table 指針為空,這樣在內(nèi)核注冊此 pci 設(shè)備時(shí),會(huì)找不到匹配的設(shè)備,就不會(huì)調(diào)用 igb_uio 驅(qū)動(dòng)中的探測 probe 函數(shù) uio 用戶態(tài)驅(qū)動(dòng),運(yùn)行 dpdk 提供的 Python 腳本 dpdk-devbind.py 綁定網(wǎng)卡設(shè)備后才會(huì)執(zhí)行其 probe 函數(shù)。

uio 用戶態(tài)驅(qū)動(dòng)則是在 dpdk 實(shí)例程序初始化 EAL 環(huán)境抽象層時(shí)才會(huì)進(jìn)行驅(qū)動(dòng)與設(shè)備匹配加載。

Dingtalk_20221229120319.jpg

上圖為 UIO 框架,運(yùn)行在內(nèi)核空間的驅(qū)動(dòng)程序功能為:分配、記錄設(shè)備需要的資源,注冊 uio 設(shè)備,中斷應(yīng)答處理。

2.內(nèi)核UIO框架

2.1 UIO 的內(nèi)核使能及初始化

uio 的支持需要進(jìn)行內(nèi)核配置,配置路徑為 Device Drivers ---> Userspace I/O drivers,具體配置如下圖所示。

Dingtalk_20221206154648.jpg

2.2 UIO 重要數(shù)據(jù)結(jié)構(gòu)

內(nèi)核態(tài) uio 驅(qū)動(dòng)有兩個(gè)重要的數(shù)據(jù)結(jié)構(gòu),分別為 uio_device 和 uio_info,該結(jié)構(gòu)定義如下所示。

struct uio_device {
struct module *owner;
struct device dev;
int minor; /* 次設(shè)備號(hào) */
atomic_t event; /* 中斷事件計(jì)數(shù) */
struct fasync_struct *async_queue; /* 異步等待隊(duì)列 */
wait_queue_head_t wait; /* 等待隊(duì)列     */
struct uio_info *info; /* uio設(shè)備信息 */
struct mutex info_lock;
struct kobject *map_dir;
struct kobject *portio_dir;
};


struct uio_info {
struct uio_device *uio_dev; /* uio_info所屬的uio設(shè)備 */
const char *name; /* 設(shè)備名稱 */
const char *version; /* 設(shè)備驅(qū)動(dòng)版本 */
struct uio_mem mem[MAX_UIO_MAPS]; /* 可映射內(nèi)存區(qū)域列表 */
struct uio_port port[MAX_UIO_PORT_REGIONS]; /* 端口區(qū)域列表 */
long irq; /* 中斷號(hào) */
unsigned long irq_flags; /* 中斷請(qǐng)求標(biāo)志 */
void *priv; /* 可選的私有數(shù)據(jù) */
/* 設(shè)備中斷處理程序 */
irqreturn_t (*handler)(int irq, struct uio_info *dev_info);
/* 此uio設(shè)備的mmap操作 */
int (*mmap)(struct uio_info *info, struct vm_area_struct *vma);
/* 此uio設(shè)備的open操作 */
int (*open)(struct uio_info *info, struct inode *inode);
/* 此uio設(shè)備的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" 的字符設(shè)備;用戶驅(qū)動(dòng)的內(nèi)核部分(igb_uio)使用 uio_register_device 向 uio 核心部分注冊 uio 設(shè)備,uio 核心的任務(wù)就是管理好注冊的 uio 設(shè)備,uio 設(shè)備使用的數(shù)據(jù)結(jié)構(gòu)是 uio_devic;設(shè)備屬性,比如 name,open(),release()等操作都放在了 uio_info 結(jié)構(gòu)中,用戶使用 uio_register_device 注冊這些驅(qū)動(dòng)之前要設(shè)置好 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 核心字符設(shè)備注冊 uio_open ,uio_release,uio_read ,uio_write 中除了完成相關(guān)的維護(hù)工作外,還調(diào)用了注冊在 uio_info 中的相關(guān)方法。比如,在 uio_open 中調(diào)用了 uio_info 中注冊的 open 方法。

2.3 UIO 設(shè)備注冊接口

在用戶驅(qū)動(dòng)的內(nèi)核部分 igbuio_pci_probe() 調(diào)用 uio_register_device((宏定義為__uio_register_device)注冊 uio 設(shè)備時(shí),在 __uio_register_device 中調(diào)用 uio_get_minor 函數(shù),在 uio_get_minor 函數(shù)中,利用 idr 機(jī)制建立了次設(shè)備號(hào)(整數(shù)ID)和 uio_device 類型指針之間的聯(lián)系,uio_device 指針指向了代表注冊的 uio 設(shè)備的內(nèi)核結(jié)構(gòu),device_add()調(diào)用完畢后在 /sys/class/uio/ 下就會(huì)出現(xiàn)代表 uio 設(shè)備的 uioX 文件夾,其中 X 為 uio 設(shè)備的次設(shè)備號(hào),執(zhí)行流程如下。

/* 用戶驅(qū)動(dòng)的內(nèi)核部分igb_uio */
igbuio_pci_probe(); /* 設(shè)備與驅(qū)動(dòng)匹配后執(zhí)行 */
  uio_register_device(); /* 設(shè)備注冊 */
/* 內(nèi)核部分uio核心 */
    __uio_register_device();
     kzalloc(); /* 為uio設(shè)備申請(qǐng)空間 */
     init_waitqueue_head(&idev->wait); /* 初始化等待隊(duì)列 */
     atomic_set(&idev->event, 0); /* 清空中斷事件計(jì)數(shù)器 */
     uio_get_minor(idev); /* 獲取次設(shè)備號(hào) */
     device_initialize(&idev->dev); /* 設(shè)備初始化 */
     dev_set_name(&idev->dev, "uio%d", idev->minor); /* 設(shè)置設(shè)備名稱 */
     device_add(&idev->dev); /* 設(shè)備添加 */

3.UIO內(nèi)核驅(qū)動(dòng)

3.1 驅(qū)動(dòng)模塊編譯

DPDK 在編譯生成 igb_uio.ko 時(shí)需要依賴運(yùn)行系統(tǒng)的內(nèi)核,具體編譯方法可參考DPDK官方文檔或者本專欄的《DPDK交叉編譯》。

3.2 驅(qū)動(dòng)模塊加載

在模塊編譯成功后,需要在 DPDK 運(yùn)行環(huán)境中插入 igb_uio 模塊,igb_uio 驅(qū)動(dòng)主要做的就是注冊一個(gè) pci 設(shè)備,但是 igbuio_pci_driver 對(duì)應(yīng)保存 pci 設(shè)備信息的 id_table 指針為空,這樣在內(nèi)核注冊此 pci 設(shè)備時(shí),會(huì)找不到匹配的設(shè)備,就不會(huì)調(diào)用 igb_uio 驅(qū)動(dòng)中的探測 probe 函數(shù)(對(duì)應(yīng)igb_uio的igbuio_pci_probe()不會(huì)被調(diào)用到),只會(huì)在 /sys/bus/pci/drivers/ 目錄下創(chuàng)建 igb_uio 相應(yīng)的目錄。

3.3 綁定網(wǎng)卡

DPDK 工程中提供了綁定網(wǎng)卡的 Python 腳本(dpdk-devbind.py),使用腳本或命令將指定的網(wǎng)卡綁定到igb_uio模塊后,igb_uio 的 probe 函數(shù)會(huì)執(zhí)行(id_table非空),這是因?yàn)閽呙璧搅似ヅ涞脑O(shè)備,同時(shí)生成 /dev/uioX 設(shè)備(X為次設(shè)備號(hào)),/sys/class/uio 目錄下也產(chǎn)生與 /dev/uioX 設(shè)備對(duì)應(yīng)的內(nèi)容。

3.4 probe 函數(shù)執(zhí)行

UIO 內(nèi)核驅(qū)動(dòng)注冊機(jī)制與其他驅(qū)動(dòng)類似,通過調(diào)用 linux 提供的 uio API 接口進(jìn)行注冊,在注冊之前所做的主要工作如下。

a. 分配一個(gè)封裝的 UIO 設(shè)備數(shù)據(jù)結(jié)構(gòu),包括了 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);
    /* 省略無關(guān)代碼 */
}

b. 使能 PCI 設(shè)備。

c. 填充 uio_info 結(jié)構(gòu)體的信息。

/* 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 設(shè)備 PCI 資源空間(PCI 設(shè)備的物理地址及大小)。

調(diào)用函數(shù)為 igbuio_setup_bars()--->igbuio_pci_setup_iomem(),并且填充 uio_info 結(jié)構(gòu)體的內(nèi)存信息,映射 UIO 設(shè)備的內(nèi)存空間后,就可以在用戶態(tài)直接對(duì)設(shè)備內(nèi)存進(jìn)行操作。

e. 根據(jù) uio_info 的信息注冊 UIO 設(shè)備。

f. 中斷注冊。

初始化中斷的中斷號(hào)、中斷模式、中斷標(biāo)志等,并初始化 uio_info 的 handler 字段,在產(chǎn)生中斷時(shí),中斷處理函數(shù)將會(huì)被調(diào)用。如果有實(shí)際的硬件設(shè)備,那么 irq 應(yīng)該是硬件設(shè)備實(shí)際使用的中斷號(hào)。

4.用戶驅(qū)動(dòng)加載

網(wǎng)卡驅(qū)動(dòng)模型一般包含三層,即 PCI 總線設(shè)備、網(wǎng)卡設(shè)備以及網(wǎng)卡設(shè)備的私有數(shù)據(jù)結(jié)構(gòu),即將設(shè)備的共性一層層的抽象,PCI 總線設(shè)備包含網(wǎng)卡設(shè)備,網(wǎng)卡設(shè)備又包含其私有數(shù)據(jù)結(jié)構(gòu)。在 DPDK 中,首先會(huì)注冊設(shè)備驅(qū)動(dòng),然后查找當(dāng)前系統(tǒng)有哪些 PCI 設(shè)備,并通過 PCI_ID 為 PCI 設(shè)備找到對(duì)應(yīng)的驅(qū)動(dòng),最后調(diào)用驅(qū)動(dòng)初始化設(shè)備。

4.1 網(wǎng)卡驅(qū)動(dòng)注冊

網(wǎng)卡驅(qū)動(dòng)的注冊使用了 GCC attribute 擴(kuò)展屬性的 constructor 屬性,使得網(wǎng)卡驅(qū)動(dòng)的注冊在程序 MAIN 函數(shù)之前就執(zhí)行了,以 e1000 網(wǎng)卡驅(qū)動(dòng)為例。

/* 驅(qū)動(dòng)注冊調(diào)用的宏 */
RTE_PMD_REGISTER_PCI(net_e1000_igb, rte_igb_pmd);
RTE_PMD_REGISTER_PCI(net_e1000_igb_vf, rte_igbvf_pmd);


/* 對(duì)RTE_PMD_REGISTER_PCI宏進(jìn)行展開 */
#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 函數(shù)執(zhí)行前執(zhí)行 rte_pci_register 函數(shù),將net_e1000_igb驅(qū)動(dòng)掛載到全局rte_pci_bus.driver_list 鏈表上,其他的網(wǎng)卡驅(qū)動(dòng)也是使用相同的方式掛載到該鏈表上。

4.2 掃描當(dāng)前系統(tǒng) PCI 設(shè)備

DPDK 例程中,main 函數(shù)調(diào)用rte_eal_init()—>rte_bus_scan()—>rte_pci_scan() 函數(shù),查找當(dāng)前系統(tǒng)中有哪些網(wǎng)卡,分別是什么類型,并將它們掛到全局鏈表 rte_pci_bus.device_list 上。

rte_pci_scan() 通過讀取 "/sys/bus/pci/devices/" 目錄下的信息,掃描當(dāng)前系統(tǒng)的PCI設(shè)備,并按照PCI地址從大到小的順序?qū)⒃O(shè)備掛載到 rte_pci_bus.device_list 鏈表上。

4.3 PCI 驅(qū)動(dòng)注冊

在 4.1 節(jié)的分析中,網(wǎng)卡的驅(qū)動(dòng)在 main 函數(shù)執(zhí)行之前已經(jīng)注冊了,掛載到全局rte_pci_bus.driver_list 鏈表上,4.2 節(jié)流程中也將設(shè)備保存在 rte_pci_bus.device_list 鏈表上,接下來分析設(shè)備與驅(qū)動(dòng)是如何進(jìn)行匹配的,匹配的流程如下。

rte_eal_init
  rte_bus_probe
    pci_probe
      pci_probe_all_drivers
        rte_pci_probe_one_drive

當(dāng)設(shè)備與驅(qū)動(dòng)的 vendor/device ID 等信息匹配時(shí),就會(huì)執(zhí)行驅(qū)動(dòng)的 probe 函數(shù)。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 內(nèi)核
    +關(guān)注

    關(guān)注

    3

    文章

    1375

    瀏覽量

    40312
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11314

    瀏覽量

    209773
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    3031

    瀏覽量

    74119
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4333

    瀏覽量

    62720
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    使用uio Stick v2升級(jí)uIO-Stick_Firmware后無法連接怎么解決?

    uio,選擇了uIO_V222.hex升級(jí),升級(jí)后uio stick的指示燈ACT直高速閃爍,顯示無法連接uio 問題: 1、SPOC的
    發(fā)表于 01-26 07:06

    嵌入式Linux系統(tǒng)開發(fā)技術(shù)詳解——基于ARM

    嵌入式Linux系統(tǒng)開發(fā)技術(shù)詳解——基于ARM,共6個(gè)部分,給大家參考
    發(fā)表于 06-24 21:19

    linux核心技術(shù)詳解

    很好的Linux技術(shù)詳解,值得讀。
    發(fā)表于 11-09 17:35 ?5次下載

    Zynq UltraScale+ MPSoC 上的多個(gè)Linux UIO設(shè)計(jì)

    本實(shí)驗(yàn)工程將介紹如何利在賽靈思異構(gòu)多處理器產(chǎn)品系列 Zynq UtralScale+ MPSoC ZCU102 嵌入式評(píng)估板上實(shí)現(xiàn)多個(gè) UIO,同時(shí)借助賽靈思的工具完成硬件工程和 linux BSP 的開發(fā),最后通過測試應(yīng)用程序完成測試。
    發(fā)表于 03-21 14:55 ?3339次閱讀
    Zynq UltraScale+ MPSoC 上的多個(gè)<b class='flag-5'>Linux</b> <b class='flag-5'>UIO</b>設(shè)計(jì)

    如何在 Vitis 中使用 UIO 驅(qū)動(dòng)框架創(chuàng)建簡單的 Linux 用戶應(yīng)用

    Linux嵌入式設(shè)計(jì)中最基本的任務(wù)之是創(chuàng)建用戶應(yīng)用程序。 在本篇博文中,我們將探討如何在 Vitis 中使用 UIO 驅(qū)動(dòng)框架創(chuàng)建簡單的 Linux 用戶應(yīng)用。 1 硬件設(shè)計(jì) 本次
    的頭像 發(fā)表于 11-20 14:05 ?4318次閱讀

    詳解藍(lán)牙模塊原理與結(jié)構(gòu)

    電子發(fā)燒友網(wǎng)站提供《詳解藍(lán)牙模塊原理與結(jié)構(gòu).pdf》資料免費(fèi)下載
    發(fā)表于 11-26 16:40 ?94次下載

    詳解精密封裝技術(shù)

    詳解精密封裝技術(shù)
    的頭像 發(fā)表于 12-30 15:41 ?1676次閱讀

    詳解分立元件門電路

    詳解分立元件門電路
    的頭像 發(fā)表于 03-27 17:44 ?3250次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>詳解</b>分立元件門電路

    詳解pcb和smt的區(qū)別

    詳解pcb和smt的區(qū)別
    的頭像 發(fā)表于 10-08 09:31 ?3393次閱讀

    詳解pcb地孔的作用

    詳解pcb地孔的作用
    的頭像 發(fā)表于 10-30 16:02 ?1682次閱讀

    詳解pcb不良分析

    詳解pcb不良分析
    的頭像 發(fā)表于 11-29 17:12 ?1196次閱讀

    詳解pcb的msl等級(jí)

    詳解pcb的msl等級(jí)
    的頭像 發(fā)表于 12-13 16:52 ?9809次閱讀

    詳解pcb微帶線設(shè)計(jì)

    詳解pcb微帶線設(shè)計(jì)
    的頭像 發(fā)表于 12-14 10:38 ?3386次閱讀

    詳解pcb的組成和作用

    詳解pcb的組成和作用
    的頭像 發(fā)表于 12-18 10:48 ?1588次閱讀

    詳解pcb回流焊溫度選擇與調(diào)整

    詳解pcb回流焊溫度選擇與調(diào)整
    的頭像 發(fā)表于 12-29 10:20 ?1701次閱讀