Linux网络设备驱动-- skb_neilengineer的博客-程序员信息网

技术标签: Linux  Network&TCP/IP  

Linux网络设备驱动-- skb

 

 

代码基于Linux kernel 3.13.0

 

传说中的skb, 其数据结构是struct sk_buff (include/linux/skbuff.h)。 这里捡个人认为重要的讲讲:

struct sk_buff {

/* These two members must be first. */

struct sk_buff*next;

struct sk_buff*prev;

ktime_ttstamp;

struct sock*sk;

struct net_device*dev;      //对应的net_device

/*

 * 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.

 */

charcb[48] __aligned(8);   //cb, 驱动经常用它来保存一些private结构

unsigned long_skb_refdst;

#ifdef CONFIG_XFRM

structsec_path*sp;

#endif

unsigned intlen,                              //skb sizeskb head size(data_len!=0)

data_len;        //data_len!=0说明有分片

__u16mac_len,

hdr_len;

union {

__wsumcsum;

struct {

__u16csum_start;

__u16csum_offset;

};

};

__u32priority;            //优先级

kmemcheck_bitfield_begin(flags1);

__u8local_df:1,

cloned:1,

ip_summed:2,

nohdr:1,

nfctinfo:3;

__u8pkt_type:3,

fclone:2,

ipvs_property:1,

peeked:1,

nf_trace:1;

kmemcheck_bitfield_end(flags1);

__be16protocol;

void(*destructor)(struct sk_buff *skb);

#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)

struct nf_conntrack*nfct;

#endif

#ifdef CONFIG_BRIDGE_NETFILTER

struct nf_bridge_info*nf_bridge;

#endif

intskb_iif;

__u32rxhash;

__be16vlan_proto;

__u16vlan_tci;

#ifdef CONFIG_NET_SCHED

__u16tc_index;/* traffic control index */

#ifdef CONFIG_NET_CLS_ACT

__u16tc_verd;/* traffic control verdict */

#endif

#endif

__u16queue_mapping;

kmemcheck_bitfield_begin(flags2);

#ifdef CONFIG_IPV6_NDISC_NODETYPE

__u8ndisc_nodetype:2;

#endif

__u8pfmemalloc:1;

__u8ooo_okay:1;

__u8l4_rxhash:1;

__u8wifi_acked_valid:1;

__u8wifi_acked:1;

__u8no_fcs:1;

__u8head_frag:1;

/* Encapsulation protocol and NIC drivers should use

 * this flag to indicate to each other if the skb contains

 * encapsulated packet or not and maybe use the inner packet

 * headers if needed

 */

__u8encapsulation:1;

/* 6/8 bit hole (depending on ndisc_nodetype presence) */

kmemcheck_bitfield_end(flags2);

#ifdef CONFIG_NET_RX_BUSY_POLL

unsigned intnapi_id;

#endif

#ifdef CONFIG_NETWORK_SECMARK

__u32secmark;

#endif

union {

__u32mark;

__u32dropcount;

__u32reserved_tailroom;

};

__be16inner_protocol;

__u16inner_transport_header;

__u16inner_network_header;

__u16inner_mac_header;

__u16transport_header;    //TCP/UDP header

__u16network_header;      //IP header

__u16mac_header;         //Eth header

/* These elements must be at the end, see alloc_skb() for details.  */

sk_buff_data_ttail;

sk_buff_data_tend;

unsigned char*head,

*data;

unsigned inttruesize;

atomic_tusers;

};

Skb末尾有skb_shared_info结构用于分片,可用skb_shinfo(skb)访问。

 

对于驱动,一般用kmalloc/kfree 来分配各种结构用的memory

对于经常要allocfreememory,一般预先分配一个memory cache,每次从这个cache中分配和释放。

对于skb,skb_init会分配一个sk_buff memory cache叫skbuff_head_cache。

 

void __init skb_init(void) (net/core/skbuff.c)

{

    skbuff_head_cache = kmem_cache_create("skbuff_head_cache",

                          sizeof(struct sk_buff),

                          0,

                          SLAB_HWCACHE_ALIGN|SLAB_PANIC,

                          NULL);

    skbuff_fclone_cache = kmem_cache_create("skbuff_fclone_cache",

                        (2*sizeof(struct sk_buff)) +

                        sizeof(atomic_t),

                        0,

                        SLAB_HWCACHE_ALIGN|SLAB_PANIC,

                        NULL);

}

 

驱动用dev_alloc_skb/dev_kfree_skb来分配和释放skb

dev_alloc_skbèalloc_skbè__alloc_skb,从mem cache中分配一个sk_buff结构,再用kmalloc分配skb->datamemorydev_alloc_skb反过来。

dev_alloc_skb比alloc_skb多分配16字节用于优化。

 

struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,

                int flags, int node)

{

    skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);

……

    data = kmalloc_reserve(size, gfp_mask, node, &pfmemalloc);

……

}

 

对于TX, 一个Eth  frame如果大于MTU,  IP分片会把它分为多个skb发给驱动。这些skb可能用->next link起来,也可能没有。

对于RX,  Jumbo frame驱动可能用frag_list(non-linear)或frags[](paged_data)来存放。一般用skb list

 

 

///

Skb常用操作

 

This first diagram illustrates the layout of the SKB data area and where in that area the various pointers in 'struct sk_buff' point. 

The rest of this page will walk through what the SKB data area looks like in a newly allocated SKB. How to modify those pointers to add headers, add user data, and pop headers. 

Also, we will discuss how page non-linear data areas are implemented. We will also discuss how to work with them. 


 

skb = alloc_skb(len, GFP_KERNEL);

 

This is what a new SKB looks like right after you allocate it using alloc_skb() 

As you can see, the head, data, and tail pointers all point to the beginning of the data buffer. And the end pointer points to the end of it. Note that all of the data area is considered tail room. 

The length of this SKB is zero, it isn't very interesting since it doesn't contain any packet data at all. Let's reserve some space for protocol headers using skb_reserve() 


 

skb_reserve(skb, header_len);

 

This is what a new SKB looks like right after the skb_reserve() call. 

Typically, when building output packets, we reserve enough bytes for the maximum amount of header space we think we'll need. Most IPV4 protocols can do this by using the socket value sk->sk_prot->max_header. 

When setting up receive packets that an ethernet device will DMA into, we typically call skb_reserve(skb, NET_IP_ALIGN). By default NET_IP_ALIGN is defined to '2'. This makes it so that, after the ethernet header, the protocol header will be aligned on at least a 4-byte boundary. Nearly all of the IPV4 and IPV6 protocol processing assumes that the headers are properly aligned. 

Let's now add some user data to the packet.     

For L2 device driver, it’ll be used to reserve for IP header alignment, skb_reserve(skb,2),  eth header is 14 bytes, followed by IP header 16 bytes,  so 2 bytes reserved for 16-byte alignment.  Some driver will align with cache line size, e.g.

    if(align){

        offset = ((unsigned long) skb->data) % align;

        if(offset)

            skb_reserve(skb, align - offset);

    }

 

unsigned char *data = skb_put(skb, user_data_len);

int err = 0;

skb->csum = csum_and_copy_from_user(user_pointer, data,

    user_data_len, 0, &err);

if (err)

goto user_fault;

 

This is what a new SKB looks like right after the user data is added. 

skb_put() advances 'skb->tail' by the specified number of bytes, it also increments 'skb->len' by that number of bytes as well. This routine must not be called on a SKB that has any paged data. You must also be sure that there is enough tail room in the SKB for the amount of bytes you are trying to put. Both of these conditions are checked for by skb_put() and an assertion failure will trigger if either rule is violated. 

The computed checksum is remembered in 'skb->csum'. Now, it's time to build the protocol headers. We'll build a UDP header, then one for IPV4. 


 

struct inet_sock *inet = inet_sk(sk);

struct flowi *fl = &inet->cork.fl;

struct udphdr *uh;

 

skb->h.raw = skb_push(skb, sizeof(struct udphdr));

uh = skb->h.uh

uh->source = fl->fl_ip_sport;

uh->dest = fl->fl_ip_dport;

uh->len = htons(user_data_len);

uh->check = 0;

skb->csum = csum_partial((char *)uh,

 sizeof(struct udphdr), skb->csum);

uh->check = csum_tcpudp_magic(fl->fl4_src, fl->fl4_dst,

      user_data_len, IPPROTO_UDP, skb->csum);

if (uh->check == 0)

uh->check = -1;

 

This is what a new SKB looks like after we push the UDP header to the front of the SKB. 

skb_push() will decrement the 'skb->data' pointer by the specified number of bytes. It will also increment 'skb->len' by that number of bytes as well. The caller must make sure there is enough head room for the push being performed. This condition is checked for by skb_push() and an assertion failure will trigger if this rule is violated. 

Now, it's time to tack on an IPV4 header. 


 

struct rtable *rt = inet->cork.rt;

struct iphdr *iph;

 

skb->nh.raw = skb_push(skb, sizeof(struct iphdr));

iph = skb->nh.iph;

iph->version = 4;

iph->ihl = 5;

iph->tos = inet->tos;

iph->tot_len = htons(skb->len);

iph->frag_off = 0;

iph->id = htons(inet->id++);

iph->ttl = ip_select_ttl(inet, &rt->u.dst);

iph->protocol = sk->sk_protocol; /* IPPROTO_UDP in this case */

iph->saddr = rt->rt_src;

iph->daddr = rt->rt_dst;

ip_send_check(iph);

 

skb->priority = sk->sk_priority;

skb->dst = dst_clone(&rt->u.dst);

 

This is what a new SKB looks like after we push the IPv4 header to the front of the SKB. 

Just as above for UDP, skb_push() decrements 'skb->data' and increments 'skb->len'. We update the 'skb->nh.raw' pointer to the beginning of the new space, and build the IPv4 header. 

This packet is basically ready to be pushed out to the device once we have the necessary information to build the ethernet header (from the generic neighbour layer and ARP). 


 

Things start to get a little bit more complicated once paged data begins to be used. For the most part the ability to use [page, offset, len] tuples for SKB data came about so that file system file contents could be directly sent over a socket. But, as it turns out, it is sometimes beneficial to use this for nomal buffering of process sendmsg() data. 

It must be understood that once paged data starts to be used on an SKB, this puts a specific restriction on all future SKB data area operations. In particular, it is no longer possible to do skb_put() operations. 

We will now mention that there are actually two length variables assosciated with an SKB, len and data_len. The latter only comes into play when there is paged data in the SKB. skb->data_len tells how many bytes of paged data there are in the SKB. From this we can derive a few more things: 

· The existence of paged data in an SKB is indicated by skb->data_len being non-zero. This is codified in the helper routine skb_is_nonlinear() so that it the function you should use to test this. 

· The amount of non-paged data at skb->data can be calculated as skb->len - skb->data_len. Again, there is a helper routine already defined for this called skb_headlen() so please use that. 

The main abstraction is that, when there is paged data, the packet begins at skb->data for skb_headlen(skb) bytes, then continues on into the paged data area for skb->data_len bytes. That is why it is illogical to try and do an skb_put(skb) when there is paged data. You have to add data onto the end of the paged data area instead. 

Each chunk of paged data in an SKB is described by the following structure: 

struct skb_frag_struct {

struct page *page;

__u16 page_offset;

__u16 size;

};

There is a pointer to the page (which you must hold a proper reference to), the offset within the page where this chunk of paged data starts, and how many bytes are there. 

The paged frags are organized into an array in the shared SKB area, defined by this structure: 

#define MAX_SKB_FRAGS (65536/PAGE_SIZE + 2)

 

struct skb_shared_info {

    unsigned char   nr_frags;

    __u8        tx_flags;

    unsigned short  gso_size;

    /* Warning: this field is not always filled in (UFO)! */

    unsigned short  gso_segs;

    unsigned short  gso_type;

    struct sk_buff  *frag_list;

    struct skb_shared_hwtstamps hwtstamps;

    __be32          ip6_frag_id;

 

    /*

     * Warning : all fields before dataref are cleared in __alloc_skb()

     */

    atomic_t    dataref;

 

    /* Intermediate layers must ensure that destructor_arg

     * remains valid until skb destructor */

    void *      destructor_arg;

 

    /* must be last field, see pskb_expand_head() */

    skb_frag_t  frags[MAX_SKB_FRAGS];

};

The nr_frags member states how many frags there are active in the frags[] array. The tso_size and tso_segs is used to convey information to the device driver for TCP segmentation offload. The frag_list is used to maintain a chain of SKBs organized for fragmentation purposes, it is _not_ used for maintaining paged data. And finally the frags[] holds the frag descriptors themselves. 

A helper routine is available to help you fill in page descriptors. 

 

void skb_fill_page_desc(struct sk_buff *skb, int i,

struct page *page,

int off, int size)

 

This fills the i'th page vector to point to page at offset off of size size. It also updates the nr_frags member to be one past i. 

If you wish to simply extend an existing frag entry by some number of bytes, increment the size member by that amount. 

 

With all of the complications imposed by non-linear SKBs, it may seem difficult to inspect areas of a packet in a straightforward way, or to copy data out from a packet into another buffer. This is not the case. There are two helper routines available which make this pretty easy. 

First, we have: 

 

void *skb_header_pointer(const struct sk_buff *skb, int offset, int len, void *buffer) 

 

You give it the SKB, the offset (in bytes) to the piece of data you are interested in, the number of bytes you want, and a local buffer which is to be used _only_ if the data you are interested in resides in the non-linear data area. 

You are returned a pointer to the data item, or NULL if you asked for an invalid offset and len parameter. This pointer could be one of two things. First, if what you asked for is directly in the skb->data linear data area, you are given a direct pointer into there. Else, you are given the buffer pointer you passed in. 

Code inspecting packet headers on the output path, especially, should use this routine to read and interpret protocol headers. The netfilter layer uses this function heavily. 

For larger pieces of data other than protocol headers, it may be more appropriate to use the following helper routine instead. 

 

int skb_copy_bits(const struct sk_buff *skb, int offset,

  void *to, int len);

 

This will copy the specified number of bytes, and the specified offset, of the given SKB into the 'to'buffer. This is used for copies of SKB data into kernel buffers, and therefore it is not to be used for copying SKB data into userspace. There is another helper routine for that: 

 

int skb_copy_datagram_iovec(const struct sk_buff *from,

    int offset, struct iovec *to,

    int size);

 

Here, the user's data area is described by the given IOVEC. The other parameters are nearly identical to those passed in to skb_copy_bits() above. 


 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/neilengineer/article/details/46489087

智能推荐

5G NR 下行同步SSB(3)-- PBCH/MIB的用途_GiveMe5G的博客-程序员信息网

我们知道PBCH其实就是一个物理信道,使用Polar码进行信道编码,调制方式为QPSK。PBCH上承载着MIB消息,那么PBCH里面是否只有MIB消息?PBCH DMRS除了有助于PBCH解调还有什么重要功能?终端读完SSB是如何完成帧同步的?首先,看看PBCH里面都承载了那些内容?MIB其实是一个RRC层面的概念,物理信道PBCH上的内容包括23bit MIB+8 bit additiona...

js实现倒计时5秒提交及redis防止多人同时提交_阑珊处的秋月的博客-程序员信息网_js实现5秒倒计时

目录项目背景一、相关配置说明二、使用步骤1.开发思路2.代码示例效果展示项目背景在多人同时提交文档时,js前往后台进行redis验证,验证通过前台显示一个倒计时5秒进行,点击确定立即提交,倒计时结束自动提交,避免用户中断了提交过程。相关技术:redis缓存,layui框架,javascript,ajax。一、相关配置说明开发前配置:redis安装:安装包-Releases · tporadowski/redis · GitHu...

PostgreSQL pg_resetwal处理机制_yzs87的博客-程序员信息网

pg_resetwal的参数ControlFile结构及pg_resetwal参数影响的字段static struct option long_options[] = { {&#3...

改进JAVA字符串分解的方法_yuxuepiaoguo的博客-程序员信息网

改进JAVA字符串分解的方法一、概述       大多数Java程序员都曾经使用过java.util.StringTokenizer类。它是一个很方便的字符串分解器,主要用来根据分隔符把字符串分割成标记(Token),然后按照请求返回各个标记。这个过程称为Tokenization,实际上就是把字符序列转换成应用程序能够理解的多个标记。       虽然StringTokenizer用起来很方便

DataNode功能详解_初于久歌的博客-程序员信息网_datanode

Datanode功能一共分为三点一、负责管理它所在结点上存储的数据的读写,及存储数据。一般是文件系统客户端需要请求对指定数据结点进行读写操作,Datanode作为数据结点的服务进程来与文件系统客户端打交道。。二、向Namenode结点报告状态。每个Datanode结点会周期性地向Namenode发送心跳信号和文件块状态报告。(心跳是每3秒一次,心跳返回结果带有namenode给该datano...

随便推点

完美卸载SQL Server2014数据库_刘佳宇-Kevin的博客-程序员信息网

在作品展期间电脑安装了SQL,现在进度到这了也不用安装了,不是自己的装的总感觉像不是自己的东西,在配置的时候出现了好多问题,就在网上找了许多教程来卸载下面是我自己总结的。1.在运行中输入services.msc,然后找到所有跟Sql Server有关的服务,并且停止这些服务。2.从控制面板卸载。 3.选择实例时,有多少ID就删除多少次,如...

xgboost算法_XGBoost的原理、推导、代码和应用_weixin_39833454的博客-程序员信息网

XGBoost(eXtreme Gradient Boosting)极致梯度提升,是一种基于GBDT的算法或者说工程实现。 XGBoost的基本思想和GBDT相同,但是做了一些优化,比如二阶导数使损失函数更精准;正则项避免树过拟合;Block存储可以并行计算等。XGBoost具有高效、灵活和轻便的特点,在数据挖掘、推荐系统等领域得到广泛的应用。本文先回顾GBDT、泰勒公式、正则、一元二次函数等X...

hi3556 MIPI屏调试指南_return(b,a%b);的博客-程序员信息网_hi3556

3556v200 MIPI屏调试指南1 概述 :​ a、 海思屏幕调试目前调通的只有MIPI接口屏幕,此文档只对海思平台MIPI屏幕调试做说明,目的做到不用过多的熟悉海思给出的文档,参照此文档实现新屏适配。​ b、 调试屏幕还需依赖海思提供的以下两个文档:​ RGB_MIPI屏幕时钟时序计算.xlsx​ Hi3556V200 2K Mobile Camera SoC 用户指南.pdf​ c、 屏幕相关代码路径:reference\ha

【FPGA基础】一文带你快速了解FPGA内部资源(面试常问,属于概念性问题)_ReCclay的博客-程序员信息网_fpga内部资源

【FPGA基础】一文带你快速了解FPGA内部资源(面试常问,属于概念性问题)

Hiveserver2的代理执行之路_教练_我要踢球的博客-程序员信息网_hive 代理

hiveserver2的代理访问可以使得平台端代理任意用户执行SQL操作就像该用户自己执行的操作一样(就像一个普通用户直接使用hive CLI执行操作),本文主要探索hiveserver2是如何使用代理的方式实现支持不同用户完成SQL操作,为修改impala支持对应的操作做铺垫。

java Ajax入门_失落的爱的博客-程序员信息网_java+ajax

java Ajax入门文章目录java Ajax入门前言一、同步和异步二、Ajax的使用JavaScript方法JQuery方法总结前言接下来整理一下Ajax相关的内容,AJAX(asynchronous js and xml),即异步的JavaScript和xml,它的主要作用就是让页面不必等待服务器返回整个页面信息,而可以通过异步的方式给服务器发送数据和获取数据,对页面进行局部刷新,是一种提高用户体验的技术。一、同步和异步同步和异步的主要区别如下;(1)同步:一个响应结束后才能发送下

推荐文章

热门文章

相关标签