Linux内核NIC网卡驱动实战案例分析

以下Linux 内核模块实现了一个虚拟网络设备驱动程序,其作用和意义如下:


1. 作用

(1)创建虚拟网络设备对
  • 驱动程序动态创建了两个虚拟网络设备(nic_dev[0]nic_dev[1]),模拟物理网卡的功能。这两个设备可以像真实网卡一样配置 IP 地址、启用 / 禁用接口,并参与网络通信。
(2)模拟数据包的环回传输
  • 当数据包通过其中一个虚拟设备发送时(如eth0),驱动程序的nic_hw_xmit函数会直接将数据包传递给另一个虚拟设备(如eth1)的接收函数nic_rx,实现数据包在两个虚拟设备之间的 “环回” 传输。这种模拟避免了对物理网络硬件的依赖。
(3)提供网络协议栈接口
  • 驱动程序实现了网络设备的核心操作(如打开、关闭、发送数据包、验证 MAC 地址等),通过net_device_ops结构体与 Linux 内核网络协议栈无缝对接。这使得用户空间的网络工具(如pingifconfig)可以像操作真实网卡一样操作虚拟设备。
(4)支持基本网络功能
  • 驱动程序支持设置 MAC 地址、修改 MTU、校验和计算等基本网络功能,能够满足简单网络通信的需求。

2. 意义

(1)简化网络开发与测试
  • 虚拟网络设备为开发者提供了一个无需物理硬件的测试环境。例如,可以在同一台主机上通过这两个虚拟设备测试网络协议(如 IP、TCP)的实现,验证数据包的路由、转发和处理逻辑。
(2)降低开发成本
  • 无需真实网卡和网络环境,减少了硬件依赖,降低了开发和调试的成本。开发者可以在隔离的虚拟环境中复现网络问题,提高开发效率。
(3)演示网络驱动原理
  • 驱动程序的代码结构清晰,展示了 Linux 内核网络驱动的基本框架(如设备注册、数据包收发、统计信息维护等),适合作为学习网络驱动开发的示例。
(4)支持特殊网络场景
  • 虚拟设备对可以用于模拟网桥、隧道或其他虚拟网络拓扑,满足特定场景下的网络需求(如容器网络、网络虚拟化)。

一、nic.c

#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/inetdevice.h>
#include <linux/ip.h>
#include <linux/skbuff.h>MODULE_AUTHOR("jerry");
MODULE_DESCRIPTION("Kernel module for nic");
MODULE_LICENSE("GPL");//定义以太网帧的最大缓冲区大小,用于驱动中发送(TX)和接收(RX)缓冲区的内存分配。标准以太网帧的最大长度为 1518 字节(14 字节头部 + 1500 字节数据 + 4 字节 FCS 校验)
//但在某些场景下(如包含 VLAN 标签、QinQ 封装或其他扩展头部),以太网帧的总长度会超过 1518 字节。
#define MAX_ETH_FRAME_SIZE 1792
//定义调试信息输出的默认控制位掩码,用于启用或禁用不同级别的日志输出,这里所有 16 位均为 1,表示 启用所有调试信息
#define DEF_MSG_ENABLE 0xffffstruct nic_priv {//发送的数据放在tx里unsigned char *tx_buf;unsigned int tx_len;unsigned char *rx_buf;unsigned int rx_len;u32 msg_enable;
};static struct net_device *nic_dev[2];static int nic_open(struct net_device *dev);
static int nic_stop(struct net_device *dev);
static netdev_tx_t nic_start_xmit(struct sk_buff *skb, struct net_device *dev);
static int nic_validate_addr(struct net_device *dev);
static int nic_change_mtu(struct net_device *dev, int new_mtu);
static int nic_set_mac_addr(struct net_device *dev, void *addr);//这个 dump 函数的功能是将缓冲区 buffer 中的以太网头部、IP 头部以及负载的前 4 个字节以十六进制字符串的形式打印出来,用于调试网络数据包的内容。
static void dump(unsigned char *buffer){   //参数 unsigned char *buffer:指向要转储(打印)的数据包缓冲区,通常包含以太网帧、IP 数据包等数据。unsigned char *p;  //用于操作字符数组 sbuf 的指针,指向当前字符串拼接的位置。//每个字节需要用 2 个十六进制字符表示(例如 0A),因此缓冲区大小为 2 * (以太网头部长度 + IP 头部长度)。unsigned char sbuf[2*(sizeof(struct ethhdr) + sizeof(struct iphdr))];int i;//将指针 p 指向 sbuf 的起始位置,准备开始拼接字符串p = sbuf;//打印以太网头部(Ethernet Header)for (i = 0; i < sizeof(struct ethhdr); i++) {//将 buffer 中第 i 个字节格式化为两位大写十六进制字符串(例如 0A),并拼接到 sbuf 中p += sprintf(p, "%02X", buffer[i]);}printk("eth %s\n", sbuf);//打印 IP 头部(IP Header)p = sbuf;for (i = 0; i < sizeof(struct iphdr); i++) {p += sprintf(p, "%02X", buffer[sizeof(struct ethhdr) + i]);}printk("iph %s\n", sbuf);//打印负载前 4 个字节(Payload)p = sbuf;for (i = 0; i < 4; i++) {p += sprintf(p, "%02X", buffer[sizeof(struct ethhdr) + sizeof(struct iphdr) + i]);}printk("payload %s\n", sbuf);
}//nic_rx 函数的主要功能是处理网络设备接收到的数据包。它会为接收到的数据包分配一个 sk_buff(socket buffer)结构体,
//将数据包内容复制到 sk_buff 中,设置 sk_buff 的相关属性,更新网络设备的统计信息,最后将 sk_buff 传递给上层网络协议栈进行进一步处理
/*
struct net_device *dev:指向网络设备结构体 net_device 的指针,代表接收数据包的网络设备。通过这个指针可以访问该网络设备的各种属性和操作函数。
int len:表示接收到的数据包的长度,以字节为单位。
unsigned char *buf:指向接收到的数据包数据的指针,存储着实际的数据包内容。
*/
static void nic_rx(struct net_device *dev,int len,unsigned char *buf){//存储接收到的数据包struct sk_buff *skb;struct nic_priv *priv = netdev_priv(dev);netif_info(priv, hw, dev, "%s(#%d), rx:%d\n",__func__, __LINE__, len);//调用 dev_alloc_skb 函数为接收到的数据包分配一个 sk_buff 结构体,分配的大小为 len + 2 字节,多分配 2 字节可能是为了预留一些空间用于后续操作skb = dev_alloc_skb(len + 2);if (!skb) {netif_err(priv, rx_err, dev,"%s(#%d), rx: low on mem - packet dropped\n",__func__, __LINE__);dev->stats.rx_dropped++;return;}skb_reserve(skb, 2);//此时此刻网卡接收到的数据已经从buf复制到skb中,包括协议类型memcpy(skb_put(skb, len), buf, len);skb->dev = dev;//但是这里解析并赋值是为了将协议类型单独提出来,防止每次都需要解析skb->protocol = eth_type_trans(skb, dev);//表示不需要对该数据包进行校验和计算skb->ip_summed = CHECKSUM_UNNECESSARY;//将网络设备的统计信息中的接收数据包数量加 1dev->stats.rx_packets++;//将网络设备的统计信息中的接收字节数增加 lendev->stats.rx_bytes += len;//调用 netif_rx 函数将处理好的 sk_buff 传递给上层网络协议栈进行进一步处理,比如 IP 层、TCP 层等netif_rx(skb);
}//当使用ifconfig eth2 192.168.186.138 up 的时候会调用到这个函数
//该命令是:为指定的网络接口(eth2)分配一个静态的 IPv4 地址(192.168.186.138),并且激活该网络接口
static int nic_open(struct net_device *dev) {struct nic_priv *priv = netdev_priv(dev);priv->tx_buf = kmalloc(MAX_ETH_FRAME_SIZE, GFP_KERNEL);if (!priv->tx_buf) {return -ENOMEM;}priv->rx_buf = kmalloc(MAX_ETH_FRAME_SIZE, GFP_KERNEL);if (!priv->rx_buf) {kfree(priv->tx_buf); // 释放已分配的 tx_bufreturn -ENOMEM;}netif_start_queue(dev);return 0;
}//ifconfig eth2 down
int nic_stop(struct net_device *dev){struct nic_priv *priv = netdev_priv(dev);kfree(priv->tx_buf);kfree(priv->rx_buf);netif_stop_queue(dev);return 0;
}//nic_hw_xmit 函数的主要功能是模拟网络设备的硬件传输过程。模拟发送(不是真的发送只是组织好数据包,并直接自己接收),目的是降低成本,便于调试等
//重新计算 IP 头部校验和,更新设备的发送统计信息,最后将修改后的数据包模拟为接收到的数据包,调用 nic_rx 函数进行处理。
static void nic_hw_xmit(struct net_device *dev) {struct nic_priv *priv = netdev_priv(dev);//声明一个指向 iphdr 结构体的指针 iph,用于指向 IP 头部struct iphdr *iph;//声明两个指向 32 位无符号整数的指针 saddr 和 daddr,分别用于存储源 IP 地址和目的 IP 地址u32 *saddr, *daddr;//检查发送缓冲区中的数据包长度 priv->tx_len 是否小于以太网头部长度和 IP 头部长度之和。if (priv->tx_len < sizeof(struct ethhdr) + sizeof(struct iphdr)) {netif_info(priv, hw, dev, "%s(#%d), too short\n",__func__, __LINE__);return ;}//打印信息dump(priv->tx_buf);iph = (struct iphdr*)(priv->tx_buf + sizeof(struct ethhdr));saddr = &iph->saddr;daddr = &iph->daddr;netif_info(priv, hw, dev, "%s(#%d), orig, src:%pI4, dst:%pI4, len:%d\n",__func__, __LINE__, saddr, daddr, priv->tx_len);//将 IP 头部的校验和字段 iph->check 置为 0iph->check = 0;//调用 ip_fast_csum 函数重新计算 IP 头部的校验和,并将结果赋值给 iph->checkiph->check = ip_fast_csum((unsigned char*)iph, iph->ihl);//dev->stats.tx_packets ++;dev->stats.tx_packets ++;//dev->stats.tx_bytes += priv->tx_len;dev->stats.tx_bytes += priv->tx_len;//调用 nic_rx 函数,将修改后的数据包模拟为接收到的数据包,传递给另一个网络设备进行处理nic_rx(nic_dev[(dev == nic_dev[0] ? 1 : 0)], priv->tx_len, priv->tx_buf);
}//该函数是网络设备驱动的核心发送函数,负责将内核传递的 skb 数据包转换为硬件可发送的格式,并触发实际的发送操作
/*
struct sk_buff *skb:套接字缓冲区(Socket Buffer),包含待发送的数据包。
struct net_device *dev:当前网络设备结构体,代表数据包要从哪个设备发送。
*/
netdev_tx_t nic_start_xmit(struct sk_buff *skb,struct net_device *dev){struct nic_priv *priv = netdev_priv(dev);netif_info(priv, drv, dev, "%s(#%d), orig, src:%pI4, dst:%pI4\n",__func__, __LINE__, &(ip_hdr(skb)->saddr), &(ip_hdr(skb)->daddr));priv->tx_len = skb->len;if (likely(priv->tx_len < MAX_ETH_FRAME_SIZE)) {if (priv->tx_len < ETH_ZLEN) {memset(priv->tx_buf, 0, ETH_ZLEN);priv->tx_len = ETH_ZLEN;}//函数将 skb 中的数据复制到驱动的发送缓冲区 priv->tx_buf,并计算硬件校验和(如 CRC)。这一步是为了让硬件可以直接发送数据,无需再次计算校验和skb_copy_and_csum_dev(skb, priv->tx_buf);//数据包数据已复制到 tx_buf,不再需要 skb,调用 dev_kfree_skb_any 释放 skb 内存,避免内存泄漏dev_kfree_skb_any(skb);}else {  //如果数据包长度超过 MAX_ETH_FRAME_SIZE,直接释放 skb,增加设备统计中的发送丢弃计数(tx_dropped),并返回 NETDEV_TX_OKdev_kfree_skb_any(skb);dev->stats.tx_dropped++;return NETDEV_TX_OK;}//调用模拟的发送函数nic_hw_xmit(dev);return NETDEV_TX_OK;
}//设置网络设备的 MAC 地址
static int nic_set_mac_addr(struct net_device *dev, void *addr) {// 获取设备的私有数据结构指针struct nic_priv *priv = netdev_priv(dev);// 打印调试信息:函数名、行号、私有数据指针netif_info(priv, drv, dev, "%s(#%d), priv:%p\n", __func__, __LINE__, priv);// 调用内核的通用以太网 MAC 地址设置函数return eth_mac_addr(dev, addr);
}//验证网络设备的 MAC 地址是否有效
int nic_validate_addr(struct net_device *dev){// 获取设备的私有数据结构指针struct nic_priv *priv = netdev_priv(dev);// 打印调试信息:函数名、行号、私有数据指针netif_info(priv, drv, dev, "%s(#%d), priv:%p\n", __func__, __LINE__, priv);// 调用内核的通用以太网 MAC 地址验证函数return eth_validate_addr(dev);
}//修改网络设备的 MTU(Maximum Transmission Unit,最大传输单元)
static int nic_change_mtu(struct net_device *dev, int new_mtu) {struct nic_priv *priv = netdev_priv(dev);netif_info(priv, drv, dev, "%s(#%d), priv:%p, mtu%d\n",__func__, __LINE__, priv, new_mtu);// 直接设置新的MTU值并返回0(成功)dev->mtu = new_mtu;return 0;
}netmap,将网卡内容映射到内存中,可以从用户空间直接拿到数据//为网络数据包创建以太网头部,包含了源 MAC 地址、目的 MAC 地址以及协议类型等信息
static int nic_header_create (struct sk_buff *skb, struct net_device *dev,unsigned short type, const void *daddr,const void *saddr, unsigned int len) {/*struct sk_buff *skb:指向 sk_buff 结构体的指针,sk_buff 是 Linux 内核中用于存储网络数据包的结构体,它包含了数据包的数据和相关的元信息。struct net_device *dev:指向 net_device 结构体的指针,代表当前发送数据包的网络设备。unsigned short type:表示以太网帧的协议类型,例如 ETH_P_IP 表示 IPv4 协议,ETH_P_IPV6 表示 IPv6 协议。const void *daddr:指向目的 MAC 地址的指针,如果为 NULL,则需要使用其他默认地址。const void *saddr:指向源 MAC 地址的指针,如果为 NULL,则使用当前网络设备的 MAC 地址。unsigned int len:数据包的长度。    */struct nic_priv *priv = netdev_priv(dev);//将预留空间的起始地址强制转换为 struct ethhdr 类型的指针,并赋值给 eth 指针,这样就可以通过 eth 指针来操作以太网头部struct ethhdr *eth = (struct ethhdr*)skb_push(skb, ETH_HLEN);struct net_device *dst_netdev;//输出当前函数名和行号的调试信息。其中 priv 是网络设备的私有数据,drv 表示调试信息的类别,dev 是当前网络设备,__func__ 是当前函数名,__LINE__ 是当前行号netif_info(priv, drv, dev, "%s(#%d)\n",__func__, __LINE__);//根据当前网络设备 dev 来确定目的网络设备。如果 dev 是 nic_dev[0],则目的网络设备是 nic_dev[1];反之,如果 dev 是 nic_dev[1],则目的网络设备是 nic_dev[0]dst_netdev = nic_dev[(dev == nic_dev[0] ? 1 : 0)];//将传入的协议类型 type 转换为网络字节序后,赋值给以太网头部的 h_proto 字段,表示该以太网帧所承载的协议类型eth->h_proto = htons(type);//复制源MAC地址和目的MAC地址memcpy(eth->h_source, saddr ? saddr : dev->dev_addr, dev->addr_len);memcpy(eth->h_dest, dst_netdev->dev_addr, dst_netdev->addr_len);//返回当前网络设备的硬件头部长度,通常为 14 字节。这个返回值可以让调用者知道添加的以太网头部的长度return dev->hard_header_len;
}static const struct header_ops nic_header_ops = {.create = nic_header_create,
};static struct net_device_ops nic_device_ops = {.ndo_open = nic_open,.ndo_stop = nic_stop,.ndo_start_xmit = nic_start_xmit,.ndo_set_mac_address = nic_set_mac_addr,.ndo_validate_addr = nic_validate_addr,.ndo_change_mtu = nic_change_mtu,
};static struct net_device *nic_alloc_netdev(void){//alloc_etherdev:内核函数,用于分配一个以太网设备(struct net_device)的内存,参数是私有数据区域的大小struct net_device *netdev = alloc_etherdev(sizeof(struct nic_priv));if (!netdev) {pr_err("%s(#%d): alloc dev failed", __func__, __LINE__);return NULL;}//设置随机 MAC 地址eth_hw_addr_random(netdev);//绑定网络设备操作函数,:将设备的操作函数表绑定到新分配的网络设备,定义其行为(例如如何处理数据包发送、打开/关闭设备等)netdev->netdev_ops = &nic_device_ops;//表示该设备需要 ARP(地址解析协议)netdev->flags &= ~IFF_NOARP;//启用硬件校验和功能,告诉内核网络协议栈,设备可以自行处理 IP/TCP/UDP 等协议的校验和计算,无需软件参与netdev->features |= NETIF_F_HW_CSUM;//设置头部操作函数,nic_header_create 用于在发送数据包时构造以太网头部return netdev;
}//加载设备
static int __init nic_init(void){int ret = 0;struct nic_priv *priv;pr_info("%s(#%d): install module\n", __func__, __LINE__);//1.malloc net_devicenic_dev[0] = nic_alloc_netdev();if (!nic_dev[0]) {printk("%s(#%d): alloc netdev[0] failed\n", __func__, __LINE__);return -ENOMEM;}nic_dev[1] = nic_alloc_netdev();if (!nic_dev[1]) {printk("%s(#%d): alloc netdev[1] failed\n", __func__, __LINE__);goto alloc_2nd_failed;}//2.registerret = register_netdev(nic_dev[0]);if (ret) {printk("%s(#%d): reg net driver failed. ret: %d\n", __func__, __LINE__, ret);goto reg1_failed;}ret = register_netdev(nic_dev[1]);if (ret) {printk("%s(#%d): reg net driver failed. ret:%d\n", __func__, __LINE__, ret);goto reg2_failed;}//初始化两个网络设备的私有数据结构,具体来说是为每个设备的 msg_enable 字段赋值为 DEF_MSG_ENABLE(即 0xffff)/*在 Linux 内核里,struct net_device 结构体代表一个网络设备。为了让驱动程序能够存储与特定网络设备相关的私有数据,内核采用了一种特殊的存储方式。当使用 alloc_etherdev 等函数分配网络设备时,会额外分配一块内存空间用于存储私有数据,这块空间紧跟在 struct net_device 结构体之后。*/priv = netdev_priv(nic_dev[0]);      // 获取 nic_dev[0] 的私有数据指针priv->msg_enable = DEF_MSG_ENABLE;   // 设置 msg_enable 为 0xffffpriv = netdev_priv(nic_dev[1]);      // 获取 nic_dev[1] 的私有数据指针priv->msg_enable = DEF_MSG_ENABLE;   // 设置 msg_enable 为 0xffffreturn 0;reg2_failed:unregister_netdev(nic_dev[0]);
reg1_failed:free_netdev(nic_dev[1]);
alloc_2nd_failed:free_netdev(nic_dev[0]);return ret;
}//卸载设备
static void __exit nic_exit(void){int i = 0;pr_info("%s(#%d): remove module\n", __func__, __LINE__);for (i = 0;i < ARRAY_SIZE(nic_dev);i ++) {unregister_netdev(nic_dev[i]);free_netdev(nic_dev[i]);}
}module_init(nic_init);
module_exit(nic_exit);

二、Makefile

#!/bin/bashccflags_y += -O2ifneq ($(KERNELRELEASE),)
obj-m := nic.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)default:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules 
endifclean:rm -rf *.o *.ko *.mod.cdepend .depend dep:$(CC)  -M *.c > .depend 

三、编译插入模块

 1.make

2.insmod插入模块

四、运行

1.查看网卡 ifconfig -a ,发现新增两个设备eth0和eth1

2.配置虚拟网卡的ip

ip link set eth0 down
ip link set eth0 upip link set eth1 down
ip link set eth1 upip addr add 192.168.1.1/24 dev eth0
ip addr add 192.168.1.2/24 dev eth1

3.再次查看网卡

4.执行ping操作

        ping 192.168.1.1
        ping 192.168.1.2  

        

5.卸载模块rmmod

        rmmod nic.ko

五、心得解读

        在使用这两个虚拟网卡执行ping命令(比如ping 192.168.1.2,这个是eth1),首先会进行目标ip匹配,此时eth(192.168.1.1/24)和eth1(192.168.1.2/24)都符合,此时路由决策都匹配,则会使用先启用的接口或者默认主接口(这里是eth0),因此选择eth0发送数据包。

        当 eth0 发送 ICMP 请求到 192.168.1.2(即 eth1 的 IP)时,驱动通过 nic_hw_xmit 直接调用 eth1 的 nic_rx,模拟 eth1 接收到该数据包。nic_rx 提交的 数据包内容 是 eth0 发送的 ICMP 请求,内核协议栈识别到该数据包是发送给 eth1 的本地 IP,会触发 ICMP 响应生成(Echo Reply)。在nic_rx函数的最后使用netif_rx(skb);提交给内核协议栈处理,内核协议栈会生成响应并回发,具体流程如下:
(1)ICMP 请求阶段

// eth0 发送请求
nic_start_xmit(eth0) -> nic_hw_xmit(eth0) -> nic_rx(eth1)// eth1 接收请求
nic_rx(eth1) -> 协议栈生成响应 -> nic_start_xmit(eth1) -> nic_hw_xmit(eth1) -> nic_rx(eth0)
(2)ICMP 响应阶段
// eth1 发送响应
nic_start_xmit(eth1) -> nic_hw_xmit(eth1) -> nic_rx(eth0)// eth0 接收响应
nic_rx(eth0) -> 协议栈处理 -> 用户空间 `ping` 进程收到响应。

0voice · GitHub

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/75555.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Trae初使用心得(Java后端)

1.前提 2025年3月3日&#xff0c;字节跳动正式官宣“中国首个 AI 原生集成开发环境&#xff08;AI IDE&#xff09;”Trae 国内版正式上线&#xff0c;由于之前项目的原因小编没有及时的去体验&#xff0c;这几日专门抽空去体验了一下感觉还算可以。 2.特点 Trade重在可以白嫖…

[项目]基于FreeRTOS的STM32四轴飞行器: 十二.角速度加速度滤波

基于FreeRTOS的STM32四轴飞行器: 十二.滤波 一.滤波介绍二.对角速度进行一阶低通滤波三.对加速度进行卡尔曼滤波 一.滤波介绍 模拟信号滤波&#xff1a; 最常用的滤波方法可以在信号和地之间并联一个电容&#xff0c;因为电容通交隔直&#xff0c;信号突变会给电容充电&#x…

UNIX网络编程笔记:TCP、UDP、SCTP编程的区别

一、核心特性对比 特性TCPUDPSCTP连接方式面向连接&#xff08;三次握手&#xff09;无连接面向连接&#xff08;四次握手&#xff09;可靠性可靠传输&#xff08;重传、确认机制&#xff09;不可靠传输可靠传输&#xff08;多路径冗余&#xff09;传输单位字节流&#xff08;…

Python爬虫异常处理:自动跳过无效URL

爬虫在运行过程中常常会遇到各种异常情况&#xff0c;其中无效URL的出现是较为常见的问题之一。无效URL可能导致爬虫程序崩溃或陷入无限等待状态&#xff0c;严重影响爬虫的稳定性和效率。因此&#xff0c;掌握如何在Python爬虫中自动跳过无效URL的异常处理技巧&#xff0c;对于…

C++语法学习的主要内容

科技特长生方向&#xff0c;主要学习的内容为 一&#xff0c;《C语法》 二&#xff0c;《数据结构》 三&#xff0c;《算法》 四&#xff0c;《计算机基础知识》 五&#xff0c;《初高中的数学知识》 其中&#xff0c;《C语法》学习的主要内容如下: 1,cout输出语句和键盘…

3、孪生网络/连体网络(Siamese Network)

目的: 用Siamese Network (孪生网络) 解决Few-shot learning (小样本学习)。 Siamese Network并不是Meta Learning最好的方法, 但是通过学习Siamese Network,非常有助于理解其他Meta Learning算法。 这里介绍了两种方法:Siamese Network (孪生网络)、Trplet Loss Siam…

从零构建大语言模型全栈开发指南:第二部分:模型架构设计与实现-2.2.1从零编写类GPT-2模型架构(规划模块与代码组织)

👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 2.2.1 从零编写类GPT-2模型架构(规划模块与代码组织)1. 模型架构设计规划1.1 架构核心组件2. 模块化设计实现2.1 输入处理模块2.1.1 分词与嵌入2.1.2 位置编码2.2 解码块设计2.2.1 多头注意力子层2.2.…

消息队列(Kafka及RocketMQ等对比联系)

目录 消息队列 一、为什么使用消息队列&#xff1f;消息队列有什么优点/缺点&#xff1f;介绍下Kafka、ActiveMQ、RabbitMQ、RocketMQ有什么优点缺点&#xff0c;如何取舍&#xff1f; 1.公司业务场景是什么&#xff0c;这个业务场景有什么挑战&#xff0c;如果不用MQ有什么麻…

Android 13系统定制实战:基于系统属性的音量键动态屏蔽方案解析

1. 需求背景与实现原理 在Android 13系统定制化开发中&#xff0c;需根据设备场景动态屏蔽音量键&#xff08;VOLUME_UP/VOLUME_DOWN&#xff09;功能。其核心诉求是通过系统属性&#xff08;persist.sys.roco.volumekey.enable&#xff09;控制音量键的响应逻辑&#xff0c;确…

解锁DeepSeek潜能:Docker+Ollama打造本地大模型部署新范式

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《深度探秘&#xff1a;AI界的007》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、什么是Docker 2、什么是Ollama 二、准备工作 1、操…

uv - Guides 指南 [官方文档翻译]

文章目录 Guides 指南概述安装 Python入门安装特定版本重新安装 Python查看 Python 安装自动 Python 下载使用现有的 Python 版本 运行脚本在没有依赖的情况下运行脚本运行带有依赖的脚本创建一个Python脚本声明脚本依赖使用替代包索引锁定依赖提高可重复性使用不同的 Python 版…

根据模板将 Excel 明细数据生成 PDF 文档 | PDF实现邮件合并功能

在日常办公中&#xff0c;我们常常会面临这样的需求&#xff1a;依据特定的模板&#xff0c;把 Excel 里的每一条数据转化为单独的 PDF 文档&#xff0c;且这些 PDF 文档中的部分内容会根据 Excel 数据动态变化。这一功能不仅能高效完成任务&#xff0c;还支持图片的动态替换&a…

apache安装脚本使用shell建立

注意防火墙&#xff0c;yum&#xff0c;网络连接等 以下是具体的apache安装脚本 #!/bin/bash # Set Apache version to install ## author: yuan # 检查外网连接 echo "检查外网连接..." ping www.baidu.com -c 3 > /dev/null 2>&1 if [ $? -eq 0 ]; …

wordpress主题使用中常见错误汇总

在WordPress主题的使用过程中&#xff0c;开发者可能会遇到各种问题。下面是一些常见错误的汇总&#xff0c;并给出了相应的解决方法。 一、主题安装与激活错误 无法激活主题&#xff1a;检查主题文件是否完整&#xff0c;以及是否符合WordPress的主题规范。 激活主题后出现…

如何设计一个订单号生成服务?应该考虑那些问题?

如何设计一个订单号生成服务&#xff1f;应该考虑那些问题&#xff1f; description: 在高并发的电商系统中&#xff0c;生成全局唯一的订单编号是关键。本文探讨了几种常见的订单编号生成方法&#xff0c;包括UUID、数据库自增、雪花算法和基于Redis的分布式组件&#xff0c;并…

Springboot 集成 Flowable 6.8.0

1. 创建 Spring Boot 项目 通过 Spring Initializr&#xff08;https://start.spring.io/ &#xff09;创建一个基础的 Spring Boot 项目&#xff0c;添加以下依赖&#xff1a; Spring WebSpring Data JPAMySQL DriverLombok&#xff08;可选&#xff0c;用于简化代码&#x…

《TCP/IP网络编程》学习笔记 | Chapter 22:重叠 I/O 模型

《TCP/IP网络编程》学习笔记 | Chapter 22&#xff1a;重叠 I/O 模型 《TCP/IP网络编程》学习笔记 | Chapter 22&#xff1a;重叠 I/O 模型理解重叠 I/O 模型重叠 I/O本章讨论的重叠 I/O 的重点不在于 I/O 创建重叠 I/O 套接字执行重叠 I/O 的 WSASend 函数进行重叠 I/O 的 WSA…

搭建Redis哨兵集群

停掉现有的redis集群 因为这篇文章我是在 搭建完redis主从集群之后写的&#xff0c;如果要是没有搭建过这些&#xff0c;可以直接略过。要是从我上一篇 搭建redis主从集群过来的&#xff0c;可以执行下。 docker compose down 查找下redis相关进程 ps -ef | grep redis 可以看…

MySQL中,聚集索引和非聚集索引到底有什么区别?

文章目录 1. 数据存储方式2. 索引结构3. 查询效率4. 索引数量5. 适用场景6. 示例说明7. 总结 在MySQL中&#xff0c;聚集索引和非聚集索引&#xff08;也称二级索引&#xff09;的区别主要体现在数据存储方式、索引结构和查询效率等方面。以下是详细对比&#xff1a; 1. 数据存…

看 MySQL InnoDB 和 BoltDB 的事务实现

BoltDB 事务实现 BoltDB 支持多读单写方式的并发级别 事务操作会锁表 它的 MVCC 为 2 个版本&#xff0c;当前版本和正在写的版本 多读&#xff1a;可以并发读当前版本 单写&#xff08;串行写&#xff09;&#xff1a;写时拷贝当前 B 树&#xff0c;构建新 B 树&#xff…