zoukankan      html  css  js  c++  java
  • USBIP源码分析

    简介

    在普通的电脑上,想使用USB设备,必须将插入到主机。USBIP却可以通过网络,让主机访问其他主机上的外部设备,而用户程序完全感知不到区别。

    usbip的文章在这里:https://pdfs.semanticscholar.org/c7c4/cb054d75810fdb0a2affa11c288b7687267f.pdf

    本文基于linux内核4.4.3,分析USBIP这部分的源码。先介绍整体结构,然后分两部分介绍USBIP源码

    USBIP整体架构

    从体系机构的角度上来说,USB的设备和总线都是通过Host分出,host叫做主机控制器,一个主机控制器会有一个root hub,然后root hub再通过接口分出多个hub,最后,一个hub的一个端口都可以用来连接一个外部设备或是宁一个hub,形成一个以host为跟的树状结构。而Host最终是作为一个PCI的设备,连接在PCI总线上。

    在linux内核中,USB驱动的架构可以分成3层。

    • 最靠近用户层的是USB设备驱动,这是每个USB设备有独有的,在它之上可以直接与VFS交互,比如键盘和鼠标这些设备,USB设备驱动就足以处理外部设备的交互任务,因为他们发送过来的就只有是一些很简单的数据,但是如果是一个插在USB上的网卡等设备,则需要在USB设备驱动之上再覆盖一层网卡等驱动,用来解析USB总线传输过来的数据。它与下层的USB Core通过urb交换数据。
    • USB Core则是承上启下的一层,USB设备驱动通过urb的抽次昂概念来和底层交互,USB Core就用来解析urb,同时,还会包含Hub驱动等等一些USB驱动框架的其他主要部分。
    • 最下层的是主机控制器驱动,前面看到USB外部设备最终通过Host和总线、CPU交互,也就是说最底层还需要有一个主机控制器的驱动,它主要负责Host的相关工作,将USB设备的信息通过主机控制器向上传递软件层。这部分会有一个PCI驱动,然后控制Host硬件

     理解了USB驱动的框架,USBIP的架构就比价容易了,主要部分也是两个,读取设备的主机端,设置一个虚拟的主机控制器接口VHCI,它不操纵底层的主机控制器,而是将上层的消息通过网络转发到另一个主机,在另一侧,实现一个USB设备驱动,它不是将USB Core的内容向上传递,同样是通过网络发送出去,叫做Stub端。

    在Linux内核中,USBIP的源码写在drivers/usb/usbip目录下,在这些文件中,以stub开头的都是server端的代码,vhci开头的是client端的代码,其余是公共部分的代码。

    下面从Stub和VHCI的角度来分析。

    Stub端

    主要结构体

    usbip_common中有一个usbip_device结构,这是stub和vhci两边设备从后向出来的公共部分。这里有3个内核线程,一个socket,以及和eh相关的等待队列和操作集合。usbip_common中其他几个结构就不再叙述,比较简单

    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个,做的工作如下

    1、初始化全局变量busid_table
    2、分配一个slab用来做stub_priv的分配工作
    3、注册一个usb驱动,这里是stub的初始化,这里注册的驱动也是位于设备端,USB核心上层的USB驱动
    4、创建两个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驱动

    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
    ");
            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和tx两个内核线程和stub_device中的5个链表

    先看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这边的几个函数就都已经完了

    VHCI端

    这边的代码和上面已经非常相似了,抓住3个点

    1、模块的初始化时注册的驱动程序,在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,
    };

    2、创建的几个sysfs文件。

    3、3个内核线程的工作

    就不赘述了

  • 相关阅读:
    PAT 1010. 一元多项式求导 (25)
    PAT 1009. 说反话 (20) JAVA
    PAT 1009. 说反话 (20)
    PAT 1007. 素数对猜想 (20)
    POJ 2752 Seek the Name, Seek the Fame KMP
    POJ 2406 Power Strings KMP
    ZOJ3811 Untrusted Patrol
    Codeforces Round #265 (Div. 2) 题解
    Topcoder SRM632 DIV2 解题报告
    Topcoder SRM631 DIV2 解题报告
  • 原文地址:https://www.cnblogs.com/likaiming/p/10949172.html
Copyright © 2011-2022 走看看