- 浏览: 1407244 次
- 性别:
- 来自: 火星
-
文章分类
最新评论
-
aidd:
内核处理time_wait状态详解 -
ahtest:
赞一下~~
一个简单的ruby Metaprogram的例子 -
itiProCareer:
简直胡说八道,误人子弟啊。。。。谁告诉你 Ruby 1.9 ...
ruby中的类变量与类实例变量 -
dear531:
还得补充一句,惊群了之后,数据打印显示,只有一个子线程继续接受 ...
linux已经不存在惊群现象 -
dear531:
我用select试验了,用的ubuntu12.10,内核3.5 ...
linux已经不存在惊群现象
这次主要介绍一些ip层管理以及统计相关的东西.
首先来看 long-living ip peer information.
我们知道ip协议是无状态的协议.这里内核为了提升性能.为每个目的ip地址(换句话说,也就是和本机进行通信过的主机)保存了一些信息.
peer子系统一般是被tcp,或者routing子系统所使用.
这个信息的数据结构是inet_peer,它是一棵avl树,每个节点的key就是一个ip地址.由于是avl树,因此每次搜索都是O(lg n):
peer子系统的初始化是在inet_initpeers中进行的,它是被ipv4协议的初始化函数ip_init调用的.这个函数的主要任务有三个:
1 allocate一个将要保存inet_peer数据的cache.
2 定义能被inet_peer所使用的最大内存限制.
3 开启gc定时器.
peer系统的核心函数就是inet_getpeer,它是提供给其他子系统的接口,它封装了lookup函数,而loopup函数只是很简单的avl树的查找函数.
而 inet_getpeer函数它通过传入的key(也就是ip地址)和一个flag(比如赋值为create),可以做到,当查找失败后,能创建一个新的树的拣点,并初始化ip包 的id(使用id模块的secure_ip_id)来初始化.
首先来看它的调用图,然后我们再分析整个函数:
这里只有一个要注意的就是我们检测peer是否存在,检测了两次,这是我们在第二次得到锁之前与第一次释放锁之后,这段时间内有可能一个新的peer被加入.
接下来我们来看clean_once这个函数.这个函数不仅会被inet_getpeer调用,还会被peer_periodic_timer调用:
接下来来看定时器处理函数:
然后我们来看下ip头的id域在内核中的实现(也就是ip包的id的选择).
内核中实现这个的函数是__ip_select_ident,一般我们调用,都是调用它的包装函数ip_select_ident,这个函数只不过是判断了下DF位(主要是为了处理win95的bug),然后调用__ip_select_ident.
我们来看实现:
来看下ip层的统计信息的表示.
这里的统计信息是通过per cpu的变量ip_statistics来表示的,这里要知道其实网络子系统很多地方都有一个统计信息,这些统计信息的初始化是通过ipv4_mib_init_net来做的.
每个cpu所统计的信息就是通过他处理的中断所传递的包的信息.
而它提供了几个宏来执行统计,这几个宏分为中端上下文外和内执行.
最后看下,ip的一些配置工具的实现.
这里只是简单的介绍下,具体的去看源码就好了.
这里有4种途经来进行配置:
1 ioctl
这个主要是被ifconfig使用.对应内核就是netdev的do_ioctl函数.
2 netlink
主要被iproute2使用.比如RTMGRP_IPV4_IFADDR广播组,来通知用户空间,网络地址的改变.
3 /proc文件系统.
也就是/proc/sys/net/ipv4
4 RAPP/BOOTP/DHCP
这些也就是通过远程host来配置ip地址等.
ip子系统还提供了一个inetaddr_chain通知链来通知内核其他子系统(比如路由子系统,以及nerfilter masquerading)ip配置的改变.
首先来看 long-living ip peer information.
我们知道ip协议是无状态的协议.这里内核为了提升性能.为每个目的ip地址(换句话说,也就是和本机进行通信过的主机)保存了一些信息.
peer子系统一般是被tcp,或者routing子系统所使用.
这个信息的数据结构是inet_peer,它是一棵avl树,每个节点的key就是一个ip地址.由于是avl树,因此每次搜索都是O(lg n):
struct inet_peer { ///avl树的左子树和右子树 struct inet_peer *avl_left, *avl_right; ///远端peer的ip地址 __be32 v4daddr; /* peer's address */ ///树的高度 __u16 avl_height; ///下一个使用这个peer的包id(我们的包id的选择,就是基于这个域,也就是每次通过传入ip地址,从而得到当前应使用的id(通过inet_getid函数)). __u16 ip_id_count; /* IP ID for the next packet */ ///这个链表包含了所有定时器到期的peer(由于peer初始化的时候内存大小有限制,因此我们就需要定时将在给定时间内没有使用的peer放到这个链表中).这里只有当它的引用计数为0时,才会最终从unused中移除. struct list_head unused; ///当这个inet_peer元素被加入到unused链表中(通过inet_putpeer)的时间. __u32 dtime; /* the time of last use of not ///引用计数 * referenced entries */ atomic_t refcnt; ///帧结束的计数器. atomic_t rid; /* Frag reception counter */ ///下面这两个是被tcp使用来管理时间戳的. __u32 tcp_ts; unsigned long tcp_ts_stamp; };
peer子系统的初始化是在inet_initpeers中进行的,它是被ipv4协议的初始化函数ip_init调用的.这个函数的主要任务有三个:
1 allocate一个将要保存inet_peer数据的cache.
2 定义能被inet_peer所使用的最大内存限制.
3 开启gc定时器.
///内存限制 extern int inet_peer_threshold; ///cache static struct kmem_cache *peer_cachep __read_mostly; ///定时器.可以看到它的处理函数是peer_check_expire,我们后面会介绍这个函数. static DEFINE_TIMER(peer_periodic_timer, peer_check_expire, 0, 0); ///相应的读写锁. static DEFINE_RWLOCK(peer_pool_lock); void __init inet_initpeers(void) { struct sysinfo si; /* Use the straight interface to information about memory. */ si_meminfo(&si); ///上面是取得系统的一些信息,我们这里主要用到的就是内存信息,因此这里通过总内存大小,来对inet_peer_threshold进行赋值. if (si.totalram <= (32768*1024)/PAGE_SIZE) inet_peer_threshold >>= 1; /* max pool size about 1MB on IA32 */ if (si.totalram <= (16384*1024)/PAGE_SIZE) inet_peer_threshold >>= 1; /* about 512KB */ if (si.totalram <= (8192*1024)/PAGE_SIZE) inet_peer_threshold >>= 2; /* about 128KB */ ///create一个cache. peer_cachep = kmem_cache_create("inet_peer_cache", sizeof(struct inet_peer), 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL); ///初始化定时器. peer_periodic_timer.expires = jiffies + net_random() % inet_peer_gc_maxtime + inet_peer_gc_maxtime; add_timer(&peer_periodic_timer); }
peer系统的核心函数就是inet_getpeer,它是提供给其他子系统的接口,它封装了lookup函数,而loopup函数只是很简单的avl树的查找函数.
而 inet_getpeer函数它通过传入的key(也就是ip地址)和一个flag(比如赋值为create),可以做到,当查找失败后,能创建一个新的树的拣点,并初始化ip包 的id(使用id模块的secure_ip_id)来初始化.
首先来看它的调用图,然后我们再分析整个函数:
![](/upload/attachment/131297/2d54d755-47eb-3562-ae71-9519e528a61d.jpg)
这里只有一个要注意的就是我们检测peer是否存在,检测了两次,这是我们在第二次得到锁之前与第一次释放锁之后,这段时间内有可能一个新的peer被加入.
struct inet_peer *inet_getpeer(__be32 daddr, int create) { struct inet_peer *p, *n; struct inet_peer **stack[PEER_MAXDEPTH], ***stackptr; ///查找是否存在这个peer节点. read_lock_bh(&peer_pool_lock); p = lookup(daddr, NULL); ///存在的话引用计数加1. if (p != peer_avl_empty) atomic_inc(&p->refcnt); read_unlock_bh(&peer_pool_lock); if (p != peer_avl_empty) { ///如果这个节点在unused中,则从unused中移除它.并返回 unlink_from_unused(p); return p; } ///如果create参数为null,则返回null. if (!create) return NULL; ///开始创建一个新的peer节点. n = kmem_cache_alloc(peer_cachep, GFP_ATOMIC); if (n == NULL) return NULL; n->v4daddr = daddr; atomic_set(&n->refcnt, 1); atomic_set(&n->rid, 0); ///得到合适的包id. n->ip_id_count = secure_ip_id(daddr); n->tcp_ts_stamp = 0; write_lock_bh(&peer_pool_lock); /* Check if an entry has suddenly appeared. */ p = lookup(daddr, stack); if (p != peer_avl_empty) goto out_free; ///加入到avl树. link_to_pool(n); ///初始化它的unused链表. INIT_LIST_HEAD(&n->unused); peer_total++; write_unlock_bh(&peer_pool_lock); ///如果此时内存超过限制,则remove掉链表头的元素(也就是LRU算法了,后面我们会分析cleanup_once这个函数. if (peer_total >= inet_peer_threshold) /* Remove one less-recently-used entry. */ cleanup_once(0); return n; out_free: ........................................ }
接下来我们来看clean_once这个函数.这个函数不仅会被inet_getpeer调用,还会被peer_periodic_timer调用:
static int cleanup_once(unsigned long ttl) { struct inet_peer *p = NULL; /* Remove the first entry from the list of unused nodes. */ spin_lock_bh(&inet_peer_unused_lock); if (!list_empty(&unused_peers)) { __u32 delta; p = list_first_entry(&unused_peers, struct inet_peer, unused); ///计算出这个peer最后一次被使用(也就是操作引用计数)到当前过去了多久. delta = (__u32)jiffies - p->dtime; ///如果这个时间小于传进来的ttl,就不进行任何操作.直接返回(这个ttl也就表示一个在unused链表中的元素在删除前,需要等待多久).而我们上面的inet_getpeer中,传进来的是0,这就会直接删除掉第一个peer. if (delta < ttl) { /* Do not prune fresh entries. */ spin_unlock_bh(&inet_peer_unused_lock); return -1; } list_del_init(&p->unused); ///引用计数-1. atomic_inc(&p->refcnt); } spin_unlock_bh(&inet_peer_unused_lock); if (p == NULL) /* It means that the total number of USED entries has * grown over inet_peer_threshold. It shouldn't really * happen because of entry limits in route cache. */ return -1; ///这个函数就简单介绍一下,先会判断p的引用计数,如果引用计数为1,则说明可以从avl树中删除它,然后将它彻底free掉.当引用技术不为1,则会将它直接加入到unused链表中(这里要注意,它并没有从avl树中删除). unlink_from_pool(p); return 0; }
接下来来看定时器处理函数:
static void peer_check_expire(unsigned long dummy) { unsigned long now = jiffies; int ttl; ///如果内存过大则将ttl设置为最小. if (peer_total >= inet_peer_threshold) ttl = inet_peer_minttl; else ///其实也就是根据使用的内存peer_total,来设置ttl. ttl = inet_peer_maxttl - (inet_peer_maxttl - inet_peer_minttl) / HZ * peer_total / inet_peer_threshold * HZ; while (!cleanup_once(ttl)) { if (jiffies != now) break; } ///这里要注意,我们的定时器时间也是根据当前使用的内存peer_total来进行调节的. if (peer_total >= inet_peer_threshold) peer_periodic_timer.expires = jiffies + inet_peer_gc_mintime; else peer_periodic_timer.expires = jiffies + inet_peer_gc_maxtime - (inet_peer_gc_maxtime - inet_peer_gc_mintime) / HZ * peer_total / inet_peer_threshold * HZ; add_timer(&peer_periodic_timer); }
然后我们来看下ip头的id域在内核中的实现(也就是ip包的id的选择).
内核中实现这个的函数是__ip_select_ident,一般我们调用,都是调用它的包装函数ip_select_ident,这个函数只不过是判断了下DF位(主要是为了处理win95的bug),然后调用__ip_select_ident.
我们来看实现:
void __ip_select_ident(struct iphdr *iph, struct dst_entry *dst, int more) { struct rtable *rt = (struct rtable *) dst; if (rt) { ///如果peer为空,则调用rt_bind_peer新创建一个peer. if (rt->peer == NULL) rt_bind_peer(rt, 1); /* If peer is attached to destination, it is never detached, so that we need not to grab a lock to dereference it. */ if (rt->peer) { ///取得当前的peer的id(也就是ip_id_count域).这里要注意调用inet_getid后,ip_id_count域会自动增长), iph->id = htons(inet_getid(rt->peer, more)); return; } } else printk(KERN_DEBUG "rt_bind_peer(0) @%p\n", __builtin_return_address(0)); ///如果peer创建失败,则调用ip_select_fb_ident ip_select_fb_ident(iph); } static void ip_select_fb_ident(struct iphdr *iph) { static DEFINE_SPINLOCK(ip_fb_id_lock); static u32 ip_fallback_id; u32 salt; spin_lock_bh(&ip_fb_id_lock); ///由于无法得到peer,因此需要跳过peer子系统,直接取得id. salt = secure_ip_id((__force __be32)ip_fallback_id ^ iph->daddr); iph->id = htons(salt & 0xFFFF); ip_fallback_id = salt; spin_unlock_bh(&ip_fb_id_lock); }
来看下ip层的统计信息的表示.
这里的统计信息是通过per cpu的变量ip_statistics来表示的,这里要知道其实网络子系统很多地方都有一个统计信息,这些统计信息的初始化是通过ipv4_mib_init_net来做的.
static __net_init int ipv4_mib_init_net(struct net *net) { ///我们看到有tcp层,ip层等等的统计信息.这里每个统计变量都是 per cpu的. if (snmp_mib_init((void **)net->mib.tcp_statistics, sizeof(struct tcp_mib)) < 0) goto err_tcp_mib; if (snmp_mib_init((void **)net->mib.ip_statistics, sizeof(struct ipstats_mib)) < 0) goto err_ip_mib; if (snmp_mib_init((void **)net->mib.net_statistics, sizeof(struct linux_mib)) < 0) goto err_net_mib; if (snmp_mib_init((void **)net->mib.udp_statistics, sizeof(struct udp_mib)) < 0) goto err_udp_mib; if (snmp_mib_init((void **)net->mib.udplite_statistics, sizeof(struct udp_mib)) < 0) goto err_udplite_mib; if (snmp_mib_init((void **)net->mib.icmp_statistics, sizeof(struct icmp_mib)) < 0) goto err_icmp_mib; if (snmp_mib_init((void **)net->mib.icmpmsg_statistics, sizeof(struct icmpmsg_mib)) < 0) goto err_icmpmsg_mib; tcp_mib_init(net); return 0; ........................... }
每个cpu所统计的信息就是通过他处理的中断所传递的包的信息.
而它提供了几个宏来执行统计,这几个宏分为中端上下文外和内执行.
#define IP_INC_STATS(net, field) SNMP_INC_STATS((net)->mib.ip_statistics, field) //这2个都是在中断上下文外的 #define IP_INC_STATS_BH(net, field) SNMP_INC_STATS_BH((net)->mib.ip_statistics, field) #define IP_ADD_STATS_BH(net, field, val) SNMP_ADD_STATS_BH((net)->mib.ip_statistics, field, val)
最后看下,ip的一些配置工具的实现.
这里只是简单的介绍下,具体的去看源码就好了.
这里有4种途经来进行配置:
1 ioctl
这个主要是被ifconfig使用.对应内核就是netdev的do_ioctl函数.
2 netlink
主要被iproute2使用.比如RTMGRP_IPV4_IFADDR广播组,来通知用户空间,网络地址的改变.
3 /proc文件系统.
也就是/proc/sys/net/ipv4
4 RAPP/BOOTP/DHCP
这些也就是通过远程host来配置ip地址等.
ip子系统还提供了一个inetaddr_chain通知链来通知内核其他子系统(比如路由子系统,以及nerfilter masquerading)ip配置的改变.
发表评论
-
Receive packet steering patch详解
2010-07-25 16:46 12207Receive packet steering简称rp ... -
内核中拥塞窗口初始值对http性能的影响分析
2010-07-11 00:20 9730这个是google的人提出的 ... -
linux 内核tcp拥塞处理(一)
2010-03-12 16:17 9625这次我们来分析tcp的拥塞控制,我们要知道协议栈都是很保守的, ... -
内核tcp协议栈SACK的处理
2010-01-24 21:13 12222上一篇处理ack的blog中我 ... -
内核tcp的ack的处理
2010-01-17 03:06 11217我们来看tcp输入对于ack,段的处理。 先是ack的处理, ... -
内核处理time_wait状态详解
2010-01-10 17:39 6859这次来详细看内核的time_wait状态的实现,在前面介绍定时 ... -
tcp协议栈处理各种事件的分析
2009-12-30 01:29 13664首先我们来看socket如何将一些状态的变化通知给对应的进程, ... -
linux内核sk_buff的结构分析
2009-12-25 00:42 47955我看的内核版本是2.6.32. 在内核中sk_buff表示一 ... -
tcp的输入段的处理
2009-12-18 00:56 8386tcp是全双工的协议,因此每一端都会有流控。一个tcp段有可能 ... -
内核协议栈tcp层的内存管理
2009-11-28 17:13 12133我们先来看tcp内存管理相关的几个内核参数,这些都能通过pro ... -
linux内核定时器的实现
2009-10-31 01:44 10219由于linux还不是一个实时的操作系统,因此如果需要更高精度, ... -
linux内核中tcp连接的断开处理
2009-10-25 21:47 10373我们这次主要来分析相关的两个断开函数close和shotdow ... -
linux内核tcp的定时器管理(二)
2009-10-05 20:52 5458这次我们来看后面的3个定时器; 首先是keep alive定 ... -
linux内核tcp的定时器管理(一)
2009-10-04 23:29 9866在内核中tcp协议栈有6种 ... -
linux 内核tcp接收数据的实现
2009-09-26 20:24 14569相比于发送数据,接收数据更复杂一些。接收数据这里和3层的接口是 ... -
linux 内核tcp数据发送的实现
2009-09-10 01:41 19844在分析之前先来看下SO_RCVTIMEO和SO_SNDTIME ... -
tcp connection setup的实现(三)
2009-09-03 00:34 5231先来看下accept的实现. 其实accept的作用很简单, ... -
tcp connection setup的实现(二)
2009-09-01 00:46 8451首先来看下内核如何处理3次握手的半连接队列和accept队列( ... -
tcp connection setup的实现(一)
2009-08-23 04:10 5855bind的实现: 先来介绍几个地址结构. struct ... -
linux内核中socket的实现
2009-08-15 04:38 21133首先来看整个与socket相关的操作提供了一个统一的接口sys ...
相关推荐
对于Linux系统而言,其内核中的TCP/IP协议栈是实现网络通信的基础,它负责解析、处理并转发网络数据。本文将深入探讨Linux协议栈实现的细节,特别是与TCP/IP协议流程相关的部分。 首先,TCP/IP协议栈可以分为四层,...
本文主要关注的是IP协议的实现,因为它是TCP/IP模型中最核心的部分之一。 #### 二、消息通讯简介 ##### 2.1 网络通讯路径 在Linux系统中,网络通讯路径涉及多个层次,从应用程序到物理层的数据传输过程中会经过多...
在构建Linux平台上的IP摄像头采集处理平台时,我们需要考虑多种技术方案来实现高效且可靠的视频流获取、处理和展示。以下是一些可行的解决方案及其详细说明: 1. USB摄像头采集处理方案: 利用Video4Linux接口是...
本教程将围绕Linux_v4l2接口来探讨如何在Linux环境下处理图像。 1. **Linux_v4l2接口**: Linux_v4l2是V4L(Video for Linux)的升级版,它提供了更强大的功能和更好的性能。V4L2接口为用户空间程序提供了与各种...
### Linux下基于MPEG4IP的实时授课同步技术和实现方法 #### 概述 随着Linux在教育领域的广泛应用,开发基于Linux平台的网络实时授课系统变得尤为重要。本文介绍了一种利用MPEG4IP流媒体处理平台在Linux环境下实现...
9. 通信工程:本文介绍了如何使用通信工程技术实现视频服务器信号传输,该系统使用TCP/IP协议将视频数据传输到客户端。 10. 软件工程:本文介绍了如何使用软件工程技术实现视频监控系统,该系统使用Linux内核和V4L2...
流媒体服务器则负责将这些数据通过TCP/IP协议在网络上传输,实现多用户访问。 V4L2是Linux下的一套视频设备驱动接口,提供了一种统一的方式来控制不同硬件的视频设备,简化了开发过程。使用V4L2接口,可以进行诸如...
本项目主要使用了Linux的Video for Linux Two(V4L2)框架来采集视频源,然后通过QT进行图像处理和TCP/IP协议传输,最终在WEB端展示,实现远程监控的功能。 1. **Video for Linux Two (V4L2)**: V4L2是Linux内核中...
7. **网络通信**:讲述如何配置网络接口,包括以太网控制器驱动和TCP/IP协议栈的配置,实现嵌入式设备的网络功能。 8. **应用程序开发**:介绍如何在Linux环境下进行C/C++编程,开发针对S3C2440的应用程序,以及...
标题 "v4l2onvif-master_ONVIF_ONVIFSERVER_onvifserver_v4l2onvif_" 暗示这是一个关于ONVIF协议实现的项目,特别是与Linux环境下的服务器和客户端相关。ONVIF是Open Network Video Interface Forum(开放网络视频...
总的来说,webcam_v4l2_x264项目是一个实用的示例,展示了如何在Linux环境下利用V4L2接口和x264编码器实现摄像头视频的捕捉、压缩和网络传输,以及在远程端进行解码和播放。这为开发自己的视频流应用或者研究相关...
总的来说,这个项目涉及到了Linux系统编程、QT图形界面开发、RTP协议的实现、V4L2接口的使用以及OpenCV的人脸识别技术,是一个综合性的实时视频传输解决方案。开发这样的系统有助于提升开发者在跨平台实时多媒体应用...
通过TCP/IP协议,它可以将视频流发送到网络,使用户能够通过监控终端(如Web浏览器)访问视频监控画面。 6. **硬件平台搭建**:系统硬件通常包括处理器(如S3C2440A)、存储器(如NAND/SDRAM/NOR Flash)以及视频...
7. 网络传输:如果摄像头用于网络视频传输,需要了解TCP/IP协议和相关网络编程知识。 总结,这个项目是关于在网络摄像头应用中整合v4l2框架,以提升其在Linux环境下的性能和兼容性。这涵盖了从底层硬件驱动到上层...
总的来说,EDNS的内核实现是一项复杂的工作,涉及到对Linux内核代码的深入了解,以及对DNS协议和网络安全的深刻理解。通过这种方式增强DNS功能,可以提高系统的效率和安全性,但同时也需要谨慎处理潜在的负面影响。
NAT-PT技术是IPv4与IPv6互通的重要手段,它通过改变IP地址和端口号来实现不同协议间的转换。DNS-ALG则是在DNS查询过程中帮助处理NAT-PT转换的组件,确保通信的正确性。 传统方案存在的问题是,当IPv6网络扩大、通信...
2. **服务器端的网络传输**:使用TCP/IP协议,通过QTcpServer监听客户端连接,当有连接建立时,将视频流和语音数据实时发送给客户端。 3. **客户端**:使用QTcpSocket建立与服务器的连接,接收视频流并解码显示,...
这里主要采用H.263编码器对视频数据进行压缩,再通过TCP/IP协议栈中的RTP(Real-time Transport Protocol)进行传输。 ##### 1. H.263视频编码 H.263是一种适用于低带宽环境下的视频编码标准,特别适合嵌入式系统中...
可以使用TCP/IP协议栈,结合socket编程,实现实时图像数据的封装和发送。 3. 性能优化与调试 3.1 性能优化:为了提高图像采集速度和处理效率,可以考虑硬件加速、多线程处理、内存管理优化等方法。 3.2 调试与...
- IP over IB (IPoIB):在InfiniBand网络上运行IP协议,实现IP网络的高速传输。 - iSCSI Extensions for RDMA (iSER):通过RDMA技术扩展了iSCSI协议,提高了存储系统的性能和效率。 - Sockets Direct Protocol ...