SKB_BUFF整理笔记

【协议森林】深入浅出理解skb_buff 1.前言 在互联网技术里,有两件事最为重要,一个是 TCP/IP 协议,它是万物互联的事实标准;另一个是 Linux 操作系统,它是推动互联网技术走向繁荣的基石。在 Linux内核的协议栈中的实现中,数据结构skb_buff是最关键和最核心的数据,它表示接收或发送数据包的包头信息,并包含很多成员变量供网络代码中的各子系统使用。本文以及后续关于skb_buff的介绍,均来源于经典著作《深入理解lin... 阅读详情
 

一. SKB_BUFF的基本概念
1. 一个完整的skb buff组成
(1) struct sk_buff--用于维护socket buffer状态和描述信息
(2) header data--独立于sk_buff结构体的数据缓冲区,用来存放报文分组,使各层协议的header存储在连续的空间中,以方便协议栈对其操作
(3) struct skb_shared_info --作为header data的补充,用于存储ip分片,其中sk_buff *frag_list是一系列子skbuff链表,而frag[]是由一组单独的page组成的数据缓冲区
skb buff结构图如下:
 


struct skb_buff
表示接收或发送数据包的包头信息,其成员变量在从一层向另一层传递时会发生修改。例如L3向L2传递前,会添加一个L3的头部,所以在添加头部前调用skb_reserve在缓冲区的头部给协议头预留一定的空间;L2向L3传递时候,L2的头部只有在 网络驱动处理L2的协议时有用,L3是不会关心它的信息的。但是,内核不会把L2的头部从缓冲区中删除,

sk_buff->h
sk_buff->nh
sk_buff->mac
指向TCP/IP各层协议头的指针:h指向L4(传输层),nh指向L3(网络层),mac指向L2(数据链路层)。每个指针的类型都是一个联合, 包含多个数据结构,


sk_buff->head
sk_buff->data
sk_buff->tail
sk_buff->end
   表示缓冲区和数据部分的边界。在每一层申请缓冲区时,它会分配比协议头或协议数据大的空间。head和end指向缓冲区的头部和尾部,而data和 tail指向实际数据的头部和尾部。每一层会在head和data之间填充协议头,或者在tail和end之间添加新的协议数据。数据部分会在尾部包含一 个附加的头部。
下图是TCP(L4)向下发送数据给链路层L2的过程。注意skb_buff->data在从L4向L2穿越过程中的变化
 


几个len的区别?
(1)sk_buff->len:表示当前协议数据包的长度。它包括主缓冲区中的数据长度(data指针指向它)和分片中的数据长度。比如,处在网络层,len指的是ip包的长度,如果包已经到了应用层,则len是应用层头部和数据载荷的长度。

(2)sk_buff->data_len: data_len只计算分片中数据的长度,即skb_shared_info中有效数据总长度(包括frag_list,frags[]中的扩展数据),一般为0

(3)sk_buff->truesize:这是缓冲区的总长度,包括sk_buff结构和数据部分。如果申请一个len字节的缓冲区,alloc_skb函数会把它初始化成len+sizeof(sk_buff)。当skb->len变化时,这个变量也会变化。

通常,Data Buffer 只是一个简单的线性 buffer,这时候 len 就是线性 buffer 中的数据长度;
但在有 ‘paged data’ 情况下, Data Buffer 不仅包括第一个线性 buffer ,还包括多个 page buffer;这种情况下, ‘data_len’ 指的是 page buffer 中数据的长度,’len’ 指的是线性 buffer 加上 page buffer 的长度;len – data_len 就是线性 buffer 的长度。

 

 二. sk_buff结构操作函数
内核通过alloc_skb()和dev_alloc_skb()为套接字缓存申请内存空间。这两个函数的定义位于net/core/skbuff.c文件内。通过这alloc_skb()申请的内存空间有两个,一个是存放实际报文数据的内存空间,通过kmalloc()函数申请;一个是sk_buff数据结构的内存空间,通过 kmem_cache_alloc()函数申请。dev_alloc_skb()的功能与alloc_skb()类似,它只被驱动程序的中断所调用,与alloc_skb()比较只是申请的内存空间长度多了16个字节。

内核通过kfree_skb()和dev_kfree_skb()释放为套接字缓存申请的内存空间。dev_kfree_skb()被驱动程序使用,功能与kfree_skb()一样。当skb->users为1时kfree_skb()才会执行释放内存空间的动作,否则只会减少skb->users的值。skb->users为1表示已没有其他用户使用该缓存了。

skb_reserve()函数为skb_buff缓存结构预留足够的空间来存放各层网络协议的头信息。该函数在在skb缓存申请成功后,加载报文数据前执行。在执行skb_reserve()函数前,skb->head,skb->data和skb->tail指针的位置的一样的,都位于skb内存空间的开始位置。这部份空间叫做headroom。有效数据后的空间叫tailroom。skb_reserve的操作只是把skb->data和skb->tail指针向后移,但缓存总长不变。

运行skb_reserve()前sk_buff的结构
 
        sk_buff
 ----------------------   ---------->  skb->head,skb->data,skb->tail
|                      |
|                      |
|                      |
|                      |
|                      |
|                      |
|                      |
|                      |
|                      |
 ---------------------    ---------->  skb->end
 
运行skb_reserve()后sk_buff的结构
 
        sk_buff
 ----------------------   ---------->  skb->head
|                      |
|      headroom        |
|                      |
|--------------------- |  ---------->  skb->data,skb->tail
|                      |
|                      |
|                      |
|                      |
|                      |
 ---------------------    ---------->  skb->end
        
skb_put()向后扩大数据区空间,tailroom空间减少,skb->data指针不变,skb->tail指针下移。

skb_push()向前扩大数据区空间,headroom空间减少,skb->tail指针不变,skb->data指针上移

skb_pull()缩小数据区空间,headroom空间增大,skb->data指针下移,skb->tail指针不变。

skb_shared_info结构位于skb->end后,用skb_shinfo函数申请内存空间。该结构主要用以描述data内存空间的信息。

 ---------------------  ----------->  skb->head
|                     |
|                     |
|      sk_buff        |
|                     |
|                     |
|                     |
|---------------------| ----------->  skb->end
|                     |
|   skb_share_info    |
|                     |
 ---------------------
skb_clone和skb_copy可拷贝一个sk_buff结构,skb_clone方式是clone,只生成新的sk_buff内存区,不会生成新的data内存区,新sk_buff的skb->data指向旧data内存区。skb_copy方式是完全拷贝,生成新的sk_buff内存区和data内存区。。

 

三. skb 的分配细节
1. 关于 SKB 的分配细节.

LINUX 中 SKB 的分配最终是由函数 : struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,int fclone) 来完成.
SKB 可以分为 SKB 描述符与 SKB 数据区两个部分,其中描述符必须从 CACHE 中来分配 : 或者从skbuff_fclone_cache 中分配,或者从 skbuff_head_cache 中来分配.
如果从分配描述符失败,则直接反悔 NULL,表示 SKB 分配失败.

SKB 描述符分配成功后,即可分配数据区.
在具体分配数据区之前首先要对数据区的长度进行 ALIGN 操作, 通过宏 SKB_DATA_ALIGN 来重新确定 size 大小. 然后戏台调用 kmalloc 函数分配数据区 :
data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
需要注意的是数据区的大小是 SIZE 的大小加上 skb_shared_info 结构的大小.

数据区分配成功后,便对 SKB 描述符进行与此数据区相关的赋值操作 :
    memset(skb, 0, offsetof(struct sk_buff, truesize));
    skb->truesize = size + sizeof(struct sk_buff);
    atomic_set(&skb->users, 1);
    skb->head = data;
    skb->data = data;
    skb->tail = data;
    skb->end  = data + size;
需要主意的是, SKB 的 truesize 的大小并不包含 skb_shared_info 结构的大小. 另外,skb 的 end 成员指针也就是skb_shared_info 结构的起始指针,系统用
一个宏 : skb_shinfo 来完成寻找 skb_shared_info 结构指针的操作.

最后,系统初始化 skb_shared_info 结构的成员变量 :
    atomic_set(&(skb_shinfo(skb)->dataref), 1);
    skb_shinfo(skb)->nr_frags  = 0;
    skb_shinfo(skb)->tso_size = 0;
    skb_shinfo(skb)->tso_segs = 0;
    skb_shinfo(skb)->frag_list = NULL;
    skb_shinfo(skb)->ufo_size = 0;
    skb_shinfo(skb)->ip6_frag_id = 0;

最后,返回 SKB 的指针.

2. SKB 的分配时机
SKB 的分配时机主要有两种,最常见的一种是在网卡的中断中,有数据包到达的时,系统分配 SKB 包进行包处理; 第二种情况是主动分配 SKB 包用于各种调试或者其他处理环境.

3. SKB 的 reserve 操作
SKB 在分配的过程中使用了一个小技巧 : 即在数据区中预留了 128 个字节大小的空间作为协议头使用, 通过移动 SKB 的 data 与 tail 指针的位置来实现这个功能.

4. SKB 的 put 操作
put 操作是 SKB 中一个非常频繁也是非常重要的操作, 但是, skb_put()函数其实什么也没做!
它只是根据数据的长度移动了 tail 指针并改写了 skb->len 的值,其他的什么都没做,然后就返回了 skb->data 指针(就是 tail 指针在移动之前的位置). 看上去此函数仿佛要拷贝数据到 skb 的数据区中,其实这事儿是 insl 这个函数干的,跟 skb_put() 函数毫不相关,不过它仍然很重要.

5. 中断环境下 SKB 的分配流程
当数据到达网卡后,会触发网卡的中断,从而进入 ISR 中,系统会在 ISR 中计算出此次接收到的数据的字节数 : pkt_len, 然后调用 SKB 分配函数来分配 SKB :
skb = dev_alloc_skb(pkt_len+5);
我们可以看到, 实际上传入的数据区的长度还要比实际接收到的字节数多,这实际上是一种保护机制. 实际上,在 dev_alloc_skb 函数调用 __dev_alloc_skb 函数,而 __dev_alloc_skb 函数又调用 alloc_skb 函数时,其数据区的大小又增加了 128 字节, 这 128 字节就事前面我们所说的 reserve 机制预留的 header 空间.

 

 

四. 不同情况下构造skb数据包的实现

http://blog.youkuaiyun.com/efan_linux/archive/2009/09/23/4580024.aspx

 在我这个网络接口的程序中(can0),其实难点就是怎样组包。怎样在原来数据包的基础加上自己的数据,怎样构造ip头,怎样构造udp头。

调试了两个星期,终于是调通了,在这个过程中,通过看内核源代码和自己组包的尝试,大概对组包的方法有了些了解,记录在此,留做备忘,也希望能给需要这方面信息的朋友一点帮助吧。

1,正常网卡收到数据包后的情况:

她的工作就是剥离mac头,然后给一些字段赋值,最后调用netif_rx将剥离mac头后的数据报(比如ip数据包)发送到上层协议。由协议栈处理。在此以ldd3中的snull为例,虽然snull跟硬件不相关,但这个过程都是类似的。

    struct sk_buff *skb;
    struct snull_priv *priv = netdev_priv(dev);


    skb = dev_alloc_skb(pkt->datalen + 2);
    if (!skb) {
        if (printk_ratelimit())
            printk(KERN_NOTICE "snull rx: low on mem - packet dropped/n");
        priv->stats.rx_dropped++;
        goto out;
    }
    skb_reserve(skb, 2); /* align IP on 16B boundary */
    memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);

    /* Write metadata, and then pass to the receive level */
    skb->dev = dev;
   skb->protocol = eth_type_trans(skb, dev);
    skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
    priv->stats.rx_packets++;
    priv->stats.rx_bytes += pkt->datalen;
    netif_rx(skb);
 

注意:上面代码中红色放大的地方是重要的。

因为此刻收到的数据包的格式如下:mac+ip+udp/udp+data

这时候的处理就是剥离mac头,然后需要更新的一些域值。这些都是在函数eth_type_trans函数里做的。需要注意的是,skb->dev = dev;这条语句是很重要的,如果没有此语句,将会导致系统错误而死机(至少在我的板子上是这样的)。

注意:eth_type_trans()函数主要赋值的是:

skb->mac.raw,skb->protocol和skb->pkt_type。见下面的代码有无mac头的情况。

2,完全从一个字符串开始构造一个新的skb数据包。

以前只是看过如何修改数据包,自己构造数据包,这还是头一次,刚开始确实给我难住了,来来经过看内核代码和自己摸索,我自己写的代码如下:

/*假设:data是一个指向字符串的指针,data_len是data的长度*/

struct ipv6hdr *ipv6h;
struct udphdr *udph;
struct sk__buff * new_skb;

int length = data_len + sizeof(struct ipv6hdr) + sizeof(udphdr);
new_skb = dev_alloc_skb(length);
if(!new_skb)
    {
        printk("low memory.../n"):
        return -1;
    }

skb_reserve(new_skb,length);

memcpy(skb_push(new_skb,data_len),data,data_len);

new_skb->h.uh = udph = (struct udphdr *)skb_push(new_skb,sizeof(struct udphdr));
memcpy(udph,&udph_tmp,sizeof(struct udphdr)); //注意,此刻我的udph_tmp是在另一个过程中截获的数据包的udp头,如果完全是自己构造数据包,则需要自己填充udp数据头中的字段。


udph->len = .............. ; //此处需要给udph->len赋值。注意udph->len是__u16的。存储时是高低位互换的,所以你应该先将你要更新的数字编成16进制的数,然后高低位互换,在赋值给udh->len。

udplen = new_skb->len;

new_skb->nh.ipv6h = ipv6h = (struct ipv6hdr *)skb_push(new_skb,sizeof(struct ipv6hdr));
memcpy(ipv6h,&ipv6h_tmp,sizeof(struct ipv6hdr)); //同udp头注释。

ipb6h->payload_len = ..........; //此处同udph->len.需要注意的是,此处所指的长度并不包括ipv6头的长度,而是去掉ipv6头后的长度。

udph->check = 0;
udph->check = csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, udplen, IPPROTO_UDP, csum_partial((char *)udph, udplen, 0));

///////////////注意,如果是ipv4,则还需要计算ip校验和,但此处是ipv6,不用计算ip检验和,所以此处没有ipv6头的校验。//////////////////////////

new_skb->mac.raw = new_skb->data; //因为无mac头
new_skb->protocol = htons(ETH_P_IPV6); //表明包是ipv6数据包
new_skb->pkt_type = PACKET_HOST; //表明是发往本机的包

new_skb->dev = &can_control; //此处很重要,如果没有这条语句,则内核跑死。至少在我板子上是这样的。can_control是我的net_device结构体变量。

netif_rx(new_skb);
 

3,当需要改变原有skb的数据域的情况。

此时,有两种办法:
可以先判断skb的tailroom,如果空间够大,则我们可以把需要添加的数据放在skb的tailroom里。如果tailroom不够大,则需要调用skb_copy_expand函数来扩充tailroom或者headroom。
 
例如我们需要在skb的后面加上一个16个字节的字符串,则代码类似如下:
if(skb_tailroom(skb) < 16)
{
    nskb = skb_copy_expand(skb, skb_headroom(skb), skb_tailroom(skb)+16,GFP_ATOMIC);
    if(!nskb)
    {
        printk("low memory..../n");       
        dev_kfree_skb(skb);
        return -1;
    }
   
    else
    {
        kfree_skb(skb); // 注意,如果此时是钩子函数钩出来的,则skb不能在这里释放,否则会造成死机。

        skb = nskb;
    }
   
    memcpy(skb_put(skb,16),ipbuf,16); //ipbuf为要加到skb后面的字符串

   
    udplen = skb->len - sizeof(struct ipv6hdr);
   
    udph->len += 0x1000; //换成十进制为 + 16

    ipv6h->payload_len += 0x1000;
   
    udph->check = 0;
    udph->check = csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, udplen, IPPROTO_UDP, csum_partial((char *)udph,udplen,0));   
   
    skb->mac.raw = new_skb->data; //因为无mac头

    skb->protocol = htons(ETH_P_IPV6); //表明包是ipv6数据包

    skb->pkt_type = PACKET_HOST; //表明是发往本机的包


    skb->dev = &can_control; //此处很重要,如果没有这条语句,则内核跑死。至少在我板子上是这样的。can_control是我的net_device结构体变量。


  netif_rx(skb);
}
 

注意:当调用skb_copy_expand或者修改了skb的数据域后,一定要更新udph->len和ipv6h->payload_len。否则上层应用(比如udp套接字)收到的数据包还是原来的数据包而不是修改后的数据包,因为udph->len的原因。

 

本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/efan_linux/archive/2009/09/23/4580024.aspx

sk_buff数据结构详解 sk_buff 简介 封包存储于struct sk_buff中,所有网络分层都会使用这个结构来储存其报头、有关用户数据的信息,以及协调其工作的其他内部信息。从第二层到第四层都会使用这个数据结构。L4附加一个报头传给L3,L3附加自己的报头传给L2,L2附加自己的报头传给L1转发出去。设备收到sk_buff之后,每层都只处理自己的报头,并将其呈交给上层。 sk_buff数据结构 内核使用一个双向链表来维护sk_buff,链表的开端使用了一个哑元元素sk_buff_head,如下图所示为sk_buff链表 阅读详情

相关推荐

sk_buff整理笔记(四、克隆与复制)

上面的pskb_copy()函数和skb_clone()函数类似:skb_clone()函数克隆出来的skb结构不能修改其共享数据区的数据,而pskb_copy()函数也是一样的,克隆出来的skb及数据区不能修改共享的分片结构数据区内容。所以如果想要修改分片结构数据区的内容,则必须要用skb_copy()函数来克隆skb结构体。skb_copy()函数是对skb结构体真正的完全复制拷贝。不仅是sk_buff结构体还有data指针指向的数据区(包括分片结构)以及分片结构中指针指向的数据区,都各自复制拷贝一份。

YuZhiHui_No1的专栏 7651

Linux内核中sk_buff结构详解

sk_buff结构体中的都是sk_buff的控制信息,是网络数据包的一些配置,真正储存数据的是sk_buff结构体中几个指针指向的数据区中,线性数据区的大小 = (skb->end - skb->head),对于每个数据包来说这个大小都是固定不变的,在传输过程中skb->end和skb->head所指向的地址都是不变的,这里要注意这个地址不是本机的地址,如果是本机的地址那么数据包传到其他主机上这个地址就是无效的,所以这个地址是这个skb缓冲区的相对地址。: skb中的分片数据(非线性数据)的长度。

赵凯月的博客 2279

sk_buff整理笔记(三、内存申请和释放)

内核对于sk_buff结构的内存分配不是和一般的结构动态内存申请一样:只分配指定大小的内存空间。而是在开始的时候,在初始化函数skb_init()中就分配了两段内存(skbuff_head_cache和skbuff_fclone_cache )来供sk_buff后期申请时用,所以后期要为sk_buff结构动态申请内存时,都会从这两段内存中来申请(其实这不叫申请了,因为这两段内存开始就申请好了的,只是根据你要的内存大小从某个你选定的内存段中还回个指针给你罢了)。如果在这个内存段中申请失败,则再用内核中用最低层

YuZhiHui_No1的专栏 1万+

linux 网络 sk_buff结构

sk_buff的意思是socket buffer,这是Linux网络子系统中的核心数据结构。定义在中,它由许多变量组成,目标就是满足所有网络协议的需要。sk_buff在不同的网络层被使用(MAC 或其他在 L2 的协议,在 L3 的 IP 协议,在 L4 的 TCP 或 UDP 等),当它从一层传递到另一层时,各个字段也会发生变化。在被传递到 L3 之前,L4 会追加头信息,然后在被传递到 L2 之前,L3 会追加头信息。。

wwwlyj123321的博客 4476

第四章 内核数据转发sk_buff结构说明

编写内核数据转发代码之前需要对内核报文的数据结构以及函数说明有详细的了解,才能在后续的编码中能够根据自己的需求进行快速的开发。

flowhehe的博客 802

反无人机作战体系全解析:从低慢小探测到分层拦截

随着小型无人机在军事和民用领域的广泛使用,低慢小目标探测与拦截成为低空安全的核心挑战。传统防空体系面对成本低廉、飞行高度低、雷达特征小的无人机集群时,暴露出探测难、识别难、处置成本失衡等问题。为应对威胁,反无人机体系采用分层闭环设计,融合雷达、光电、声学与无线电侦测实现多源感知,通过电子战软杀伤与导弹、高炮、激光等硬杀伤手段协同配合,并借助自动化指控系统缩短“传感器到射手”的响应时延。该体系既可支撑战场要地防护,也能为民用低空安防提供参考。围绕分层反制逻辑,梳理探测、软硬杀伤、指控布防及实战短板,提供理解反

weixin_42533120的博客 333

sk_buff(整理)(未完)

参考原文地址:http://blog.youkuaiyun.com/tqyou85/archive/2008/12/30/3645065.aspx   sk_buff是一个控制结构,通过它,才可以访问网络报文里的各种数据。所以在分配网络报文存储空间时,同时也分配它的控制结构sk_buff。在这个控制结构里,有指向网络报文的指针,也有描述网络报文的变量。下面是sk_buff的定义,依次注释如下: CODE: struct sk_buff { struct sk_buff * next; struct s

J.H.Z的专栏 769

从SIGGRAPH审稿人视角看:什么样的TOG论文更容易被接收?

本文从SIGGRAPH审稿人视角深入解析TOG论文的审稿标准与投稿策略,揭示如何提升论文接收概率。重点探讨创新性、技术深度、实验验证等核心评分维度,并提供写作技巧与审稿意见应对策略,帮助研究者优化ACM Transactions on Graphics投稿质量。

weixin_33721344的博客 108

sk_buff整理笔记(两、操作函数)

        承接上一:sk_buff 整理笔记(一、数据结构)这一篇要讲的是内核为sk_buff结构提供的一些操作函数。         第一、首先要讲的是sk_buff中的四大指针:         四大指针各自是:head、data、tail、end;这四个指针都是指向了数据区的。数据区中存放的是协议头和数据。head和end指针是每一个数据包一样的,也是有这两个指针来确定数据...

weixin_33885676的博客 289

Unity WebGL发布避坑指南:从内存设置到字体打包,一次搞定所有常见问题

本文详细解析Unity WebGL发布的完整流程,从内存设置到字体打包,提供全面的避坑指南。涵盖关键检查清单、内存与性能优化配置、资源处理专项优化及平台适配技巧,帮助开发者高效解决常见问题,确保项目顺利发布。

weixin_28693309的博客 147

多周期路径及set_multicycle_path详解

默认情况下,每条路径都被定义为单周期,即源触发器在时钟的任一边沿启动(launch)的数据都应该由目的触发器在时钟的下一上升沿捕获(capture)。 有的设计需要在数据被捕获前提供一些额外的周期,这类路径被称为多周期路径。 一、set_multicycle_path命令 命令格式如下: set_multicycle_path .........

Jackiezhang1993的博客 4万+

sk_buff整理笔记(二、操作函数)

sk_buff整理笔记(一、数据结构)这一篇要讲的是内核为sk_buff结构提供的一些操作函数:(a)skb_put(), (b)skb_push(), (c)skb_pull(), and (d)skb_reserve(); 四大指针分别是:head、data、tail、end;这四个指针都是指向了数据区的,数据区中存放的是协议头和数据。head和end指针是每个数据包一样的,也是有这两个指针来确定数据区的大小的。最开始的时候head、data、tail三个指针都是指向一起的,当有协议数据加进来时才更

YuZhiHui_No1的专栏 9448

扩散模型中的UNET

一、为什么UNET模型可以用于去噪网络 二、扩散模型中的UNET是一个条件去噪网络,怎么实现的 三、UNET用于分割和用去去噪的区别

m0_37737957的博客 3816

sk_buff 整理笔记(一、数据结构)

对sk_buff结构体以及相关的sk_buff_head结构体、sk_buff结构体数据区、sk_shared_info分片结构体、分片结构体的数据区的常用成员字段进行透彻的分析。以及分析这几个结构体的关系与一个sk_buff数据包的形成和其data指针的变化。 (1)sk_buff->data_len:只计算分片中数据的长度,即是分片结构体中page指向的数据区长度。这个在分片结构体中会再详细讲解下。 (2)sk_buff->len:表示当前缓冲区中数据块的大小的总长度。它包括主缓冲中(即是sk_

YuZhiHui_No1的专栏 3万+

MediaPipe 教程:从安装到应用-全网最全

MediaPipe是Google推出的开源框架,专为构建机器学习管道设计,可以高效处理视频、音频等时间序列数据。本文教程从安装到应用逐步讲解: 安装步骤简单,只需通过pip安装mediapipe包并验证版本; 重点演示人体姿态检测功能,可识别33个身体关键点; 提供基础代码实现,包括视频捕获、关键点检测与可视化; 强调模块化开发,将功能封装成类以提高代码复用性。 教程适合初学者,通过清晰指引和实用代码示例,帮助快速实现计算机视觉应用。

FJN110的博客 1563

Python爬虫实战:手把手教你如何抓取农作物品种名录,构建标准化种业索引数据库!

我长期专注 Python 爬虫工程化实战,主理专栏 《Python爬虫实战》:从采集策略到反爬对抗,从数据清洗到分布式调度,持续输出可复用的方法论与可落地案例。内容主打一个“能跑、能用、能扩展”,让数据价值真正做到——抓得到、洗得净、用得上。

喵手的博客 290

sk_buff整理笔记(五、队列管理函数)

分析sk_buff结构及相关结构的队列管理函数:初始化函数,skb_queue_head_init(struct sk_buff_head *list);插入函数,kb_queue_head(),skb_queue_tail();出队列函数,sk_buff *skb_dequeue(struct sk_buff_head *list),sk_buff *skb_dequeue_tail(struct sk_buff_head *list);清空队列函数,skb_queue_purge(struct sk_b

YuZhiHui_No1的专栏 7674

SAP_SD模块更改销售订单定价简介

方式优点缺点适用场景MASS简单,无需开发不能触发重新定价逻辑修改特定字段BDC (VA02)可模拟人工操作维护复杂,速度慢少量订单批改稳定、安全需ABAP支持推荐用于批量重新定价VBOF标准重算返利仅适返利类条件返利更新通过本文,我们了解了 SAP 的[SAP_SD模块更改销售订单定价]的基本概念和应用方法。希望这些信息能帮助您在实际工作中更好地使用 SAP 系统,提高工作效率和管理水平。更多SAP文章请点击更多SAP文章更新,大家一起学习进步!!!

我小时候很黑的博客 712

linux skb_buf大小,linux网络 skb_buff

sbk_buff中的data_len指的是尾部带的page数据的长度,len指的是总共的data的长度,len-data_len是第一个线性buf的数据长度。sk_buff->len:表示当前协议数据包的长度。它包括主缓冲区中的数据长度(data指针指向它)和分片中的数据长度。比如,处在网络层,len指的是ip包的长度,如果包已经到了应用层,则len是应用层头部和数据载荷的长度。sk_buf...

weixin_39842611的博客 733

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

上一篇: 专业评测工具CHARIOT的应用
下一篇: 网络字节序与主机字节序
wifi狂人
博客等级 码龄17年 98粉丝 13原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值