PMD是Poll Mode Driver的縮寫,即基於用戶態的輪詢機制的驅動。本文將介紹PMD的基本原理。
在不考慮vfio的情況下,PMD的結構圖如下:
圖1. PMD結構圖
雖然PMD是在用戶態實現設備驅動,但還是依賴於內核提供的策略。其中uio模塊,是內核提供的用戶態驅動框架,而igb_uio是DPDK kit中擁有與uio交互,bind指定網卡的內核模塊。
當使用DPDK腳本dpdk-devbind來bind網卡時,會通過sysfs與內核交互,讓內核使用指定驅動來匹配網卡。具體的行爲向/sys/bus/pci/devices/(pci id)/driver_override寫入指定驅動名稱,或者向/sys/bus/pci/drivers/igb_uio(驅動名稱)/new_id寫入要綁定網卡的PCI ID。前者是配置設備,讓其選擇驅動。後者是是配置驅動,讓其支持新的PCI設備。按照內核的文檔https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-pci,這兩個動作都會促使驅動bind新設備。但是在dpdk-devbind腳本中,還是通過向/sys/bus/pci/drivers/igb_uio(驅動名稱)/bind寫入pci id來保證bind。—— 也許是處於兼容性考慮吧。
當使用igb_uio bind指定設備後,內核會調用igb_uio註冊的struct pci_driver的probe函數,即igbuio_pci_probe。在這個函數中,設置PCI的一些操作(如設置PCI BAR、DMA等),不是重點,那是驅動工程師的職責:) 對於PMD來說,重點是與UIO的交互。
1. 調用igbuio_setup_bars,設置uio_info的uio_mem和uio_port。
圖2. igbuio_setup_bars
2. 設置uio_info的其他成員
圖3. igb_uio設置uio_info
3. 調用uio_register_device,註冊uio設備。
查看/dev目錄,可以發現對應的uioX設備,如下圖:
圖4. uio設備
打開uio設備
這時,應用層已經可以使用uio設備了。DPDK的應用層代碼,會打開uioX設備。在函數pci_uio_alloc_resource中,
圖5. 打開uio設備
當open對應的uio設備時,對應的內核操作爲uio_open,其又會調用igb_uio的open函數,流程圖如下:
圖6. open uio設備
igb_uio的默認中斷模式爲RTE_INTR_MODE_MSIX,在igbuio_pci_enable_interrupts的關鍵代碼如下:
圖7. 設置中斷信息
圖8. 註冊中斷
當打開uio設備時,igb_uio註冊了一箇中斷。這時大家應該有個疑問,PMD不是用戶態輪詢設備嗎?爲什麼還要申請中斷,註冊中斷處理函數呢?這是因爲,即使應用層可以通過uio來實現設備驅動,但是設備的某些事件還是需要內核進行響應,然後通知應用層。當然,現在的中斷處理已經非常簡單了。
圖9. igb_uio中斷處理函數
其中的關鍵步驟是調用uio_event_notify。
圖10. uio_event_notify
這個函數很簡單:1. 增加uio設備的“事件”數; 2. 喚醒在idev->wait等待隊列中的task;3. 使用信號異步通知async_queue隊列中的進程;目前DPDK沒有使用異步IO的方式,所有對於DPDK的PMD來說,只有前兩個語句有用。
uio模塊除了實現了上面的“事件”通知,還支持了mmap方法,用於將註冊的uio設備的“內存空間”映射到應用空間。其mmap的函數爲uio_mmap,關鍵代碼如下:
圖11.uio_mmap
至此,uio已經可以讓PMD的應用層訪問設備的大部分資源了。接下來,要轉過去看看PMD的應用層。
當DPDK的app啓動時,會進行EAL初始化,如下圖:
圖12. 應用層uio初始化
在pci_uio_alloc_resource中,主要是打開dpdk要管理的uio設備
圖13. 打開ui設備
同時,DPDK還需要把PCI設備的BAR映射到應用層。在pci_uio_map_resource函數中,除了調用上圖中的pci_uio_alloc_resource,還會調用pci_uio_map_resource_by_index做資源映射。
圖14. pci_uio_map_resource
下面就是PMD的應用層的驅動實現了。以最簡單的e1000驅動爲例,在其初始化函數eth_igb_dev_init中,
圖15. 註冊e1000的中斷處理函數
從圖11和圖12的代碼中,可以看出當uio設備有事件時,由eth_igb_interrupt_handler負責處理,實現了用戶態的中斷處理。
圖16. eth_igb_interrupt_handler
eth_igb_interrupt_handler非常簡單,只是處理設備的狀態變化事件,如link status。
接下來,就是最重要的了,PMD如何讀取網卡數據。DPDK的應用代碼,會調用rte_eth_rx_burst讀取數據報文。
圖17.rte_eth_rx_burst
在這個函數中,會調用驅動dev->rx_pkt_burst來做實際的操作。以e1000爲例,即eth_igb_recv_pkts。
圖18 eth_igb_recv_pkts
圖19 eth_igb_recv_pkts
這裏的實現很簡單。如果網卡接收buffer的描述符表示已經完成一個報文的接收(有E1000_RXD_STAT_DD標誌),則rte_mbuf_raw_alloc一個mbuf,進行處理。如果沒有報文,直接跳出循環。
對應RTC模型的DPDK應用來說,就是不斷的調用rte_eth_rx_burst去“問”網卡是否有新的報文。如果有,就取走所有的報文或達到參數nb_pkts的上限。然後進行報文處理,處理完畢,再次循環。
以上就是PMD的大體流程。