在普通的电脑上,想使用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个内核线程的工做
就不赘述了