title: usb输入子系统写程序

tags: linux

date: 2018/12/18/ 18:46:04

toc: true

usb输入子系统写程序

目标:usb鼠标模拟一个键盘,左键L,右键S,中键enter

参考: drivers/hid/usbhid/usbmouse.c这是自带的USB鼠标驱动

入口函数

static int __init usb_mouse_init(void)
{
int retval = usb_register(&usb_mouse_driver);
if (retval == 0)
info(DRIVER_VERSION ":" DRIVER_DESC);
return retval;
} static struct usb_driver usb_mouse_driver = {
.name = "usbmouse",
.probe = usb_mouse_probe,
.disconnect = usb_mouse_disconnect,
.id_table = usb_mouse_id_table,
};

小结

  1. 构造匹配的id_table以供usb总线驱动的match匹配,然后执行驱动的probe

    struct usb_device_id xxx[]={}
  2. 构造probe来达到初始化相关操作

  3. usb传输数据使用urb = usb request block 也就是usb 请求块来传输,数据传输三要素源+长度+目的

    • 源=Usb设备的端点,也就是包含了usb设备地址+端点地址+端点类型

      struct usb_host_interface *interface;
      interface = intf->cur_altsetting;
      //获得端点地址
      endpoint = &interface->endpoint[0].desc;
      //usb_rcvintpipe 构造了设备地址+端点地址
      pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
      //PIPE_INTERRUPT 表示端点的类型
      #define usb_rcvintpipe(dev,endpoint) \
      ((PIPE_INTERRUPT << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN)
      //__create_pipe 包含了设备地址和端点的地址
      static inline unsigned int __create_pipe(struct usb_device *dev,
      unsigned int endpoint)
      {
      return (dev->devnum << 8) | (endpoint << 15);
      }
    • 长度从usb的端点描述符中获取

      len = endpoint->wMaxPacketSize;
    • 目的缓存需要是一块连续的物理地址,需要分配

      static char *usb_buf;			//目的buf虚拟地址
      static dma_addr_t usb_buf_phys; //目的buf实际获得的地址
      usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);
  4. 设置这个URB,首先分配一块空间,然后填充相关数据

    static struct urb *uk_urb;
    uk_urb = usb_alloc_urb(0, GFP_KERNEL);
    // urb 分配得到的urb
    // pipe usb设备端点
    // transfer_buffer 传输的目的地址,虚拟地址
    // buffer_length 传输的长度
    // complete_fn 收到传输数据的回调函数
    // context 可以为null
    // interval 轮询间隔,在端点描述符中查询
    static inline void usb_fill_int_urb (struct urb *urb,
    struct usb_device *dev,
    unsigned int pipe,
    void *transfer_buffer,
    int buffer_length,
    usb_complete_t complete_fn,
    void *context,
    int interval)
    // 需要设置物理地址和标志
    uk_urb->transfer_dma = usb_buf_phys;
    uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
  5. 启动这个urb,也就是说每次传输前都要先操作一遍,每次数据获取后也需要重新启动

    usb_submit_urb(uk_urb, GFP_KERNEL);
  6. 如何获取数据上报? 在urb的完成回调函数中实现,usb_bufurb中的虚拟地址,获取数据后需要重新启动urb

    static void usbxxx_irq(struct urb *urb)
    {
    for (i = 0; i < len; i++)
    {
    printk("%02x ", usb_buf[i]);
    }
    usb_submit_urb(uk_urb, GFP_KERNEL);
    }
  7. 如何上报到按键?这里用到以前的输入子系统,也就是在probe注册input_dev,然后在urb中的回调函数中获取到函数后上报

    input_dev = input_allocate_device();
    /* b. 设置 */
    /* b.1 能产生哪类事件 */
    input_dev->evbit[0]=0;
    set_bit(EV_KEY, input_dev->evbit);
    set_bit(EV_REP, input_dev->evbit);
    /* b.2 能产生哪些事件 */
    set_bit(KEY_L, input_dev->keybit);
    set_bit(KEY_S, input_dev->keybit);
    set_bit(KEY_ENTER, input_dev->keybit);
    /* c. 注册 */
    input_register_device(input_dev);

内核修改

去除内核原有的模块Device Drivers > HID Devices > USB Human Interface Device (full HID) support

怎么写代码

usb总线设备也是一种总线设备驱动,总体上可以分为两个部分,左边部分是usb总线程序做好了,我们需要编写右边的usb_driver部分,也就是分配设置注册usb_driver

类型匹配

首先是usb子系统会调用match来匹配id_table中的类型后会调用probe。所以先来确定这个类型匹配,也就是设备描述符的匹配。例子如下:

static struct usb_device_id usb_mouse_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
};

这里的宏展开如下,通过接口类匹配,也就是匹配了类+子类+协议

#define USB_INTERFACE_INFO(cl,sc,pr) \
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, .bInterfaceClass = (cl), \
.bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr) cl:接口类,我们USB鼠标为HID类,所以填入0X03,也就是USB_INTERFACE_CLASS_HID
sc:接口子类为启动设备,填入USB_INTERFACE_SUBCLASS_BOOT
pr:接口协议为鼠标协议,填入USB_INTERFACE_PROTOCOL_MOUSE //ch9.h
/* USB_DT_INTERFACE: Interface descriptor */
struct usb_interface_descriptor {
__u8 bLength;
__u8 bDescriptorType; __u8 bInterfaceNumber;
__u8 bAlternateSetting;
__u8 bNumEndpoints;
__u8 bInterfaceClass; //类
__u8 bInterfaceSubClass; //子类
__u8 bInterfaceProtocol; //协议
__u8 iInterface;
} __attribute__ ((packed));

也可以单独匹配某个厂家ID使用USB_DEVICE

#define USB_DEVICE(vend,prod) \
.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = (vend), \
.idProduct = (prod)

可以在usb.h查看这个匹配的大类

#define USB_DEVICE_ID_MATCH_DEVICE \
(USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT)
#define USB_DEVICE_ID_MATCH_DEV_RANGE \
(USB_DEVICE_ID_MATCH_DEV_LO | USB_DEVICE_ID_MATCH_DEV_HI)
#define USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION \
(USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_RANGE)
#define USB_DEVICE_ID_MATCH_DEV_INFO \
(USB_DEVICE_ID_MATCH_DEV_CLASS | \
USB_DEVICE_ID_MATCH_DEV_SUBCLASS | \
USB_DEVICE_ID_MATCH_DEV_PROTOCOL)
#define USB_DEVICE_ID_MATCH_INT_INFO \
(USB_DEVICE_ID_MATCH_INT_CLASS | \
USB_DEVICE_ID_MATCH_INT_SUBCLASS | \
USB_DEVICE_ID_MATCH_INT_PROTOCOL) #define USB_DEVICE(vend,prod) \
.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = (vend), \
.idProduct = (prod)
#define USB_DEVICE_VER(vend,prod,lo,hi) \
.match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, \
.idVendor = (vend), .idProduct = (prod), \
.bcdDevice_lo = (lo), .bcdDevice_hi = (hi)
#define USB_DEVICE_INFO(cl,sc,pr) \
.match_flags = USB_DEVICE_ID_MATCH_DEV_INFO, .bDeviceClass = (cl), \
.bDeviceSubClass = (sc), .bDeviceProtocol = (pr)
#define USB_INTERFACE_INFO(cl,sc,pr) \
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, .bInterfaceClass = (cl), \
.bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)

probe

参考usb_mouse_probe可以看到有以下操作

  1. 分配一个 input_dev 结构体
  2. 设置事件
  3. 注册输入子系统
  4. 硬件相关操作

disconnect

拔掉USB会调用该函数,可以加入打印先

程序设计

1th匹配probe

验证id匹配后执行probe函数,这里并没有加入输入子系统

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h> //struct input_dev *input_dev; static struct usb_device_id myusb_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
}; static int myusb_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
//input_dev = input_allocate_device();
struct usb_device *dev = interface_to_usbdev(intf); printk("found usbmouse!\n");
printk("bcdUSB = %x\n", dev->descriptor.bcdUSB);
printk("VID = 0x%x\n", dev->descriptor.idVendor);
printk("PID = 0x%x\n", dev->descriptor.idProduct); return 0; } static void myusb_disconnect(struct usb_interface *intf)
{
printk("disconnect usbmouse!\n");
}
static struct usb_driver myusb_driver = {
.name = "myusb",
.probe = myusb_probe,
.disconnect = myusb_disconnect,
.id_table = myusb_id_table,
}; static int __init myusb_init(void)
{
int retval = usb_register(&myusb_driver);
return retval;
} static void __exit myusb_exit(void)
{
usb_deregister(&myusb_driver);
} module_init(myusb_init);
module_exit(myusb_exit);
MODULE_LICENSE("GPL");

测试

烧录去除Device Drivers > HID Devices > USB Human Interface Device (full HID) support 的内核,加载模块,显示如下

# insmod myusb_1th.ko
new id 0x6019 !
found usbmouse!
bcdUSB = 110
VID = 0x93a
PID = 0x2510
usbcore: registered new interface driver myusb

2th 获取usb数据

  • 数据鼠标上报的数据,在这里其实并不需要输入子系统的参与,输入子系统是为了上报按键的数据,而不是为了获取鼠标的数据
  • 可以进一步在probe中判断是否为鼠标设备,可以获取接口描述符,获取端点个数(除了端点0)
  • 如何上报数据? usb这里使用urb usb request block也就是usb 请求块来传输数据

测试后可以看到打印的数据,可以根据这个判断转换为按键值左键L,右键S,中键ENTER

data cnt 187: 01 00 00 00
data cnt 188: 00 00 00 00
data cnt 189: 02 00 00 00
data cnt 190: 00 00 00 00
data cnt 191: 04 00 00 00
data cnt 192: 00 00 00 00
上报次数 指示了按键 其他值
data cnt 187: 01 左键 00 00 00
data cnt 188: 00 00 00 00
data cnt 189: 02 右键 00 00 00
data cnt 190: 00 00 00 00
data cnt 191: 04 中键 00 00 00
data cnt 192: 00 00 00 00

完整的程序

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h> static int len;
static char *usb_buf; //目的buf虚拟地址
static dma_addr_t usb_buf_phys; //目的buf实际获得的地址
struct input_dev *input_dev;
static struct urb *uk_urb; static struct usb_device_id myusb_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) }, //{USB_DEVICE(0x17EF,0x6019) } /* right id */
//{USB_DEVICE(0x15EF,0x6055) } /* err */
}; static void usbmouse_as_key_irq(struct urb *urb)
{
int i;
static int cnt = 0;
printk("data cnt %d: ", ++cnt);
for (i = 0; i < len; i++)
{
printk("%02x ", usb_buf[i]);
}
printk("\n");
usb_submit_urb(uk_urb, GFP_KERNEL);
}
static int myusb_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
int pipe;
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_endpoint_descriptor *endpoint;
struct usb_host_interface *interface; printk("new id 2 !\n");
printk("found usbmouse!\n");
printk("bcdUSB = %x\n", dev->descriptor.bcdUSB);
printk("VID = 0x%x\n", dev->descriptor.idVendor);
printk("PID = 0x%x\n", dev->descriptor.idProduct);
interface = intf->cur_altsetting; //data translate for urb //源地址构造
//获得端点地址
endpoint = &interface->endpoint[0].desc;
//usb_rcvintpipe 构造了设备地址+端点地址
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
//长度获取
len = endpoint->wMaxPacketSize;
//目的地址构造,返回虚拟地址和实际地址
usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys); /* 使用"3要素" */
/* 分配usb request block */
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
/* 使用"3要素设置urb" */
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
uk_urb->transfer_dma = usb_buf_phys;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 使用URB */
usb_submit_urb(uk_urb, GFP_KERNEL);
return 0; } static void myusb_disconnect(struct usb_interface *intf)
{ struct usb_device *dev = interface_to_usbdev(intf);
printk("disconnect usbmouse!\n");
usb_kill_urb(uk_urb);
usb_free_urb(uk_urb);
usb_buffer_free(dev, len, usb_buf, usb_buf_phys);
}
static struct usb_driver myusb_driver = {
.name = "myusb",
.probe = myusb_probe,
.disconnect = myusb_disconnect,
.id_table = myusb_id_table,
}; static int __init myusb_init(void)
{
int retval = usb_register(&myusb_driver);
return retval;
} static void __exit myusb_exit(void)
{
usb_deregister(&myusb_driver);
} module_init(myusb_init);
module_exit(myusb_exit);
MODULE_LICENSE("GPL");

3th 输入子系统上报按键

加入输入子系统上报按键事件,测试如下,这里只是输出到显示,并没有执行命令,如果要执行命令需要定位到标准输入

# cat /dev/tty0
ls
llllllllllllllllllll
llllllllllll

或者使用hexdump来显示

# hexdump /dev/event0
【按键值】26 左键 1f右键 1C中键
00001a0 16a7 0000 c426 000b 0001 0026 0001 0000
00001b0 16a7 0000 c430 000b 0000 0000 0000 0000
00001c0 16a7 0000 3b15 000d 0001 0026 0000 0000
00001d0 16a7 0000 3b1d 000d 0000 0000 0000 0000
00001e0 16a8 0000 e834 0005 0001 001f 0001 0000
00001f0 16a8 0000 e83e 0005 0000 0000 0000 0000
0000200 16a8 0000 fb66 0007 0001 001f 0000 0000
0000210 16a8 0000 fb6e 0007 0000 0000 0000 0000
0000220 16a8 0000 f0c1 000e 0001 001c 0001 0000
0000230 16a8 0000 f0cc 000e 0000 0000 0000 0000
0000240 16a9 0000 8319 0001 0001 001c 0000 0000
0000250 16a9 0000 8322 0001 0000 0000 0000 0000 //include/linux
#define KEY_L 38
#define KEY_S 31
#define KEY_ENTER 28

或者使用重定位按键

# exec 0</dev/tty1
# ls
Makefile myusb.ko myusb.o myusb_1th.mod.c
Module.symvers myusb.mod.c myusb_1th.c myusb_1th.mod.o
myusb.c myusb.mod.o myusb_1th.ko myusb_1th.o

完整的程序如下

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h> static int len;
static char *usb_buf; //目的buf虚拟地址
static dma_addr_t usb_buf_phys; //目的buf实际获得的地址
struct input_dev *input_dev;
static struct urb *uk_urb; static struct usb_device_id myusb_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) }, //{USB_DEVICE(0x17EF,0x6019) } /* right id */
//{USB_DEVICE(0x15EF,0x6055) } /* err */
}; static void usbmouse_as_key_irq(struct urb *urb)
{ #if(0)
int i;
static int cnt = 0;
printk("data cnt %d: ", ++cnt);
for (i = 0; i < len; i++)
{
printk("%02x ", usb_buf[i]);
}
printk("\n");
#else
static unsigned char pre_val;
// bit0 left_button bit1=right_button bit2=mid_button
if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))
{
/* 左键发生了变化 */
input_event(input_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
input_sync(input_dev);
} if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
{
/* 右键发生了变化 */
input_event(input_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
input_sync(input_dev);
} if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
{
/* 中键发生了变化 */
input_event(input_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
input_sync(input_dev);
} pre_val = usb_buf[0]; #endif usb_submit_urb(uk_urb, GFP_KERNEL);
}
static int myusb_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
int pipe;
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_endpoint_descriptor *endpoint;
struct usb_host_interface *interface; printk("new id 2 !\n");
printk("found usbmouse!\n");
printk("bcdUSB = %x\n", dev->descriptor.bcdUSB);
printk("VID = 0x%x\n", dev->descriptor.idVendor);
printk("PID = 0x%x\n", dev->descriptor.idProduct);
interface = intf->cur_altsetting; input_dev = input_allocate_device();
/* b. 设置 */
/* b.1 能产生哪类事件 */
input_dev->evbit[0]=0;
set_bit(EV_KEY, input_dev->evbit);
set_bit(EV_REP, input_dev->evbit);
/* b.2 能产生哪些事件 */
set_bit(KEY_L, input_dev->keybit);
set_bit(KEY_S, input_dev->keybit);
set_bit(KEY_ENTER, input_dev->keybit);
/* c. 注册 */
input_register_device(input_dev); //data translate for urb //源地址构造
//获得端点地址
endpoint = &interface->endpoint[0].desc;
//usb_rcvintpipe 构造了设备地址+端点地址
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
//长度获取
len = endpoint->wMaxPacketSize;
//目的地址构造,返回虚拟地址和实际地址
usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys); /* 使用"3要素" */
/* 分配usb request block */
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
/* 使用"3要素设置urb" */
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
uk_urb->transfer_dma = usb_buf_phys;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 使用URB */
usb_submit_urb(uk_urb, GFP_KERNEL);
return 0; } static void myusb_disconnect(struct usb_interface *intf)
{ struct usb_device *dev = interface_to_usbdev(intf);
printk("disconnect usbmouse!\n");
usb_kill_urb(uk_urb);
usb_free_urb(uk_urb);
usb_buffer_free(dev, len, usb_buf, usb_buf_phys);
input_unregister_device(input_dev);
input_free_device(input_dev);
}
static struct usb_driver myusb_driver = {
.name = "myusb",
.probe = myusb_probe,
.disconnect = myusb_disconnect,
.id_table = myusb_id_table,
}; static int __init myusb_init(void)
{
int retval = usb_register(&myusb_driver);
return retval;
} static void __exit myusb_exit(void)
{
usb_deregister(&myusb_driver);
} module_init(myusb_init);
module_exit(myusb_exit);
MODULE_LICENSE("GPL");

usb输入子系统写程序(三)的更多相关文章

  1. usb输入子系统键盘(四)

    目录 usb输入子系统键盘 设计思路 内核的上报代码 完整代码 title: usb输入子系统键盘 tags: linux date: 2018/12/20/ 17:05:08 toc: true - ...

  2. Linux驱动之一个简单的输入子系统程序编写

    的在Linux驱动之输入子系统简析已经分析过了输入子系统的构成,它是由设备层.核心层.事件层共同组成的.其中核心层提供一些设备层与事件层公用的函数,比如说注册函数.反注册函数.事件到来的处理函数等等: ...

  3. 最新用WPF为触摸屏写了一个手写程序,双格输入的

    原文:最新用WPF为触摸屏写了一个手写程序,双格输入的 双格输入可以提高手写速度,当前字写完以后可以自动识别提交,写下一个字.这样比单格手写速度提高一倍.特别适合触摸屏程序使用 界面如下: 程序如下: ...

  4. Linux输入子系统 转载

    NQian 记录成长~ 首页 新随笔 联系 订阅 管理 随笔 - 305  文章 - 0  评论 - 254 12.Linux之输入子系统分析(详解)   在此节之前,我们学的都是简单的字符驱动,涉及 ...

  5. linux内核输入子系统分析

    1.为何引入input system? 以前我们写一些输入设备(键盘.鼠标等)的驱动都是采用字符设备.混杂设备处理的.问题由此而来,Linux开源社区的大神们看到了这大量输入设备如此分散不堪,有木有可 ...

  6. 嵌入式Linux驱动学习之路(十六)输入子系统

    以前写的一些输入设备的驱动都是采用字符设备处理的.问题由此而来,Linux开源社区的大神们看到了这大量输入设备如此分散不堪,有木有可以实现一种机制,可以对分散的.不同类别的输入设备进行统一的驱动,所以 ...

  7. linux input输入子系统应用分析

    输入设备(如按键.键盘.触摸屏.鼠标等)是典型的字符设备,其一般的工作机理是底层在按键.触摸等动作发送时产生一个中断(或驱动通过timer定时查询),然后CPU通过SPI.I2 C或外部存储器总线读取 ...

  8. Linux输入子系统(一) _驱动编码

    输入设备都有共性:中断驱动+字符IO,基于分层的思想,Linux内核将这些设备的公有的部分提取出来,基于cdev提供接口,设计了输入子系统,所有使用输入子系统构建的设备都使用主设备号13,同时输入子系 ...

  9. 12.Linux之输入子系统分析(详解)

    版权声明:本文为博主原创文章,转载请标注出处:   在此节之前,我们学的都是简单的字符驱动,涉及的内容有字符驱动的框架.自动创建设备节点.linux中断.poll机制.异步通知.同步互斥/非阻塞.定时 ...

随机推荐

  1. MySQL服务器的安装和配置,MySQL Workbench 8.0.12安装,MySQL的基本使用

    一 MySQL服务器的安装和配置 二 MySQL Workbench 8.0.12安装 三 MySQL的基本使用 一MySQL服务器的安装和配置 MySQL是目前最为流行的开放源码的数据库,是完全网络 ...

  2. SQL Server 数据库状态选项-用户使用

    选项 1. single_user(单用户),multi_user(多用户),restricted_user(受限用户); 描述数据库的用户访问属性,它们互斥,设置其中任何一个选项就会取消对其它选项的 ...

  3. cmd切换目录

    想必大家都用过命令行工具来完成一些骚操作: 今天我在用cmd命令的时候,需要切换不同的目录来获取我所需要的文件,但是发现用cd的话切换不了: 如下图所示,我用cd切换到E盘下的一个文件夹,但是按回车之 ...

  4. Think_in_java_4th(并发学习一)

    Java的并发是在顺序语言的基础上提供对线程的支持的. 并发能够更加有效的执行我们的代码,也就是更加合理的应用CPU资源. 并发程序往往CPU和内存使用率,要高于同等的非并发程序. 下面就用Think ...

  5. 基于 PHP 的数据爬取(QueryList)

    基于PHP的数据爬取 官方网站站点 简单. 灵活.强大的PHP采集工具,让采集更简单一点. 简介: QueryList使用jQuery选择器来做采集,让你告别复杂的正则表达式:QueryList具有j ...

  6. 先vue-cli,再nuxt试试路由

    https://segmentfault.com/a/1190000007933349

  7. 小程序第三方框架对比 ( wepy / mpvue / taro )(转)

    文章转自  https://www.cnblogs.com/Smiled/p/9806781.html 众所周知如今市面上端的形态多种多样,手机Web.ReactNative.微信小程序, 支付宝小程 ...

  8. centos7 安装mysql5.7

    [root@izbp1buyhgwtrvlxv3u2gqz soft]# wget http://dev.mysql.com/get/mysql57-community-release-el7-8.n ...

  9. 如何在Jupyter里以不同的运行模式使用Pyspark

    假设你的环境已经安装好了以下东西,如何详细的安装它们不在本文的讨论范围之内 具体的可疑参考三分钟搞定jupyter和pyspark整合 anaconda2 findspark pyspark 这里多说 ...

  10. springboot打jar包正常无法访问页面

    网上看到太多说版本换成 1.4.2.RELEASE. 可以将程序打成war包发布, 1.启动类改为 @Overrideprotected SpringApplicationBuilder config ...