Linux-USB总线驱动分析

如下图所示,以windows为例,我们插上一个没有USB设备驱动的USB,就会提示你安装驱动程序

为什么一插上就有会提示信息?

是因为windows自带了USB总线驱动程序,

USB总线驱动程序负责:

识别USB设备,给USB设备找到对应的驱动程序

新接入的USB设备的默认地址(编号)是0,在未分配新编号前,PC主机使用0地址和它通信。

然后USB总线驱动程序都会给它分配一个地址(编号)

PC机想访问USB总线上某个USB设备时,发出的命令都含有对应的地址(编号)

USB是一种主从结构。主机叫做Host,从机叫做Device,所有的USB传输,都是从USB主机这方发起;USB设备没有"主动"通知USB主机的能力。

例子:USB鼠标滑动一下立刻产生数据,但是它没有能力通知PC机来读数据,只能被动地等得PC机来读。

USB可以热插拔的硬件原理

   在USB集线器(hub)的每个下游端口的D+和D-上,分别接了一个15K欧姆的下拉电阻到地。这样,在集线器的端口悬空时,就被这两个下拉电阻拉到了低电平。

而在USB设备端,在D+或者D-上接了1.5K欧姆上拉电阻。对于全速和高速设备,上拉电阻是接在D+上;而低速设备则是上拉电阻接在D-上。这样,当设备插入到集线器时,由1.5K的上拉电阻和15K的下拉电阻分压,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上一层的集线器报告给USB主控制器),这样就检测到设备的插入了。USB高速设备先是被识别为全速设备,然后通过HOST和DEVICE两者之间的确认,再切换到高速模式的。在高速模式下,是电流传输模式,这时将D+上的上拉电阻断开。

USB的4大传输类型:

控制传输(control)

是每一个USB设备必须支持的,通常用来获取设备描述符、设置设备的状态等等。一个USB设备从插入到最后的拔出这个过程一定会产生控制传输(即便这个USB设备不能被这个系统支持)。 中断传输(interrupt)

支持中断传输的典型设备有USB鼠标、 USB键盘等等。中断传输不是说我的设备真正发出一个中断,然后主机会来读取数据。它其实是一种轮询的方式来完成数据的通信。USB设备会在设备驱动程序中设置一个参数叫做interval,它是endpoint的一个成员。 interval是间隔时间的意思,表示我这个设备希望主机多长时间来轮询自己,只要这个值确定了之后,我主机就会周期性的来查看有没有数据需要处理

批量传输(bulk)

支持批量传输最典型的设备就是U盘,它进行大数量的数据传输,能够保证数据的准确性,但是时间不是固定的。

实时传输(isochronous)  USB摄像头就是实时传输设备的典型代表,它同样进行大数量的数据传输,数据的准确性无法保证,但是对传输延迟非常敏感,也就是说对实时性要求比较高

USB端点:

USB设备与主机会有若干个通信的”端点”,每个端点都有个端点号,除了端点0外,每一个端点只能工作在一种传输类型(控制传输、中断传输、批量传输、实时传输)下,一个传输方向下

传输方向都是基于USB主机的立场说的,

比如:鼠标的数据是从鼠标传到PC机, 对应的端点称为"中断输入端点"

其中端点0是设备的默认控制端点, 既能输出也能输入,用于USB设备的识别过程

同样linux内核也自带了USB总线驱动程序,框架如下:

要想成为一个USB主机,硬件上就必须要有USB主机控制器才行,USB主机控制器又分为4种接口:

OHCI(Open Host Controller Interface): 微软主导的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),OHCI接口的软件简单,硬件复杂  

UHCI(Universal Host Controller Interface): Intel主导的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps), 而UHCI接口的软件复杂,硬件简单  

EHCI(Enhanced Host Controller Interface):高速USB2.0(480Mbps),

xHCI(eXtensible Host Controller Interface):USB3.0(5.0Gbps),采用了9针脚设计,同时也支持USB2.0、1.1等

接下来进入正题,开始分析USB总线驱动,如何识别USB设备

由于内核自带了USB驱动,所以我们先插入一个USB键盘到开发板上看打印信息

发现以下字段:

如下图,找到第一段话是位于drivers/usb/core/hub.c的第2186行

这个hub其实就是我们的USB主机控制器的集线器,用来管理多个USB接口

1. drivers/usb/core/hub.c的第2186行位于hub_port_init()函数里

它又是被谁调用的,如下图所示,我们搜索到它是通过hub_thread()函数调用的

hub_thread()函数如下:

  1. static int hub_thread(void *__unused)
  2. {
  3.  
  4. do {
  5. hub_events(); //执行一次hub事件函数
  6. wait_event_interruptible(khubd_wait,!list_empty(&hub_event_list) ||kthread_should_stop());                            //(1).每次执行一次hub事件,都会进入一次等待事件中断函数
  7. try_to_freeze();
  8. } while (!kthread_should_stop() || !list_empty(&hub_event_list));
  9.  
  10. pr_debug("%s: khubd exiting\n", usbcore_name);
  11. return 0;
  12. }

从上面函数中得到, 要想执行hub_events(),都要等待khubd_wait这个中断唤醒才行

2.我们搜索”khubd_wait”,看看是被谁唤醒

找到该中断在kick_khubd()函数中唤醒,代码如下:

  1. static void kick_khubd(struct usb_hub *hub)
  2. {
  3. unsigned long flags;
  4. to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;
  5.  
  6. spin_lock_irqsave(&hub_event_lock, flags);
  7. if (list_empty(&hub->event_list)) {
  8. list_add_tail(&hub->event_list, &hub_event_list);
  9. wake_up(&khubd_wait); //唤醒khubd_wait这个中断
  10. }
  11.  
  12. spin_unlock_irqrestore(&hub_event_lock, flags);
  13. }

3.继续搜索kick_khubd,发现被hub_irq()函数中调用

显然,就是当USB设备插入后,D+或D-就会被拉高,然后USB主机控制器就会产生一个hub_irq中断.

4.接下来我们直接分析hub_port_connect_change()函数,如何连接端口的

  1. static void hub_port_connect_change(struct usb_hub *hub, int port1,u16 portstatus, u16 portchange)
  2. {
  3.   ... ...
  4.   udev = usb_alloc_dev(hdev, hdev->bus, port1);   //(1)注册一个usb_device,然后会放在usb总线上
  5.  
  6.   usb_set_device_state(udev, USB_STATE_POWERED); //设置注册的USB设备的状态标志
  7.   ... ...
  8.  
  9.   choose_address(udev);  //(2)给新的设备分配一个地址编号
  10.    status = hub_port_init(hub, udev, port1, i);     //(3)初始化端口,与USB设备建立连接
  11.   ... ...
  12.  
  13.   status = usb_new_device(udev);        //(4)创建USB设备,与USB设备驱动连接
  14.   ... ...
  15. }

(usb_device设备结构体参考:http://www.cnblogs.com/lifexy/p/7634511.html

所以最终流程图如下:

5.我们进入hub_port_connect_change()->usb_alloc_dev(),来看看它是怎么设置usb_device的

  1. 1 usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
  2. 2
  3. 3 {
  4. 4
  5. 5 struct usb_device *dev;
  6. 6
  7. 7
  8. 8
  9. 9 dev = kzalloc(sizeof(*dev), GFP_KERNEL); //分配一个usb_device设备结构体
  10. 10
  11. 11 ... ...
  12. 12
  13. 13
  14. 14
  15. 15 device_initialize(&dev->dev); //初始化usb_device
  16. 16
  17. 17 dev->dev.bus = &usb_bus_type; //(1)设置usb_device的成员device->bus等于usb_bus总线
  18. 18
  19. 19 dev->dev.type = &usb_device_type; //设置usb_device的成员device->type等于usb_device_type
  20. 20
  21. 21 ... ...
  22. 22
  23. 23 return dev; //返回一个usb_device结构体
  24. 24
  25. 25 }

(1)在第17行上,设置device成员,主要是用来后面8.2小节,注册usb总线的device表上.

其中usb_bus_type是一个全局变量, 它和我们之前学的platform平台总线相似,属于USB总线, 是Linux中bus的一种.

如下图所示,每当创建一个USB设备,或者USB设备驱动时,USB总线都会调用match成员来匹配一次,使USB设备和USB设备驱动联系起来.

usb_bus_type结构体如下:

  1. struct bus_type usb_bus_type = {
  2. .name = "usb",    //总线名称,存在/sys/bus下
  3. .match = usb_device_match,       //匹配函数,匹配成功就会调用usb_driver驱动的probe函数成员
  4. .uevent = usb_uevent, //事件函数
  5. .suspend = usb_suspend,      //休眠函数
  6. .resume = usb_resume,     //唤醒函数
  7. };  

6.我们进入hub_port_connect_change()->choose_address(),来看看它是怎么分配地址编号的

  1. static void choose_address(struct usb_device *udev)
  2. {
  3. int devnum;
  4. struct usb_bus *bus = udev->bus;
  5.  
  6. devnum = find_next_zero_bit(bus->devmap.devicemap, 128,bus->devnum_next);
  7.                      //在bus->devnum_next~128区间中,循环查找下一个非0(没有设备)的编号
  8.  
  9. if (devnum >= 128) //若编号大于等于128,说明没有找到空余的地址编号,从头开始找
  10. devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);
  11. bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1); //设置下次寻址的区间+1
  12.  
  13. if (devnum < 128) {
  14. set_bit(devnum, bus->devmap.devicemap);     //设置位
  15. udev->devnum = devnum;              
  16. }
  17. }

从上面代码中分析到每次的地址编号是连续加的,USB接口最大能接127个设备,我们连续插拔两次USB键盘,也可以看出,如下图所示:

7.我们再来看看hub_port_connect_change()->hub_port_init()函数是如何来实现连接USB设备的

  1. 1 static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,int retry_counter)
  2. 2 {
  3. 3 ... ...
  4. 4 for (j = 0; j < SET_ADDRESS_TRIES; ++j)
  5. 5 {
  6. 6 retval = hub_set_address(udev); //(1)设置地址,告诉USB设备新的地址编号
  7. 7
  8. 8 if (retval >= 0)
  9. 9 break;
  10. 10 msleep(200);
  11. 11 }
  12. 12 retval = usb_get_device_descriptor(udev, 8); //(2)获得USB设备描述符前8个字节
  13. 13 ... ...
  14. 14
  15. 15 retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); //重新获取设备描述符信息
  16. 16 ... ...
  17. 17 }

(1)上面第6行中,hub_set_address()函数主要是用来告诉USB设备新的地址编号, hub_set_address()函数如下:

  1. static int hub_set_address(struct usb_device *udev)
  2. {
  3. int retval;
  4. ... ...
  5. retval = usb_control_msg(udev, usb_sndaddr0pipe(),USB_REQ_SET_ADDRESS,0, udev->devnum, 0,NULL, 0, USB_CTRL_SET_TIMEOUT);                                              //(1.1)等待传输完成
  6. if (retval == 0) { //设置新的地址,传输完成,返回0
  7. usb_set_device_state(udev, USB_STATE_ADDRESS); //设置状态标志
  8. ep0_reinit(udev);
  9. }
  10. return retval;
  11. }

usb_control_msg()函数就是用来让USB主机控制器把一个控制报文发给USB设备,如果传输完成就返回0.其中参数udev表示目标设备;使用的管道为usb_sndaddr0pipe(),也就是默认的地址0加上控制端点号0; USB_REQ_SET_ADDRESS表示命令码,既设置地址; udev->devnum表示要设置目标设备的设备号;允许等待传输完成的时间为5秒,因为USB_CTRL_SET_TIMEOUT定义为5000。

2)上面第12行中,usb_get_device_descriptor()函数主要是获取目标设备描述符前8个字节,为什么先只开始读取8个字节?是因为开始时还不知道对方所支持的信包容量,这8个字节是每个设备都有的,后面再根据设备的数据,通过usb_get_device_descriptor()重读一次目标设备的设备描述结构.

其中USB设备描述符结构体如下所示:

  1. struct usb_device_descriptor {
  2. __u8 bLength; //本描述符的size
  3. __u8 bDescriptorType;   //描述符的类型,这里是设备描述符DEVICE
  4. __u16 bcdUSB; //指明usb的版本,比如usb2.0
  5. __u8 bDeviceClass;   //类
  6. __u8 bDeviceSubClass;    //子类
  7. __u8 bDeviceProtocol; //指定协议
  8. __u8 bMaxPacketSize0;    //端点0对应的最大包大小
  9. __u16 idVendor; //厂家ID
  10. __u16 idProduct; //产品ID
  11. __u16 bcdDevice; //设备的发布号
  12. __u8 iManufacturer; //字符串描述符中厂家ID的索引
  13. __u8 iProduct; //字符串描述符中产品ID的索引
  14. __u8 iSerialNumber;   //字符串描述符中设备序列号的索引
  15. __u8 bNumConfigurations; //可能的配置的数目
  16. } __attribute__ ((packed));

8.我们来看看hub_port_connect_change()->usb_new_device()函数是如何来创建USB设备的

  1. int usb_new_device(struct usb_device *udev)
  2. {
  3. ... ...
  4. err = usb_get_configuration(udev); //(1)获取配置描述块
  5.   ... ...
  6.   err = device_add(&udev->dev); // (2)把device放入bus的dev链表中,并寻找对应的设备驱动
  7. }

(1)其中usb_get_configuration()函数如下,就是获取各个配置

  1. int usb_get_configuration(struct usb_device *dev)
  2. {
  3. ... ...
  4. /* USB_MAXCONFIG 定义为8,表示设备描述块下有最多不能超过8个配置描述块 */
  5. /*ncfg表示 设备描述块下 有多少个配置描述块 */
  6. if (ncfg > USB_MAXCONFIG) {
  7. dev_warn(ddev, "too many configurations: %d, "
  8. "using maximum allowed: %d\n", ncfg, USB_MAXCONFIG);
  9. dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;
  10. }
  11. ... ...
  12. for (cfgno = 0; cfgno < ncfg; cfgno++) //for循环,从USB设备里依次读入所有配置描述块
  13. {
  14. result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,buffer, USB_DT_CONFIG_SIZE);
  15. //每次先读取USB_DT_CONFIG_SIZE个字节,也就是9个字节,暂放到buffer中
  16. ... ...
  17.  
  18. length = max((int) le16_to_cpu(desc->wTotalLength),USB_DT_CONFIG_SIZE);
  19.                   //通过wTotalLength,知道实际数据大小
  20.  
  21. bigbuffer = kmalloc(length, GFP_KERNEL); //然后再来分配足够大的空间
  22. ... ...
  23.  
  24. result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,bigbuffer, length);
  25. //在调用一次usb_get_descriptor,把整个配置描述块读出来,放到bigbuffer中
  26. ... ...
  27.  
  28. dev->rawdescriptors[cfgno] = bigbuffer; //再将bigbuffer地址放在rawdescriptors所指的指针数组中
  29.  
  30. result = usb_parse_configuration(&dev->dev, cfgno,&dev->config[cfgno],
  31.  
  32.     bigbuffer, length); //最后在解析每个配置块
  33.  
  34. }
  35. ... ...
  36. }

(2)其中device_add ()函数如下

  1. int usb_get_configuration(struct usb_device *dev)
  2. {
  3.  
  4. dev = get_device(dev); //使dev等于usb_device下的device成员
  5. ... ...
  6.  
  7. if ((error = bus_add_device(dev))) // 把这个设备添加到dev->bus的device表中
  8. goto BusError;
  9. ... ...
  10.  
  11. bus_attach_device(dev); //来匹配对应的驱动程序
  12. ... ...
  13. }

当bus_attach_device()函数匹配成功,就会调用驱动的probe函数

9.我们再来看看usb_bus_type这个的成员usb_device_match函数,看看是如何匹配的

usb_device_match函数如下所示:

  1. static int usb_device_match(struct device *dev, struct device_driver *drv)
  2. {
  3.  
  4. if (is_usb_device(dev)) { //判断是不是USB设备
  5. if (!is_usb_device_driver(drv))
  6. return 0;
  7. return 1;
  8. }
  9. else { //否则就是USB驱动或者USB设备的接口
  10.  
  11. struct usb_interface *intf;
  12. struct usb_driver *usb_drv;
  13. const struct usb_device_id *id;
  14.  
  15. if (is_usb_device_driver(drv)) //如果是USB驱动,就不需要匹配,直接return
  16. return 0;
  17.  
  18. intf = to_usb_interface(dev); //获取USB设备的接口
  19. usb_drv = to_usb_driver(drv); //获取USB驱动
  20.  
  21. id = usb_match_id(intf, usb_drv->id_table); //匹配USB驱动的成员id_table
  22. if (id)
  23. return 1;
  24.  
  25. id = usb_match_dynamic_id(intf, usb_drv);
  26. if (id)
  27. return 1;
  28. }
  29. return 0;
  30. }

显然就是匹配USB驱动的id_table

10.那么USB驱动的id_table又该如何定义?

id_table的结构体为usb_device_id,如下所示:

  1. struct usb_device_id {
  2.  
  3. __u16 match_flags; //与usb设备匹配那种类型?比较类型的宏如下:
  4. //USB_DEVICE_ID_MATCH_INT_INFO : 用于匹配设备的接口描述符的3个成员
  5. //USB_DEVICE_ID_MATCH_DEV_INFO: 用于匹配设备描述符的3个成员
  6. //USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION: 用于匹配特定的USB设备的4个成员
  7. //USB_DEVICE_ID_MATCH_DEVICE:用于匹配特定的USB设备的2个成员(idVendor和idProduct)
  8.  
  9.  
  10. /* 以下4个用匹配描述特定的USB设备 */
  11. __u16 idVendor; //厂家ID
  12. __u16 idProduct; //产品ID
  13. __u16 bcdDevice_lo; //设备的低版本号
  14. __u16 bcdDevice_hi; //设备的高版本号
  15.  
  16. /*以下3个就是用于比较设备描述符的*/
  17. __u8 bDeviceClass; //设备类
  18. __u8 bDeviceSubClass; //设备子类
  19. __u8 bDeviceProtocol; //设备协议
  20.  
  21. /* 以下3个就是用于比较设备的接口描述符的 */
  22. __u8 bInterfaceClass; //接口类型
  23. __u8 bInterfaceSubClass; //接口子类型
  24. __u8 bInterfaceProtocol; //接口所遵循的协议
  25.  
  26. /* not matched against */
  27. kernel_ulong_t driver_info;
  28. };

(设备描述符合接口描述符结构体参考:http://www.cnblogs.com/lifexy/p/7634511.html

我们参考/drivers/hid/usbhid/usbmouse.c(内核自带的USB鼠标驱动),是如何使用的,如下图所示:

发现它是通过USB_INTERFACE_INFO()这个宏定义的.该宏如下所示:

  1. #define USB_INTERFACE_INFO(cl,sc,pr) \
  2. .match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \ //设置id_table的.match_flags成员
  3. .bInterfaceClass = (cl), .bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)
  4. //设置id_table的3个成员,用于与匹配USB设备的3个成员

然后将上图里的usb_mouse_id_table []里的3个值代入宏USB_INTERFACE_INFO(cl,sc,pr)中:

  1. .bInterfaceClass =USB_INTERFACE_CLASS_HID;
  2. //设置匹配USB的接口类型为HID类, 因为USB_INTERFACE_CLASS_HID=0x03
  3. //HID类是属于人机交互的设备,比如:USB键盘,USB鼠标,USB触摸板,USB游戏操作杆都要填入0X03
  4.  
  5. .bInterfaceSubClass =USB_INTERFACE_SUBCLASS_BOOT;
  6. //设置匹配USB的接口子类型为启动设备
  7.  
  8. .bInterfaceProtocol=USB_INTERFACE_PROTOCOL_MOUSE;
  9. //设置匹配USB的接口协议为USB鼠标的协议,等于2
  10. //当.bInterfaceProtocol=1也就是USB_INTERFACE_PROTOCOL_KEYBOARD时,表示USB键盘的协议

如下图,我们也可以通过windows上也可以找到鼠标的协议号,也是2:

其中VID:表示厂家(vendor)ID

PID:表示产品(Product) ID

总结:当我们插上USB设备时,系统就会获取USB设备的设备、配置、接口、端点的数据,并创建新设备,所以我们的驱动就需要写id_table来匹配该USB设备

usb 枚举流程的更多相关文章

  1. usb 枚举流程简介

    1. 枚举是什么?        枚举就是从设备读取一些信息,知道设备是什么样的设备,如何进行通信,这样主机就可以根据这些信息来加载合适的驱动程序.调试USB设备,很重要的一点就是USB的枚举过程,只 ...

  2. USB枚举过程的详细流程

    USB枚举过程的详细流程 用户将一个USB设备插入USB端口,主机为端口供电,设备此时处于上电状态.主机检测设备.1>Hub使用中断通道将事件报告给Host.2>Host发送Get_Por ...

  3. USB枚举详细过程剖析(转)

    USB枚举详细过程剖析(转) 原文地址:http://blog.163.com/luge_arm/blog/static/6774972620071018117290/ 从驱动开发网看到一篇<U ...

  4. 基于STM32的USB枚举过程学习笔记

    源:基于STM32的USB枚举过程学习笔记 基于STM32的USB枚举过程学习笔记(一) 基于STM32的USB枚举过程学习笔记(二) 基于STM32的USB枚举过程学习笔记(三) 基于STM32的U ...

  5. 基于STM32的USB枚举过程学习笔记(转)

    之前使用ST官方的库以及网络的资料,完成了使用USB HID类进行STM32和PC机的通讯.由于其他原因并没有深入的分析,虽然实现了功能,但是关于USB设备的枚举,以及具体的通讯方式都没有清晰的概念, ...

  6. usb枚举

    源: usb枚举

  7. linux usb枚举过程分析之守护进程及其唤醒【转】

    转自:http://blog.csdn.net/xuelin273/article/details/38646765 usb热插拔,即usb设备可以实现即插即用,像U盘一样,插到电脑里就可以用,不用时 ...

  8. USB枚举的详细流程

    附一个很好的枚举过程的详细流程: ◆ 用户将一个USB设备插入USB端口,主机为端口供电,设备此时处于上电状态.◆ 主机检测设备.◆ 集线器使用中断通道将事件报告给主机.◆ 主机发送Get_Port_ ...

  9. USB插入电脑的硬件检测和枚举流程

    USB协议定义了设备的6种状态,仅在枚举过程种,设备就经历了4个状态的迁移:上电状态(Powered),默认状态(Default),地址状态(Address)和配置状态(Configured)(其他两 ...

随机推荐

  1. Spring再接触 Annotation part1

    使用annotation首先得加这两条代码 beans.xml <?xml version="1.0" encoding="UTF-8"?> < ...

  2. Nagios监控

    1.Nagios监控软件 Nagios是一款开源的免费网络监视工具,能有效监控Windows.Linux和Unix的主机状态,交换机路由器等网络设置,打印机等.在系统或服务状态异常时发出邮件或短信报警 ...

  3. Python 3之Django2部署(centos7+nginx+python3+django2.0)

    前置工具,系统为centos7.5,为了方便管理,可以安装宝塔免费版本 首先, yum install -y wget && wget -O install.sh http://dow ...

  4. poj 2778 AC自动机+矩阵快速幂

    题目链接:https://vjudge.net/problem/POJ-2778 题意:输入n和m表示n个病毒,和一个长为m的字符串,里面只可以有'A','C','G','T' 这四个字符,现在问这个 ...

  5. C# 截取两个指定字符串中间的字符串列表

    /// <summary> /// 截取两个指定字符串中间的字符串列表(开始和结束两个字符串不能相同!) /// </summary> /// <param name=& ...

  6. paloalto防火墙注册

    一.创建账户 1.注册网站: https://support.paloaltonetworks.com/Support/Index 2.单击 Activate My Account 3.输入 Your ...

  7. mysql学习3:mysql之my.cnf详解

    mysql之my.cnf详解 本文转自:https://www.cnblogs.com/panwenbin-logs/p/8360703.html 以下是 my.cnf 配置文件参数解释: #*** ...

  8. mysql 悲观锁与乐观锁的理解

    悲观锁与乐观锁是人们定义出来的概念,你可以理解为一种思想,是处理并发资源的常用手段. 不要把他们与mysql中提供的锁机制(表锁,行锁,排他锁,共享锁)混为一谈. 一.悲观锁 顾名思义,就是对于数据的 ...

  9. stark组件开发之组合搜索高级显示和扩展

    上一篇,我只是做了. 默认的显示. def __iter__(self): '''默认显示. 用户可以自定制''' if isinstance(self.queryset_or_tuple, list ...

  10. How to decode input data from a contract transaction without ABI?

    1 I've found some libraries which decode input from transaction, but all of them require ABI of cont ...