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.pswp.cn/diannao/75555.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/75555.shtml
英文地址,請注明出處:http://en.pswp.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…