前面学习了SDIO接口的WiFi驱动,现在我们来学习一下USB接口的WiFi驱动,二者的区别在于接口不同。而USB接口的设备驱动,我们前面也有学习,比如USB摄像头驱动、USB鼠标驱动,同样都符合LinuxUSB驱动结构:

USB设备驱动(字符设备、块设备、网络设备)

|

USB 核心

|

USB主机控制器驱动

不同之处只是在于USB摄像头驱动是字符设备,而我们今天要学习的WiFi驱动是网络设备;当然由我们编写的部分还是USB设备驱动部分,下面进入USB接口WiFi驱动的分析,如何分析呢?我们下面从这几个方面入手:

从硬件层面上看,WIFI设备与CPU通信是通过USB接口的,与其他WIFI设备之间的通信是通过无线射频(RF)。

从软件层面上看,Linux操作系统要管理WIFI设备,那么就要将WIFI设备挂载到USB总线上,通过USB子系统实现管理。而同时为了对接网络,又将WIFI设备封装成一个网络设备。

我们以USB接口的WIFI模块进行分析:

a -- 从USB总线的角度去看,它是USB设备;

b -- 从Linux设备的分类上看,它又是网络设备;

c -- 从WIFI本身的角度去看,它又有自己独特的功能及属性,因此它又是一个私有的设备;

通过上述的分析,我们只要抓住这三条线索深入去分析它的驱动源码,整个WIFI驱动框架就会浮现在你眼前。

一、框架整理

1、USB设备驱动

现在我们先从USB设备开始,要写一个USB设备驱动,那么大致步骤如下:

a -- 需要针对该设备定义一个USB驱动,对应到代码中即定义一个usb_driver结构体变量

代码如下:

[cpp] view plaincopy
  1. struct usb_driver xxx_usb_wifi_driver;

b -- 填充该设备的usb_driver结构体成员变量

代码如下:

[cpp] view plaincopy
  1. static struct usb_driver xxx_usb_wifi_driver = {
  2. .name = "XXX_USB_WIFI",
  3. .probe = xxx_probe,
  4. .disconnect = xxx_disconnect,
  5. .suspend = xxx_suspend,
  6. .resume = xxx_resume,
  7. .id_table = xxx_table,
  8. };

c -- 将该驱动注册到USB子系统

代码如下:

[cpp] view plaincopy
  1. usb_register(&xxx_usb_wifi_driver);

以上步骤只是一个大致的USB驱动框架流程,而最大和最复杂的工作是填充usb_driver结构体成员变量。以上步骤的主要工作是将USB接口的WIFI设备挂载到USB总线上,以便Linux系统在USB总线上就能够找到该设备。

2、网络设备驱动

接下来是网络设备的线索,网络设备驱动大致步骤如下:

a -- 定义一个net_device结构体变量ndev

代码如下:

[cpp] view plaincopy
  1. struct net_device *ndev;

b -- 初始化ndev变量并分配内存

代码如下:

[cpp] view plaincopy
  1. ndev=alloc_etherdev();

c -- 填充ndev -> netdev_ops结构体成员变量

代码如下:

[cpp] view plaincopy
  1. static const struct net_device_ops xxx_netdev_ops= {
  2. .ndo_init = xxx_ndev_init,
  3. .ndo_uninit = xxx _ndev_uninit,
  4. .ndo_open = netdev_open,
  5. .ndo_stop = netdev_close,
  6. .ndo_start_xmit = xxx_xmit_entry,
  7. .ndo_set_mac_address = xxx_net_set_mac_address,
  8. .ndo_get_stats = xxx_net_get_stats,
  9. .ndo_do_ioctl = xxx_ioctl,
  10. };

d -- 填充ndev->wireless_handlers结构体成员变量,该变量是无线扩展功能

代码如下:

[cpp] view plaincopy
  1. ndev->wireless_handlers = (struct iw_handler_def *)&xxx_handlers_def;

e -- 将ndev设备注册到网络子系统

代码如下:

[cpp] view plaincopy
  1. register_netdev(ndev);

3、 WIFI设备本身私有的功能及属性

如自身的配置及初始化、建立与用户空间的交互接口、自身功能的实现等。

a -- 自身的配置及初始化

代码如下:

[cpp] view plaincopy
  1. xxx_read_chip_info();
  2. xxx_chip_configure();
  3. xxx_hal_init();

b -- 主要是在proc和sys文件系统上建立与用户空间的交互接口

代码如下:

[cpp] view plaincopy
  1. xxx_drv_proc_init();
  2. xxx_ndev_notifier_register();

c -- 自身功能的实现

WIFI的网络及接入原理,如扫描等。同时由于WIFI在移动设备中,相对功耗比较大,因此,对于功耗、电源管理也会在驱动中体现。

二、USB 设备驱动分析

在分析之前,我们需要理解在整个wifi模块中,USB充当什么角色?它的作用是什么?实质上wifi模块上的数据传输有两端,一端是wifi芯片与wifi芯片之间,通过无线射频(RF)进行数据传输;另一端则是wifi芯片与CPU之间,通过USB进行数据传输。

了解Linux的USB驱动的读者都知道,USB驱动分为两种:一种是USB主机驱动;另一种是USB设备驱动。而我们的USB接口的wifi模块对于CPU(主机)来说,属于USB设备,因此采用USB设备驱动。

有了以上信息之后,我们先让Linux系统识别该USB接口的wifi模块,首先我们在驱动源码中大致添加以下几步工作(前面分析过,这里只看步骤,不看代码):

a -- 定义一个usb_driver结构体变量

b -- 填充该设备的usb_driver结构体成员变量

c -- 将该驱动注册到USB子系统

简单完成以上几步工作,再加上板级文件(arch/mach-xxx.c)对USB设备的支持,Linux的USB子系统几乎可以挂载该wifi模块为USB设备了。但是这并不是我们最终想要的结果。我们还要让Linux系统知道它挂载的USB设备属于无线网络设备,同时能够访问它,利用它实施无线网络的工作。

我们都知道,若要让USB设备真正工作起来,需要对USB设备的4个层次(设备、配置、接口、端点)进行初始化。当然这四个层次并不是一定都要进行初始化,而是根据你的USB设备的功能进行选择的,大致初始化流程如下伪代码:

[cpp] view plaincopy
  1. static struct dvobj_priv *usb_dvobj_init(struct usb_interface *usb_intf)
  2. {
  3. int    i;
  4. u8     val8;
  5. int    status= _FAIL;
  6. struct dvobj_priv *pdvobjpriv;
  7. //设备
  8. struct usb_device *pusbd;
  9. struct usb_device_descriptor *pdev_desc;
  10. //配置
  11. struct usb_host_config *phost_conf;
  12. struct usb_config_descriptor *pconf_desc;
  13. //接口
  14. struct usb_host_interface *phost_iface;
  15. struct usb_interface_descriptor *piface_desc;
  16. //端点
  17. struct usb_host_endpoint *phost_endp;
  18. struct usb_endpoint_descriptor *pendp_desc;
  19. //设备的初始化
  20. pdvobjpriv->pusbintf = usb_intf ;
  21. pusbd =pdvobjpriv->pusbdev = interface_to_usbdev(usb_intf);
  22. usb_set_intfdata(usb_intf, pdvobjpriv);
  23. pdev_desc =&pusbd->descriptor;
  24. //配置的初始化
  25. phost_conf =pusbd->actconfig;
  26. pconf_desc =&phost_conf->desc;
  27. //接口的初始化
  28. phost_iface =&usb_intf->altsetting[0];
  29. piface_desc =&phost_iface->desc;
  30. //端点的初始化,由于wifi模块属于网络设备,传输批量数据,因此需要初始化为批量端点,端点方向(输入、输出)等。同时,由于wifi驱动功能比较多,需要初始化几个输入输出端点。
  31. for (i = 0; i <pdvobjpriv->nr_endpoint; i++)
  32. {
  33. phost_endp = phost_iface->endpoint +i;
  34. if (phost_endp)
  35. {
  36. pendp_desc =&phost_endp->desc;
  37. //检查是否为输入端点
  38. usb_endpoint_is_bulk_in(pendp_desc);
  39. //检查是否为输出端点
  40. usb_endpoint_is_bulk_out(pendp_desc);
  41. }
  42. }
  43. usb_get_dev(pusbd);
  44. }

完成以上的初始化工作之后,接下来我们需要理清一下USB接口的作用,它是wifi芯片内部的固件程序与主机上的Linux系统进行数据通信。USB设备通信不像普通字符设备那样采用I/O内存和I/O端口的访问,而是采用一种称为URB(USB Request Block)的USB请求块,URB在整个USB子系统中,相当于通电设备中的“电波”,USB主机与设备的通信,通过“电波”来传递。下面我们就来编写USB接口的读写操作函数,伪代码如下:

[cpp] view plaincopy
  1. void xxx_wifi_usb_intf_ops(struct _io_ops     *pops)
  2. {
  3. //当需要进行简单数据的读取时,采用以下操作
  4. pops->_read8 = &usb_read8;
  5. pops->_read16 = &usb_read16;
  6. pops->_read32 = &usb_read32;
  7. //当需要进行批量数据的读取时,采用以下操作
  8. pops->_read_port = &usb_read_port;
  9. //当需要进行简单数据的写时,采用以下操作
  10. pops->_write8 = &usb_write8;
  11. pops->_write16 = &usb_write16;
  12. pops->_write32 = &usb_write32;
  13. pops->_writeN = &usb_writeN;
  14. //当需要进行批量数据的写时,采用以下操作
  15. pops->_write_port = &usb_write_port;
  16. //取消读写urb
  17. pops->_read_port_cancel = &usb_read_port_cancel;
  18. pops->_write_port_cancel = &usb_write_port_cancel;
  19. }

在进行批量数据的读写时,如usb_read_port()和usb_write_port()函数,需要完成urb创建、初始化、提交、完成处理这个完整的流程。伪代码如下:

1)批量读操作

[cpp] view plaincopy
  1. static u32 usb_read_port(struct intf_hdl *pintfhdl, u32 addr, u32 cnt, u8 *rmem)
  2. {
  3. int err;
  4. unsigned intpipe;
  5. PURB purb =NULL;
  6. structrecv_buf         *precvbuf = (structrecv_buf *)rmem;
  7. structusb_device    *pusbd = pdvobj->pusbdev;
  8. //创建urb,这里是在其它地方创建完成之后,传递过来
  9. purb =precvbuf->purb;
  10. //初始化批量urb
  11. usb_fill_bulk_urb(purb, pusbd, pipe,
  12. precvbuf->pbuf,
  13. MAX_RECVBUF_SZ,
  14. usb_read_port_complete,
  15. precvbuf);//contextis precvbuf
  16. //提交urb
  17. err =usb_submit_urb(purb, GFP_ATOMIC);
  18. }

2)批量写操作

[cpp] view plaincopy
  1. u32 usb_write_port(struct intf_hdl *pintfhdl, u32 addr, u32 cnt, u8 *wmem)
  2. {
  3. unsigned int pipe;
  4. intstatus;
  5. PURB        purb = NULL;
  6. structxmit_priv       *pxmitpriv =&padapter->xmitpriv;
  7. structxmit_buf *pxmitbuf = (struct xmit_buf *)wmem;
  8. structxmit_frame *pxmitframe = (struct xmit_frame *)pxmitbuf->priv_data;
  9. structusb_device *pusbd = pdvobj->pusbdev;
  10. structpkt_attrib *pattrib = &pxmitframe->attrib;
  11. //创建urb,这里是在其它地方创建完成之后,传递过来
  12. purb = pxmitbuf->pxmit_urb[0];
  13. //初始化批量urb
  14. usb_fill_bulk_urb(purb, pusbd, pipe,
  15. pxmitframe->buf_addr,//= pxmitbuf->pbuf
  16. cnt,
  17. usb_write_port_complete,
  18. pxmitbuf);//contextis pxmitbuf
  19. //提交urb
  20. status = usb_submit_urb(purb,GFP_ATOMIC);
  21. return ret;
  22. }

完成以上批量数据的读写操作之后,大家可能会疑问:这不是一般USB设备驱动的操作流程吗?貌似和wifi没有半毛钱的关系啊!从上面看,确实和wifi没有任何联系,但是以上只是一个铺垫。我们一直强调USB接口在wifi模块中充当什么角色,既然是接口,那么它就是为数据传输而生。所以,和wifi扯上关系的就在于usb_read_port()和usb_write_port()这两个函数。

三、读写函数分析

USB接口在wifi模块中的最重要两个函数是usb_read_port()和usb_write_port()。那它们是怎么和wifi扯上关系的呢?我们可以从以下三个方面去分析:

a -- 首先需要明确wifi模块是USB设备,主控(CPU)端是USB主机;

b -- USB主机若需要对wifi模块进行数据的读写时,就必须经过USB接口;

c -- 既然涉及到数据的读写操作,必然要用相应的读写函数,那么usb_read_port()和usb_write_port()即是它们的读写函数。

我们先从读数据开始进行分析,在分析之前,我们必须了解USB设备驱动的读数据过程。USB读取数据操作流程如下:

a -- 通过usb_alloc_urb()函数创建并分配一个URB,作为传输USB数据的载体;

b -- 创建并分配DMA缓冲区,以DMA方式快速传输数据;

c -- 初始化URB,根据wifi的传输数据量,我们需要初始化为批量URB,相应操作函数为usb_fill_bulk_urb();

d -- 将URB提交到USB核心;

e -- 提交成功后,URB的完成函数将被USB核心调用。

我们知道只有当wifi模块有数据可读时,主控端才能成功地读取数据。那么wifi模块什么时候有数据可读呢?——下面重点来了!wifi模块通过RF端接收到无线网络数据,然后缓存到wifi芯片的RAM中,此时,wifi模块就有数据可读了。

经过上面的分析,我们找到了一条USB接口与wifi模块扯上关系的线索,就是wifi模块的接收数据,会引发USB接口的读数据;

现在,我们转到wifi模块的接收函数中,看看是不是真的这样?

在wifi接收函数初始化中,我们可以看到usb_alloc_urb()创建一个中断URB。伪代码如下:

[cpp] view plaincopy
  1. int xxxwifi_init_recv(_adapter *padapter)
  2. {
  3. struct recv_priv *precvpriv = &padapter->recvpriv;
  4. int i, res = _SUCCESS;
  5. struct recv_buf *precvbuf;
  6. tasklet_init(&precvpriv->recv_tasklet, (void(*)(unsigned long))rtl8188eu_recv_tasklet, (unsigned long)padapter);
  7. precvpriv->int_in_urb = usb_alloc_urb(0, GFP_KERNEL); //创建一个中断URB
  8. precvpriv->int_in_buf = rtw_zmalloc(INTERRUPT_MSG_FORMAT_LEN);
  9. //init recv_buf
  10. _rtw_init_queue(&precvpriv->free_recv_buf_queue);
  11. _rtw_init_queue(&precvpriv->recv_buf_pending_queue);
  12. precvpriv -> pallocated_recv_buf = rtw_zmalloc(NR_RECVBUFF *sizeof(struct recv_buf) + 4);
  13. precvbuf = (struct recv_buf*)precvpriv->precv_buf;
  14. for(i=0; i < NR_RECVBUFF ; i++)
  15. {
  16. _rtw_init_listhead(&precvbuf->list);
  17. _rtw_spinlock_init(&precvbuf->recvbuf_lock);
  18. precvbuf->alloc_sz = MAX_RECVBUF_SZ;
  19. res = rtw_os_recvbuf_resource_alloc(padapter, precvbuf);
  20. precvbuf->ref_cnt = 0;
  21. precvbuf->adapter =padapter;
  22. precvbuf++;
  23. }
  24. precvpriv->free_recv_buf_queue_cnt = NR_RECVBUFF;
  25. skb_queue_head_init(&precvpriv->rx_skb_queue);
  26. #ifdef CONFIG_PREALLOC_RECV_SKB
  27. {
  28. int i;
  29. SIZE_PTR tmpaddr=0;
  30. SIZE_PTR alignment=0;
  31. struct sk_buff *pskb=NULL;
  32. skb_queue_head_init(&precvpriv->free_recv_skb_queue);
  33. for(i=0; i<NR_PREALLOC_RECV_SKB; i++)
  34. {
  35. pskb = rtw_skb_alloc(MAX_RECVBUF_SZ + RECVBUFF_ALIGN_SZ);
  36. if(pskb)
  37. {
  38. pskb->dev = padapter->pnetdev;
  39. tmpaddr = (SIZE_PTR)pskb->data;
  40. alignment = tmpaddr & (RECVBUFF_ALIGN_SZ-1);
  41. skb_reserve(pskb, (RECVBUFF_ALIGN_SZ - alignment));
  42. skb_queue_tail(&precvpriv->free_recv_skb_queue, pskb);
  43. }
  44. pskb=NULL;
  45. }
  46. }
  47. #endif
  48. return res;
  49. }

在rtw_os_recvbuf_resource_alloc函数中,创建一个批量URB和一个DMA缓冲区。伪代码如下:

[cpp] view plaincopy
  1. int rtw_os_recvbuf_resource_alloc(_adapter *padapter, struct recv_buf *precvbuf)
  2. {
  3. int res=_SUCCESS;
  4. struct dvobj_priv   *pdvobjpriv = adapter_to_dvobj(padapter);
  5. struct usb_device   *pusbd = pdvobjpriv->pusbdev;
  6. precvbuf->irp_pending = _FALSE;
  7. precvbuf->purb = usb_alloc_urb(0, GFP_KERNEL); //创建一个批量URB
  8. precvbuf->pskb = NULL;
  9. precvbuf->reuse = _FALSE;
  10. precvbuf->pallocated_buf  = precvbuf->pbuf = NULL;
  11. precvbuf->pdata = precvbuf->phead = precvbuf->ptail = precvbuf->pend = NULL;
  12. precvbuf->transfer_len = 0;
  13. precvbuf->len = 0;
  14. #ifdef CONFIG_USE_USB_BUFFER_ALLOC_RX
  15. precvbuf->pallocated_buf = rtw_usb_buffer_alloc(pusbd, (size_t)precvbuf->alloc_sz, &precvbuf->dma_transfer_addr);  //创建一个DMA缓冲区
  16. precvbuf->pbuf = precvbuf->pallocated_buf;
  17. if(precvbuf->pallocated_buf == NULL)
  18. return _FAIL;
  19. #endif //CONFIG_USE_USB_BUFFER_ALLOC_RX
  20. return res;
  21. }

在usb_read_port()函数中,通过usb_fill_bulk_urb()初始化批量URB,并且提交给USB核心,也即USB读取数据操作流程的第3、4步。在usb_fill_bulk_urb()函数中,初始化URB的完成函数usb_read_port_complete(),只有当URB提交完成后,函数usb_read_port_complete()将被调用。伪代码如下:

[cpp] view plaincopy
  1. static u32 usb_read_port(struct intf_hdl *pintfhdl, u32 addr, u32 cnt, u8 *rmem)
  2. {
  3. struct recv_buf *precvbuf = (struct recv_buf *)rmem;
  4. _adapter        *adapter = pintfhdl->padapter;
  5. struct dvobj_priv   *pdvobj = adapter_to_dvobj(adapter);
  6. struct pwrctrl_priv *pwrctl = dvobj_to_pwrctl(pdvobj);
  7. struct recv_priv    *precvpriv = &adapter->recvpriv;
  8. struct usb_device   *pusbd = pdvobj->pusbdev;
  9. rtl8188eu_init_recvbuf(adapter, precvbuf);
  10. precvpriv->rx_pending_cnt++;
  11. purb = precvbuf->purb;
  12. //translate DMA FIFO addr to pipehandle
  13. pipe = ffaddr2pipehdl(pdvobj, addr);
  14. usb_fill_bulk_urb(purb, pusbd, pipe,
  15. precvbuf->pbuf,
  16. MAX_RECVBUF_SZ,
  17. usb_read_port_complete,
  18. precvbuf);//context is precvbuf
  19. err = usb_submit_urb(purb, GFP_ATOMIC);
  20. return ret;
  21. }

通过上面的代码,我们可以得知在wifi模块为接收数据做初始化准备时,分配了URB和DMA缓冲区。而在usb_read_port()函数中初始化URB和提交URB。

致谢

1、USB接口WiFi驱动浅析

USB接口WiFi驱动浅析相关推荐

  1. Linux 下wifi 驱动开发(四)—— USB接口WiFi驱动浅析

    前面学习了SDIO接口的WiFi驱动,现在我们来学习一下USB接口的WiFi驱动,二者的区别在于接口不同.而USB接口的设备驱动,我们前面也有学习,比如USB摄像头驱动.USB鼠标驱动,同样都符合Li ...

  2. linux wifi设置端口号,Linux 下wifi 驱动开发(四)—— USB接口WiFi驱动浅析

    前面学习了SDIO接口的WiFi驱动,现在我们来学习一下USB接口的WiFi驱动,二者的区别在于接口不同.而USB接口的设备驱动,我们前面也有学习,比如USB摄像头驱动.USB鼠标驱动,同样都符合Li ...

  3. Linux 下wifi 驱动开发(三)—— SDIO接口WiFi驱动浅析

    SDIO-Wifi模块是基于SDIO接口的符合wifi无线网络标准的嵌入式模块,内置无线网络协议IEEE802.11协议栈以及TCP/IP协议栈,能够实现用户主平台数据通过SDIO口到无线网络之间的转 ...

  4. 树莓派2B安装TP-Link usb无线wifi驱动

    标题树莓派2B安装TP-Link usb无线wifi驱动 前言 买了好多年的树莓派2B,都没怎么玩,仅仅捣鼓过几次系统.最近因为有个初中弟娃,学习没多大兴趣,加之前几年也准备过一些材料,这次趁暑假和他 ...

  5. usb 接口触摸屏驱动

    以前写的 USB 接口的触摸屏驱动,那段时间简单的看了下 USB 协议的一些东西,主要是 HID 相关的,代码记录: /*Created by_fire 2012.2.13 */ #include & ...

  6. Win7 USB接口无法使用/驱动错误/该设备无法启动。(代码10) 故障解决方法

    电脑USB接口突然有一个不能用了,开始以为是驱动问题,可是用好几个驱动软件(驱动精灵.驱动人生等)更新驱动都无法解决,后来发现在设备管理器里有一个设备驱动有问题,尝试卸载后自动重装,然后居然能用了,但 ...

  7. usb接口wifi模块rtl8188cus issue

    奇怪的问题,在冷开机的时候,rtl8188cus可以正常上网,可只要一进入sleep然后再resume, 就会出现wifi出错的情况. <4>###=> urb_write_port ...

  8. Linux驱动学习--USB接口wifi/BT芯片开发之BT开发(BlueDroid框架)

    目录 一.引言 二.整体框架分析(结合实际芯片分析) 三.内核中的相关配置 四.厂家驱动分析 五.蓝牙BlueDroid协议 一.引言 之前我们简单分析过BlueDroid框架,今天来结合源码,挑重点 ...

  9. RTL8192系列RL-UM02B-8192EU双通道USB接口WiFi模块选型参考

    RTL8192系列可以看成是RTL8188/RTL8189系列的高配,因为有两条通信信道!数据传输速率有很大提升!           其中单频方面,有RTL8192CU和RTL8192EU两个方案, ...

  10. RK3308 WIFI驱动调试

    一:概述 本章节将记录在rk3308平台上就wifi驱动的调试过程进行记录,wifi驱动源码包一般都是由供应商提供,我们只需要将其编译进内核,由于wifi的接口有多种,例如常见的sdio,usb等,所 ...

最新文章

  1. oracle dba_tables各字段含义
  2. 刚进园子,广州的冬天像夏天
  3. 计算机显示桌面的按钮,显示桌面按钮不见了怎么办_显示桌面按钮不见了
  4. CSS中clear属性的both、left和right浅析
  5. GPU(CUDA)学习日记(九)------ CUDA存储器模型
  6. BeanDefinitionRegistryPostProcessor​ 的处理源码流程
  7. harmonyos2.0三大技术特点,科普干货|漫谈鸿蒙LiteOS-M与HUAWEI LiteOS内核的几大不同...
  8. python只保留大写字母_python - 匹配某一行并保留大写字母?
  9. ASC2BCD及奇偶校验位
  10. Vue - 多图片预加载解决方案
  11. Html 和 CSS笔记
  12. 文本分类上分利器: Bert微调trick大全
  13. 电子设计教程30:温度滞回控制系统
  14. 【计算机组成原理】冯诺伊曼结构和计算机性能指标
  15. 5700: 还钱问题
  16. Boost 学习之算法篇 mismatch
  17. 身价过亿的妖媚子对小码农说串口能传送我的爱吗?
  18. 文献科普|DNA甲基化通过CTCF和黏着蛋白复合物调节选择性聚腺苷酸化
  19. 普通路由器改4g路由器_工业级路由器凭什么牛?智能组网、4G全是干货!
  20. Python EMA计算

热门文章

  1. 工作用oracle18c还是11g,Oracle 18c体验
  2. Linux:CPU中断绑定----计算 SMP IRQ Affinity
  3. 子层div浮动导致父层无法自适应高度的解决方法
  4. 如何查询网站被搜狗收录,搜狗收录查询工具
  5. 电脑清灰你要知道的那些事(二)
  6. 计算机键盘锁不了怎么办,笔记本电脑键盘没反应是哪个键锁了?该怎么办
  7. 此生未完成 --- 于娟
  8. GPS 原始坐标 (WGS-84) ddmm.mmmm 格式转高德(GCJ)百度(BD09)坐标
  9. 【项目二、蜂巢检测项目】二、模型改进:YOLOv5s-ShuffleNetV2
  10. 制作flash cs的简单小游戏