前文回顾

《Linux驱动开发(一)—环境搭建与hello world》
《Linux驱动开发(二)—驱动与设备的分离设计》
《Linux驱动开发(三)—设备树》
《Linux驱动开发(四)—树莓派内核编译》
《Linux驱动开发(五)—树莓派设备树配合驱动开发》
《Linux驱动开发(六)—树莓派配合硬件进行字符驱动开发》
《Linux驱动开发(七)—树莓派按键驱动开发》
《Linux驱动开发(八)—树莓派SR04驱动开发》
《Linux驱动开发(九)—树莓派I2C设备驱动开发(BME280)》
《Linux驱动开发(十)—树莓派输入子系统学习(红外接收)》
《Linux驱动开发(十一)—树莓派SPI驱动学习(OLED)》
《Linux驱动开发(十二)—树莓派framebuffer学习(改造OLED)》
《Linux驱动开发(十三)—USB驱动HID开发学习(鼠标)》
《Linux驱动开发(十四)—USB驱动开发学习(键盘+鼠标)》
《Linux驱动开发(十五)—如何使用内核现有驱动(显示屏)》
《Linux驱动开发(十六)—块设备驱动》
《Linux驱动开发(十七)—树莓派PWM驱动》

今天来学一下驱动中的第三类设备,网络设备的驱动。边学边写,慢慢理解。

网络设备驱动程序结构

Linux网络设备驱动程序的体系结构如图所示,从上到下可以划分为4层,依次为网络协议接口层、网络设备接口层、提供实际功能的设备驱动功能层以及网络设备与媒介层,这4层的作用如下所示:

  1. 网络协议接口层
    向网络层协议提供统一的数据包收发接口,不论上层协议是ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()数接收数据。这一层的存在使得上层协议独立于具体的设备。
  2. 网络设备接口层
    向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。
  3. 设备驱动功能层
    设备驱动功能层的各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,它通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作。
  4. 网络设备与媒介层
    网络设备与媒介层是完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的函数在物理上驱动。对于Linux系统而言,网络设备和媒介都可以是虚拟的。

    以上内容来自《Linux设备驱动开发详解》,其中下面这句就是关键
    在设计具体的网络设备驱动程序时,我们需要完成的主要工作是
    编写设备驱动功能层的相关函数

    填充net device数据结构
    的内容
    并将net device注册入内核。
    看看,三个核心操作。

虚拟网卡

来看一个虚拟网卡的例子,就是直接构造软件网卡,来看一下实现过程

#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/netdev_features.h>static struct net_device *virt_net;static void virt_rs_packet(struct sk_buff *skb, struct net_device *dev)
{unsigned char *type;struct iphdr *ih;__be32 *saddr, *daddr, tmp;unsigned char tmp_dev_addr[ETH_ALEN];struct ethhdr *ethhdr;struct sk_buff *rx_skb;int ret;//对调ethhdr结构体 "源/目的"MAC地址*/ethhdr = (struct ethhdr *)skb->data;memcpy(tmp_dev_addr, ethhdr->h_dest, ETH_ALEN);memcpy(ethhdr->h_dest, ethhdr->h_source, ETH_ALEN);memcpy(ethhdr->h_source, tmp_dev_addr, ETH_ALEN);//对调iphdr结构体"源/目的" IP地址ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));saddr = &ih->saddr;daddr = &ih->daddr;tmp = *saddr;*saddr = *daddr;*daddr = tmp;ih->check=0;ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);//之前是发送ping包0x08,需要改为0x00,表示接收ping包type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);*type = 0; rx_skb = dev_alloc_skb(skb->len + 2);skb_reserve(rx_skb, 2);memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);rx_skb->dev = dev;  rx_skb->ip_summed = CHECKSUM_UNNECESSARY;rx_skb->protocol = eth_type_trans(rx_skb, dev);ret=netif_rx(rx_skb);dev->stats.rx_packets++;        dev->stats.rx_bytes += skb->len;pr_info("rx_packets=%ld rx_bytes=%ld ret=%d\n",dev->stats.rx_packets,dev->stats.rx_bytes,ret);
}static int virt_send_packet(struct sk_buff *skb, struct net_device *dev)
{netif_stop_queue(dev);virt_rs_packet(skb,dev);dev_kfree_skb(skb);dev->stats.tx_packets++; dev->stats.tx_bytes+=skb->len; pr_info("tx_packets=%ld tx_bytes=%ld\n",dev->stats.tx_packets,dev->stats.tx_bytes);netif_wake_queue(dev); return NETDEV_TX_OK;
}static int set_mac_address(struct net_device *dev,void *p)
{struct sockaddr *addr = p;pr_info("set_mac_address\n");if (netif_running(dev)){return -EBUSY;}memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);return 0;
}
void virt_tx_timeout(struct net_device *net,unsigned int txqueue)
{pr_info("virt_tx_timeout\n");
}static const struct net_device_ops net_ops =
{.ndo_start_xmit        = virt_send_packet,.ndo_set_mac_address    =set_mac_address,.ndo_tx_timeout       = virt_tx_timeout,
};static int virt_net_init(void){virt_net= alloc_netdev(sizeof(struct net_device), "virt_net", NET_NAME_UNKNOWN,ether_setup);virt_net->netdev_ops= &net_ops;virt_net->flags = IFF_NOARP;virt_net->dev_addr[0] = 0x88;virt_net->dev_addr[1] = 0x88;virt_net->dev_addr[2] = 0x88;virt_net->dev_addr[3] = 0x88;virt_net->dev_addr[4] = 0x88;virt_net->dev_addr[5] = 0x88;register_netdev(virt_net);return 0;
}static void virt_net_exit(void)
{unregister_netdev(virt_net);free_netdev(virt_net);
}module_init(virt_net_init);
module_exit(virt_net_exit);MODULE_LICENSE("GPL");

编译加载之后,就有了这么一个网卡


不过这个169.254.106.62,为什么他会有这么一个IP

为什么某些电脑获得了形如169.254..之类的IP地址呢?
这说明这些电脑根本未能获得DHCP服务。如DHCP服务器太忙导致无法及时给客户机应答,使客户机认为网络里不存在DHCP服务器。这时,微软的操作系统会自动查找一个形如169.254.*.*的IP地址分配给客户机

不过不知道为啥我这个linux也吃了这么一个瓜

不过还是可以ping通的

为啥能ping通,因为我们在代码里给它返回了正确应答。

虚拟网卡代码分析

其中,在模块初始化函数中就做了前面说的三个步骤,过程也很简单。

  1. 申请一个net_device结构的内存
virt_net= alloc_netdev(sizeof(struct net_device), "virt_net", NET_NAME_UNKNOWN,ether_setup);
  1. 填充数据

包括操作netdev_ops和一些参数,例如下面的网卡mac

virt_net->netdev_ops= &net_ops;
virt_net->flags = IFF_NOARP;virt_net->dev_addr[0] = 0x88;
virt_net->dev_addr[1] = 0x88;
virt_net->dev_addr[2] = 0x88;
virt_net->dev_addr[3] = 0x88;
virt_net->dev_addr[4] = 0x88;
virt_net->dev_addr[5] = 0x88;
  1. 注册网络设备到内核
register_netdev(virt_net);

net_device的数据结构很庞大,感兴趣的可以在netdevice.h中查看一下,本身就是网络设备的参数和状态信息。其中的操作结构net_device_ops也是一样的庞大,这个里面是操作处理函数,例如接收到数据的回调函数。

那在这个虚拟设备中,就只是封装了三个接口

static const struct net_device_ops net_ops =
{.ndo_start_xmit        = virt_send_packet,.ndo_set_mac_address    =set_mac_address,.ndo_tx_timeout       = virt_tx_timeout,
};
netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb, struct net_device *dev);

功能:当需要传输数据包时调用。返回NETDEV_TX_OK。可以返回NETDEV_TX_BUSY,但应在这之前停止队列;它适用于过时的冷门的设备,但如果您返回NETDEV_TX_BUSY,堆栈确实会做大量无用的工作。必填的;不能为空。
这翻译是不是有点那味

int (*ndo_set_mac_address)(struct net_device *dev, void *addr);

功能:当MAC地址需要改变时,如果未定义此接口,则MAC地址无法更改。

void (*ndo_tx_timeout)(struct net_device *dev, unsigned int txqueue);

功能:当发送器未对dev->watchdog ticks取得任何进展时使用的回调。

那么整体来看,这个例子的功能,就是在数据来临之后,一旦调用virt_send_packet函数,就把收到的数据,利用本地函数virt_rs_packet将数据的源目的地址调换,修改成应答,发送出去,在ping的时候,就会有应答的反应了。
参照icmp的格式。
把请求

变成了应答

所以ping的时候,就会有反应。

学习RTL8187驱动

不过前面的虚拟网卡比较简单,内核中已经有好多网卡的驱动,我这有一个usb的无线网卡,不妨来学习一下,型号是rtl8187,所有代码都在这个路径下

其中dev.c是模块的主文件,创建模块,probe函数等都在这里。
首先是作为usb设备进行注册。毕竟是usb模块嘛

在probe函数中,用到了一个ieee80211的子系统,具体学习可以参考《Linux无线驱动简介及mac80211源码分析》

mac80211:是一个Linux内核子系统,是驱动开发者可用于为SoftMAC无线设备写驱动的框架

不过我看了半天,还是没看太懂,过于复杂,不做展示

在probe函数中,主要相关是下面三个函数的使用。

申请

dev = ieee80211_alloc_hw(sizeof(*priv), &rtl8187_ops);

配置

ieee80211_hw_set(dev, RX_INCLUDES_FCS);
……

注册

err = ieee80211_register_hw(dev);

在申请步骤,和rtl8187相关的操作都在rtl8187_ops结构中

这里面的大部分操作,都是和usb读写相关的,举个例子tx函数

 * @tx: Handler that 802.11 module calls for each transmitted frame.*   skb contains the buffer starting from the IEEE 802.11 header.*  The low-level driver should send the frame out based on*    configuration in the TX control data. This handler should,* preferably, never fail and stop queues appropriately.*  Must be atomic.

最终调用到

static void rtl8187_tx(struct ieee80211_hw *dev,struct ieee80211_tx_control *control,struct sk_buff *skb)

最终也是通过usb结构urb发送出去

然后就是中间的相关配置整个模块,里面用到了usb传输方式配置寄存器,也用了eep配置网卡中的数据。
最终注册函数
ieee80211_register_hw->
ieee80211_if_add->
cfg80211_register_netdevice->
register_netdevice

完成了网卡的注册。

整个过程目前只捋顺了这些东西,要想看懂每一行,估计也是很困难,只是把关键的操作找出来了。

可以再来看一个更简单的协议

学习RTL8150驱动

首先也是注册一个usb设备

然后看probe函数

static int rtl8150_probe(struct usb_interface *intf,const struct usb_device_id *id)
{struct usb_device *udev = interface_to_usbdev(intf);rtl8150_t *dev;struct net_device *netdev;netdev = alloc_etherdev(sizeof(rtl8150_t));if (!netdev)return -ENOMEM;dev = netdev_priv(netdev);dev->intr_buff = kmalloc(INTBUFSIZE, GFP_KERNEL);if (!dev->intr_buff) {free_netdev(netdev);return -ENOMEM;}tasklet_setup(&dev->tl, rx_fixup);spin_lock_init(&dev->rx_pool_lock);dev->udev = udev;dev->netdev = netdev;netdev->netdev_ops = &rtl8150_netdev_ops;netdev->watchdog_timeo = RTL8150_TX_TIMEOUT;netdev->ethtool_ops = &ops;dev->intr_interval = 100;   /* 100ms */if (!alloc_all_urbs(dev)) {dev_err(&intf->dev, "out of memory\n");goto out;}if (!rtl8150_reset(dev)) {dev_err(&intf->dev, "couldn't reset the device\n");goto out1;}fill_skb_pool(dev);set_ethernet_addr(dev);usb_set_intfdata(intf, dev);SET_NETDEV_DEV(netdev, &intf->dev);if (register_netdev(netdev) != 0) {dev_err(&intf->dev, "couldn't register the device\n");goto out2;}dev_info(&intf->dev, "%s: rtl8150 is detected\n", netdev->name);return 0;out2:usb_set_intfdata(intf, NULL);free_skb_pool(dev);
out1:free_all_urbs(dev);
out:kfree(dev->intr_buff);free_netdev(netdev);return -EIO;
}

是不是和虚拟网卡的有点类似了,简单清楚

几个核心操作

  1. 申请设备的私有空间
 netdev = alloc_etherdev(sizeof(rtl8150_t));

这里就是开发网络驱动的核心部分,这个netdev是一个私有空间,不同类型的网卡,通常结构不一样,里面存放的就是针对这个设备驱动的数据
例如

  1. 注册接收终端下半部
 tasklet_setup(&dev->tl, rx_fixup);
  1. 配置操作函数
 netdev->netdev_ops = &rtl8150_netdev_ops;
  1. 甚至于更相似的设置网卡mac地址
 set_ethernet_addr(dev);
  1. 最后注册
 if (register_netdev(netdev) != 0) {dev_err(&intf->dev, "couldn't register the device\n");goto out2;}

这个感觉还是清楚多了。不过实际上,可能真的没什么机会去写一个完整的驱动,应该是以移植驱动到一个嵌入式平台为主吧。

结束语

最近的大事就是辅助驾驶出事故,辅助驾驶在目前来看还是不够成熟,辅助驾驶还得驾驶员紧盯着,也不知道在辅助什么,而且都知道无法识别静态物体,反正是做不到自动驾驶的阶段,还是都别用的比较好。

首先,路面上临时出现的路障、三角、警示牌、施工作业区由于出现的周期太短,往往无法被现成的自动驾驶高精地图收录。于是识别上述物体的任务落在本就压力山大的自动驾驶机器头上。
机器感知与人类感知的逻辑不同。人眼可以清晰看到前方路标、路牌、前车尾灯并将其分类,而机器识别的结果只能由数据和算法决定。开放道路场景千变万化,只要物体简单变换外观,就必须重新识别。机器或许能识别出一个推自行车的行人,但难以在短时间内识别一个戴皮卡丘头套推车的行人。数据场景库的丰富度和算法质量不足以应付这一罕见场景。
静止物体的感知和识别是更有难度的工作。在自动驾驶感知系统中,雷达“看到”的是点云,摄像头“看到”的是图像像素,二者数据特征不同,需要复杂的融合过程。反观移动的目标点,由于一直在变化,相对容易判断。而静止障碍物混在静止路牌、路标、绿植中,只有经过多轮筛选才能标识出。一旦算法不够成熟,很容易出现某一传感器识别出障碍物,但被承担巨大高速运转压力的算法当作错误或不重要数据直接过滤。

你们想想,你带着老婆,出了城,吃着火锅还唱着歌!突然就撞了人!

不过,就目前的人工智能来说,代替财务应该是没啥问题的。但是为啥没有财务被代替呢?因为人工智能,不会坐牢。

今天是中元节,还是要早点回家,本来阳气就不足,可别被偷走了。

Linux驱动开发(十八)---网络(网卡)驱动学习相关推荐

  1. windows xp 驱动开发(十八) USB驱动程序开发用到的工具总结

    转载请标明是引用于 http://blog.csdn.net/chenyujing1234 欢迎拍砖! 观察USB设备的工具. 通过这些工具可以方便学习USB协议 一.   usbview 请参考我的 ...

  2. linux 内核 网卡驱动 移植,linux内核移植步骤添加dm9000网卡驱动(设备树).docx

    linux内核移植步骤添加dm9000网卡驱动(设备树).docx LINUX内核移植步骤2015年05月13日星期三上午1105往设备树中添加网卡驱动1.选平台,指定交叉编译工具链1.在MAKEFI ...

  3. 极智开发 | ubuntu 安装有线网卡驱动

      欢迎关注我的公众号 [极智视界],获取我的更多笔记分享   大家好,我是极智视界,本文介绍一下 ubuntu 安装有线网卡驱动方法.   这里的现象是:ubuntu 机器能连接 wifi 上网,但 ...

  4. 嵌入式Linux——网卡驱动(1):网卡驱动框架介绍

    声明:文本是看完韦东山老师的视频和看了一些文章后,所写的总结.我会尽力将自己所了解的知识写出来,但由于自己感觉并没有学的很好,所以文中可能有错的地方敬请指出,谢谢. 在介绍本文之前,我想先对前面的知识 ...

  5. linux debian/ubuntu RTL8111/8168/8411网卡驱动怎么正确安装?

    linux debian/ubuntu RTL8111/8168/8411网卡驱动怎么正确安装? 1.下载驱动 或者云盘链接 链接: https://pan.baidu.com/s/1B0USXWRp ...

  6. 32驱动_轻松掌握pinctrl子系统驱动开发——一个虚拟pinctrl dev驱动开发

    这周主要对pinctrl子系统进行分析,该分析的基本上已经分析完成,唯一没有细说的估计就是gpio与pinctrl之间的关联了.本章即是pinctrl子系统分析的最后一章,本章我们主要实现一个虚拟的p ...

  7. 台式计算机用什么网卡,台式机怎样安装网卡驱动,教您电脑安装网卡驱动

    电脑存在的第一大意义是什么?没错,就是上网,如果电脑没有了网络了,我们该怎么去解决呢?首先得去检查原因,是病毒的,就杀毒,如果是没有正确安装网卡驱动就去安装,但是出于网友对安装网卡驱动不知道如何操作, ...

  8. html安装网卡驱动,网卡驱动怎么装,教您网卡驱动怎么安装

    装完系统后一般需要安装一些硬件驱动,说的简单点,电脑要上网就要有网卡,而要让网卡发挥作用就要有网卡驱动,就比如汽车要有汽油才能跑一样的,没有了驱动,网卡就发挥不了作用.那么网卡驱动怎么安装?下面,小编 ...

  9. 驱动开发:配置Visual Studio驱动开发环境

    在正式开始驱动开发之前,需要自行搭建驱动开发的必要环境,首先我们需要安装Visual Studio 2013这款功能强大的程序开发工具,在课件内请双击ISO文件并运行内部的vs_ultimate.ex ...

  10. 强化学习系列文章(二十八):进化强化学习EvoRL的预实验

    强化学习系列文章(二十八):进化强化学习EvoRL的预实验 最近在研究强化学习解决离散空间的组合优化问题时,接触到了很多进化算法,实际体验也是与RL算法不相上下.进化算法也常用于优化神经网络的参数,C ...

最新文章

  1. 使用Canu对三代测序进行基因组组装
  2. MFC创建属性页详细步骤与注意事项
  3. linux下交叉编译libusb的方法及编译一个使用了libusb库的test程序的方法
  4. [MEGA DEAL]完整的Java编程训练营(94%)
  5. 想都不敢想!这8个神奇“黑科技”原来已经有人弄出来了
  6. java date类1900,java - 使用javax.validation验证日期不得少于1900-01-01 - 堆栈内存溢出...
  7. Qt更改字体为思源黑体
  8. NGINX简介及工作原理
  9. 解密Google Deepmind AlphaGo围棋算法
  10. word中如何插入参考文献
  11. python:实现lowest common ancestor最低共同祖先算法(附完整源码)
  12. 如何做好性能压测(二)| 施压方式选择(rps or 并发)
  13. 36种漂亮的CSS3网页按钮Button样式
  14. 房子要室内设计了,先从讲装修的施工工艺开始!
  15. server 08 做DNS和域分离
  16. DJI M210+manifold 2C配置
  17. PMOS管/NMOS管控制供电电路
  18. DISTINCT和COUNT的组合使用
  19. NLP-二分类的应用-区分外卖评论好评/差评
  20. 广深IT之行:传统模式与技术创新的融合

热门文章

  1. 太牛了,24 个好用到爆的 Python 实用技巧
  2. 刷脸时代的安全防护体系到位吗?
  3. 魔兽8.0最新服务器人口普查,人口普查#8:血精灵增长第一 魔兽进入平缓期
  4. Atlas(2):开源数据治理方案
  5. STC-ISP程序下载软件
  6. matlab 硬件驱动,基于MATLAB的驱动电路硬件仿真与研究
  7. 联想同传系统安装即使用心得
  8. python海伦公式_「海伦公式」海伦公式: - seo实验室
  9. 资源共享软件有哪些?团队共享便签了解一下
  10. Unity_Shader高级篇_14_Unity Shader入门精要