skb数据的发送

本文围绕数据发送展开,介绍了skb结构和分配,包括其重要成员及相关操作函数,如skb_put、skb_push等。还阐述了数据在传输层、网络层的处理流程,如tcp_sendmsg、ip_queue_xmit等函数,以及在数据链路层驱动的实现和网络子系统对传输队列的管理。

数据的发送

skb结构和分配

skb分配释放的频率非常高,用kmem_cache分配skb_buf的头部,alloc_skb分配其数据区,alloc_skb最终调用了_kmalloc分配连续物理内存,skb_buf结构体中重要成员:

head指向已分配空间的头部,end指向该空间尾部,data指向有效数据头部,tail指向有效数据尾部,当skb在各层流动时,head和end是不变的。

skb_put:向后移动tail来增加有效数据空间的大小,将skb->tail移动到新的位置 后,老的tail将作为返回值返回;

skb_push:将data前移来增大有效数据空间的大小,将移动后的skb->data返回;

skb_headroom:返回head和data之间的空间大小

skb_tailroom:返回tail和end的空间大小

skb_reserve:将有效数据空间向后移动len,相等于减少了tail空间,拓展了同样大小的head空间

alloc_skb是__alloc_skb的封装,它分配sk_buff结构及数据缓存块:(a)通过kmem_cache_alloc函数从缓存里分配sk_buff数据结构;(b)通过kmalloc分配数据缓冲区。dev_alloc_skb函数主要用于设备驱动中,并在中断上下文中使用。它包装了skb_alloc函数并且在分配空间上多分了16字节空间,当skb数据头需要增加而又长度又小于16字节时,这样就可利用skb_reserve出来的空间,避免了重新分配,由于该函数主要在中断里使用,其在分配空间要求置上原子标志位GFP_ATOMIC。

layer4:传输层(Transport)

tcp_sendmsg##net/ipv4/tcp.c

int tcp_sendmsg(structsock *sk, struct msghdr *msg, size_t size)

Layer 3:网络层(Network Layer)

ip_queue_xmit## net/ipv4/ip_output.c

int ip_queue_xmit(structsock *sk, struct sk_buff *skb, struct flowi *fl)

{

struct inet_sock *inet = inet_sk(sk);

struct net *net = sock_net(sk);

struct ip_options_rcu *inet_opt;

struct flowi4 *fl4;

struct rtable *rt;

struct iphdr *iph;

int res;

rt = skb_rtable(skb);

if (rt)

            goto packet_routed;如果已经路由

/* Make sure we can route this packet.*/

rt = (struct rtable*)__sk_dst_check(sk, 0);如果没被路由

   if (!rt){      

/* If this fails, retransmitmechanism of transport layer will

* keep trying until routeappears or the connection times

* itself out.

*/

rt = ip_route_output_ports(net,fl4, sk,

daddr, inet->inet_saddr,

  inet->inet_dport,

inet->inet_sport,

sk->sk_protocol,

RT_CONN_FLAGS(sk),

sk->sk_bound_dev_if);

}

res = ip_local_out(net, sk, skb); //触使包发到数据链路层

}

在分配路由表的时候关联netdev

ip_route_output_ports

---> ip_route_output_flow

--->__ip_route_output_key

--->__ip_route_output_key_hash

--->__mkroute_output

--->rt_dst_alloc

struct rtable*__ip_route_output_key_hash(struct net *net, struct flowi4 *fl4,

intmp_hash)

{

struct net_device *dev_out = NULL;

dev_out = dev_get_by_index_rcu(net,fl4->flowi4_oif);

rth = __mkroute_output(&res, fl4, orig_oif,dev_out, flags);

}

net/ipv4/route.c

static struct rtable*rt_dst_alloc(struct net_device *dev,

unsigned intflags, u16 type,

boolnopolicy, bool noxfrm, bool will_cache)

{

struct rtable *rt;

rt = dst_alloc(&ipv4_dst_ops, dev,1, DST_OBSOLETE_FORCE_CHK,

(will_cache ? 0 :(DST_HOST | DST_NOCACHE)) |

(nopolicy ? DST_NOPOLICY: 0) |

(noxfrm ? DST_NOXFRM :0));

if (rt) {

rt->rt_genid =rt_genid_ipv4(dev_net(dev));

rt->rt_flags = flags;

rt->rt_type = type;

rt->rt_is_input = 0;

rt->rt_iif = 0;

   rt->rt_pmtu = 0;

rt->rt_gateway = 0;

rt->rt_uses_gateway = 0;

rt->rt_table_id = 0;

INIT_LIST_HEAD(&rt->rt_uncached);

     rt->dst.output = ip_output;

if (flags & RTCF_LOCAL)

rt->dst.input =ip_local_deliver;

}

return rt;

}

int ip_local_out(structnet *net, struct sock *sk, struct sk_buff *skb)

{      

int err;

err = __ip_local_out(net, sk, skb);

if (likely(err == 1))

 err = dst_output(net, sk, skb);

return err;

}

/* Output packet tonetwork from transport. */

static inline intdst_output(struct net *net, struct sock *sk, struct sk_buff *skb)

{

return skb_dst(skb)->output(net, sk,skb);调用rt_dst_alloc中的ip_output

}

/*ip_output单播包,ip_mc_output组播包*/

int ip_output(struct net*net, struct sock *sk, struct sk_buff *skb)

{      

struct net_device *dev =skb_dst(skb)->dev;    

IP_UPD_PO_STATS(net, IPSTATS_MIB_OUT,skb->len);

     skb->dev = dev;

skb->protocol =htons(ETH_P_IP);  

return NF_HOOK_COND(NFPROTO_IPV4,NF_INET_POST_ROUTING,

net, sk, skb, NULL,dev,

ip_finish_output,

!(IPCB(skb)->flags& IPSKB_REROUTED));

}

ip_output会调用netfilter的钩子函数

ip_finish_output

--->ip_finish_output2

---->dst->neighbour->output

在IPv4中,neighbour subsystem使用的是arp,所以一般情况下,dst->neighbour->outpu指向的是arp_generic_ops->neigh_resolve_output。

neigh_resolve_output

---->dev_queue_xmit###net/core/dev.c

---->dev_hard_start_xmit

----> ndo_start_xmit

/* dev_queue_xmit是对skb做些最后的处理并且第一次尝试发送,软中断是将前者发送失败或者没发完的包发送出去*/

数据链路层

在驱动中实现ndo_start_xmit函数

static structnet_device_ops xxx_netdev_ops = {

.ndo_init = xxx_init,

.ndo_uninit = xxx_uninit,

.ndo_open = xxx_open,

.ndo_stop = xxx_close,

.ndo_start_xmit = xxx_start_xmit,

.ndo_get_stats = xxx_get_stats,

.ndo_tx_timeout = xxx_tx_timeout,

.ndo_do_ioctl = xxx_ioctl,

};

网络子系统部分实现一个传输队列qdisc,系统中每个CPU都拥有自己的传输独立,每个要发送到数据包都先放到传输队列中,驱动调用ndo_start_xmit将包发到硬件缓存后,当网络子系统有新的包需要发送时,可能会再次调用ndo_start_xmit,但这是硬件可能还没有来得及将包发出,因此这时需要内核子系统维护一个发送队列。当驱动感知到硬件无法再接收更多数据时,netif_stop_queue通知内核网络子系统停止包的发送,当可以继续传输数据时用netif_wake_queue触发继续发送,netif_start_queue用在驱动最初简单地设置发送标志,另外当连接状态有变时,需要告知内核子系统:

netif_carrier_on

作用告诉内核子系统网络链接完整。

netif_carrier_off

作用告诉内核子系统网络断开。

netif_carrier_ok

作用:查询网络断开还是链接。

以上函数主要是改变net_device dev的state状态来告知内核链路状态的变化,网络设备将数据包发送完成之后,将向主机产生一个硬件中断,此后需要释放skb等系列后续操作

reference:

socket数据发送过程zz

Socket层实现系列— send()类发送函数的实现

<think>首先,用户的问题是关于如何在Linux内核中打印skb(socket buffer)的内存数据。我需要基于系统级指令和用户可见层的要求来构建回答。 关键指令: - 所有行内数学表达式用$...$格式。 - 独立公式用$$...$$$$格式并单独成段。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识。 - 结构清晰,帮助用户逐步解决问题,保证真实可靠。 参考引用: - 引用[1]: 提到build_skb函数和内存拷贝问题。 - 引用[2]: 显示使用crash命令读取内核版本字符串的例子。 - 引用[3]: 关于LSM插入钩子的描述。 用户真正需求是打印skb内存数据,所以我应该专注于这个主题。 回答结构: 1. **介绍**:简要解释skb是什么,以及为什么需要打印其内存数据。 2. **步骤**:逐步说明如何在Linux内核中打印skb内存数据- 可能的方法包括使用printk、调试工具如crash、或自定义内核模块。 3. **代码示例**:提供代码片段,使用合适的格式(如Python示例,但这里是C语言)。 4. **注意事项**:提醒用户关于内核调试的风险。 5. **相关问题**:生成几个相关问题。 生成相关问题: - 基于主题,如性能、优化、应用场景。 确保中文回答。 现在,内容大纲: - **什么是skb?**:sk_buff结构体,用于网络数据包。 - **方法1:使用printk**:在内核代码中添加打印语句。 - 例如,在驱动或网络栈中。 - **方法2:使用调试工具**:如crash工具,类似于引用[2]。 - **方法3:内核模块**:编写一个模块来访问和打印skb数据- **代码示例**:提供一个简单的内核模块示例。 - **引用**:自然地融入引用。 引用相关: - 引用[1]提到build_skb和内存拷贝,这相关因为skb涉及内存管理。 - 引用[2]展示crash命令,可用于调试。 - 引用[3]关于LSM钩子,可能不直接相关,但skb打印可能用于安全分析。 真实可靠:基于Linux内核知识。skb是struct sk_buff,数据skb->data。 步骤: 1. 获取skb指针。 2. 访问skb->data和skb->len。 3. 打印数据,例如使用printk。 在printk中打印内存数据时,要小心,因为可能打印大量数据;最好限制长度。 使用crash工具:可以转储内存。 相关问题: 1. 如何安全地打印内核内存数据? 2. 有哪些工具可以调试Linux内核网络栈? 3. skb内存管理有哪些优化方法? 最终回答结构: - 开头:解释主题。 - 方法部分。 - 代码块。 - 相关问题。</think>### 如何在Linux内核中打印skb内存数据 在Linux内核中,skb(socket buffer)是网络子系统中的核心数据结构,用于管理网络数据包的接收和发送。struct sk_buff(通常缩写为skb)包含数据包内容、元数据(如协议头)和状态信息。打印skb内存数据是调试网络问题(如数据包损坏或协议错误)的常见需求。以下我将逐步说明几种可靠方法,包括使用内核函数、调试工具和自定义模块。所有方法均基于Linux内核机制,确保真实性和安全性。 #### 1. **使用内核函数`printk`打印skb数据** `printk`是内核的标准日志函数,可直接在驱动或网络栈代码中添加打印语句。此方法适用于开发阶段的内联调试。 - **步骤**: 1. 获取skb指针(通常在网络驱动或协议处理函数中可用)。 2. 使用`print_hex_dump`函数安全打印内存数据,避免缓冲区溢出。 3. 限制打印长度(例如前64字节),防止日志洪水。 - **代码示例**(在内核代码中添加): ```c #include <linux/printk.h> #include <linux/hexdump.h> void print_skb_data(struct sk_buff *skb) { if (skb) { printk(KERN_INFO "SKB data dump, len: %u\n", skb->len); print_hex_dump(KERN_INFO, "skb data: ", DUMP_PREFIX_OFFSET, 16, 1, skb->data, min(skb->len, 64), true); } } ``` - **说明**:此函数打印skb数据的十六进制和ASCII格式。`DUMP_PREFIX_OFFSET`添加偏移量前缀,便于分析。实际使用时,在需要调试的位置调用`print_skb_data(skb)`。 - **注意事项**: - 过度使用`printk`可能降低性能或导致日志溢出,建议仅在调试时启用。 - 在内存敏感操作中,build_skb函数可能引入额外拷贝,影响效率[^1]。 #### 2. **使用调试工具`crash`动态分析skb** `crash`是一个离线内核调试工具,可直接读取内存转储(如vmcore文件),无需修改内核代码。此方法适合生产环境问题诊断。 - **步骤**: 1. 生成内核转储文件(例如通过`kdump`或`sysrq`触发)。 2. 启动`crash`工具:`crash /path/to/vmcore /path/to/vmlinux`。 3. 查找skb地址(可通过`struct`命令或符号表)。 4. 使用`rd`命令读取内存数据- **示例命令**(参考引用[2]的格式): ```bash crash> struct sk_buff -o | grep "data" # 获取skb->data地址 crash> rd -x <skb->data_address> 64 # 以十六进制打印前64字节数据 ``` - **说明**:`rd -x`以十六进制格式输出内存。类似引用[2]中读取内核版本的方法,但针对skb数据- **注意事项**: - 此方法要求系统配置了kdump,且需内核符号文件(vmlinux)。 - 对于实时系统,可结合`perf`或`ftrace`捕获skb事件。 #### 3. **编写自定义内核模块打印skb** 如果需要灵活控制(如定期打印或过滤特定数据包),可编写内核模块。此方法安全可控,但需编译和加载模块。 - **步骤**: 1. 创建内核模块,使用`netfilter`钩子或`kprobe`拦截网络路径。 2. 在钩子函数中访问skb并打印数据。 3. 编译并加载模块。 - **代码示例**(完整模块): ```c #include <linux/module.h> #include <linux/kernel.h> #include <linux/skbuff.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> static struct nf_hook_ops nfho; unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { printk(KERN_INFO "Intercepted SKB, len: %u\n", skb->len); print_hex_dump(KERN_INFO, "skb content: ", DUMP_PREFIX_OFFSET, 16, 1, skb->data, min(skb->len, 32), true); return NF_ACCEPT; } static int __init skb_printer_init(void) { nfho.hook = hook_func; nfho.hooknum = NF_INET_PRE_ROUTING; // 在路由前拦截数据包 nfho.pf = PF_INET; nfho.priority = NF_IP_PRI_FIRST; nf_register_net_hook(&init_net, &nfho); printk(KERN_INFO "SKB printer module loaded\n"); return 0; } static void __exit skb_printer_exit(void) { nf_unregister_net_hook(&init_net, &nfho); printk(KERN_INFO "SKB printer module unloaded\n"); } module_init(skb_printer_init); module_exit(skb_printer_exit); MODULE_LICENSE("GPL"); ``` - **说明**:此模块在IPv4数据包路由前拦截skb,并打印前32字节数据。使用`print_hex_dump`确保安全输出。编译后,通过`insmod`加载。 - **注意事项**: - 模块需在root权限下加载,错误代码可能导致内核崩溃。 - LSM(Linux Security Module)钩子可增强安全审计,但本模块未涉及,引用[3]中提到的钩子机制可用于扩展功能[^3]。 #### 一般建议 - **安全第一**:始终在测试环境验证,避免在生产系统直接修改内核。 - **性能影响**:打印大量数据时,监控系统负载;build_skb等函数可能引入内存拷贝开销[^1]。 - **替代工具**:对于动态跟踪,可考虑`bpftrace`或`systemtap`,它们提供更高效的skb分析。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值