skb_copy_and_csum_dev()函数

本文详细解析了RTL8139网卡驱动的初始化过程,包括请求IRQ、分配内存、重置设备、配置寄存器等关键步骤,并介绍了如何使设备准备好发送数据包。
 void skb_copy_and_csum_dev(const struct sk_buff *skb, u8 *to)
1825 {
1826         __wsum csum;
1827         long csstart;
1828 
1829         if (skb->ip_summed == CHECKSUM_PARTIAL)
1830                 csstart = skb->csum_start - skb_headroom(skb);
1831         else
1832                 csstart = skb_headlen(skb);
1833 
1834         BUG_ON(csstart > skb_headlen(skb));
1835 
1836         skb_copy_from_linear_data(skb, to, csstart);
1837 
1838         csum = 0;
1839         if (csstart != skb->len)
1840                 csum = skb_copy_and_csum_bits(skb, csstart, to + csstart,
1841                                               skb->len - csstart, 0);
1842 
1843         if (skb->ip_summed == CHECKSUM_PARTIAL) {
1844                 long csstuff = csstart + skb->csum_offset;
1845 
1846                 *((__sum16 *)(to + csstuff)) = csum_fold(csum);
1847         }
1848 }



今天还发现了一个特别好的网站,可以去查各类的linux函数


http://lxr.free-electrons.com/ident?v=2.6.35&i=



另外还有一篇文章,介绍乐很多网卡驱动中的函数:

Now, we explain the code in Table 11. The function rtl8139_open starts with requesting the IRQ by calling API request_irq. In this function, weregister the interrupt handler rtl8139_interrupt. This function shall becalled by kernel, whenever the device generates an interrupt. Now, weallocate memory, where outgoing packets reside before being sent onwire. Note that API pci_allocate_consistant returns kernel virtualaddress. The physical address is returned in third argument, which islater used by driver. Also observe that we have allocated memory neededfor all four descriptors. Function rtl8139_init_ring distributes thismemory to four descriptors. Here, we call function rtl8139_hw_start tomake the device ready for transmitting packets. At first, we reset thedevice, so that device shall be in a predictable and known state. Thisis done by writing reset value (described in specification) in CR(Command Register). We wait until the written value is read back, whichmeans device has reset. The next function, barrier ( ), is called to force thekernel to do required memory I/O immediately without doing anyoptimization. Once the device is reset, we enable transmission mode ofthe device by writing transmission enable value in CR. Next, we configure TCR(Transmission Configuration Register). The only thing we are specifying toTCR register is "Max DMA Burst Size per Tx DMA Burst". The rest we leave atdefault values. (See specification for more details.) Now we write theDMAable address of all four descriptors to TSAD (Transmission StartAddress Descriptor) registers. Next, we enable the interrupt, by writingin IMR (Interrupt Mask Register). This register lets us configure theinterrupts; the device will be generating. Last, we callnetif_start_queue to tell the kernel that device is ready. The onlything remaining is writing the rtl8139_interrupt function. For the time being,let's skip this. At this time, the device is ready to sendpackets, but the function to send packets out is missing. (Rememberhard_start_xmit.) So, let's do it.

连接在 这里

struct sk_buff { union { struct { /* These two members must be first. */ struct sk_buff *next; struct sk_buff *prev; union { struct net_device *dev; /* Some protocols might use this space to store information, * while device pointer would be NULL. * UDP receive path is one user. */ unsigned long dev_scratch; }; }; struct rb_node rbnode; /* used in netem, ip4 defrag, and tcp stack */ struct list_head list; }; union { struct sock *sk; int ip_defrag_offset; }; union { ktime_t tstamp; u64 skb_mstamp; }; /* * This is the control buffer. It is free to use for every * layer. Please put your private variables there. If you * want to keep them across layers you have to do a skb_clone() * first. This is owned by whoever has the skb queued ATM. */ char cb[48] __aligned(8); unsigned long _skb_refdst; void (*destructor)(struct sk_buff *skb); #ifdef CONFIG_XFRM struct sec_path *sp; #endif #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) unsigned long _nfct; #endif #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) struct nf_bridge_info *nf_bridge; #endif unsigned int len, data_len; __u16 mac_len, hdr_len; /* Following fields are _not_ copied in __copy_skb_header() * Note that queue_mapping is here mostly to fill a hole. */ __u16 queue_mapping; /* if you move cloned around you also must adapt those constants */ #ifdef __BIG_ENDIAN_BITFIELD #define CLONED_MASK (1 << 7) #else #define CLONED_MASK 1 #endif #define CLONED_OFFSET() offsetof(struct sk_buff, __cloned_offset) __u8 __cloned_offset[0]; __u8 cloned:1, nohdr:1, fclone:2, peeked:1, head_frag:1, xmit_more:1, pfmemalloc:1; /* fields enclosed in headers_start/headers_end are copied * using a single memcpy() in __copy_skb_header() */ /* private: */ __u32 headers_start[0]; /* public: */ /* if you move pkt_type around you also must adapt those constants */ #ifdef __BIG_ENDIAN_BITFIELD #define PKT_TYPE_MAX (7 << 5) #else #define PKT_TYPE_MAX 7 #endif #define PKT_TYPE_OFFSET() offsetof(struct sk_buff, __pkt_type_offset) __u8 __pkt_type_offset[0]; __u8 pkt_type:3; __u8 ignore_df:1; __u8 nf_trace:1; __u8 ip_summed:2; __u8 ooo_okay:1; __u8 l4_hash:1; __u8 sw_hash:1; __u8 wifi_acked_valid:1; __u8 wifi_acked:1; __u8 no_fcs:1; /* Indicates the inner headers are valid in the skbuff. */ __u8 encapsulation:1; __u8 encap_hdr_csum:1; __u8 csum_valid:1; __u8 csum_complete_sw:1; __u8 csum_level:2; __u8 csum_not_inet:1; __u8 dst_pending_confirm:1; #ifdef CONFIG_IPV6_NDISC_NODETYPE __u8 ndisc_nodetype:2; #endif __u8 ipvs_property:1; __u8 inner_protocol_type:1; __u8 remcsum_offload:1; #ifdef CONFIG_NET_SWITCHDEV __u8 offload_fwd_mark:1; #endif #ifdef CONFIG_NET_CLS_ACT __u8 tc_skip_classify:1; __u8 tc_at_ingress:1; __u8 tc_redirected:1; __u8 tc_from_ingress:1; #endif #ifdef CONFIG_NET_SCHED __u16 tc_index; /* traffic control index */ #endif union { __wsum csum; struct { __u16 csum_start; __u16 csum_offset; }; }; __u32 priority; int skb_iif; __u32 hash; __be16 vlan_proto; __u16 vlan_tci; #if defined(CONFIG_NET_RX_BUSY_POLL) || defined(CONFIG_XPS) union { unsigned int napi_id; unsigned int sender_cpu; }; #endif #ifdef CONFIG_NETWORK_SECMARK __u32 secmark; #endif union { __u32 mark; __u32 reserved_tailroom; }; union { __be16 inner_protocol; __u8 inner_ipproto; }; __u16 inner_transport_header; __u16 inner_network_header; __u16 inner_mac_header; __be16 protocol; __u16 transport_header; __u16 network_header; __u16 mac_header; #if defined(CONFIG_CAVIUM_IPFWD_OFFLOAD) struct cvm_packet_info cvm_info; #endif /* private: */ __u32 headers_end[0]; /* public: */ /* These elements must be at the end, see alloc_skb() for details. */ sk_buff_data_t tail; sk_buff_data_t end; unsigned char *head, *data; unsigned int truesize; refcount_t users; }; 写一个解析这个数据结构全部内容的函数
最新发布
10-22
<think>我们正在讨论Linux内核网络栈中`pskb_copy`和`skb_copy`两个函数的区别。根据引用内容,我们可以总结如下: 1. **pskb_copy**: - 拷贝`sk_buff`结构体和线性数据区(即线性部分)。 - 对于非线性数据(例如分片数据,位于`skb_shared_info`结构中),它不会进行深拷贝,而是通过引用计数共享(即增加引用计数,不复制实际数据)[^3]。 - 因此,如果原始SKB有非线性数据,那么通过`pskb_copy`得到的新SKB将共享这些非线性数据(通过指针指向相同的数据区域)。 2. **skb_copy**: - 不仅拷贝`sk_buff`结构体和线性数据区,还会拷贝非线性数据(即完全深拷贝)[^3]。 - 这意味着它会为非线性数据(如分片)分配新的内存,并将数据复制过去,因此新SKB与原始SKB在数据区上完全独立。 ### 关键区别总结 | 特性 | `pskb_copy` | `skb_copy` | |---------------------|----------------------------------------|-----------------------------------------| | 拷贝内容 | `sk_buff`结构体 + 线性数据 | `sk_buff`结构体 + 线性数据 + 非线性数据 | | 非线性数据处理 | 共享(引用计数增加) | 深拷贝(独立内存) | | 内存使用 | 较少(共享数据区) | 较多(完全独立拷贝) | | 适用场景 | 不需要修改非线性数据时 | 需要完全独立的SKB副本(可能修改任何部分)| ### 适用场景 - 使用`pskb_copy`: 当你只需要修改SKB的头部(例如修改协议头)或者线性数据部分,并且不会修改非线性数据(如分片数据)时,可以选择`pskb_copy`。因为它避免了复制非线性数据的开销,效率更高。 - 使用`skb_copy`: 当你需要一份完全独立的SKB副本,并且可能会修改非线性数据(例如修改分片内容)时,必须使用`skb_copy`。因为共享的非线性数据一旦被修改,会影响到原始SKB和其他共享该数据的SKB。 ### 代码示例说明 假设我们有一个SKB(可能包含非线性数据): ```c struct sk_buff *skb = ...; // 原始SKB ``` - 使用`pskb_copy`: ```c struct sk_buff *pskb = pskb_copy(skb, GFP_ATOMIC); ``` 此时,如果原始SKB有非线性数据(例如通过`skb_shinfo(skb)->frags`指向的分片数组),那么`pskb`将共享这些分片数据(即`skb_shinfo(pskb)->frags`指向相同的分片数组)。因此,如果你修改了`pskb`的非线性数据,原始`skb`也会被修改。 - 使用`skb_copy`: ```c struct sk_buff *skb_copy = skb_copy(skb, GFP_ATOMIC); ``` 此时,无论原始SKB是否有非线性数据,`skb_copy`都会复制一份完整的数据,包括非线性部分。因此,修改`skb_copy`的任何数据都不会影响原始SKB。 ### 引用说明 根据引用[3]: > 前者(pskb_copy)仅仅是将sk_buff的结构体和线性数据copy过来,对于非线性数据,是引用原始的skb的数据的!而后者(skb_copy)是不仅将sk_buff和线性数据拷贝,同时将非线性数据也copy了一份。 这清楚地说明了两个函数在非线性数据处理上的区别。 ### 注意事项 - 当使用`pskb_copy`时,由于非线性数据是共享的,因此必须确保不会修改这些共享数据(除非你确实希望原始SKB和其他克隆的SKB都看到修改)。如果需要修改,应该先通过`skb_linearize`将整个SKB数据线性化(变成连续线性数据),然后再修改。 - `skb_copy`虽然更安全(完全独立),但代价是更高的内存分配和复制开销,特别是当SKB包含大量非线性数据(如大文件传输的分片)时。 §§相关问题§§ 1. 在什么情况下应该优先选择`pskb_copy`而不是`skb_copy`? 2. 如何判断一个SKB是否包含非线性数据? 3. `skb_linearize`函数在什么情况下会失败? 4. 为什么网络协议栈在处理大文件传输时需要非线性SKB?</think>### `pskb_copy` vs `skb_copy` 在 Linux 内核网络栈中的区别 在 Linux 内核网络栈中,`pskb_copy` 和 `skb_copy` 都是用于复制 `sk_buff`(SKB)结构的函数,但它们在内存管理和数据复制行为上有本质区别: #### 1. **数据复制范围** | 函数 | 复制内容 | 非线性数据处理 | |------|----------|----------------| | **`pskb_copy`** | 仅复制 `sk_buff` 结构体和**线性数据区** | 共享原始 SKB 的非线性数据(`skb_shared_info`)[^3] | | **`skb_copy`** | 复制完整的 SKB:结构体 + 线性数据 + **非线性数据** | 深拷贝所有分片数据(完全独立副本)[^1][^3] | #### 2. **内存使用差异** ```c // pskb_copy 伪代码逻辑 n = alloc_skb(size); // 分配新SKB结构 memcpy(n->data, orig->data); // 仅复制线性数据 n->shinfo = orig->shinfo; // 共享非线性数据区 atomic_inc(orig->dataref); // 增加引用计数 ``` ```c // skb_copy 伪代码逻辑 n = alloc_skb(full_size); // 分配完整空间 memcpy(n->data, orig->data); // 复制线性数据 copy_frag_list(n, orig); // 深拷贝所有分片数据 ``` 关键区别: - `pskb_copy` 内存开销小(仅复制线性数据) - `skb_copy` 内存开销大(完全深拷贝) #### 3. **数据共享机制** | 特性 | `pskb_copy` | `skb_copy` | |------|-------------|------------| | **非线性数据共享** | 是(引用计数+1) | 否 | | **修改安全性** | 修改非线性数据会影响原始SKB | 完全隔离 | | **适用场景** | 只读访问或仅修改线性数据 | 需要修改分片数据时 | #### 4. **典型应用场景** - **使用 `pskb_copy`**: ```c // 仅需检查数据包头部的场景 struct sk_buff *clone = pskb_copy(skb, GFP_ATOMIC); iph = ip_hdr(clone); // 安全访问IP头(线性区) tcp_hdr(clone)->ack++; // 修改TCP头 ``` 适合:协议头修改、数据包监控(不需要修改分片数据)[^3] - **使用 `skb_copy`**: ```c // 需要修改分片数据的场景 struct sk_buff *deep_copy = skb_copy(skb, GFP_KERNEL); for (i = 0; i < shinfo->nr_frags; i++) { deep_copy->frags[i].page_offset += 10; // 安全修改分片 } ``` 适合:数据包内容篡改、加密操作、分片重组[^1] #### 5. **性能影响对比 | 指标 | `pskb_copy` | `skb_copy` | |------|-------------|------------| | 内存消耗 | 低(共享分片数据) | 高(完全复制) | | CPU 开销 | 低(仅复制线性数据) | 高(复制所有分片) | | 锁竞争 | 可能(引用计数原子操作) | 无 | | 吞吐量 | 适合高速路径 | 大包处理性能差 | > **设计哲学**:Linux 网络栈通过这种分层复制机制,在数据安全性和性能之间取得平衡。小包处理优先使用 `pskb_copy` 减少内存复制,大包处理需要完整性时使用 `skb_copy`[^1][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值