在普通的電腦上,想使用USB設備,必須將插入到主機。USBIP卻能夠經過網絡,讓主機訪問其餘主機上的外部設備,而用戶程序徹底感知不到區別。linux
usbip的文章在這裏:https://pdfs.semanticscholar.org/c7c4/cb054d75810fdb0a2affa11c288b7687267f.pdf數組
本文基於linux內核4.4.3,分析USBIP這部分的源碼。先介紹總體結構,而後分兩部分介紹USBIP源碼網絡
從體系機構的角度上來講,USB的設備和總線都是經過Host分出,host叫作主機控制器,一個主機控制器會有一個root hub,而後root hub再經過接口分出多個hub,最後,一個hub的一個端口均可以用來鏈接一個外部設備或是寧一個hub,造成一個以host爲跟的樹狀結構。而Host最終是做爲一個PCI的設備,鏈接在PCI總線上。架構
在linux內核中,USB驅動的架構能夠分紅3層。app
理解了USB驅動的框架,USBIP的架構就比價容易了,主要部分也是兩個,讀取設備的主機端,設置一個虛擬的主機控制器接口VHCI,它不操縱底層的主機控制器,而是將上層的消息經過網絡轉發到另外一個主機,在另外一側,實現一個USB設備驅動,它不是將USB Core的內容向上傳遞,一樣是經過網絡發送出去,叫作Stub端。框架
在Linux內核中,USBIP的源碼寫在drivers/usb/usbip目錄下,在這些文件中,以stub開頭的都是server端的代碼,vhci開頭的是client端的代碼,其他是公共部分的代碼。socket
下面從Stub和VHCI的角度來分析。tcp
usbip_common中有一個usbip_device結構,這是stub和vhci兩邊設備從後向出來的公共部分。這裏有3個內核線程,一個socket,以及和eh相關的等待隊列和操做集合。usbip_common中其餘幾個結構就再也不敘述,比較簡單ide
struct usbip_device { enum usbip_side side; enum usbip_device_status status; /* lock for status */ spinlock_t lock; struct socket *tcp_socket;//用來通訊的socket struct task_struct *tcp_rx;//收消息的線程 struct task_struct *tcp_tx;//發送消息的線程 unsigned long event;//記錄事件 struct task_struct *eh;//內核線程 wait_queue_head_t eh_waitq;//eh等待隊列 struct eh_ops { void (*shutdown)(struct usbip_device *); void (*reset)(struct usbip_device *); void (*unusable)(struct usbip_device *); } eh_ops; };
stub.h中定義了關鍵的結構體,他們的內容和含義以下:函數
stub_device是stub端的設備抽象,表明以一個外部設備
struct stub_device { struct usb_interface *interface;//接口描述符指針 struct usb_device *udev; struct usbip_device ud;//對於stub和vhci兩端的設備都作了抽象,用來表示兩端交互中都須要的內容 __u32 devid; spinlock_t priv_lock; struct list_head priv_init;//urb初始隊列 struct list_head priv_tx;//urb被提交以後 struct list_head priv_free;//urb的內容被髮送給了chci struct list_head unlink_tx;//unlink請求隊列 struct list_head unlink_free;//unlink請求被處理 wait_queue_head_t tx_waitq;//等待隊列 };
stub_priv被賦值給urb->priv字段,主要是給stub端來管理urb結構體
struct stub_priv { unsigned long seqnum;//給每一個urb都有一個序列號 struct list_head list; struct stub_device *sdev; struct urb *urb; int unlinking;//是否被unlink };
stub_unlink表示一個urb的unlink請求
struct stub_unlink { unsigned long seqnum;//和stub_priv對於的序列號 struct list_head list; __u32 status;//unlink是否成功 };
stub_main中有一個全局變量的數組,用來管理全部的設備
static struct bus_id_priv busid_table[MAX_BUSID];
其中一個設備用bus_id_priv來表示
struct bus_id_priv { char name[BUSID_SIZE]; char status; int interf_count; struct stub_device *sdev; struct usb_device *udev; char shutdown_busid; };
先看stub_main中最後幾行,有模塊的的初始和卸載函數
module_init(usbip_host_init);
module_exit(usbip_host_exit);
usbip_host_init中主要函數有4個,作的工做以下
一、初始化全局變量busid_table
二、分配一個slab用來作stub_priv的分配工做
三、註冊一個usb驅動,這裏是stub的初始化,這裏註冊的驅動也是位於設備端,USB核心上層的USB驅動
四、建立兩個sysfs文件,這兩個文件的操做就在上面,rebind_store和store_match_busid、show_match_busid。
下面的退出函數usbip_host_exit同理,只是作了這幾個函數的清理工做
先看看它建立的兩個sysfs文件。這是給用戶態的接口。一個是match_busid
static DRIVER_ATTR(match_busid, S_IRUSR | S_IWUSR, show_match_busid,store_match_busid);
涉及的函數是show_match_busid和store_match_busid,show_match_busid用來輸出名字,store_match_busid則用來增長或是刪除busid_table中的條目
另外一個文件則是用來設置設備綁定的驅動。
這幾個文件的操做都是圍繞busid_table和它的幾個操做函數開展開,也比較簡單。整個文件的內容也分析完了。下面就還有一個關鍵的usb驅動,也就是Stub驅動,很顯然,主要的工做都是經過這個驅動來開展的。
stub_dev文件最底部有這個驅動
struct usb_device_driver stub_driver = { .name = "usbip-host", .probe = stub_probe, .disconnect = stub_disconnect, #ifdef CONFIG_PM .suspend = stub_suspend, .resume = stub_resume, #endif .supports_autosuspend = 0, };
從probe函數開始,這裏只說一些和Stub驅動自身邏輯相關的重要部分,proe函數經過stub_device_alloc分配了一個stub_device函數。而這個stub_device_alloc函數中還有一個usbip_start_eh函數。用來建立了一個內核線程eh。
int usbip_start_eh(struct usbip_device *ud) { init_waitqueue_head(&ud->eh_waitq); ud->event = 0; ud->eh = kthread_run(event_handler_loop, ud, "usbip_eh"); if (IS_ERR(ud->eh)) { pr_warn("Unable to start control thread\n"); return PTR_ERR(ud->eh); } return 0; } EXPORT_SYMBOL_GPL(usbip_start_eh);
前面看到usbip_device結構中有3個內核線程,這是其中一個,二且eh_waitq就是用來給eh睡眠的,出發事件寫在usbip_event_happened函數中,只是檢查usbip_device上是否有事件產生,event字段會否爲0。
好,接着回到probe函數,裏面有一個stub_add_files函數。能夠知道這個函數一樣建立了幾個sysfs文件,他們爲dev_attr_usbip_status、dev_attr_usbip_sockfd、dev_attr_usbip_debug。
其中比較關鍵的是dev_attr_usbip_sockfd,當寫入數據爲-1時執行關閉操做,另外一條分支則會經過寫入數據打開socket,賦值給tcp_socket字段,接着建立了兩個內核線程。
看看這個stub_dev文件,剩下的代碼都是處理斷開和釋放的函數了,這裏就不分析,剩下的兩個文件stub_rx和stub_tx能夠知道這是和內核線程相關的了。因此說剩下的主要內容就是這兩個內核線程。
先看rx,主函數以下,沿着這個邏輯往下看
int stub_rx_loop(void *data) { struct usbip_device *ud = data; while (!kthread_should_stop()) { if (usbip_event_happened(ud)) break; stub_rx_pdu(ud); } return 0; }
stub_rx_pdu函數中,調用usbip_recv收一個消息,usbip_header_correct_endian轉換成小端,而後根據收到的消息來作不一樣的處理,若是是unlink消息則調用stub_recv_cmd_unlink,若是是urb的提交消息則調用stub_recv_cmd_submit。
說一下priv_init、priv_tx、priv_free這3個隊列。stub中的urb經過stub_priv來進行管理。經過stub_priv,給每一個urb分配了序列號,同時,經過stub_priv中的list字段,將urb鏈接到上述3個隊列中,當urb被提交給USB Core,直到完成前,放在priv_init,提交完成後,還須要經過網絡發送給vhci,發送以前放在priv_tx,發送以後放在priv_free。
因此說,stub_recv_cmd_unlink的行爲就是遍歷priv_init,找到那些尚未完成的urb,調用usb_unlink_urb讓USB Core去取消他們。
stub_recv_cmd_submit函數,調用了stub_priv_alloc,這個函數的末尾將urb加入到了priv_init中。最後調用usb_submit_urb提交urb,注意這裏的回調函數,是stub_complete。這個函數在urb執行完後執行下面的語句,將urb移入priv_tx
list_move_tail(&priv->list, &sdev->priv_tx);
而後,調用了wake_up,喚醒tx內核線程
wake_up(&sdev->tx_waitq);
接着看rx線程,主要是兩個函數stub_send_ret_submit和stub_send_ret_unlink,由於這是一個發送消息的線程,因此發送的消息有兩種,一個是USB設備的交互內容,一種是vhci發送的unlink消息的回覆。
stub_send_ret_submit處理的就是USB設備的返回內容,dequeue_from_priv_tx將urb從priv_tx取下,放入priv_free。stub_send_ret_submit最後再將內容封裝成usbip協議規定的樣子,經過網絡發送出去。
下面再介紹剩下的兩個隊列,unlink_tx和unlink_free,這是用來處理unlink的兩個隊列,一個stub_unlink結構表示一個unlink請求,unlink_tx存放尚未被回覆的unlink請求,而unlink_free則是存放已經回覆了的。
因此stub_send_ret_unlink中的dequeue_from_unlink_tx用來完成unlink的隊列轉換,剩下的代碼就會經過網絡發送回覆消息
這樣,stub這邊的幾個函數就都已經完了
這邊的代碼和上面已經很是類似了,抓住3個點
一、模塊的初始化時註冊的驅動程序,在usb_add_hcd中會調用reset和start函數,接着抓住urb的幾個操做函數就能夠了
static struct hc_driver vhci_hc_driver = { .description = driver_name, .product_desc = driver_desc, .hcd_priv_size = sizeof(struct vhci_hcd), .flags = HCD_USB2, .start = vhci_start, .stop = vhci_stop, .urb_enqueue = vhci_urb_enqueue, .urb_dequeue = vhci_urb_dequeue, .get_frame_number = vhci_get_frame_number, .hub_status_data = vhci_hub_status, .hub_control = vhci_hub_control, .bus_suspend = vhci_bus_suspend, .bus_resume = vhci_bus_resume, };
二、建立的幾個sysfs文件。
三、3個內核線程的工做
就不贅述了