Linux usb gadget框架概述
很幸运,在公司开发了gadget相关驱动,总结下来,大大小小开发了四个与gadget相关的驱动,字符驱动、g_multi、g_ether、g_zero,在这里把自己对gadget的开发中自己的感悟记录之。
想要了解gadget,必须了解其框架,知道composite、gadget、udc三者之间的联系,知道usb描述符的作用。
一个usb device有一个设备描述符。
有一个或者多个配置描述符
一个配置描述符有一个或者多个接口(在gadget端,接口正式命名是usb_func)。
一个接口有0个或者多个端点。
编写gadget的关键是在于了解udc、gadget、composite三者之间的联系和架构层次,在实际应用中gadget是不需要我们去编写的,需要我们自己去编写的是composite层,以及地对udc层的修改,下面开始详细介绍着三者。
1、composite英文意思是复合的意思,估计是编写usb gadget层设备驱动都整合到一起,通过统一的函数usb_composite_register注册。功能各异,杂七杂八,所以称为复合层吧。
在该层,我们需要注意的相关结构体和函数有如下:
struct usb_composite_dev { //作为composite复合设备,所有composite设备都必须实现该设备。
    struct usb_gadget        *gadget; //设备和gadget交互,gadget和udc交互。
    struct usb_request        *req;   //每个设备自带一个usb请求,所有的数据交互都是通过该请求发送的。
    struct usb_configuration    *config; 一个设备有一个或者多个配置。
    /* private: */
    /* internals */
    unsigned int            suspended:;
    struct usb_device_descriptor    desc;  //设备描述符,唯一
    struct list_head        configs; //配置
    struct list_head        gstrings; //字符描述
    struct usb_composite_driver    *driver; //设备绑定的驱动
    u8                next_string_id;
    char                *def_manufacturer;  //默认制造商
    /* the gadget driver won't enable the data pullup
     * while the deactivation count is nonzero.
     */
    unsigned            deactivations;
    /* the composite driver won't complete the control transfer's
     * data/status stages till delayed_status is zero.
     */
    int                delayed_status;
    /* protects deactivations and delayed_status counts*/
    spinlock_t            lock;
};
 struct usb_composite_driver {  //所有compesite驱动必须填充该结构体。
     const char              *name;
     const struct usb_device_descriptor  *dev; //必须实现
     struct usb_gadget_strings       **strings;
     enum usb_device_speed           max_speed;
     unsigned        needs_serial:;
     int         (*bind)(struct usb_composite_dev *cdev); //必须实现的
     int         (*unbind)(struct usb_composite_dev *); //必须实现
     void            (*disconnect)(struct usb_composite_dev *);
     /* global suspend hooks */
     void            (*suspend)(struct usb_composite_dev *);
     void            (*resume)(struct usb_composite_dev *);
     struct usb_gadget_driver        gadget_driver; //这个地方的驱动由composite提供,所有和composite相关的驱动都会默认分配该驱动。该驱动是
 };
  struct usb_gadget_driver {  //该驱动是usbcore和composite之间交互必不可少的一环,两者之间的联系主要靠他来维持,内核已经提供好了,不需要我们去实现。
      char            *function;
      enum usb_device_speed   max_speed;
      int         (*bind)(struct usb_gadget *gadget,
                      struct usb_gadget_driver *driver);
      void            (*unbind)(struct usb_gadget *);
      int         (*setup)(struct usb_gadget *,    //枚举过程中必不可少的函数。不需要驱动去实现。
                      const struct usb_ctrlrequest *);
      void            (*disconnect)(struct usb_gadget *);
      void            (*suspend)(struct usb_gadget *);
      void            (*resume)(struct usb_gadget *);
      /* FIXME support safe rmmod */
      struct device_driver    driver;
  }; 
 static const struct usb_gadget_driver composite_driver_template = { //所有的composite设备都会在注册gadet驱动的时候采用该实例填充。
                                                                         //笔者认为这么做的原因是gadget驱动永远只有一个,composite可以随便实现。体现分层的思想。
     .bind       = composite_bind,
     .unbind     = composite_unbind,    
     .setup      = composite_setup,
     .disconnect = composite_disconnect,                                                                                                                           
     .suspend    = composite_suspend,
     .resume     = composite_resume,    
     .driver = {
         .owner      = THIS_MODULE,
     },
 }; 
下面首先介绍composite驱动的注册过程,讲完后介绍驱动的编写过程。
以zero.c为例:
- static int __init init(void) 
 {
 return usb_composite_register(&zero_driver);
 }
 - usb_composite_register(&zero_driver); 
 1========》 driver->gadget_driver = composite_driver_template; //此过程并未涉及到对compoite设备的注册的操作,
 //而是将composite驱动中注册的相关信息填充到gadget中,利用gadget去和udc打交道
 ---->return usb_gadget_probe_driver(gadget_driver); ////该函数首先判定bind setup等函数是否实现了。不需要我们去实现。- ----> list_for_each_entry(udc, &udc_list, list) //查找注册在内核中的udc实例,找到了进行下一步操作,没找到退出。驱动注册失败。 - ----> ret = udc_bind_to_driver(udc, driver); //将udc和gadget驱动绑定在一起。 - 2======》 udc_bind_to_driver(udc, driver); - ---->404 ret = driver->bind(udc->gadget, driver);//最关键的莫过于该函数了,最初笔者分析的时候,以为是composite的bind函数,后来才弄清楚是gadget层 - //的bind函数composite_bind ,将在后面介绍。 - -----> ret = usb_gadget_udc_start(udc->gadget, driver); //此处可以理解为一切就绪,udc相关设置已经写入寄存器。 - -----> ret = usb_gadget_connect(udc->gadget); //插入host usb口,检查D+电平的变化。也就是枚举过冲 - 3=====》 composite_bind //最重要的函数了。是理解gadget设计的关键 - static int composite_bind(struct usb_gadget *gadget,//该函数zhu要是实现配置描述符接口等操作。 
 struct usb_gadget_driver *gdriver)
 {
 struct usb_composite_dev *cdev;
 struct usb_composite_driver *composite = to_cdriver(gdriver);
 int status = -ENOMEM; cdev = kzalloc(sizeof *cdev, GFP_KERNEL);
 if (!cdev)
 return status; spin_lock_init(&cdev->lock);
 cdev->gadget = gadget;
 set_gadget_data(gadget, cdev);
 INIT_LIST_HEAD(&cdev->configs);
 INIT_LIST_HEAD(&cdev->gstrings); status = composite_dev_prepare(composite, cdev);
 if (status)
 goto fail; /* composite gadget needs to assign strings for whole device (like
 1694 ┊* serial number), register function drivers, potentially update
 1695 ┊* power state and consumption, etc
 1696 ┊*/
 /*此处才是开始调用驱动的bind函数*/
 status = composite->bind(cdev);
 if (status < )
 goto fail; update_unchanged_dev_desc(&cdev->desc, composite->dev); /* has userspace failed to provide a serial number? */
 if (composite->needs_serial && !cdev->desc.iSerialNumber)
 WARNING(cdev, "userspace failed to provide iSerialNumber\n"); INFO(cdev, "%s ready\n", composite->name);
 return ; fail:
 __composite_unbind(gadget, false);
 return status;
 }
 1====》 composite_bind
 ---->1676 struct usb_composite_driver *composite = to_cdriver(gdriver); //这个函数就是将gadet转换为composite的关键。在gadget驱动注册时联系在一起。
 //return container_of(gdrv, struct usb_composite_driver, gadget_driver);
 ---->cdev = kzalloc(sizeof *cdev, GFP_KERNEL); //实现cdev设备。
 ---->set_gadget_data(gadget, cdev); //填充私有数据,以便内核中可以通过gadget获取cdev.
 ---->INIT_LIST_HEAD(&cdev->configs); //初始化配置描述链表,在开头本人介绍过,一个设备有一个或者多种配置。
 ---->1689 status = composite_dev_prepare(composite, cdev); //这个函数十分重要,包括usb_request、complete(回调函数实现)、设备和驱动的绑定等)
 ---->1698 status = composite->bind(cdev) //此处才是开始调用驱动的bind函数,后面会详细介绍该函数。
 ----> INFO(cdev, "%s ready\n", composite->name); //至此,composite设备创建成功。- int composite_dev_prepare(struct usb_composite_driver *composite, //该函数主要是实现了至关重要的usb_request,针对端点0,即控制端点 
 struct usb_composite_dev *cdev)
 {
 struct usb_gadget *gadget = cdev->gadget;
 int ret = -ENOMEM; /* preallocate control response and buffer */
 cdev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL); //申请控制端点usb请求
 if (!cdev->req)
 return -ENOMEM; cdev->req->buf = kmalloc(USB_COMP_EP0_BUFSIZ, GFP_KERNEL); //请求发送内容将保持在此
 if (!cdev->req->buf)
 goto fail; ret = device_create_file(&gadget->dev, &dev_attr_suspended);//创建设备文件,位于/sys/class/dev目录下
 if (ret)
 goto fail_dev; cdev->req->complete = composite_setup_complete; //回调函数。
 gadget->ep0->driver_data = cdev; //通过端点即可获取设备。 cdev->driver = composite; //将composite设备和驱动绑定在一起,所以usb gadget端是没有枚举过程的,驱动直接注册成功,创建设备。 /*
 1635 ┊* As per USB compliance update, a device that is actively drawing
 1636 ┊* more than 100mA from USB must report itself as bus-powered in
 1637 ┊* the GetStatus(DEVICE) call.
 1638 ┊*/
 if (CONFIG_USB_GADGET_VBUS_DRAW <= USB_SELF_POWER_VBUS_MAX_DRAW)
 usb_gadget_set_selfpowered(gadget); /* interface and string IDs start at zero via kzalloc.
 1643 ┊* we force endpoints to start unassigned; few controller
 1644 ┊* drivers will zero ep->driver_data.
 1645 ┊*/
 usb_ep_autoconfig_reset(gadget); //
 return ;
 fail_dev 1649 kfree(cdev->req->buf); 1650 fail:- /* 此处知识初始化了usb配置链表,并未实例化接口和端点,所以将端点driver_data又重设为空,批量输入和输出点号为0; - 1642 /* interface and string IDs start at zero via kzalloc. 
 1643 ┊* we force endpoints to start unassigned; few controller
 1644 ┊* drivers will zero ep->driver_data.
 1645 ┊*/
 1646 usb_ep_autoconfig_reset(gadget);- */ - 1651 usb_ep_free_request(gadget->ep0, cdev->req); 
 1652 cdev->req = NULL;
 1653 return ret;
 1654 }- 下面即将进行到至关重要的一环,调用composite驱动的bind函数。 - static int __init zero_bind(struct usb_composite_dev *cdev) //里面是实现composite设备的关键,设计到strings,配置描述符,接口(function)、端点的实例化。 
 /276 {
 struct f_ss_opts *ss_opts;
 struct f_lb_opts *lb_opts;
 int status; /* Allocate string descriptor numbers ... note that string
 282 ┊* contents can be overridden by the composite_dev glue.
 283 ┊*/
 status = usb_string_ids_tab(cdev, strings_dev);
 if (status < )
 return status; device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id;//制造商
 device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id;//产品Id
 device_desc.iSerialNumber = strings_dev[USB_GADGET_SERIAL_IDX].id;//设备序列号功能索引,个人认为是针对多function设备而言的。 setup_timer(&autoresume_timer, zero_autoresume, (unsigned long) cdev); func_inst_ss = usb_get_function_instance("SourceSink"); //获取function,此处的实现十分巧妙,通过usb_function_register实现。
 if (IS_ERR(func_inst_ss))
 return PTR_ERR(func_inst_ss); ss_opts = container_of(func_inst_ss, struct f_ss_opts, func_inst);
 ss_opts->pattern = gzero_options.pattern;
 ss_opts->isoc_interval = gzero_options.isoc_interval;
 ss_opts->isoc_maxpacket = gzero_options.isoc_maxpacket;
 ss_opts->isoc_mult = gzero_options.isoc_mult;
 ss_opts->isoc_maxburst = gzero_options.isoc_maxburst;
 ss_opts->bulk_buflen = gzero_options.bulk_buflen; //每次传送的buf大小 func_ss = usb_get_function(func_inst_ss); //获取source_link实例,同样通过usb_function_register注册获取。
 if (IS_ERR(func_ss)) {
 status = PTR_ERR(func_ss);
 goto err_put_func_inst_ss;
 } func_inst_lb = usb_get_function_instance("Loopback");
 if (IS_ERR(func_inst_lb)) {
 status = PTR_ERR(func_inst_lb);
 goto err_put_func_ss;
 } lb_opts = container_of(func_inst_lb, struct f_lb_opts, func_inst);
 lb_opts->bulk_buflen = gzero_options.bulk_buflen; //在usb_function_registe注册时就已经分配了。
 lb_opts->qlen = gzero_options.qlen; func_lb = usb_get_function(func_inst_lb);
 if (IS_ERR(func_lb)) {
 status = PTR_ERR(func_lb);
 goto err_put_func_inst_lb;
 } sourcesink_driver.iConfiguration = strings_dev[USB_GZERO_SS_DESC].id;//设置配置描述符索引
 loopback_driver.iConfiguration = strings_dev[USB_GZERO_LB_DESC].id; /* support autoresume for remote wakeup testing */
 sourcesink_driver.bmAttributes &= ~USB_CONFIG_ATT_WAKEUP;
 loopback_driver.bmAttributes &= ~USB_CONFIG_ATT_WAKEUP;
 sourcesink_driver.descriptors = NULL;
 loopback_driver.descriptors = NULL;
 if (autoresume) {
 sourcesink_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
 loopback_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
 autoresume_step_ms = autoresume * ;
 } /* support OTG systems */
 if (gadget_is_otg(cdev->gadget)) {
 sourcesink_driver.descriptors = otg_desc;
 sourcesink_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
 loopback_driver.descriptors = otg_desc;
 loopback_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
 } /* Register primary, then secondary configuration. Note that
 351 ┊* SH3 only allows one config...
 352 ┊*/
 if (loopdefault) {//若只支持loopback即回环模式。
 usb_add_config_only(cdev, &loopback_driver);//则loopback配置先注册
 usb_add_config_only(cdev, &sourcesink_driver);//后注册
 } else {
 usb_add_config_only(cdev, &sourcesink_driver);//同上
 usb_add_config_only(cdev, &loopback_driver);
 }
 status = usb_add_function(&sourcesink_driver, func_ss);//将功能即接口绑定到配置描述符,此处还有一次bind操作,隐藏的极深。
 if (status)
 goto err_conf_flb; usb_ep_autoconfig_reset(cdev->gadget);//重新设置ep
 status = usb_add_function(&loopback_driver, func_lb);
 if (status)
 goto err_conf_flb; usb_ep_autoconfig_reset(cdev->gadget);
 usb_composite_overwrite_options(cdev, &coverwrite);//支持传参。修改iverdor iproduct等。 INFO(cdev, "%s, version: " DRIVER_VERSION "\n", longname); return ; err_conf_flb:
 usb_put_function(func_lb);
 func_lb = NULL;
 err_put_func_inst_lb:
 usb_put_function_instance(func_inst_lb);
 func_inst_lb = NULL;
 err_put_func_ss:
 usb_put_function(func_ss);
 func_ss = NULL;
 err_put_func_inst_ss:
 usb_put_function_instance(func_inst_ss);
 func_inst_ss = NULL;
 return status;
 }- int usb_add_function(struct usb_configuration *config, //配置中实例化接口。 
 struct usb_function *function)
 {
 int value = -EINVAL; DBG(config->cdev, "adding '%s'/%p to config '%s'/%p\n",
 function->name, function,
 config->label, config); if (!function->set_alt || !function->disable)//接口是否设置了set_alt函数,该函数调用表示当前接口可用,其他接口不可用。
 goto done; function->config = config;
 list_add_tail(&function->list, &config->functions); /* REVISIT *require* function->bind? */- 205 if (function->bind) { 
 206 value = function->bind(config, function);//对于一个配置多个接口的cdev设备,再次对function进行bin操作。
 207 if (value < 0) {
 208 list_del(&function->list);
 209 function->config = NULL;
 210 }
 211 } else
 212 value = 0;
 213
 214 /* We allow configurations that don't work at both speeds.
 215 ┊* If we run into a lowspeed Linux system, treat it the same
 216 ┊* as full speed ... it's the function drivers that will need
 217 ┊* to avoid bulk and ISO transfers.
 218 ┊*/
 219 if (!config->fullspeed && function->fs_descriptors)
 220 config->fullspeed = true;
 221 if (!config->highspeed && function->hs_descriptors)
 222 config->highspeed = true;
 223 if (!config->superspeed && function->ss_descriptors)
 224 config->superspeed = true;
 225
 226 done:
 227 if (value)
 228 DBG(config->cdev, "adding '%s'/%p --> %d\n",
 229 function->name, function, value);
 230 return value;
 231 }- 以f_loopback.c中的bind为例。 
- static int loopback_bind(struct usb_configuration *c, struct usb_function *f) //将配置和功能绑定在一起 
 {
 struct usb_composite_dev *cdev = c->cdev;
 struct f_loopback *loop = func_to_loop(f);
 int id;
 int ret; /* allocate interface ID(s) */
 id = usb_interface_id(c, f);//一般从0开始配置。分配接口id号
 if (id < )
 return id;
 loopback_intf.bInterfaceNumber = id; id = usb_string_id(cdev);//获取字符描述符索引
 if (id < )
 return id;
 strings_loopback[].id = id;
 loopback_intf.iInterface = id; /* allocate endpoints */ loop->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_source_desc);//分配批量输入端点
 if (!loop->in_ep) {
 autoconf_fail:
 ERROR(cdev, "%s: can't autoconfigure on %s\n",
 f->name, cdev->gadget->name);
 return -ENODEV;
 }
 loop->in_ep->driver_data = cdev; /* claim */ loop->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_sink_desc);//分配批量输出端点
 if (!loop->out_ep)
 goto autoconf_fail;
 loop->out_ep->driver_data = cdev; /* claim */ /* support high speed hardware */
 hs_loop_source_desc.bEndpointAddress =
 fs_loop_source_desc.bEndpointAddress;
 hs_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress; /* support super speed hardware */
 ss_loop_source_desc.bEndpointAddress =
 fs_loop_source_desc.bEndpointAddress;
 ss_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress; ret = usb_assign_descriptors(f, fs_loopback_descs, hs_loopback_descs, //此处主要设设置usb速度。
 ss_loopback_descs);
 if (ret)
 return ret; DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
 ┊ (gadget_is_superspeed(c->cdev->gadget) ? "super" :
 ┊ ┊(gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full")),
 f->name, loop->in_ep->name, loop->out_ep->name);
 return ;
 }- 至此gadget框架只驱动注册过程已经介绍完成。下面再来介绍下驱动的注册流程。 - 填充usb_composite_driver驱动实例,调用usb_composite_probe进行注。
- 移花接木,填充usb_composite驱动中gadget_driver,调用usb_gadget_probe_driver(gadget_driver);使得udc能够和composite设备联系起来。
- 调用udc_bind_to_driver,慢慢的将udc和composite绑定在一起。
- 调用driver->bind(udc->gadget, driver);实际上是调用composite_bind函数,该函数由内核实现。该函数主要是创建cdev设备,将真正的驱动和cdev绑定在一起。
- 此后再调用编写的驱动的bind函数。此时主要是讲cdev设备的配置和function进行填充。设备必须有配置,配置必须有接口。
- 针对多function的驱动,必须再次绑定bind函数,此次主要是设置接口id,实例化ep等。
- 前6步操作的完成,表示composite设备驱动已经注册成功了。成功了之后呢?那就涉及到对udc的操作了,udc进入请求连接状态,等待中断的响应。
- 中断响应也就是响应主设备发起的枚举操作,完成枚举过程,枚举响应主要是调用function->setup函数。枚举过程将在另外一篇文章中介绍。
 
 
Linux usb gadget框架概述的更多相关文章
- Linux USB驱动框架分析 【转】
		转自:http://blog.chinaunix.net/uid-11848011-id-96188.html 初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结 ... 
- Linux USB驱动框架分析【转】
		转自:http://blog.csdn.net/jeffade/article/details/7701431 Linux USB驱动框架分析(一) 初次接触和OS相关的设备驱动编写,感觉还挺有意思的 ... 
- Linux USB驱动框架分析(2)【转】
		转自:http://blog.chinaunix.net/uid-23046336-id-3243543.html 看了http://blog.chinaunix.net/uid-11848011 ... 
- Android USB gadget框架学习笔记
		一 Gadget框架结构 kernel/drivers/usb/gadget,这个目录是android下usbgadget的主要目录. Gadget功能组织单元:主要文件android.c,usb g ... 
- Linux下USB驱动框架分析【转】
		转自:http://blog.csdn.net/brucexu1978/article/details/17583407 版权声明:本文为博主原创文章,未经博主允许不得转载. http://www.c ... 
- Linux USB ECM Gadget 驱动介绍
		1 USB ECM介绍 USB ECM,属于USB-IF定义的CDC(Communication Device Class)下的一个子类:Ethernet Networking Control Mo ... 
- 12.2 linux USB框架分析(详细注册match匹配过程)
		首先我们先来简单说一说USB的框架,之后在来具体分析源码,以便加深理解!其实USB的框架比较像“平台总线.设备.驱动”的框架,也分为总线.设备.驱动三大块.其中总线驱动是已经由内核完成的,一旦接入u ... 
- Linux  usb子系统(一):子系统架构
		一.USB协议基础知识 前序:USB概念概述 USB1.0版本速度1.5Mbps(低速USB) USB1.1版本速度12Mbps(全速USB) USB2.0版本速度480Mbps(高速USB). ... 
- Linux usb子系统(一) _写一个usb鼠标驱动
		USB总线是一种典型的热插拔的总线标准,由于其优异的性能几乎成为了当下大小设备中的标配. USB的驱动可以分为3类:SoC的USB控制器的驱动,主机端USB设备的驱动,设备上的USB Gadget驱动 ... 
随机推荐
- VirtualBox 在Centos 7 中安装增强功能 (共享文件夹)
			1.分配光驱 2.安装相关依赖包 yum install -y bzip2 gcc gcc-devel gcc-c++ gcc-c++-devel make kernel-d 3.创建临时文件夹 mk ... 
- .Net core使用XRPC创建远程接口的Actor对象
			Actor是一种高并发处理模型,每个Actor都有着自己的状态有序消息处理机制,所以在业务处理的情况并不需要制定锁的机制,从而达到更高效的处理能性.XRPC是一个基于远程接口调用的RPC组件,它可以简 ... 
- GitLab常用命令整理
			进入本地仓库访问位置之后执行命令 1) 远程仓库相关命令 检出仓库:$ git clone git://github.com/jquery/jquery.git 查看远程仓库:$ git remote ... 
- swift 2特性记录
			swift 团队一直在优化,让大家准备在秋天的时候,迁移到swift2做准备. 一.错误处理 异常处理,不是NSError对象和双指针. 可以使用 throws 来指定方法来抛出一个错误. 调用d ... 
- Windows8 64位运行Silverlight程序不能访问WCF的解决方案
			公司的项目是Silverlight+WCF,而我的本本是Win8 64位系统,一直无法正常运行Silverlight程序,一个同事找到了方案,现分享出来 一种情况是,Vs2010运行程序时,报无法加载 ... 
- 渐进式jpeg(progressive jpeg)图片及其相关
			最近看有些网站上的jpg格式的图片在呈现的时候,有两种方式,一种是自上而下扫描式的,还有一种就是先是全部的模糊图片,然后逐渐清晰(就像GIF格式的交错显示). 一.基本JPEG(baseline jp ... 
- 服网LNMP集群-1.0.5
			平台: arm 类型: ARM 模板 软件包: haproxy linux mysql nginx application server arm basic software fuwang infra ... 
- 修复SQL中的孤立账户
			EXEC sys.sp_change_users_login 'AUTO_FIX','登录名',NULL,'登录密码' 
- 关于docker容器内核参数修改问题
			以下内容截取自docker官方文档 地址:https://docs.docker.com/edge/engine/reference/commandline/run/#configure-namesp ... 
- C++ list类详解
			转自:http://blog.csdn.net/whz_zb/article/details/6831817 双向循环链表list list是双向循环链表,,每一个元素都知道前面一个元素和后面一个元素 ... 
