在Linux,网络分为两个层,各自是网络堆栈协议支持层,以及接收和发送网络协议的设备驱动程序层。

网络堆栈是硬件中独立出来的部分。主要用来支持TCP/IP等多种协议,网络设备驱动层是连接网络堆栈协议层和网络硬件的中间层。

网络设备驱动程序的主要功能是:

(1)模块载入或内核启动相关的初始化处理

(2)清除模块时的处理

(3)网络设备的检索和探測

(4)网络设备的初始化和注冊

(5)打开或关闭网络设备

(6)发送网络数据

(7)接收网络数据

(8)中断处理(在发送完数据时。硬件向内核产生一个中断。告诉内核数据已经发送完成。在网络设备接收到数据时,也要发生一个中断,告诉内核。数据已经到达,请及时处理)

(9)超时处理

(10)多播处理

(11)网络设备的控制ioctl

而Linux网络设备驱动的主要功能就是网络设备的初始化,网络设备的配置,数据包的收发。

以下代码是关于虚拟硬件的网络驱动的样例

2、代码

#undef PDEBUG             /* undef it, just in case */
#ifdef SNULL_DEBUG
#  ifdef __KERNEL__/* This one if debugging is on, and kernel space */
#    define PDEBUG(fmt, args...) printk( KERN_DEBUG "snull: " fmt, ## args)
#  else/* This one for user space */
#    define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
#  endif
#else
#  define PDEBUG(fmt, args...) /* not debugging: nothing */
#endif
#undef PDEBUGG
#define PDEBUGG(fmt, args...) /* nothing: it's a placeholder *//* These are the flags in the statusword */
#define SNULL_RX_INTR 0x0001
#define SNULL_TX_INTR 0x0002
/* Default timeout period */
#define SNULL_TIMEOUT 6   /* In jiffies */
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/errno.h>  /* error codes */
#include <linux/types.h>  /* size_t */
#include <linux/interrupt.h> /* mark_bh */
#include <linux/in.h>
#include <linux/netdevice.h>   /* struct device, and other headers */
#include <linux/etherdevice.h> /* eth_type_trans */
#include <linux/ip.h>          /* struct iphdr */
#include <linux/tcp.h>         /* struct tcphdr */
#include <linux/skbuff.h>
#include <linux/if_ether.h>
#include <linux/in6.h>
#include <asm/uaccess.h>
#include <asm/checksum.h>static int lockup = 0;
static int timeout = SNULL_TIMEOUT;
struct net_device snull_devs[2];//这里定义两个设备,一个是snull0,一个是snull1
//网络设备结构体,作为net_device->priv
struct snull_priv {struct net_device_stats stats;//实用的统计信息int status;//网络设备的状态信息。是发完数据包。还是接收到网络数据包int rx_packetlen;//接收到的数据包长度u8 *rx_packetdata;//接收到的数据int tx_packetlen;//发送的数据包长度u8 *tx_packetdata;//发送的数据struct sk_buff *skb;//socket buffer结构体,网络各层之间传送数据都是通过这个结构体来实现的spinlock_t lock;//自旋锁
};void snull_tx_timeout (struct net_device *dev);//网络接口的打开函数
int snull_open(struct net_device *dev){printk("call snull_open/n");memcpy(dev->dev_addr, "/0SNUL0", ETH_ALEN);//分配一个硬件地址,ETH_ALEN是网络设备硬件地址的长度netif_start_queue(dev);//打开传输队列,这样才干进行传输数据return 0;
}
int snull_release(struct net_device *dev)
{printk("call snull_release/n");netif_stop_queue(dev); //当网络接口关闭的时候,调用stop方法。这个函数表示不能再发送数据return 0;
}//接包函数
void snull_rx(struct net_device *dev, int len, unsigned char *buf)
{struct sk_buff *skb;struct snull_priv *priv = (struct snull_priv *) dev->priv;/** The packet has been retrieved from the transmission* medium. Build an skb around it, so upper layers can handle it*/skb = dev_alloc_skb(len+2);//分配一个socket buffer,而且初始化skb->data,skb->tail和skb->headif (!skb) {printk("snull rx: low on mem - packet dropped/n");priv->stats.rx_dropped++;return;}skb_reserve(skb, 2); /* align IP on 16B boundary */ memcpy(skb_put(skb, len), buf, len);//skb_put是把数据写入到socket buffer/* Write metadata, and then pass to the receive level */skb->dev = dev;skb->protocol = eth_type_trans(skb, dev);//返回的是协议号skb->ip_summed = CHECKSUM_UNNECESSARY; //此处不校验priv->stats.rx_packets++;//接收到包的个数+1priv->stats.rx_bytes += len;//接收到包的长度netif_rx(skb);//通知内核已经接收到包。而且封装成socket buffer传到上层return;
}/** The typical interrupt entry point*/
//中断处理。此程序中没有硬件,因此,没有真正的硬件中断,仅仅是模拟中断,在发送完网络数据包之后。会产生中断
//用来通知内核已经发送完数据包,当新的数据包到达网络接口时,会发生中断。通知新的数据包已经到来了
void snull_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{int statusword;//用来标识是发送完成还是接收到新的数据包struct snull_priv *priv;/** As usual, check the "device" pointer for shared handlers.* Then assign "struct device *dev"*/struct net_device *dev = (struct net_device *)dev_id;/* ... and check with hw if it's really ours */if (!dev /*paranoid*/ ) return;/* Lock the device */priv = (struct snull_priv *) dev->priv;spin_lock(&priv->lock);/* retrieve statusword: real netdevices use I/O instructions */statusword = priv->status;if (statusword & SNULL_RX_INTR) {//假设是接收/* send it to snull_rx for handling */snull_rx(dev, priv->rx_packetlen, priv->rx_packetdata);}if (statusword & SNULL_TX_INTR) {//假设发送完成/* a transmission is over: free the skb */priv->stats.tx_packets++;priv->stats.tx_bytes += priv->tx_packetlen;dev_kfree_skb(priv->skb);//释放skb 套接字缓冲区}/* Unlock the device and we are done */spin_unlock(&priv->lock);return;
}/** Transmit a packet (low level interface)*/
//真正的处理的发送数据包
//模拟从一个网络向还有一个网络发送数据包
void snull_hw_tx(char *buf, int len, struct net_device *dev){/** This function deals with hw details. This interface loops* back the packet to the other snull interface (if any).* In other words, this function implements the snull behaviour,* while all other procedures are rather device-independent*/struct iphdr *ih;//ip头部struct net_device *dest;//目标设备结构体。net_device存储一个网络接口的重要信息,是网络驱动程序的核心struct snull_priv *priv;u32 *saddr, *daddr;//源设备地址与目标设备地址/* I am paranoid. Ain't I? */if (len < sizeof(struct ethhdr) + sizeof(struct iphdr)) {printk("snull: Hmm... packet too short (%i octets)/n",len);return;}/** Ethhdr is 14 bytes, but the kernel arranges for iphdr* to be aligned (i.e., ethhdr is unaligned)*/ih = (struct iphdr *)(buf+sizeof(struct ethhdr));saddr = &ih->saddr;daddr = &ih->daddr;//在同一台机器上模拟两个网络。不同的网段地址,进行发送网络数据包与接收网络数据包((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) ^是位异或操作符把第三个部分的网络地址与1进行异或,因为同一网络的数据不进行转发*/ ((u8 *)daddr)[2] ^= 1;ih->check = 0;         /* and rebuild the checksum (ip needs it) */ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);if (dev == snull_devs)PDEBUGG("%08x:%05i --> %08x:%05i/n",ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source),ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest));elsePDEBUGG("%08x:%05i <-- %08x:%05i/n",ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest),ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source));/** Ok, now the packet is ready for transmission: first simulate a* receive interrupt on the twin device, then  a* transmission-done on the transmitting device*/dest = snull_devs + (dev==snull_devs ? 1 : 0);//假设dev是0,那么dest就是1,假设dev是1。那么dest是0priv = (struct snull_priv *) dest->priv;//目标dest中的privpriv->status = SNULL_RX_INTR;priv->rx_packetlen = len;priv->rx_packetdata = buf;snull_interrupt(0, dest, NULL);priv = (struct snull_priv *) dev->priv;priv->status = SNULL_TX_INTR;priv->tx_packetlen = len;priv->tx_packetdata = buf;if (lockup && ((priv->stats.tx_packets + 1) % lockup) == 0) {/* Simulate a dropped transmit interrupt */netif_stop_queue(dev);PDEBUG("Simulate lockup at %ld, txp %ld/n", jiffies,(unsigned long) priv->stats.tx_packets);}elsesnull_interrupt(0, dev, NULL);
}/** Transmit a packet (called by the kernel)*///发包函数
int snull_tx(struct sk_buff *skb, struct net_device *dev)
{int len;char *data;struct snull_priv *priv = (struct snull_priv *) dev->priv;if ( skb == NULL) {PDEBUG("tint for %p,  skb %p/n", dev,  skb);snull_tx_timeout (dev);if (skb == NULL)return 0;}len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len;//ETH_ZLEN是所发的最小数据包的长度data = skb->data;//将要发送的数据包中数据部分dev->trans_start = jiffies; //保存当前的发送时间priv->skb = skb;snull_hw_tx(data, len, dev);//真正的发送函数return 0; /* Our simple device can not fail */
}
/** Deal with a transmit timeout.*///一旦超出watchdog_timeo就会调用snull_tx_timeout
void snull_tx_timeout (struct net_device *dev)
{printk("call snull_tx_timeout/n");struct snull_priv *priv = (struct snull_priv *) dev->priv;PDEBUG("Transmit timeout at %ld, latency %ld/n", jiffies,jiffies - dev->trans_start);priv->status = SNULL_TX_INTR;snull_interrupt(0, dev, NULL);//超时后发生中断priv->stats.tx_errors++;//发送的错误数netif_wake_queue(dev); //为了再次发送数据,调用此函数,又一次启动发送队列return;
}/** Ioctl commands*/
int snull_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{PDEBUG("ioctl/n");return 0;
}
/** Return statistics to the caller*/
struct net_device_stats *snull_stats(struct net_device *dev)
{struct snull_priv *priv = (struct snull_priv *) dev->priv;return &priv->stats;//得到统计资料信息
}//设备初始化函数
int snull_init(struct net_device *dev)
{printk("call snull_init/n");/** Then, assign other fields in dev, using ether_setup() and some* hand assignments*/ether_setup(dev);//填充一些以太网中的设备结构体的项dev->open            = snull_open;dev->stop            = snull_release;//dev->set_config      = snull_config;dev->hard_start_xmit = snull_tx;dev->do_ioctl        = snull_ioctl;dev->get_stats       = snull_stats;//dev->change_mtu      = snull_change_mtu; // dev->rebuild_header  = snull_rebuild_header;//dev->hard_header     = snull_header;dev->tx_timeout     = snull_tx_timeout;//超时处理dev->watchdog_timeo = timeout;/* keep the default flags, just add NOARP */dev->flags           |= IFF_NOARP;dev->hard_header_cache = NULL;      /* Disable caching */SET_MODULE_OWNER(dev);/** Then, allocate the priv field. This encloses the statistics* and a few private fields.*/
//为priv分配内存dev->priv = kmalloc(sizeof(struct snull_priv), GFP_KERNEL);if (dev->priv == NULL)return -ENOMEM;memset(dev->priv, 0, sizeof(struct snull_priv));spin_lock_init(& ((struct snull_priv *) dev->priv)->lock);return 0;
}struct net_device snull_devs[2] = {{ init: snull_init, },  /* init, nothing more */{ init: snull_init, }
};int snull_init_module(void)
{int i,result=0;strcpy(snull_devs[0].name,"snull0");//net_device结构体中的name表示设备名strcpy(snull_devs[1].name,"snull1");//即定义了两个设备,snull0与snull1for (i=0; i<2;  i++)if ( (result = register_netdev(snull_devs+i)) )//注冊设备printk("snull: error %i registering device /"%s/"/n",result, snull_devs[i].name);return 0;
}
void snull_cleanup(void)
{int i;for (i=0; i<2;  i++) {kfree(snull_devs[i].priv);unregister_netdev(snull_devs+i);}return;
}module_init(snull_init_module);
module_exit(snull_cleanup);

转载于:https://www.cnblogs.com/clnchanpin/p/6790168.html

Linux 网卡驱动学习(一)(分析一个虚拟硬件的网络驱动样例)相关推荐

  1. 11-S3C2440驱动学习(五)嵌入式linux-网络设备驱动(一)虚拟网卡驱动程序

    本节是网卡驱动的入门基础部分,移植移植DM9000C网卡驱动程序 请看下节:移植DM9000C网卡驱动程序http://blog.csdn.net/fengyuwuzu0519/article/det ...

  2. diff算法_[VUE学习]徒手撸一个虚拟dom+diff算法

    首先先来聊一下,什么是虚拟dom? 为什么要使用虚拟dom? 其实答案很简单,虚拟dom就是位于js层和html层之间的一个层,使用js模拟出一个dom树,然后通过diff算法,来侦测到dom发生更改 ...

  3. Lumerical Python API学习笔记(一):Lumopt配置以及样例运行

    参考文章 完美解决Windows用户下lumerical(FDTD)的python库lumopt安装方法 bilibili–lumerical配合pycharm使用python联合开发的问题 0.先说 ...

  4. qemu图形界面linux,QEMU 简单几步搭建一个虚拟的ARM开发板

    1.安装QEMU 先在Ubuntu中安装QEMU sudo apt-get install qemu 1 安装几个QEMU需要的软件包: sudo apt-get install zlib1g-dev ...

  5. 一个优雅的后端API接口样例和代码实现方案

    前言 在移动互联网,分布式.微服务盛行的今天,现在项目绝大部分都采用的微服务框架,前后端分离方式,(题外话:前后端的工作职责越来越明确,现在的前端都称之为大前端,技术栈以及生态圈都已经非常成熟:以前后 ...

  6. SAP FICO产品成本组件分析表开发说明书(包括测试样例、程序代码仅作参考,不保证一定可以运行)

    程序说明 基于期间物料的成本组件明细报表 输入屏幕 屏幕字段中文描述 选择类型 屏幕字段名称 参考字段名称

  7. linux v4l2系统详解,Linux摄像头驱动学习之:(一)V4L2_框架分析

    这段时间开始搞安卓camera底层驱动了,把以前的的Linux视频驱动回顾一下,本篇主要概述一下vfl2(video for linux 2). 一. V4L2框架: video for linux ...

  8. Linux 网卡驱动学习(二)(网络驱动接口小结)

    [摘要]前文我们分析了一个虚拟硬件的网络驱动样例.从中我们看到了网络设备的一些接口.事实上网络设备驱动和块设备驱动的功能比較相似,都是发送和接收数据包(数据请求). 当然它们实际是有非常多不同的. 1 ...

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

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

最新文章

  1. ECCV2020论文-稀疏性表示-Neural Sparse Representation for Image Restoration翻译
  2. C++多线程简单入门(二)(Windows版)
  3. Azure data studio 跨平台数据库管理工具试用
  4. php layui table,Layui table组件相关介绍
  5. 运维工程师是桥的护栏_【消息】秭归将建螺旋桥
  6. python+unittest框架整理(一点点学习前辈们的封装思路,一点点成长。。。)
  7. 第一个Python程序——博客自动访问脚本
  8. np.linalg 线性代数
  9. 如何在 Apple TV 上安装 tvOS 15 developer beta 9?
  10. matlab myupdatefcn,MATLAB笔记
  11. 无人车业务中的视觉三维重建
  12. Java案例实现用户登录
  13. 正规手游代理该怎么选?
  14. docker实现nginx反向代理、负载均衡
  15. 数通 | 静态路由表的配置(含负载分担、路由备份)
  16. Python爬取彼岸图网10万张高清图片(入门级爬虫)_一蓑烟雨任平生
  17. 业务入云是一条不归路
  18. 基于springboot的中国国家图书馆管理系统项目(管理功能)
  19. mysql 判断时间是否当天_mysql判断时间是否是当天,昨天。。。
  20. UNIAPP----仿淘宝详情页滚动

热门文章

  1. 采用Filter的方法解决Servlet的编码问题
  2. 菜鸟学习javascript实例教程
  3. 深圳人均GDP过一万美元随想
  4. vue脚手架项目技术集合
  5. IT人为了自己父母和家庭,更得注意自己的身体和心理健康
  6. ios 关于block传值报错
  7. Maven中jar版本冲突问题的解决
  8. JS获取客户端IP地址、MAC和主机名的7个方法汇总
  9. 设计模式入门,适配器模式,c++代码实现
  10. 浏览器端JS导出EXCEL