1.1為什么出現了UIO?
硬件設備可以根據功能分為網絡設備,塊設備,字符設備,或者根據與CPU相連的方式分為PCI設備,USB設備等。它們被不同的內核子系統支持。這些標准的設備的驅動編寫較為容易而且容易維護。很容易加入主內核源碼樹。但是,又有很多設備難以划分到這些子系統中,比如I/O卡,現場總線接口或者定制的FPGA。通常這些非標准設備的驅動被實現為字符驅動。這些驅動使用了很多內核內部函數和宏。而這些內部函數和宏是變化的。這樣驅動的編寫者必須編寫一個完全的內核驅動,而且一直維護這些代碼。而且這些驅動進不了主內核源碼。於是就出現了用戶空間I/O框架(Userspace I/O framework)。
1.2 dpdk的uio使用
在基於kernel的IO模型中,所有的設備IO都要經過內核處理,在高並發的網絡數據包收發的情況下,大量硬件中斷會降低內核數據包處理能力,內核和用戶空間的數據拷貝也會造成大量的計算資源浪費。所以,作為高並發大流量網絡開發框架的DPDK,必須要找到一個能夠避免內核中斷爆炸和大量數據拷貝的方法,在用戶空間能夠直接和硬件進行交互。Linux的UIO就是這樣一個將硬件操作映射到用戶空間的kernel bypass方案。
下圖展示了UIO驅動的內核部分,用戶空間部分,和UIO框架以及內核的關系:
1.3 dpdk的網口綁定干了啥?
綁定操作:
./dpdk-devbind.py -b igb_uio 0000:00:07.0
既然是設備驅動程序一定會創建設備,/dev下查看:多出了uio0設備;
vm65:/dev # ll |grep uio crw------- 1 root root 246, 0 Feb 18 07:41 uio0 vm65:/dev #
查看/sys/class/uio/uio:
vm65:/sys/class/uio/uio0 # ll total 0 -r--r--r-- 1 root root 4096 Feb 18 06:42 dev lrwxrwxrwx 1 root root 0 Feb 18 06:42 device -> ../../../0000:00:07.0 -r--r--r-- 1 root root 4096 Feb 18 06:42 event drwxr-xr-x 4 root root 0 Feb 18 06:42 maps -r--r--r-- 1 root root 4096 Feb 18 06:42 name drwxr-xr-x 2 root root 0 Feb 18 06:42 power lrwxrwxrwx 1 root root 0 Feb 18 06:42 subsystem -> ../../../../../class/uio -rw-r--r-- 1 root root 4096 Feb 18 06:41 uevent -r--r--r-- 1 root root 4096 Feb 18 06:42 version
同時,查看dmesg,多出了以下幾行信息:
<6>[171282.211083] igb_uio 0000:00:07.0: mapping 1K dma=0x123fb9b000 host=ffff88123fb9b000 <6>[171282.211088] igb_uio 0000:00:07.0: unmapping 1K dma=0x123fb9b000 host=ffff88123fb9b000
可以看到。綁定過程中的kernel part會通過uio_register_device 注冊對應的uio設備,獲取pcie的配置空間BAR信息包括addr、name、size、offset填充到map/map0中,這些在用戶態會將其讀出,並mmap至用戶態進程空間,這樣用戶態便可直接操作設備的內存空間。
除了內存管理,第二個任務,對於設備中斷的應答必須在內核空間進行。所以在內核空間有一小部分代碼用來應答和禁止中斷,中斷的其他任務由用戶態完成;
假設用戶空間要等待一個設備中斷,它僅僅須要簡單的堵塞在對 /dev/uioX的read()操作上。 當設備產生中斷時,read()操作馬上返回。
UIO 也實現了poll()系統調用。你能夠使用 select()來等待中斷的發生。select()有一個超時參數能夠用來實現有限時間內等待中斷。
1.4 用戶態驅動
常用的用戶態驅動開發有兩種方式可選:
1、打開/dev/mem 后,使用MMAP映射出芯片物理地址對應的虛擬地址,在用戶態訪問虛擬地址加偏移就能訪問到芯片的寄存器;
2、采用UIO方式,在內核態映射地址后,在用戶態通過打開/dev/uioxxx 方式,再使用MMAP映射一次,就可采用跟方式1 相同的方式訪問芯片reg或是memory;
這兩種方式各有優缺點:
方式1:優點是實現簡單,如果不需要用到中斷,可以不用管內核態的內容,關注用戶態代碼就好。缺點是代碼運行的進程要有root權限,不然無法訪問/dev/mem,如果要使用中斷還是會涉及到內核態的驅動;
方式2:優點是無需要root權限,中斷可以直接在用戶態獲取。缺點是必須要在內核態做初始化;即需要在內核態通過uio_register_device注冊對應的uio設備,這樣user space就可以通過/sys/class/uio/uioX/mapsX來訪問這個uio設備;
關鍵結構體&函數:
注冊uio設備前需要初始化對應的uio_info結構體:
struct uio_info { struct uio_device *uio_dev; // 在__uio_register_device中初始化 const char *name; // 調用__uio_register_device之前必須初始化 const char *version; //調用__uio_register_device之前必須初始化 struct uio_mem mem[MAX_UIO_MAPS]; struct uio_port port[MAX_UIO_PORT_REGIONS]; long irq; //分配給uio設備的中斷號,調用__uio_register_device之前必須初始化 unsigned long irq_flags;// 調用__uio_register_device之前必須初始化 void *priv; // irqreturn_t (*handler)(int irq, struct uio_info *dev_info); //uio_interrupt中調用,用於中斷處理 // 調用__uio_register_device之前必須初始化 int (*mmap)(struct uio_info *info, struct vm_area_struct *vma); //在uio_mmap中被調用, // 執行設備打開特定操作 int (*open)(struct uio_info *info, struct inode *inode);//在uio_open中被調用,執行設備打開特定操作 int (*release)(struct uio_info *info, struct inode *inode);//在uio_device中被調用,執行設備打開特定操作 int (*irqcontrol)(struct uio_info *info, s32 irq_on);//在uio_write方法中被調用,執行用戶驅動的 //特定操作。 };
設備注冊函數:
#define uio_register_device(parent, info) __uio_register_device(THIS_MODULE, parent, info) int __uio_register_device(struct module *owner, struct device *parent, struct uio_info *info)
用戶態驅動示例:
https://blog.csdn.net/ganggexiongqi/article/details/6751798
https://blog.csdn.net/ganggexiongqi/article/details/6748103