Linux 内核网络协议栈 ------sk_buff 结构体 以及 完全解释 (2.6.16)

在2.6.24之后这个结构体有了较大的变化,此处先说一说2.6.16版本的sk_buff,以及解释一些问题。


一、

先直观的看一下这个结构体~~~~~~~~~~~~~~~~~~~~~~在下面解释每个字段的意义~~~~~~~~~~~

[cpp] view plain copy
 print?
  1. struct sk_buff {  
  2.          /* These two members must be first. */  
  3.          struct sk_buff          *next;  
  4.          struct sk_buff          *prev;  
  5.    
  6.          struct sock             *sk;  
  7.          struct skb_timeval      tstamp;  
  8.          struct net_device       *dev;  
  9.          struct net_device       *input_dev;  
  10.    
  11.          union {  
  12.                  struct tcphdr   *th;  
  13.                  struct udphdr   *uh;  
  14.                  struct icmphdr  *icmph;  
  15.                  struct igmphdr  *igmph;  
  16.                  struct iphdr    *ipiph;  
  17.                  struct ipv6hdr  *ipv6h;  
  18.                  unsigned char   *raw;  
  19.          } h;  
  20.    
  21.          union {  
  22.                  struct iphdr    *iph;  
  23.                  struct ipv6hdr  *ipv6h;  
  24.                  struct arphdr   *arph;  
  25.                  unsigned char   *raw;  
  26.          } nh;  
  27.    
  28.          union {  
  29.                  unsigned char   *raw;  
  30.          } mac;  
  31.    
  32.          struct  dst_entry       *dst;  
  33.          struct  sec_path        *sp;  
  34.    
  35.          /* 
  36.           * This is the control buffer. It is free to use for every 
  37.           * layer. Please put your private variables there. If you 
  38.           * want to keep them across layers you have to do a skb_clone() 
  39.           * first. This is owned by whoever has the skb queued ATM. 
  40.           */  
  41.          char                    cb[48];  
  42.    
  43.          unsigned int            len,  
  44.                                  data_len,  
  45.                                  mac_len,  
  46.                                  csum;  
  47.          __u32                   priority;  
  48.          __u8                    local_df:1,  
  49.                                  cloned:1,  
  50.                                  ip_summed:2,  
  51.                                  nohdr:1,  
  52.                                  nfctinfo:3;  
  53.          __u8                    pkt_type:3,  
  54.                                  fclone:2,  
  55.                                  ipvs_property:1;  
  56.          __be16                  protocol;  
  57.    
  58.          void                    (*destructor)(struct sk_buff *skb);  
  59. #ifdef CONFIG_NETFILTER  
  60.          __u32                   nfmark;  
  61.          struct nf_conntrack     *nfct;  
  62. #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)  
  63.          struct sk_buff          *nfct_reasm;  
  64. #endif  
  65. #ifdef CONFIG_BRIDGE_NETFILTER  
  66.          struct nf_bridge_info   *nf_bridge;  
  67. #endif  
  68. #endif /* CONFIG_NETFILTER */  
  69. #ifdef CONFIG_NET_SCHED  
  70.          __u16                   tc_index;       /* traffic control index */  
  71. #ifdef CONFIG_NET_CLS_ACT  
  72.          __u16                   tc_verd;        /* traffic control verdict */  
  73. #endif  
  74. #endif  
  75.    
  76.    
  77.          /* These elements must be at the end, see alloc_skb() for details.  */  
  78.          unsigned int            truesize;  
  79.          atomic_t                users;  
  80.          unsigned char           *head,  
  81.                                  *data,  
  82.                                  *tail,  
  83.                                  *end;  
  84. };  

> : next和prev,这两个域是用来连接相关的skb的(例如如果有分片,将这些分片连接在一起可以)

> : sk,指向报文所属的套接字指针

> : tstamp,记录接收或者传输报文的时间戳

> : dev和input_dev,记录接收或者发送的设备

>: union u,对于一个层次,例如tcp层,可能有很多不同的协议,他们的协议头不一样,那么这个联合体就是记录这些协议头的。

     此处u就是代表传输层

> : union nh,代表网络层头

> : union mac,代表链路层头

> : dst,指向des_entry结构,记录了到达目的地的路由信息,以及其他的一些网络特征信息。

> : sp:安全路径,用于xfrm

> : cb[],保存与协议相关的控制信息,每个协议可能独立使用这些信息。

> : 重要的字段 len 和 data_len:

      len代: 表整个数据区域的长度!这里要提前解释几个定义,skb的组成是有sk_buff控制 + 线性数据 + 非线性数据 

      (skb_shared_info) 组成!

     后面会具体解释是什么意思!在sk_buff这个里面没有实际的数据,这里仅仅是控制信息,数据是通过后面的data指针指向其他内

     存块的!那个内存块中是线性数据和

     非线性数据!那么len就是length(线性数据) + length(非线性数据)!!!

     data_len: 指的是length(非线性数据)!!!那么可以知道:length(线性数据) =  skb->len - skb->data_len

> : mac_len,指的是mac头长度

> : csum,某时刻协议的校验和

> : priority,报文排队优先级,取决于ip中的tos域

> : local_df,允许在本地分配

> : cloned,保存当前的skb_buff是克隆的还是原始数据

> : ip_summed,是否计算ip校验和

> : nohdr,仅仅引用数据区域

> : pkt_type,报文类型,例如广播,多播,回环,本机,传出...

> : fclone,skb_buff克隆状态

> : ipvs_property,skb_buff是否属于ipvs

> : protocal,协议信息

> : nfmark,用于钩子之间通信

> : nfct_reasm,netfilter的跟踪连接重新组装指针

> : nf_bridge,保存桥接信息

> : tc_index: Traffic control index,tc_verd: traffic control verdict

> : truesize,该缓冲区分配的所有总的内存,包括:skb_buff + 所有数据大小

> : users,保存引用skb_buff的数量

> : 重要数据字段:head,data,tail,end!!!

    head:指向分配给的线性数据内存首地址( 建立起一个观念:并不是分配这么多内存,就都能被使用作为数据存储,可能没这么多

    数据也有可能!但是也不要认为分配这么多 就足够了,也不一定(非线性数据就是例子) )

    data:指向保存数据内容的首地址!我们由head可以知道,head和data不一定就是指在同一个位置!!!

    tail:指向数据的结尾!

    end:指向分配的内存块的结尾! ( 由上面我们知道数据结尾 != 分配的内存块的结尾 )

    下面还会具体分析!!!!!!!!!!!


二、

我觉得需要先了解一些对于一个数据skb到底有什么,或者说由哪些元素组成!这就需要知道所谓的 “线性数据” 和 “非线性数据”。

基本的组成如下:

> : sk_buff : 这是一个sk_buff的控制结构

> : 线性数据区域

> : 非线性数据区域( 由skb_shared_info结构体管理 )


那么下面通过一个图来看看这个skb结构到底是怎么样的!看(图一)


                                                                                    (图一)

借助图一,我们先来分析两个重要字段:len和data_len

之前说过len代表的是整个数据的长度,data_len代表的是非线性数据长度。我们由图一可以看到线性数据长度为l1,再看看非线性数据,其实就是看frags[]和frag_list

ok...那么我们可以知道非线性数据长度为( l2 + ... + ln ) + ( l(n+1) + ... + lm )

即:len = l1 + ( l2 + ... + ln ) + ( l(n+1) + ... + lm )

        data_len = ( l2 + ... + ln ) + ( l(n+1) + ... + lm )


ok...


现在从分配内存开始解释这个图的由来:

我们使用skb_alloc给skb分配空间,那么刚刚分配结束返回时候,是什么样的情况呢?看下图(图二):

                                                

                                                                                     (图二)


刚刚开始初始化的时候,预分配一个一块线性数据区域,这个区域一般放入的是各个协议层次的不同的头,还有一些实际数据,下面的非线性区域是为了弥补当数据真的很多的时候,作为数据区域的扩展!关于skb_shared_info具体意思下面会继续说!注意在初始化的时候,head,data和tail都指向内存的开始位置,head在这个位置始终不变,它表示的是分配的内存的开始位置。end的位置也是不变的,表示的是分配的内存的结束位置。data和tail会随着数据的加入和减少变化,总之表示的是放入数据的内存区域(由图一)可知。


现在需要解释一下skb_shared_info这个结构体,这个结构体真的是很很有特色!主要是其中的两个字段frags和frag_list,下面继续解释:

[cpp] view plain copy
 print?
  1. struct skb_shared_info {  
  2.          atomic_t        dataref;        // 对象被引用次数  
  3.          unsigned short  nr_frags;       // 分页段数目,即frags数组元素个数  
  4.          unsigned short  tso_size;         
  5.          unsigned short  tso_segs;  
  6.          unsigned short  ufo_size;  
  7.          unsigned int    ip6_frag_id;  
  8.          struct sk_buff  *frag_list;    // 一般用于分段(还没有非常清楚的理解)  
  9.          skb_frag_t      frags[MAX_SKB_FRAGS]; // 保存分页数据(skb->data_len=所有的数组数据长度之和)  
  10. };  

关于frags和frag_list没有必然的联系!


> : 对于frags[]一般用在,当数据真的很多,而且在线性数据区域装不下的时候,需要使用这个,skb_frag_t中是一页一页的数据,先看看结构体:

[cpp] view plain copy
 print?
  1. struct skb_frag_struct {  
  2.          struct page *page;    // 代表一页数据  
  3.          __u16 page_offset;    // 代表相对开始位置的页偏移量  
  4.          __u16 size;           // page中数据长度  
  5. };  

需要注意的是:只有在DMA支持物理分散页的Scatter/Gather(SG,分散/聚集)操作时候才可以使用frags[]来保存剩下的数据,否则,只能扩展线性数据区域进行保存!!!

这些页其实是其实就是虚拟页映射到物理页的结构,看下图(图三):


                                                                                         (图三)


> : 对于frag_list来说,一般我们在分片的时候里面装入每个片的信息,注意,每个片最终也都是被封装成一个小的skb,这个必须

     的!

     注意:具体怎么分片的看上一篇博文:数据分片 (  看其中的ip_fragment函数  )

     那么看一下其基本结构如图四:

                                                         

                                                                                         (图四)


三、

最重要的是需要理解对于这个skb是怎么操作的,在操作的过程中,每一块的内存分配是怎么变化的,这才更重要!

看下面的函数们:


> : alloc_skb()函数

[cpp] view plain copy
 print?
  1. static inline struct sk_buff *alloc_skb(unsigned int size,  
  2.                                          gfp_t priority)  
  3. {  
  4.          return __alloc_skb(size, priority, 0);  
  5. }  


其实看__alloc_skb函数:

[cpp] view plain copy
 print?
  1. struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,  
  2.                              int fclone)  
  3. {  
  4.          kmem_cache_t *cache;  
  5.          struct skb_shared_info *shinfo;  
  6.          struct sk_buff *skb;  
  7.          u8 *data;  
  8.    
  9.          cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;    // 根据克隆状态来判断在哪一个缓冲区进行分配cache  
  10.    
  11.          /* Get the HEAD */  
  12.          skb = kmem_cache_alloc(cache, gfp_mask & ~__GFP_DMA);        // 得到skb,注意这里没有包含数据,仅仅是skb_buff这个结构体  
  13.          if (!skb)  
  14.                  goto out;  
  15.    
  16.          /* Get the DATA. Size must match skb_add_mtu(). */  
  17.          size = SKB_DATA_ALIGN(size);                                     // 获得线性数据分片长度(注意对齐)  
  18.          data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask); // 注意分配的是什么,是size + skb_shared_info!!!!!  
  19.          if (!data)  
  20.                  goto nodata;  
  21.    
  22.          memset(skb, 0, offsetof(struct sk_buff, truesize));          // 初始化  
  23.          skb->truesize = size + sizeof(struct sk_buff);               // 实际大小等于sk_buff + size,刚刚开始还没有非线性数据  
  24.          atomic_set(&skb->users, 1);                                    
  25.          skb->head = data;                                            // 注意指针,这个结合上面的图一清二楚  
  26.          skb->data = data;  
  27.          skb->tail = data;  
  28.          skb->end  = data + size;  
  29.          /* make sure we initialize shinfo sequentially */  
  30.          shinfo = skb_shinfo(skb);  
  31.          atomic_set(&shinfo->dataref, 1);  
  32.          shinfo->nr_frags  = 0;  
  33.          shinfo->tso_size = 0;  
  34.          shinfo->tso_segs = 0;  
  35.          shinfo->ufo_size = 0;  
  36.          shinfo->ip6_frag_id = 0;  
  37.          shinfo->frag_list = NULL;  
  38.    
  39.          if (fclone) {  
  40.                  struct sk_buff *child = skb + 1;  
  41.                  atomic_t *fclone_ref = (atomic_t *) (child + 1);  
  42.    
  43.                  skb->fclone = SKB_FCLONE_ORIG;  
  44.                  atomic_set(fclone_ref, 1);  
  45.    
  46.                  child->fclone = SKB_FCLONE_UNAVAILABLE;  
  47.          }  
  48. out:  
  49.          return skb;  
  50. nodata:  
  51.          kmem_cache_free(cache, skb);  
  52.          skb = NULL;  
  53.          goto out;  
  54. }  


那么alloc之后的图就是(图五):

                                               

                                                                                          (图五)

其实和图二是一样的!我们可以看到,现在仅仅是分配了线束数据区域,但是现在还没有数据!一定要注意!所以前面三个指针指在一起!因为没有数据,那么len和data_len的值就是0 !


> : skb_reserve函数

[cpp] view plain copy
 print?
  1. static inline void skb_reserve(struct sk_buff *skb, int len)  
  2. {  
  3.          skb->data += len;  
  4.          skb->tail += len;  
  5.  }  


代码其实很easy、就是移动两个指针而已~


这个函数很重要,是为“协议头”预留空间!而且是尽最大的空间预留,因为很多头都会有可选项,那么我们不知道可选项是多大,所以只能是按照最大的分配,那么也说明了一点,预留的空间headroom也就是不一定都能使用完的!可能还有剩余的,由上面的图也可以看出来!这也是为什么需要这么多指针的问题!那么这个函数直接导致head指针和tail、data指针分离,入下面图六所示:

                                           

                                                                                           (图六)


注意headroom就是用来存储各个协议头的足够大的空间,tailroom就可以认为是存储其他线性数据的空间。( 这里不要曲解协议头不是线性数据,其实协议头也是!!!所以当增加头的时候,data指针向上移动,当增加其他数据的时候,tail指针向下移动 )。现在data和tail指向一起,那么还是说明数据没有!!!


> : skb_put函数 ----> 用于操作线性数据区域(tailroom区域)的用户数据

[cpp] view plain copy
 print?
  1. static inline unsigned char *skb_put(struct sk_buff *skb, unsigned int len)  
  2. {  
  3.          unsigned char *tmp = skb->tail;  
  4.          SKB_LINEAR_ASSERT(skb);            
  5.          skb->tail += len;                 // 移动指针  
  6.          skb->len  += len;                 // 数据空间增大len  
  7.          if (unlikely(skb->tail>skb->end)) // 如果tail指针超过end指针了,那么处理错误~  
  8.                  skb_over_panic(skb, len, current_text_addr());  
  9.          return tmp;  
  10. }  


这函数其实就是从tailroom预留空间,相当于是移动tail指针,这样如果从上图(图六)开始看,也就是tail开始向下移动,和data分离了。。。一般来说,这样做都是为了用户数据再次处理,或者说为TCP/IP的负载预留空间!

看图七,当使用skb_put时候,由图六---->图七

                                                  

                                                                                          (图七)

我们可以看到指针的移动data还是在headroom的下面,中间的是用户数据预留的部分,由skb_put得到,tail表示数据结尾!再看一下sk_buff中的len,变成了数据长度ld!!


> : skb_push函数:----------> 用于操作headroom区域的协议头

[cpp] view plain copy
 print?
  1. static inline unsigned char *skb_push(struct sk_buff *skb, unsigned int len)  
  2. {  
  3.          skb->data -= len;      // 向上移动指针  
  4.          skb->len  += len;      // 数据长度增加  
  5.          if (unlikely(skb->data<skb->head))  // data指针超过head那么就是处理错误~  
  6.                  skb_under_panic(skb, len, current_text_addr());  
  7.          return skb->data;  
  8. }  

和skb_put对应,上面试操作用户数据的,这里是操作协议头的!其实就是data指针向上移动而已~注意len增大了哦~前面说了协议头也是属于数据!

如下面图所示,由图七---->图八

                                           

                                                                                        (图八)


我们可以知道,data向上移动了,同时注意len变成ld+lp了,其中lp是这个增加的协议头的长度!


> : skb_pull函数:-----------> 其实这个函数才是与skb_push函数对应的函数!因为这是去头函数,而skb_push是增头函数!所以这个函数一般用在解包的时候!

[cpp] view plain copy
 print?
  1. static inline unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)  
  2. {  
  3.          return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);  
  4. }  
  5.    
  6.    
  7. static inline unsigned char *__pskb_pull(struct sk_buff *skb, unsigned int len)  
  8. {  
  9.          if (len > skb_headlen(skb) &&  
  10.              !__pskb_pull_tail(skb, len-skb_headlen(skb)))  
  11.                  return NULL;  
  12.          skb->len -= len;                              // 长度减小  
  13.          return skb->data += len;                      // 移动指针  
  14. }  

其实就是data指针向下移动,当前一个协议头被去掉,headroom剩余的空间增大了!看下图:

由图八---->图九:

                                                     

                                                                                      (图九)


虚线是data之前的指针位置,现在移动到下面实线!!需注意:len的长度减小,减小的大小是剥去的头的大小!!


四、

最后我们从两条线整体分析一下:

1:从应用层用户数据开始,直到物理层发送出去

      > 初始化的什么就不多说了,和前面的差不多,现在也加入用户数据已经在了,如图七所示一样!那么到了TCP层,需要增加

         TCP层的头:

         如图10所示:

                                                   

                                                                                                  (图10)


            需要注意的是这里是传输层,那么传输层的结构u中的th代表的是tcp的头,那么tcp指向tcp头OK!同时注意 len长度+=l1 哦~~~

        > 再看到了IP层:如图11

                                                   

                                                                                                 (图11)


                至于需要解释什么就没什么了,都是一样的~

             > 到链路层了:如图12

                                                     

                                                                                             (图12)


  OK!


2:第二个过程其实是第一个逆过程,都差不多,所以不多说了~


五、

最后看一下操作skb的两个函数pskb_copy和skb_copy

前者仅仅是将sk_buff的结构体和线性数据copy过来,对于非线性数据,是引用原始的skb的数据的!而后者是不仅将sk_buff和线性数据拷贝,同时将非线性数据也copy了一份,看下面就明白了!这就在效率上就差了很多!所以如果不想修改数据,那么还是使用pskb_copy更好!


对于pskb_copy:



对于skb_copy:



OK  我觉得差不多了~~~~~结束~~~~~~~~~~~~~ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/385010.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

可变参数输出(三)

Linux C关于输出函数的定义&#xff1a; int printf(const char *format,…); int vprintf(const char * format,va_list ap); int vfprintf(FILE *stream,cosnt char *format,va_list ap); int vsprintf(char *str,const char *format,va_list ap); int vsnprintf(char *str,s…

最常用的设计模式---适配器模式(C++实现)

适配器模式属于结构型的设计模式&#xff0c;它是结构型设计模式之首&#xff08;用的最多的结构型设计模式&#xff09;。 适配器设计模式也并不复杂&#xff0c;适配器它是主要作用是将一个类的接口转换成客户希望的另外一个接口这样使得原本由于接口不兼容而不能一起工作的那…

Linux 简单打印日志(二)

#include<stdio.h> #include<stdlib.h> #include<string.h> #include<time.h> //#include<windows.h> #include <unistd.h> // linux下头文件#define FILE_MAX_SIZE (1024*1024)void get_local_time(char* buffer){time_t rawtime;struct …

程序随笔——C++实现的一个线程池

1.线程池简介 我们知道在线程池是一种多线程处理形式&#xff0c;处理过程中我们将相应的任务提交给线程池&#xff0c;线程池会分配对应的工作线程执行任务或存放在任务队列中&#xff0c;等待执行。 面向对象编程中&#xff0c;创建和销毁对象是需要消耗一定时间的&#xff0…

线程池原理及创建并C++实现

本文给出了一个通用的线程池框架&#xff0c;该框架将与线程执行相关的任务进行了高层次的抽象&#xff0c;使之与具体的执行任务无关。另外该线程池具有动态伸缩性&#xff0c;它能根据执行任务的轻重自动调整线程池中线程的数量。文章的最后&#xff0c;我们给出一个简单示例…

Linux 打印简单日志(一)

简单日志输出&#xff1a; #include<stdio.h> #include<string.h> #include<stdlib.h>void write(char* filename,char* szStr){FILE* fp;fp fopen(filename,"at");if(fp ! NULL){fwrite(szStr,256,1,fp); //fclose(fp);fp NULL;} }int main(int…

c++简单线程池实现

线程池&#xff0c;简单来说就是有一堆已经创建好的线程&#xff08;最大数目一定&#xff09;&#xff0c;初始时他们都处于空闲状态&#xff0c;当有新的任务进来&#xff0c;从线程池中取出一个空闲的线程处理任务&#xff0c;然后当任务处理完成之后&#xff0c;该线程被重…

Linux 打印可变参数日志

实现了传输进去的字符串所在的文档&#xff0c;函数和行数显示功能。 实现了将传入的可变参数打印到日志功能。 #include<stdio.h> #include<stdarg.h> #include<string.h>const char * g_path "/home/exbot/wangqinghe/log.txt"; #define LOG(fm…

C++强化之路之线程池开发整体框架(二)

一.线程池开发框架 我所开发的线程池由以下几部分组成&#xff1a; 1.工作中的线程。也就是线程池中的线程&#xff0c;主要是执行分发来的task。 2.管理线程池的监督线程。这个线程的创建独立于线程池的创建&#xff0c;按照既定的管理方法进行管理线程池中的所有线程&#xf…

vfprintf()函数

函数声明&#xff1a;int vfprintf(FILE *stream, const char *format, va_list arg) 函数参数&#xff1a; stream—这是指向了FILE对象的指针&#xff0c;该FILE对象标识了流。 format—c语言字符串&#xff0c;包含了要被写入到流stream中的文本。它可以包含嵌入的format标签…

Makefile(二)

将生产的.o文件放进指定的文件中&#xff08;先创建该文件夹&#xff09; src $(wildcard ./*.cpp) obj $(patsubst %.cpp,./output/%.o,$(src))target test$(target) : $(obj)g $(obj) -o $(target) %.o: %.cppg -c $< -o output/$.PHONY:clean clean:rm -f $(target) $…

TCP粘包问题分析和解决(全)

TCP通信粘包问题分析和解决&#xff08;全&#xff09;在socket网络程序中&#xff0c;TCP和UDP分别是面向连接和非面向连接的。因此TCP的socket编程&#xff0c;收发两端&#xff08;客户端和服务器端&#xff09;都要有成对的socket&#xff0c;因此&#xff0c;发送端为了将…

UML类图符号 各种关系说明以及举例

UML中描述对象和类之间相互关系的方式包括&#xff1a;依赖&#xff0c;关联&#xff0c;聚合&#xff0c;组合&#xff0c;泛化&#xff0c;实现等。表示关系的强弱&#xff1a;组合>聚合>关联>依赖 相互间关系 聚合是表明对象之间的整体与部分关系的关联&#xff0c…

寻找数组中第二大数

设置两个数值来表示最大数和第二大数&#xff0c;在循环比较赋值即可 //找给定数组中第二大的数int get_smax(int *arr,int length) {int max;int smax;if(arr[0] > arr[1]){max arr[0];smax arr[1];}else{max arr[1];smax arr[0];}for(int i 2; i < length; i){if(…

timerfd API使用总结

timerfd 介绍 timerfd 是在Linux内核2.6.25版本中添加的接口&#xff0c;其是Linux为用户提供的一个定时器接口。这个接口基于文件描述符&#xff0c;所以可以被用于select/poll/epoll的场景。当使用timerfd API创建多个定时器任务并置于poll中进行事件监听&#xff0c;当没有可…

#if/#else/#endif

在linux环境下写c代码时会尝试各种方法或调整路径&#xff0c;需要用到#if #include<stdio.h>int main(){int i; #if 0i 1; #elsei 2; #endifprintf("i %d",i);return 0; } 有时候会调整代码&#xff0c;但是又不是最终版本的更换某些值&#xff0c;就需要注…

内存分配调用

通过函数给实参分配内存&#xff0c;可以通过二级指针实现 #include<stdio.h> #incldue<stdlib.h>void getheap(int *p) //错误的模型 {p malloc(100); }void getheap(int **p) //正确的模型 {*p malloc(100); } int main() {int *p NULL;getheap(&p);free(p…

ESP传输模式拆解包流程

一、 ESP简介ESP&#xff0c;封装安全载荷协议(Encapsulating SecurityPayloads)&#xff0c;是一种Ipsec协议&#xff0c;用于对IP协议在传输过程中进行数据完整性度量、来源认证、加密以及防回放攻击。可以单独使用&#xff0c;也可以和AH一起使用。在ESP头部之前的IPV4…

结构体成员内存对齐

#include<stdio.h> struct A {int A; };int main() {struct A a;printf("%d\n",sizeof(a));return 0; } 运行结果&#xff1a;4 #include<stdio.h> struct A {int a;int b&#xff1b; };int main() {struct A a;printf("%d\n",sizeof(a))…

C库函数-fgets()

函数声明&#xff1a;char *fgets(char *str,int n,FILE *stream) 函数介绍&#xff1a;从指定的stream流中读取一行&#xff0c;并把它存储在str所指向的字符串中。当读取到&#xff08;n-1&#xff09;个字符时&#xff0c;获取读取到换行符时&#xff0c;或者到达文件末尾时…