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 内核网络协议栈无缝对接。这使得用户空间的网络工具(如ping
、ifconfig
)可以像操作真实网卡一样操作虚拟设备。(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
相关文章:
Linux内核NIC网卡驱动实战案例分析
以下Linux 内核模块实现了一个虚拟网络设备驱动程序,其作用和意义如下: 1. 作用 (1)创建虚拟网络设备对 驱动程序动态创建了两个虚拟网络设备(nic_dev[0]和nic_dev[1]),模拟物理网卡的功能。这两…...
R 基础语法
R 基础语法 引言 R 是一种针对统计计算和图形表示而设计的编程语言和环境。它广泛应用于统计学、生物信息学、数据挖掘等领域。本文将为您介绍 R 语言的基础语法,帮助您快速上手。 R 的基本结构 R 语言的基本结构包括:变量、数据类型、运算符、控制结构、函数等。 变量 …...
JDK 24 发布,新特性解读!
一、版本演进与技术格局新动向 北京时间3月20日,Oracle正式发布Java SE 24。作为继Java 21之后的第三个非LTS版本,其技术革新力度远超预期——共集成24项JEP提案,相当于Java 22(12项)与Java 23(12项&#…...
在 Qt 中,不带参数或整形的参选的信号能够从 std::thread 发送成功,而带枚举离线的信号却发送失败
在 Qt 中,不带参数或整形的参选的信号能够从 std::thread 发送成功,而带枚举离线的信号却发送失败 当信号和槽在不同线程时,默认使用 队列连接(Qt::QueuedConnection),信号会被放入接收线程的事件队列&…...
慧通测控汽车智能座舱测试技术
一、引言 随着科技的飞速发展,汽车正从单纯的交通工具向智能化移动空间转变。智能座舱作为这一转变的核心体现,融合了多种先进技术,为用户带来前所未有的驾驶体验。从简单的信息娱乐系统到高度集成的人机交互、智能驾驶辅助以及车辆状态监测…...
【CSS】CSS 使用全教程
CSS 使用全教程 介绍 CSS(层叠样式表,Cascading Style Sheets)是一种样式表语言,用于描述 HTML 或 XML 文档的布局和外观,它允许开发者将文档的内容结构与样式表现分离,通过定义一系列的样式规则来控制网页…...
【Linux知识】RPM软件包安装命令行详细说明
文章目录 概述安装软件包升级软件包卸载软件包查询软件包信息验证软件包从软件包中提取文件注意事项 概述 rpm(Red Hat Package Manager)是红帽系 Linux 发行版(如 Red Hat、CentOS、Fedora 等)用于管理软件包的工具,…...
SpringBoot3.0不建议使用spring.factories,使用AutoConfiguration.imports新的自动配置方案
文章目录 一、写在前面二、使用imports文件1、使用2、示例比对3、完整示例 参考资料 一、写在前面 spring.factories是一个位于META-INF/目录下的配置文件,它基于Java的SPI(Service Provider Interface)机制的变种实现。 这个文件的主要功能是允许开发者声明接口的…...
c++项目-KV存储-模仿redis实现kv键值对存储的基本功能。
KV存储引擎的技术解析:数组、哈希与红黑树实现及其在网络I/O中的应用。 内容概要:本文档深入介绍了基于数组、哈希表和红黑树的键值存储引擎的设计与实现。文档首先阐述了系统的总体架构与类图关系,之后分别对底层存储结构进行了详细解释&am…...
docker ssh远程连接
目录 操作命令: 确保 SSH 配置允许 root 登录: docker提交: 操作命令: # 进入容器 docker exec -ti lbg04 /bin/bash# 更新包管理并安装 SSH 服务(Ubuntu/Debian 示例) apt-get update apt-get install…...
边缘计算场景下的分布式推理系统架构设计
一、边缘节点推理优化 1.1 模型轻量化技术矩阵 1.2 TensorRT加速配置示例 # 使用TensorRT优化YOLOv8builder trt.Builder(TRT_LOGGER)network builder.create_network()parser trt.OnnxParser(network, TRT_LOGGER)with open("yolov8s.onnx", "rb") a…...
css基础-浮动
一、浮动是什么? 比喻:就像泳池里的救生圈 原始用途:让文字环绕图片(像杂志排版)意外发展:被用来做页面布局(像用救生圈搭浮桥) 二、浮动怎么产生的? 场景还原&#…...
Linux TTY设备汇总
目录 1. tty(终端设备统称) 2. ptm(伪终端主设备)与pts(伪终端从设备) 3. ttys(串行端口终端) 4. ttyACM(USB CDC ACM设备) 5. ttyGS(USB Gadget Serial设备) 主要联系 典型应用场景 TTY_CORE: drivers/tty/tty_io.c:tty_register_driver…...
Android studio组合教程--做出一个类似于QQ的登录页面
之前我们学过了html与Android的开发,以及各种组件的学习,这次我们做一个完整向的登录页面,作为一次大作业。 注意 里面的一图片可以自由发挥,但要注意文件路径保持准确,这里给出参考路径: 背景路径&…...
iPhone 16 Plus :凉凉了
大屏就是生产力,这句话就像思想钢印一样,深入人心。 但苹果用户是个例外,根据内行人的爆料,iPhone 16 Plus 彻底凉凉了,难怪它会是最后一代Plus。 根据知名博主数码闲聊站透露,截止3 月 9 号,i…...
【MySQL报错】:Column count doesn’t match value count at row 1
MySQL报错:Column count doesn’t match value count at row 1 意思是存储的数据与数据库表的字段类型定义不相匹配. 由于类似 insert 语句中,前后列数不等造成的 主要有3个易错点: 要传入表中的字段数和values后面的值的个数不相等。 由于类…...
2025 polarctf春季个人挑战赛web方向wp
来个弹窗 先用最基础的xss弹窗试一下 <script>alert("xss")</script>没有内容,猜测过滤了script,双写绕过一下 <scrscriptipt>alert("xss")</scscriptript>background 查看网页源代码 查看一下js文件 类…...
Midscene.js自然语言驱动的网页自动化全指南
一、概述 网页自动化在数据抓取、UI 测试和业务流程优化中发挥着重要作用。然而,传统工具如 Selenium 和 Puppeteer 要求用户具备编程技能,编写复杂的选择器和脚本维护成本高昂。Midscene.js 通过自然语言接口革新了这一领域,用户只需描述任…...
PDF与Markdown的量子纠缠:一场由VLM导演的文档界奇幻秀
缘起:当格式界的"泰坦尼克号"撞上"黑客帝国" 某个月黑风高的夜晚,在"二进制酒吧"的霓虹灯下: PDF(西装革履地晃着威士忌): “我的每一页都像瑞士手表般精密,连华尔街的秃鹫都为我倾倒!” Markdown(穿着带洞的拖鞋): “得了吧老古董!…...
Spring Boot JSON序列化深度管控:忽略指定字段+Jackson扩展策略破解双向实体循环引用问题
一、JsonIgnore的核心原理与工作机制 1. 注解作用原理 JsonIgnore是Jackson库的核心注解之一,其工作原理基于 Jackson的AnnotationIntrospector机制。在序列化/反序列化过程中,Jackson会扫描Java对象的所有字段和方法上的注解。当检测到JsonIgnore时&a…...
msvcp140.dll是什么文件?修复丢失msvcp140.dll的方法指南
当计算机显示"msvcp140.dll未找到"的报错信息时,这实际反映了Windows系统运行机制中的一个关键环节出现断链。作为Microsoft Visual C可再发行组件包的核心动态链接库,msvcp140.dll承担着程序与系统资源之间的桥梁作用,特别是在处理…...
ES集群的部署
实验步骤 实验目的: 验证ES集群的容错性、扩展性数据分布与查询性能优化。 环境准备 1、准备两台服务器 服务器 1、10.1.1.20 cpu 2核 内存:4G 硬盘100G 2、10.1.1.21 cpu 2核 内存:4G 硬盘100G 2、修改两台静态ip 3、关闭防…...
resetForm() 方法用于重置表单
resetForm() 方法是 Vue.js 中用于重置表单的一个常见操作。下面是对这段代码的详细解析: 1. 代码作用 resetForm() 方法的作用是重置表单,将表单中的所有输入字段恢复到初始状态(通常是清空或恢复到默认值)。 2. 代码解析 re…...
Java后端API限流秘籍:高并发的防护伞与实战指南
目录导航 📜 🛡️ 为什么需要API限流?🧠 主流限流算法大解析👩💻 阿里巴巴的限流实践📏 四大黄金定律🤼 限流策略组合拳🏆 限流场景实战💻 技术实现方案🌟 最佳实践分享📈 结语与展望📚 推荐阅读 1. 🛡️ 为什么需要API限流? 在高并发环境中,未…...
团体协作项目总结Git
使用Git开放时候发现本地, 有些代码并没有被拉取到本地仓库, 又不想再commit一次, 这时候我就想到了 git commit --amend 合并提交 git commit --amend 修改git提交记录用法详解 可以将本次提交记录合并到上一次合并提交 git commit --amendgit rebase -i master^^ // 假设我…...
mysql 入门
1.已经下载过却卸载不干净?注册表清理不到位? 使用greek绿色版 强力卸载,可以一键卸载注册表里的信息。 2.如何启动mysql服务? 以管理员方式启动cmd 输入 net start mysql80 如何停止? net stop mysql80 2.将mysql客…...
1.基于TCP的简单套接字服务器实现
目录 1. TCP通信流程 2. 服务器端的通信流程 2.1 创建用于监听的套接字 2.2 绑定本地IP地址和端口 2.3 设置监听 2.4 等待接受客户端的连接请求 2.5 与客户端进行通信 2.6 客户端连接服务器 3.代码实现 client.cpp server.cpp 运行方式 在本文中,我们将…...
MantisBT在Windows10上安装部署详细步骤
MantisBT 是一款基于 Web 的开源缺陷跟踪系统,以下是在 Windows 10 上安装部署 MantisBT 的详细步骤: 1. 安装必要的环境 MantisBT 是一个基于 PHP 的 Web 应用程序,因此需要安装 Web 服务器(如 Apache)、PHP 和数据…...
zookeepernacoskafka之间的联系
一、ZooKeeper与Kafka的协同工作原理 1. 核心关系:Kafka对ZooKeeper的依赖 在Kafka 2.8版本之前,ZooKeeper是Kafka集群的“大脑”,负责管理集群元数据、协调节点状态和故障恢复。两者的协同主要通过以下关键机制实现: Broker注册…...
【QT】 布局器
参考博客:https://blog.csdn.net/Fdog_/article/details/107522283 目录 布局管理器概念常见的布局管理器及特点🔵QHBoxLayout水平布局🔵QVBoxLayout垂直布局 🔵QGridLayout网格布局 🔵QFormLayout表单布局 QT 高级布…...
力扣45.跳跃游戏
45. 跳跃游戏 II - 力扣(LeetCode) 代码区: #include<vector> class Solution {public:int jump(vector<int>& nums) {int ans[10005] ;memset(ans,1e4,sizeof(ans));ans[0]0;for(int i0;i<nums.size();i){for(int j1;j…...
【蓝桥杯】真题 路径(数论+dp)
思路 求最小公倍数LCM问题很好求,这里看似是求图最短路径,实际上由于只有[i,i21]之间存在路径,所以用线性dp效率更高,当然用bfs,dijstra,floyed也可,毕竟是填空题。 code def gcd(a,b):if a …...
敏捷需求分析之INVEST原则
INVEST原则是什么 INVEST 是用户故事的六个核心标准,由敏捷教练 Bill Wake 提出,用于确保用户故事具备可执行性和价值导向性。 1. I - Independent(独立的) 含义:用户故事应独立于其他故事,避免依赖关系。问题:若故事 A 必须等待故事 B 完成才能开发,会导致进度阻塞。…...
Apache Flink技术原理深入解析:任务执行流程全景图
前言 本文隶属于专栏《大数据技术体系》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见大数据技术体系 思维导图 📌 引言 Apache Flink 作为一款高性能的分布式流处理引擎,其内部执行机制精妙而复杂。本文将…...
UE5小石子阴影在非常近距离才显示的问题
Unreal中采用LandscapeGrass生成的地形,在MovieRenderQueue中渲染时阴影显示距离有问题,在很近的时候才会有影子,怎么解决? 地面上通过grass生成的小石子的阴影只能在很近的时候才能显示出来,需要如下调整 r.Shadow.R…...
WebAssembly实践,性能也有局限性
个人博客原文地址 WebAssembly(简称 wasm) 是一种旨在突破 Web 性能瓶颈的技术方案。它由 W3C 官方推动,并且得到了主流浏览器的广泛支持。它的核心思想是通过运行其他高性能编程语言(比如 C、C、Rust 等)来实现复杂功…...
第一天学爬虫
阅读提示:我今天才开始尝试爬虫,写的不好请见谅。 一、准备工具 requests库:发送HTTP请求并获取网页内容。BeautifulSoup库:解析HTML页面并提取数据。pandas库:保存抓取到的数据到CSV文件中。 二、爬取步骤 发送请求…...
【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit(11)
1.问题描述: 鸿蒙push右侧图表没有正常展示。 解决方案: .jpg格式文件,头信息必须是这个“jpg:ffd8”。 2.问题描述: 安卓端App在开发者平台申请了Android应用的通知消息自分类权益,鸿蒙应用的自分类权…...
Spring WebSecurityCustomizer 的作用
Spring WebSecurityCustomizer 是 Spring Security 框架中用来 自定义 Web 安全配置 的一个接口。 它的主要作用是在开发中我们能够 精细的控制哪些请求会被 Spring Security 完全忽略,不进行任何安全检查和过滤。 我们可以把它想象成是 Spring Security 大门上的一…...
uniapp动态循环表单校验失败:初始值校验
问题现象 💥 在实现动态增减的单价输入表单时(基于uv-form组件),遇到以下诡异现象: <uv-input>的v-model绑定初始值为数字类型时,required规则失效 ❌数字类型与字符串类型校验表现不一致 🔢技术栈背景 🛠️ 框架:Vue3 + uni-appUI库:uv-ui校验方案:计算属…...
线性代数核心概念与NumPy科学计算实战全解析
前言 学习方法: 思维导图,梳理 多记忆,函数名和功能,参数 学会应用,不要钻牛角尖 一、浅解线性代数 1.1标量 标量是一个只有大小没有方向的量。在数学上,标量通常表示为一个普通的数字,如质量…...
如何理解 Apache Iceberg 与湖仓一体(Lakehouse)?
一、什么是湖仓一体(Lakehouse)? 湖仓一体是一种融合了数据湖的灵活存储能力与数据仓库的高效分析功能的现代数据架构。它通过整合两者的优势,解决了传统架构的局限性,为企业数据处理提供了更全面的解决方案。 数据湖…...
若依框架二次开发——若依集成 JSEncrypt 实现密码加密传输方式
文章目录 一、问题场景二、相关技术介绍1. RSA 加密算法2. JSEncrypt三、实现步骤1. 前端加密处理2. 后端解密处理3. 登录逻辑处理四、测试流程1. 前端测试2. 后端测试3. 运行效果五、总结一、问题场景 在 RuoYi 系统中,默认情况下,用户在登录时会将明文密码直接传输到服务器…...
Rust Web 开发新选择:探索 Hyperlane 轻量级 HTTP 服务器框架
Rust Web 开发新选择:探索 Hyperlane 轻量级 HTTP 服务器框架 在 Web 开发领域,Rust 以其高性能和内存安全性逐渐受到关注。而在众多 Web 框架中,hyperlane 作为一款轻量级、高性能的 HTTP 服务器框架,正悄然成为 Rust 生态中的明…...
初识 模版 和 STL
前言 今天简单和大家分享一下C重要的两个内容,经过之前的学习我们已经了解了C的大致语法,接下来就是C相关的库和一些操作了,他们能极大地缩小我们C语言阶段的代码量,让写代码变得轻松起来。 1.关于模版 <1>泛型编程 我们学…...
加新题了,MySQL 8.0 OCP 认证考试 题库更新
MySQL 8.0 OCP 认证考试 题库更新 MySQL 8.0 Database Administrator 考试科目:1Z0-908 近期发现,MySQL OCP认证考试题库发生变化,出现了很多新题,对此,CUUG专门收集整理了最新版本的MySQL考试原题,并会给…...
26考研——树与二叉树_树、森林(5)
408答疑 文章目录 二、树、森林树的基本概念树的定义和特性树的定义树的特性 基本术语树的基本术语和概念祖先、子孙、双亲、孩子、兄弟和堂兄弟结点的层次、度、深度和高度树的度和高度分支结点和叶结点有序树和无序树路径和路径长度 森林的基本术语和概念森林的定义森林与树的…...
26考研——图_图的基本概念(6)
408答疑 文章目录 一、图的基本概念图的定义非空性非线性结构 顶点和边的表示顶点边 有向图 & 无向图有向图有向图 G 1 G_1 G1 的表示 无向图无向图 G 2 G_2 G2 的表示 简单图 & 多重图简单图多重图 顶点的度、入度和出度顶点的度有向图的度 路径、路径长度和回路…...
笔试面试01 c/c++
基础知识 什么是数据结构?请简要描述常见的数据结构类型。 数据结构是组织和存储数据的方式,以便于高效访问和修改。常见的数据结构包括: 数组:固定大小的线性数据结构,支持随机访问。 链表:由节点组成的线…...
2025清华大学:DeepSeek教程全集(PDF+视频精讲,共10份).zip
一、资料列表 第一课:Deepseek基础入门 第二课:DeepSeek赋能职场 第三课:普通人如何抓住DeepSeek红利 第四课:让科研像聊天一样简单 第五课:DeepSeek与AI幻觉 第六课:基于DeepSeek的AI音乐词曲的创造法 第…...