十九、Linux驱动之虚拟网卡驱动

2023-11-10

1. 基本概念

    网络设备是完成用户数据包在网络媒介上发送和接收的设备,它将上层协议传递下来的数据包以特定的媒介访问控制方式进行发送,并将接收到的数据包传递给上层协议。与字符设备和块设备不同,网络设备并不对应于/dev目录下的文件,应用程序最终使用套接字完成与网络设备的接口。因而在网络设备身上并不能体现出“一切都是文件”的思想。
    Linux系统对网络设备驱动定义了4个层次, 从上到下依次为网络协议接口层、 网络设备接口层、 提供实际功能的设备驱动功能层以及网络设备与媒介层Linux网络设备驱动程序的体系结构如下图:


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

2. 分析内核

    接下来分析内核(linux-2.6.22.6)具体是如何通过这4个层次使用网络设备的。

2.1 网络协议接口层

    网络协议接口层最主要的功能是给上层协议提供透明的数据包发送和接收接口。当上层ARPIP需要发送数据包时, 它将调用网络协议接口层的dev_queue_xmit()函数发送该数据包,同时需传递给该函数一个指向struct sk_buff数据结构的指针。 dev_queue_xmit()函数的原型为:

int dev_queue_xmit(struct sk_buff *skb);

    同样地,上层对数据包的接收也通过向netif_rx()函数传递一个struct sk_buff数据结构的指针来完成。netif_rx()函数的原型为:

int netif_rx(struct sk_buff *skb);

    sk_buff结构体非常重要,它定义于include/linux/skbuff.h文件中,含义为“套接字缓冲区”,用于在Linux网络子系统中的各层之间传递数据,是Linux网络子系统数据传递的“中枢神经”。当发送数据包时,Linux内核的网络处理模块必须建立一个包含要传输的数据包的sk_buff,然后将sk_buff递交给下层,各层在sk_buff中添加不同的协议头直至交给网络设备发送。同样地,当网络设备从网络媒介上接收到数据包后,它必须将接收到的数据转换为sk_buff数据结构并传递给上层,各层剥去相应的协议头直至交给用户。sk_buff数据结构部分代码如下:

struct sk_buff {
       /* These two members must be first. */
       struct sk_buff        *next;      //指向下一个sk_buff结构体
       struct sk_buff        *prev;      //指向前一个sk_buff结构体
    ...
       unsigned int          len,      //数据包的总长度,包括线性数据和非线性数据
                            data_len,    //非线性的数据长度
                            mac_len;     //mac包头长度

    __u32          priority;       //该sk_buff结构体的优先级   

    __be16        protocol;        //存放上层的协议类型,可以通过eth_type_trans()来获取
       ...

      sk_buff_data_t              transport_header;    //传输层头部的偏移值
      sk_buff_data_t              network_header;     //网络层头部的偏移值
      sk_buff_data_t              mac_header;          //MAC数据链路层头部的偏移值

    sk_buff_data_t              tail;                    //指向缓冲区的数据包末尾
      sk_buff_data_t              end;                     //指向缓冲区的末尾
      unsigned char            *head,                   //指向缓冲区的协议头开始位置
                                  *data;                   //指向缓冲区的数据包开始位置
       ...
}

    headend指向缓冲区的头部和尾部,而datatail指向实际数据的头部和尾部。每一层会在headdata之间填充协议头,或者在tailend之间添加新的协议数据。如下图所示:

2.1.1 分配sk_buff

    Linux内核中用于分配套接字缓冲区的函数原型如下:

struct sk_buff *alloc_skb(unsigned int len, gfp_t priority);
struct sk_buff *dev_alloc_skb(unsigned int len);

    alloc_skb()函数分配一个套接字缓冲区和一个数据缓冲区,参数len为数据缓冲区的空间大小,通常以L1_CACHE_BYTES字节(对于ARM为32)对齐,参数priority为内存分配的优先级。dev_alloc_skb()函数以GFP_ATOMIC优先级进行skb的分配,原因是该函数经常在设备驱动的接收中断里被调用。

2.1.2 释放sk_buff

    Linux内核中用于释放套接字缓冲区的函数原型如下:

void kfree_skb(struct sk_buff *skb);
void dev_kfree_skb(struct sk_buff *skb);
void dev_kfree_skb_irq(struct sk_buff *skb);
void dev_kfree_skb_any(struct sk_buff *skb);

    Linux内核内部使用kree_skb()函数,而在网络设备驱动程序中则最好用dev_kfree_skb()dev_kfree_skb_irq()dev_kfree_skb_any()函数进行套接字缓冲区的释放。其中,dev_kfree_skb()函数用于非中断上下文,dev_kfree_skb_irq()函数用于中断上下文,而dev_kfree_skb_any()函数在中断和非中断上下文中皆可采用,它其实是做一个非常简单的上下文判断,然后再调用__dev_kfree_skb_irq()或者dev_kfree_skb()。

2.1.3 改变sk_buff

    在Linux内核中可以用如下函数在缓冲区尾部增加数据:

unsigned char *skb_put(struct sk_buff *skb, unsigned int len);

    它会导致skb->tail后移len(skb->tail+=len),而skb->len会增加len的大小(skb->len+=len)。通常,在设备驱动的接收数据处理中会调用此函数。
    在Linux内核中可以用如下函数在缓冲区开头增加数据:

unsigned char *skb_push(struct sk_buff *skb, unsigned int len);

    它会导致skb->data前移len(skb->data-=len),而skb->len会增加len的大小(skb->len+=len) 。与该函数的功能完成相反的函数是skb_pull(),它可以在缓冲区开头移除数据,执行的动作是skb->len-=len、skb->data+=len

2.2 网络设备接口层

    内核中使用net_device结构来描述网络设备,这个结构是网络设备接口层中最重要的结构。该结构不仅描述了接口方面的信息,还包括硬件信息,致使该结构很大很复杂。通过这个结构,内核在底层的网络驱动和网络层之间构建了一个网络接口核心层,这个中间层类似于文件子系统的VFS。这样底层的驱动程序就不需要过多地关注上层的网络协议,只需要通过内核提供的网络接口核心层就可以很方便将和网络层进行数据的交互。而网络层在向下发送数据时,只需要通过内核提供的这个中间层进行交互即可,不需要关心底层究竟是什么类型的网卡。
    net_device结构体在内核中指代一个网络设备, 它定义于include/linux/netdevice.h文件中, 网络设备驱动程序只需通过填充net_device的具体成员并注册net_device即可实现硬件操作函数与内核的挂接。

struct net_device
{
       char               name[IFNAMSIZ]; //网卡设备名称
       unsigned long      mem_end;        //该设备的内存结束地址
       unsigned long      mem_start;      //该设备的内存起始地址
       unsigned long      base_addr;      //该设备的内存I/O基地址
       unsigned int       irq;            //该设备的中断号

       unsigned char      if_port;        //多端口设备使用的端口类型
    unsigned char      dma;            //该设备的DMA通道

      struct net_device_stats* (*get_stats)(struct net_device *dev); //获取流量的统计信息
      /*运行ifconfig便会调用该成员函数,并返回一个net_device_stats结构体获取信息*/

      struct net_device_stats  stats;    //用来保存统计信息的net_device_stats结构体
 
       unsigned long              features;    //接口特征,     
       unsigned int          flags;    //flags指网络接口标志,以IFF_(Interface Flags)开头
/*当flags =IFF_UP( 当设备被激活并可以开始发送数据包时,内核设置该标志)、IFF_AUTOMEDIA(设置
设备可在多种媒介间切换)、IFF_BROADCAST( 允许广播)、IFF_DEBUG( 调试模式,可用于控制printk
调用的详细程度) 、IFF_LOOPBACK( 回环)、IFF_MULTICAST( 允许组播) 、IFF_NOARP( 接口不能
执行ARP,点对点接口就不需要运行 ARP)和IFF_POINTOPOINT(接口连接到点到点链路)等。*/
 
       unsigned        mtu;                    //最大传输单元,也叫最大数据包

       unsigned short  type;                  //接口的硬件类型

       unsigned short  hard_header_len;        //硬件帧头长度,一般被赋为ETH_HLEN,即14
 
    unsigned char   perm_addr[MAX_ADDR_LEN];  //存放网关地址

       unsigned long   last_rx;                 //接收数据包的时间戳,调用netif_rx()后赋上jiffies即可

       unsigned long   trans_start;            //发送数据包的时间戳,当要发送的时候赋上jiffies即可

       unsigned char   dev_addr[MAX_ADDR_LEN];    //MAC地址
 
       int    (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);    //数据包发送函数, sk_buff就是用来收发数据包的结构体

    void    (*tx_timeout) (struct net_device *dev);    //发包超时处理函数
    ... ...
}

    对于该层,我们只需要填充net_device数据结构的内容并将net_device注册入内核即可。

2.2.1 分配net_device结构体 

    分配net_device结构体原型如下:

struct net_device *alloc_netdev(int sizeof_priv, const char *name,void (*setup)(struct net_device *));

    sizeof_priv表示私有数据大小,name表示网卡名字,ether_setup()函数会初始化一部分net_device结构体成员。

2.2.2 注册net_device结构体

    向内核注册net_device结构体原型如下:

int register_netdev(struct net_device *dev);
int register_netdevice(struct net_device *dev);

    register_netdev()是对register_netdevice()的包装函数。在调用register_netdev()注册设备时,如果指定的名称中包含%d格式串(只支持%d),内核会选择一个适当的数字来替换格式化串,真正的注册工作由register_netdevice()来完成。

2.3 设备驱动功能层

    net_device结构体的成员(属性和net_device_ops结构体中的函数指针)需要被设备驱动功能层赋予具体的数值和函数。对于具体的设备xxx,工程师应该编写相应的设备驱动功能层的函数,这些函数形如xxx_open()、xxx_stop()、xxx_tx()、 xxx_hard_header()、xxx_get_stats()xxx_tx_timeout()等。
    由于网络数据包的接收可由中断引发,设备驱动功能层中的另一个主体部分将是中断处理函数,它负责读取硬件上接收到的数据包并传送给上层协议,因此可能包含xxx_interrupt() xxx_rx()函数,前者完成中断类型判断等基本工作,后者则需完成数据包的生成及将其递交给上层等复杂工作。
    这一层的功能函数网卡芯片厂商都会有demo,我们只需要修改与硬件相关(如中断、I/O地址等)部分即可。

3. 编写代码

    本节编写一个虚拟网卡驱动程序,由于没有真实的网卡,不会接收到数据,不能实现接收中断,所以将收包函数放在发包函数里,将要发送的skb_buff数据再提交上层。(内核驱动里接收数据包主要是通过中断函数处理,中断类型如果等于ISQ_RECEIVER_EVENT表示为接收中断,然后进入接收数据函数,通过netif_rx()将数据上交给上层)。同样我们不用户编写设备驱动功能层(对于真实网卡需要编写对应的设备驱动功能层的函数)。

3.1 代码框架

3.1.1 初始函数中

    1. 使用alloc_netdev()来分配一个net_device结构体。
    2. 设置net_device结构体的成员。
    3. 使用register_netdev()来注册net_device结构体。

3.1.2 发包函数中

    1. 使用netif_stop_queue()来阻止上层向网络设备驱动层发送数据包。
    2. 调用收包函数,并代入发送的sk_buff缓冲区,里面来伪造一个收的ping包函数提交上层。
    3. 使用dev_kfree_skb()函数来释放发送的sk_buff缓存区。
    4. 更新发送的统计信息。
    5. 使用netif_wake_queue()来唤醒被阻塞的上层。

3.1.3 收包函数中

    1. 需要对调上图的ethhdr结构体 ”源/目的”MAC地址。
    2. 需要对调上图的iphdr结构体”源/目的” IP地址。
    3. 使用ip_fast_csum()来重新获取iphdr结构体的校验码。
    4. 设置上图数据包的数据类型,之前是发送ping包0x08,需要改为0x00,表示接收ping包。
    5. 使用dev_alloc_skb()来构造一个新的sk_buff
    6. 使用skb_reserve(rx_skb, 2);将sk_buff缓冲区里的数据包先后位移2字节,来腾出sk_buff缓冲区里的头部空间。
    7. 使用memcpy()将之前修改好的sk_buff->data复制到新的sk_buff里的data成员指向的地址处。

3.2 编写代码

    驱动程序virt_net.c完整代码如下:

/*
 * 参考 drivers\net\cs89x0.c
 */
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/in.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/ip.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/irq.h>

static struct net_device *vnet_dev;

static void emulator_rx_packet(struct sk_buff *skb, struct net_device *dev)
{
	/* 参考LDD3 */
	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;
		
	// 从硬件读出/保存数据
	/* 对调"源/目的"的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);

	/* 对调"源/目的"的ip地址 */    
	ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
	saddr = &ih->saddr;
	daddr = &ih->daddr;

	tmp = *saddr;
	*saddr = *daddr;
	*daddr = tmp;
	
	//((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */
	//((u8 *)daddr)[2] ^= 1;
	type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
	//printk("tx package type = %02x\n", *type);
	// 修改类型, 原来0x8表示ping
	*type = 0; /* 0表示reply */
	
	ih->check = 0;		   /* and rebuild the checksum (ip needs it) */
	ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
	
	// 构造一个sk_buff
	rx_skb = dev_alloc_skb(skb->len + 2);
	skb_reserve(rx_skb, 2); /* align IP on 16B boundary */	
	memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);

	/* Write metadata, and then pass to the receive level */
	rx_skb->dev = dev;
	rx_skb->protocol = eth_type_trans(rx_skb, dev);
	rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
	dev->stats.rx_packets++;
	dev->stats.rx_bytes += skb->len;

	// 提交sk_buff
	netif_rx(rx_skb);
}

static int virt_net_send_packet(struct sk_buff *skb, struct net_device *dev)
{
	static int cnt = 0;
	printk("virt_net_send_packet cnt = %d\n", ++cnt);

	/* 对于真实的网卡, 把skb里的数据通过网卡发送出去 */
	netif_stop_queue(dev); /* 停止该网卡的队列 */
    /* ...... */           /* 把skb的数据写入网卡 */

	/* 构造一个假的sk_buff,上报 */
	emulator_rx_packet(skb, dev);

	dev_kfree_skb (skb);   /* 释放skb */
	netif_wake_queue(dev); /* 数据全部发送出去后,唤醒网卡的队列 */

	/* 更新统计信息 */
	dev->stats.tx_packets++;
	dev->stats.tx_bytes += skb->len;
	
	return 0;
}

static int virt_net_init(void)
{
	/* 1. 分配一个net_device结构体 */
	vnet_dev = alloc_netdev(0, "vnet%d", ether_setup);;  /* alloc_etherdev */

	/* 2. 设置 */
	vnet_dev->hard_start_xmit = virt_net_send_packet;

	/* 设置MAC地址 */
        vnet_dev->dev_addr[0] = 0x08;
        vnet_dev->dev_addr[1] = 0x89;
        vnet_dev->dev_addr[2] = 0x89;
        vnet_dev->dev_addr[3] = 0x89;
        vnet_dev->dev_addr[4] = 0x89;
        vnet_dev->dev_addr[5] = 0x11;

        /* 设置下面两项才能ping通 */
	vnet_dev->flags           |= IFF_NOARP;
	vnet_dev->features        |= NETIF_F_NO_CSUM;	

	/* 3. 注册 */
	//register_netdevice(vnet_dev);
	register_netdev(vnet_dev);
	
	return 0;
}

static void virt_net_exit(void)
{
	unregister_netdev(vnet_dev);
	free_netdev(vnet_dev);
}

module_init(virt_net_init);
module_exit(virt_net_exit);
MODULE_AUTHOR("lvzhenhai");
MODULE_LICENSE("GPL");

    Makefile代码如下:

KERN_DIR = /work/system/linux-2.6.22.6	//内核目录

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= virt_net.o

4. 测试

内核:linux-2.6.22.6
编译器:arm-linux-gcc-3.4.5
环境:ubuntu9.10

    开发板启动内核并安装编译好的驱动,执行如下命令:
      insmod virt_net.ko
      ifconfig vnet0 3.3.3.3   
(设置虚拟网卡vnet0的ip)
   

    执行如下命令ping自己:
      ping 3.3.3.3    (当ping自己时,使用回环网卡,没有调用到底层硬件发包函数)
   
    执行如下命令ping网络:
      ping 3.3.3.4   (使用我们编写的网卡驱动了,调用底层硬件发包函数)

   
    可以执行ifconfig查看,统计信息变化了:
   

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

十九、Linux驱动之虚拟网卡驱动 的相关文章

  • 一篇就让你了解进程的虚拟地址与物理地址是如何进行映射的

    这篇文章可以让你了解进程的虚拟地址是如何映射物理地址的 在刚开始接触虚拟地址的概念时 真的是一头雾水 不知道它到底是个什么东西 一直有这个疑惑 为什么要用虚拟地址 虚拟地址的作用是什么 经过这么多年做过的项目 积攒经验后 我可以略知一些虚拟
  • OpenHarmony 标准系统HDF框架之I2C驱动开发

    OpenHarmony 标准系统HDF框架之I2C驱动开发 主要内容 I2C 基础知识 I2C 基础知识 概念和特性 I2C 基础知识 协议 四种信号组合 I2C 调试手段 I2C 调试手段 硬件 I2C 调试手段 软件 HDF 框架下的
  • 全志A64触摸屏驱动调试

    一 前言 linux的触摸屏驱动一般要经历一下几个步骤 以4412为例 1 移植驱动到linux源码 driver input touchscreen 目录下 在Kconfig里面增加这款触摸屏驱动的菜单 add by long 20170
  • 内存分配---kmalloc

    kmalloc 内存分配引擎是一个功能强大的工具 下面我们来讲解一下这个函数 Kmalloc 函数分配内存时有几个特点 1 获取内存空间时不会对内存空间进行清零 也就是说 分配给它的区域仍然保持着原有的数据 2 它分配的区域在物理内存中也是
  • 【RK3399】I3399烧写Debian系统详解

    00 目录 文章目录 00 目录 01 驱动安装 02 镜像文件烧写 03 问题讨论 04 附录 01 驱动安装 1 1 没有安装驱动的时候 显示感叹号 1 2 解压DriverAssitant v5 1 1 zip 1 3 双击Drive
  • BootLoader简介——linux内核的引导

    1 BootLoader 在CPU上电启动时 一般连内存控制器都没有初始化过 根本无法在主存中运行程序 更不可能处在Linux内核启动环境中 为了初始化CPU及其他外设 使得Linux内核可以在系统主存中运行 并让系统符合Lintix内核启
  • Linux驱动之系统移植----uboot移植(有设备树版本),完整移植.

    uboot版本 uboot2020 04 开发板 100ask imx6ull pro 拿到官方uboot后第一步先编译烧写测试 查看哪些驱动可用 哪些不可用 根据开发板厂商提供的资料 使用mx6ull 14x14 evk defconfi
  • 十一、Linux驱动之platform总线设备驱动

    1 基本概念 从Linux2 6开始Linux加入了一套驱动管理和注册机制 platform平台总线驱动模型 platform平台总线是一条虚拟总线 platform device为相应的设备 platform driver为相应的驱动 与
  • linux驱动之ioctl详解

    何为ioctl 在linux对文件IO 有打开 读写 关闭 定位等命令 如果需要一些自定义的命令来操作我们写的驱动 ioctl便是传入这些自定义命令的函数 先看看在应用层的函数原型 int ioctl int fd unsigned lon
  • 段错误(核心已转储)问题的分析方法

    问题现象 今天在研究linux kernel中typedef的用法时 写了一个程序test typedef c 内容如下 include
  • petalinux添加AD9361驱动

    文章目录 一 准备工具 二 步骤 需要 petalinux2016 2 包含AD9361驱动的Linux内核 xcomm zynq 4 4 一 准备工具 ADI提供的AD9361Linux驱动 https wiki analog com r
  • make menuconfig 添加新选项

    如何在make menuconfig 界面添加新选项步骤 1 先在Linux内核源码目录下创建个新目录 哪个位置都行 一般添加新驱动时 都会在 drivers目录下创建 这里为了实验的方便就在 源码目录下创建了 mkdir hmq test
  • 嵌入式Linux驱动笔记(二十三)------使用buidroot构建文件系统

    你好 这里是风筝的博客 欢迎和我一起交流 所有的buidroot使用问题都可以看这个在线使用文档 https buildroot org downloads manual manual html about buildroot Buildr
  • 如何修改dmesg log buffer size

    需要修改 Linux 内核源码中的一个控制 log buffer size 的宏 CONFIG LOG BUF SHIFT buffer size 是 2 shift 加大这个就可以 一 配置 make menuconfig General
  • 十九、Linux驱动之虚拟网卡驱动

    1 基本概念 网络设备是完成用户数据包在网络媒介上发送和接收的设备 它将上层协议传递下来的数据包以特定的媒介访问控制方式进行发送 并将接收到的数据包传递给上层协议 与字符设备和块设备不同 网络设备并不对应于 dev目录下的文件 应用程序最终
  • LINUX软中断-ksoftirqd

    前言 在上一篇 LINUX软中断 softirq的描述中 提到过ksoftirqd 这篇文章就介绍ksoftirqd ksoftirqd 是什么 ksoftirqd 是个内核线程 在创建的时候是绑定cpu的 每一个core对应生成一个kso
  • sd卡详细资料

    1 简介 SD卡是基于flash的存储卡 SD卡和MMC卡的区别在于初始化过程不同 SD卡的通信协议包括SD总线和SPI两类 SD卡使用卡内智能控制模块进行FLASH操作控制 包括协议 安全算法 数据存取 ECC算法 缺陷处理和分析 电源管
  • Linux驱动_多点电容触摸

    一丶Linux下多点电容触摸驱动框架 电容触摸屏IC是FT5426 为IIC协议芯片 因此需要编写IIC驱动 触摸IC会发出中断信号 并在中断服务函数中上报信息 因此需要编写中断框架 触摸屏向Linux内核上报的信息都属于Input子系统
  • Kmalloc

    Kmalloc内存分配和malloc相似 除非被阻塞否则他执行的速度非常快 而且不对获得空间清零 Flags参数 include
  • 最全的交叉编译Makefile讲解

    最近正在搞交叉编译 参考很多博客 学习了一下Makefile的编写 记录一下Makefile内代码是什么意思 代码如下 简单的hello ko的makefile ifneq KERNELRELEASE obj m hello o else

随机推荐

  • DQL基础查询

    一 基础查询 1 语法 select 查询列表 from 表名 select后面跟的查询列表 可以有多个部分组成 中间用逗号隔开 例如 select 字段1 字段2 表达式 from 表 2 特点 查询的结果集是一个虚拟表 3 执行顺序 f
  • 循环神经网络RNN论文解读

    版权声明 本文为CSDN博主 了不起的赵队 的原创文章 遵循CC 4 0 by sa版权协议 转载请附上原文出处链接及本声明 原文链接 https blog csdn net zhaojc1995 article details 80572
  • Linux网络和安全:配置、远程访问与防御指南

    文章目录 Linux 网络和安全 引言 网络配置 IP地址配置 配置网络接口 防火墙设置 安全性加强 Linux网络配置及端口管理 网络配置命令 端口管理 防火墙和安全性设置 防火墙管理工具 安全性设置 Linux远程访问技术 SSH和VP
  • leetcode Path Sum

    Definition for a binary tree node struct TreeNode int val TreeNode left TreeNode right TreeNode int x val x left NULL ri
  • 基于B/S的网上零食销售系统的设计与实现(附:源码 论文 Sql文件)

    摘要 本文介绍了网上零食销售系统的整个开发过程 采用国内认准的B2C商城建站系统模式 并按照现有的购物系统的现状而设计开发的网络买卖平台 本文主要阐述的了整个系统的完成过程 模拟了一个具备卖家 买家和管理员的网络交易系统 而本平台对不同的用
  • 若依打包上线前后端配置

    1 后台访问打包设置 vue config js 文件中需要修改代理地址 devServer host 0 0 0 0 host 188 188 188 64 port port open true proxy detail https c
  • STM32进入STOP模式并唤醒实验总结

    项目需求 需要实现设备低功耗功能 实现过程中遇到几个问题 以此记录总结 stm32f103ret6 问题一 执行PWR EnterSTOPMode PWR Regulator LowPower PWR STOPEntry WFI 后 程序继
  • 进程间通信:比较脸熟的ftok()

    老师在讲这个函数时没有什么感觉 当我做了试验后 其实也没有什么感觉 就是当我在改进实验的时候 问题就来了 就在ftok 这出的的错误 不过先不去了解它的作用来先说说为什么要用它 共享内存 消息队列 信号量它们三个都是找一个中间介质 来进行通
  • OpenSSL:调用OpenSSL实现SHA算法

    安装 libssl dev sudo apt get install libssl dev example c include
  • 用python实现打字练习网站第一

    一 简介 在在线打字测试 dazi kukuw com 网站中 本人kpm一直不高 想着是否能用python写个脚本来改善下 于是选择用python的Selenium来实现 也对其中遇到一些常见问题进行解决 二 正文 1 首先 webdri
  • Notepad++安装NppFTP的和NppFTP的使用

    1 NotPad 插件的安装 NotPad 提供了很多方便的插件以供我们使用 没有的插件需要我们自己安装 安装分为手动安装和自动安装 1 gt 自动安装 选择需要的插件安装即可 安装完成就可以看见所安装的插件 如果在线不能安装就需要手动安装
  • python进阶篇

    想必看到这篇文章的朋友都已经有了一定的基础 已经能够理解python的语法 库的使用 循环遍历以及抛出异常 此时的我们已经拥有了解决问题的能力 但可能自己写不出代码 就像我们学数学 前面的低中档题可能不在话下 所向披靡 但一遇到压轴题就歇菜
  • ER模型详解-陈氏模型

    转载自 http blog 163 com magicc love blog static 18585366220142125836878 Entity Relationship Model ER模型 实体关系模型 1976年Peter C
  • thinkphp 生成runtime.php,彻底关闭ThinkPHP日志功能 关闭Runtime中的日志方法

    彻底关闭ThinkPHP日志功能 从ThinkPHP的核心文件里寻找入口 直接贴上代码 该配置文件位置为 ThinkPHP Conf debug php 保存 好了 2 将LOG LEVEL允许记录的日志级别设置为空 则不会记录日志 3 关
  • zabbix详解(二)—— zabbix故障自愈

    今天继续给大家介绍Linux运维相关知识 本文主要内容是zabbix的故障自愈 在上文zabbix的使用中 我们自定义了一个可以监控85端口jar服务的监控项 今天 我们就尝试正式使用该监控项 并定义相对应的触发器 最终实现邮件报警功能 及
  • Microsoft NNI入门

    GiantPandaCV导语 Neural Network Intelligence 是一个工具包 可以有效帮助用户设计并调优汲取学习模型的神经网络架构 以及超参数 具有易于使用 可扩展 灵活 高效的特点 本文主要讲NNI基础的概念以及一个
  • C++创建对象时报错:引发了异常: 读取访问权限冲突。 p2.m_Height 是 nullptr。

    未修改前的类代码如上 Person5类在test09 函数中创建p1对象 报错信息如下 报错信息指向第38行代码 解决方法 将Person5类中的拷贝构造函数注释掉即可编译通过 引发了异常 读取访问权限冲突 p2 m Height 是 nu
  • 简单说说多益笔试

    一 选择题 考了spring java虚拟机 二 填空题 忘了 三 简答题 1 面向对象三个特性 2 三次握手 四次握手 3 数据库操作 四 编程题 1 数学题 知道首项 前项与后项的差 2 偶数二进制数反转 3 一堆数中找出等于s的个数
  • 使用Kotlin + JetPack 对旧项目进行MVVM改造

    介绍 本次改造的项目地址为 https github com stevenwsg XSYBBS 这个项目是两年前在学校写的 当初写的时候比较赶时间 一直堆业务功能 没有考虑项目结构 写了很多重复代码 最近在看Kotlin协程和JetPack
  • 十九、Linux驱动之虚拟网卡驱动

    1 基本概念 网络设备是完成用户数据包在网络媒介上发送和接收的设备 它将上层协议传递下来的数据包以特定的媒介访问控制方式进行发送 并将接收到的数据包传递给上层协议 与字符设备和块设备不同 网络设备并不对应于 dev目录下的文件 应用程序最终