网络网页前端实时数据传输输时经历了哪些buffer

网络知识什么是Cache? 什么是Buffer? 二者的区别是什么?_百度知道
网络知识什么是Cache? 什么是Buffer? 二者的区别是什么?
我有更好的答案
通常人们所说的Cache就是指缓存SRAM。 SRAM叫静态内存,“静态”指的是当我们将一笔数据写入SRAM后,除非重新写入新数据或关闭电源,否则写入的数据保持不变。 由于CPU的速度比内存和硬盘的速度要快得多,所以在存取数据时会使CPU等待,影响计算机的速度。SRAM的存取速度比其它内存和硬盘都要快,所以它被用作电脑的高速缓存(Cache)。 有了高速缓存,可以先把数据预写到其中,需要时直接从它读出,这就缩短了CPU的等待时间。高速缓存之所以能提高系统的速度是基于一种统计规律,主板上的控制系统会自动统计内存中哪些数据会被频繁的使用,就把这些数据存在高速缓存中,CPU要访问这些数据时,就会先到Cache中去找,从而提高整体的运行速度。一般说来,256K的高速缓存能使整机速度平均提高10%左右Buffer从英文直译过来的意思是“缓冲区”,这里我们将它称为缓冲,因为它不仅是个名词,还是个动词。缓冲区是存储一系列的数据的地方,客户端所获得的数据可以从程序的执行结果直接输出,也可以从缓冲区输出。但是这两种方式在速度上是有差异的:在web中,当一个asp程序被请求的次数不多时,二者基本上没有什么差异,至少我们感觉不出来。但是当有很多人请求一个asp程序时,速度可就不一样了。如果没有缓冲区,那么每个请求asp程序的人的客户端所得到的结果都是asp程序执行一次所得到的结果,而如果预先将asp程序缓冲,那么每个客户端所得到的结果就是缓冲区的结果,不是执行一次程序的结果。比如有1000个用户同时访问一个asp页面,如果这个asp程序没有缓冲,那么程序将被执行一千次,这样服务器的负荷就回加大,从而导致客户端打开页面速度变慢;如果这个asp程序被缓冲了,那么结果就不一样了,每个客户端直接从缓冲区获得数据,服务器将不会因为访问增加而增加程序执行次数,因此客户端打开页面的速度也就比上一种情况要快。这就是Buffer的好处。
Cache和Buffer看起来好像是一种东西,Cache叫做缓存而Buffer叫做缓冲。在硬件概念中,Cache的用途是连接两种速度不同的设备,比如寄存器和内存、CPU和PCI-Bus、IDE总线和硬盘。Buffer的原意是类似弹簧的一种缓冲器,用来减轻或吸收冲击的震动的东西。Buffer是一种数据预存取的方式,它用于临时存储数据并以与接收速度不同的速度传输。Buffer的更新方式可以是按时间间隔自动刷新,而Cache则更讲究“命中率”,将当前时间段使用频繁的少量数据放到高速设备中方便读写。在程序开发中,固然没有什么高速、低速设备,不过数据源是可以有不同读写效率的。对于少量数据,文本文件的读写通常就要比数据库存取效率好,而同样是文本文件读写,在tmpfs上的效率就要比直接的磁盘IO效率好。Buffer更多地体现在进程通信和队列上,很多时候并不是因为接收方没有能力更快地读取,而是没有必要更快地读取。
为您推荐:
其他类似问题
cache的相关知识
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。没有更多推荐了,
不良信息举报
举报内容:
ProtoBuf(protocol buffer) 网络传输协议
举报原因:
原文地址:
原因补充:
最多只允许输入30个字
加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!博客分类:
原文件地址
在看kafka时读到这篇文章,感觉不错就翻译了一下,由于E文不好忘海涵。
本文向你介绍一种有效改进I/O密集型JAVA应用的技术,这种技术被叫做zero copy,适用于linux或者unix平台。Zero copy 可以避免buffers间冗余的数据拷贝,减少用户空间和内核空间的context交互。
许多的应用提供大量的静态内容服务,及从硬盘中读取数据然后将同样的数据写入socket响应。这种类型的服务需要很少的cpu性能,但是会响应低下:内核从硬盘中读取数据然后通过内核-用户通道传输到相关应用,接着应用把这些数据又通过内核-用户通道写到socket中。实际上,在数据从硬盘到socket的传输中应用扮演了一个低效的中间者角色。
每次数据穿越用户-内核管道,它都会被copy,都需要消耗cpu周期和内存空间。幸运的是你通过一种叫做zero copy的技术来减少这些copy。应用通过这项技术将将数据直接从硬盘文件拷贝到socket中,而不再绕行应用。Zero copy提高了应用的表现并且减少了内核模型和用户模式之间的上下文交互。
Java的类库支持在Linux和Unix系统中通过java.nio.channels.FileChannel类的transferTo()方法来实现zero copy。transferTo()方法通过它所在的Channel将数据传入另一个可写入的通道而不将数据流绕行应用。本文首先讲述一般文件传输方法,然后再展示zero-copy技术是如何使用transferTo()获得更好的表现的。
数据传输:传统的方式
考虑这样一种场景,从一个文件中读取数据然后通过网络传输到其他系统中去(这个场景代表了很多应用服务,包括Web应用中提供静态资源,FTP服务,邮件服务等等)。操作的核心体现在list1代码中的两次调用。
Listing 1. 从文件向socket拷贝
File.read(fileDesc, buf, len);
Socket.send(socket, buf, len);
虽然listing1逻辑上非常简单,在整个过程中用户模式和内核模式之间上下文切换共有四次。Figure 1 展示了数据是如何从文件到socket的。
Figure 1. 传统的数据copy方式
Figure 2. 传统的上下文切换
read()调用触发了从用户模式到内核模式的上下文交换(见Figure2)。在内核空间内一个sys_read()调用完成了从文件中读取数据。这第一次copy(见Figure1)是通过DMA引擎完成的,DMA引擎从硬盘中读取数据然后将他们存到内核地址空间的buffer中。
被请求的数据从内核的read buffer 拷贝到 用户buffer,并且read()调用返回。这个调用返回又引发了一次从内核到用户模式的交互。现在数据已经被加载在用户空间的buffer中了。
send()socket请求将会引起从用户模式向内核空间的切换。第三次拷贝又将数据传到内核地址空间。但是这次数据是呗写入不同的buffer,目标为socket。
send()系统调用,创建第四次上下文交换。第四次复制为异步的,DMA引擎将内核buffer中的数据写入协议引擎。
使用了中间层的内核buffer(而不是直接将数据写入用户buffer)可能看起来不直接。但是内核buffer的引入确实提高了系统表现。在读取端使用中间层buffer,使得内核buffer扮演了一个“预读高速缓存”的角色,当应用还没有请求如此多的数据。这极大提高了当请求数据量小于内核buffer时的性能。在写端的中间buffer则允许异步的完成写入。
不幸的是,当被请求的数据量大于内核buffer大小的时候,他本身成为了性能的瓶颈。数据被传到application前,在硬盘、内核buffer和用户buffer间的多次拷贝。
Zero copy 通过减少冗余的数据的拷贝提高性能。
数据传输:zero copy 方式
如果再次审视传统场景,你可能会注意到其实第二次和第三次数据拷贝不是必须的。应用除了缓存数据再将它写回socket buffer之外没有其他操作。其实,数据可以直接从read buffer直接写入socket buffer。这个transferTo()方法正是帮你做到了这一点。listing 2 向我们展示了transferTo()的细节:
listing 2. The transferTo() 方法
public void transferTo(long position, long count, WritableByteChannel target);
transferTo()将数据从file channel 写入给定的可写入channel 。实际上它依赖于系统对zero copy的支持,在unix和linux系统中,这个调用被叫做sendfile()系统调用。在listing3中,将具体展示:
listing 3. The sendfile() 系统调用
#include &sys/socket.h&
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
listing1中的file.read()和socket.send()方法被简单的transferTo()调用取代,详见listing4
listing 4. Using transferTo() to copy data from a disk file to a socket
transferTo(position, count, writableChannel);
Figure 3 展示了transferTo()的方法是如何被使用的
Figure 3. 数据拷贝通过 transferTo()
Figure 4 展示了在transferTo()中如何完成上下文切换的
Figure 4. 上下文切换在 transferTo()
以下步骤将展示在transferTo()中具体是如何完成上下文切换的大帅府
transferTo()中文件内容是被DMA引擎拷贝到一个可读的buffer。然后数据被内核直接写入与输出socket关联的内核buffer中。
第三次拷贝发生在DMA引擎将数据从socket buffer中拷贝到协议引擎。
这里有一个改进:我们将上下文切换从4次减少到2次,将copy数从4降低到3(只有一次用到了cpu)。但是仍然未达到我们的zero copy的目标。我们可以通过内核来减少数据重复,如果网卡支持进一步操作。在linux内核2.4及以后版本,socket buffer的descriptor被修改以来适应这种需求。这一修改无法减少上下文切换但是可以减少降低复制数据。在用户端使用是无区别的,但是内部已经发生改变:
transferTo() 中文件内容被DMA引擎加载入内核buffer。
数据不再被拷入socket buffer。被取代的,只有descriptor关于位置和长度的信息被附加到socket buffer。这DMA引擎将数据直接从内核buffer拷入协议引擎,从而减少了cpu拷贝。
Figure 5. Data copies when transferTo() and gather operations are used
Building a file server
现在用zero copy做个联系,运用同样的例子。TraditionalClinet.java和TraditionServer.java是基于传统方法的,使用File.read()和Socket.send()。TraditionServer.java是一个服务程序在特定的端口监听客户端的请求,然后读取4KB的数据。TraditionalClinet.java连接到服务器端,从文件中读取4KB的数据,然后传输内容到服务器端。
同样,TransferToServer.java和TransferToClient.java完成同样的功能,但是代替的使用transferTo()方法(系统中使用sendfile()调用)来讲数据从服务器写入客户端。
测试使用2.6的内核的linux,每次测试时间不少于百万秒级并且transferTo(),下表将展示结果:
Table 1. Performance comparison: Traditional approach vs. zero copy
Normal file transfer (ms)
transferTo (ms)
如你所见,transferTo()比较传统观方法大约节省65%的时间。
我们展示了使用transferTo()的性能和传统的方式比较。中间buffer的拷贝(隐藏于内核)带来了可观的消耗。在应用中2个channel之间数据传输,zero-copy拷贝技术可以得到显著的性能提升。
浏览: 4175 次
来自: 北京
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'当前位置: >>
linux内核-网络数据包传送通道解析
网络栈自下而上的数据包传输通道具体如下:(1) :硬件监听物理传输介质,进行数据的接收,当完全接收一个数据包后,产 生中断(这个过程完全由网络设备硬件负责) 。 (2) :中断产生后,系统调用驱动程序注册的中断处理程序进行数据接收,一般 在接收例程中,完成数据从硬件缓冲区到内核缓冲区的复制,将其封装为内核特 定结构(sk_buff 结构) ,最后调用链路层提供的借口函数 netif_rx,将数据包由 驱动层传递给链路层。 (3) :在 netif_tx 中,为了减少中断执行时间(netif_rx 函数一般在驱动层中断例 程中被调用) ,该函数将数据包暂存在链路层维护的一个数据包队列中(具体的 由 backlog 变量指向) 并启动专门进行网络处理的下半部分对 backlog 队列中数 , 据包进行处理。 (4) :网络下半部分执行函数 net_bh,从 backlog 队列中取数据包,在进行完本 层相关处理后,遍历 ptype_base 指向的网络层协议(由 packet_type 结构表示) 队列,进行协议号的匹配,找到协议号匹配的 packet_type 结构,调用结构中的 接收函数,完成数据包从链路层到网络层的传递。对于 ARP 协议,那么调用的 是 arp_rev 函数,IP 协议则是 ip_rcv 函数。 (5)假设数据包使用的时 IP 协议, : 那么从链路层传递到网络层时, 将进入 ip_rcv 总入口函数,ip_rcv 完成本层的处理后,以本层首部(IP 首部)中标识的传输层 协 议 号 为 散 列 值 , 对 inet_protos 散 列 表 进 行 匹 配 查 询 , 以 寻 找 到 合 适 的 inet_protocol 结构,进而调用结构中接收函数,完成数据包从网络层到传输层的 传递对于 UDP 协议,调用 udp_rcv 函数,TCP 协议调用 tcp_rcv 函数,ICMP 协 议调用 icmp_rcv 函数,IGMP 协议调用 igmp_rcv 函数。ICMP ,IGMP 是网络层协 议,但其都是封装在 IP 协议中,所以当做传输层看待。 (6) 假如数据包使用的时 TCP 传输层协议, : 那么此时将进入 tcp_rcv 函数。 TCP 协议的套接字对应 sock 结构都被挂入 tcp_prot 全局变量表示的 proto 结构之 sock_array 数组中, 采用以本地端口号为索引的插入方式, 所以当 tcp_rec 函数接 收到一个数据包,在完成必要的检查和处理后,其将以 TCP 首部中得目的端口 号 (对一个接收的数据包而言, 其目的端口号就是本地所使用的端口号) 为索引, 在 tcp_prot 对应 sock 结构之 sock_array 数组中得到正确的 sock 结构队列,再辅 之以其他条件遍历该队列进行对应的 sock 结构的查询,在得到匹配的 sock 结构 后,将数据包挂入该 sock 结构的缓存队列中(由 sock 结构中 receive_queue 字段 指向) ,从而完成数据包的最终接收。数据包发送通道是如何创建的:即是上层向下层提供发送接口函数共下层直接进行调用,入驱动层通过 device 结构 hard_start_xmit 函数指针向链 路层提 供发 送函数 , 链路层 提供 dev_queue_xmit 发送函数供网络层调用,而网络层提供 ip_queue_xmit 函数供传 输层调用。 基于 TCP 协议的数据包 从应用层调用 write 函数开始,先经历 sock_write(net/socket.c) 、inet_write (net/inet/af_inet.c)和 tcp_write( net/inet/tcp.c ),只有到达 tcp_write 函数时,才进 行数据的真正处理,sock_write,inet_write 只是进行简单的检查处理。 (只有在传 输层才能进行数据的封装,才知道需要预留多大的内核空间创建一个数据帧) 。 对于七层分层方式,在表示层所进行的加密等操作此处没有讨论,本版本网 络栈实现是针对四层模型。 (1)tcp_write 函数完成数据帧的封装。将数据从用户缓冲区复制到内核缓 (1)不经过 sock 结构之 冲区,并封装在 sk_buff 结构中,根据网络的拥塞情况: write_queue 队列直接被发送出去, 数据包被暂时缓存在 write_queue 队列中, (2) 稍后发送。其后调用 ip_queue_xmit 函数将数据包发往下层――网络层处理。 (2)ip_queue_xmit 函数继续对数据帧进行完善后,调用 dev_queue_xmit 函 数将数据包送往链路层进行处理,同时将数据包缓存与 sock 结构之 send_head 队列。这个缓存的目的在于 TCP 协议需要保证可靠性数据传输,防止数据包丢 失,一旦发生丢失,就需要重新发送 send_head 队列的数据包。 数 据 包 不 会 同 时 存 在 于 write_queue 和 send_head 中 , 数 据 包 传 给 dev_queue_xmit 后,就从 write_queue 队列中删除。 (3)dev_queue_xmit 函数完成其本层的处理后,调用发送设备 device 结构 之 hard_start_xmit 指针指向的具体硬件数据数据发送函数。linux 内核网络协议栈代码分析一.linux 内核网络栈代码的准备知识 1. linux 内核 ipv4 网络部分分层结构:BSD socket 层: 这一部分处理 BSD socket 相关操作,每个 socket 在内核中以 struct socket 结构体现。这一部分的文件 主要有:/net/socket.c /net/protocols.c etc INET socket 层:BSD socket 是个可以用于各种网络协议的接口,而当用于 tcp/ip,即建立了 AF_INET 形式的 socket 时, 还需要保留些额外的参数,于是就有了 struct sock 结构。文件主要 有:/net/ipv4/protocol.c /net/ipv4/af_inet.c /net/core/sock.c etc TCP/UDP 层: 处理传输层的操作, 传输层用 struct inet_protocol 和 struct proto 两个结构表示。文件主要 有:/net/ipv4/udp.c /net/ipv4/datagram.c /net/ipv4/tcp.c /net/ipv4/tcp_input.c /net/ipv4//tcp_output.c /net/ipv4/tcp_minisocks.c /net/ipv4/tcp_output.c /net/ipv4/tcp_timer.c etc IP 层:处理网络层的操作,网络层用 struct packet_type 结构表示。文件主要 有:/net/ipv4/ip_forward.c ip_fragment.c ip_input.c ip_output.c etc. 数据链路层和驱动程序:每个网络设备以 struct net_device 表示,通用的处理 在 dev.c 中,驱动程序都在/driver/net 目 录下。 2. 两台主机建立 udp 通信所走过的函数列表 ^ | | | | | | | | | | | | | | | | | |sys_read fs/read_write.c sock_read net/socket.c sock_recvmsg net/socket.c inet_recvmsg net/ipv4/af_inet.c udp_recvmsg net/ipv4/udp.c skb_recv_datagram net/core/datagram.c ------------------------------------------sock_queue_rcv_skb include/net/sock.h udp_queue_rcv_skb net/ipv4/udp.c udp_rcv net/ipv4/udp.c ip_local_deliver_finish net/ipv4/ip_input.c ip_local_deliver net/ipv4/ip_input.c ip_recv net/ipv4/ip_input.c net_rx_action net/dev.c ------------------------------------------netif_rx net/dev.c el3_rx driver/net/3c309.c el3_interrupt driver/net/3c309.c========================== | | | | | | | | | sys_write sock_writev sock_sendmsg inet_sendmsg udp_sendmsg ip_build_xmit output_maybe_reroute ip_output ip_finish_output fs/read_write.c net/socket.c net/socket.c net/ipv4/af_inet.c net/ipv4/udp.c net/ipv4/ip_output.c net/ipv4/ip_output.c net/ipv4/ip_output.c net/ipv4/ip_output.c | | | Vdev_queue_xmit net/dev.c -------------------------------------------el3_start_xmit driver/net/3c309.c3. 网络路径图、重要数据结构 sk_buffer 及路由介绍 linux-net.pdf 第 2.1 章 第 2.3 章 第 2.4 章 4. 从连接、发送、到接收数据包的过程 linux-net.pdf 第 4、5、6 章详细阐述二.linux 的 tcp-ip 栈代码的详细分析 1.数据结构(msghdr,sk_buff,socket,sock,proto_ops,proto) bsd 套接字层,操作的对象是 socket,数据存放在 msghdr 这样的数据结构: 创建 socket 需要传递 family,type,protocol 三个参数,创建 socket 其实就是 创建一个 socket 实例, 然后创建一个文件描述符结构, 并且互相建立一些 关联, 即建立互相连接的指针,并且初始化这些对文件的写读操作映射到 socket 的 read,write 函数上来。 同时初始化 socket 的操作函数(proto_ops 结构),如 果传入的 type 参数是 STREAM 类型,那么就初始化为 SOCKET-&ops 为 inet_stream_ops,如果是 DGRAM 类型,则 SOCKET-ops 为 inet_dgram_ops。对于 inet_stream_ops 其实是一个结 构体,包含了 stream 类型的 socket 操作 的一些入口函数,在这些函数里主要 做的是对 socket 进行相关的操作, 同时通过调用下面提到的 sock 中的相关操作 完成 socket 到 sock 层的传 递。比如在 inet_stream_ops 里有个 inet_release 的操作,这个操作除了释放 socket 的类型空间操作外,还通过调用 socket 连 接的 sock 的 close 操作,对于 stream 类型来说,即 tcp_close 来关闭 sock 释放 sock。 创建 socket 同时还创建 sock 数据空间,初始化 sock,初 始化过程主要做的事 情是初始化三个队列,receive_queue(接收到的数据包 sk_buff 链表队 列),send_queue(需要发送数据包的 sk_buff 链表队列),backlog_queue(主要 用于 tcp 中三次握手成功的那些数据包,自己猜的),根据 family、type 参数,初 始 化 sock 的操作,比如对于 family 为 inet 类型的,type 为 stream 类型的, sock-&proto 初始化为 tcp_prot.其中 包括 stream 类型的协议 sock 操作对应的 入口函数。 在一端对 socket 进行 write 的过程中,首先会把要 write 的字符串缓冲区整理 成 msghdr 的数据结构形式(参见 linux 内核 2.4 版源代码分析大全),然后调用 sock_sendmsg 把 msghdr 的数据传送至 inet 层, 对于 msghdr 结构中数据区中的 每个数据包,创建 sk_buff 结构,填充数据,挂至发送队列。一层层往下层协议 传递。一下每层协议不再对数据进行拷贝。而是对 sk_buff 结构进行操作。 inet 套接字及以下层 数据存放在 sk_buff 这样的数据结构里: 路由: 在 linux 的路由系统主要保存了三种与路由相关的数据, 第一种是在物理上 和本机相连接的主机地址信息表, 第二种是保存了在网络访问中判断一个网络地 址应该走什么路由的数据表; 第三种是最新使用过的查询路由地址的缓存地址数 据表。 1.neighbour 结构 neighbour_table{ }是一个包含和本机所连接的所有邻 元素的信息的数据结构。该结构中有个元素是 neighbour 结构的数组,数组的每 一个元素都是一个对应于邻机的 neighbour 结构,系统中由于协议的不同,会 有不同的判断邻居的方式,每种都有 neighbour_table{}类型的实例,这些实例 是通过 neighbour_table{}中的指针 next 串联起来的。在 neighbour 结构中, 包含有与该邻居相连的网络接口设备 net_device 的 指针,网络接口的硬件地 址,邻居的硬件地址,包含有 neigh_ops{}指针,这些函数指针是直接用来连接 传输数据的,包含有 queue_xmit(struct * sk_buff)函数入口地址,这个函数 可能会调用硬件驱动程序的发送函数。 2.FIB 结构 在 FIB 中保存的是最重要的路由规则,通过对 FIB 数据的查找和 换算,一定能够获得路由一个地址的方法。系统中路由一般采取的手段是:先到 路由缓存中查找 表项,如果能够找到,直接对应的一项作为路由的规则;如果 不能找到,那么就到 FIB 中根据规则换算传算出来,并且增加一项新的,在路由 缓存中将项目添加进 去。 3.route 结构(即路由缓存中的结构)数据链路层: net_device{}结构,对应于每一个网络接口设备。这个结构中包含很多可以 直接获取网卡信息的函数和变量,同时包含很多对于网卡操作的函数,这些 直 接指向该网卡驱动程序的许多函数入口,包括发送接收数据帧到缓冲区等。当这 些完成后,比如数据接收到缓冲区后便由 netif_rx(在 net/core /dev.c 各种设 备驱动程序的上层框架程序)把它们组成 sk_buff 形式挂到系统接收的 backlog 队列然后交由上层网络协议处理。同样,对于上层 协议处理下来的那些 sk_buff。便由 dev_queue_xmit 函数放入网络缓冲区,交给网卡驱动程序的发送 程序处理。 在系统中存在一张链表 dev_base 将系统中所有的 net_device{}结构连在一 起。对应于内核初始化而言,系统启动时便为每个所有可能支持的网 络接口设 备申请了一个 net_device{}空间并串连起来,然后对每个接点运行检测过程, 如果检测成功,则在 dev_base 链表中保留这个接点,否 则删除。对应于模块加 载来说,则是调用 register_netdev()注册 net_device,在这个函数中运行检测 过程,如果成功,则加到 dev_base 链表。否则就返回检测不到信息。删除同理, 调用 unregister_netdev。: 想仔细看看内核网络部分的代码,于是参照网上搜的一些 : 文档,从初始化开始(kernel 2.4.18) : : init();(xxx/main.c) : : : : : : : : : : : : : : | v xxx | v do_basic_setup();(xxx/main.c) | v xxx | v sock_init();(xxx/net/socket.c) | v : : : : : : : : : : : : : : : : : : : : : :for (i = 0; i & NPROTO; i++) net_families[i] = NULL; | v xxx | v sk_init(); | v skb_init(); | v xxx | --------/ | v start_context_thread(); | v do_initcalls();(xxx/main.c) :好像是 IP、ARP 等协议以及网卡驱动都是在 do_initcalls()这个函数中被调用的, : : : : : : : : : : : : : : } 问题是,我找了半天也没有找到__initcall_start 这个冬冬指到了 而这个函数就是一个循环: static void __init do_initcalls(void) { initcall_t * call = &__initcall_ do { (*call)(); call++; } while (call & &__initcall_end); /* Make sure there is no pending stuff from the initcall sequence */ flush_scheduled_tasks();哪里? : 虽然我知道对 IP 的初始化在 af_inet.c(xxx/net/ipv4)这个文件中被初始化,可是 : 在 : xxx/drivers/block/genhd.c 这个文件中作初始化的,那么具体的 它是怎样被调用的昵? 还有底层通用的网络接口设备好像是 某种网卡的 : : 驱动在什么地方被调用呢? 还请各位大侠多多指教 !! !2.启动分析 2.1 初始化进程 : start-kernel(main.c)----&do_basic_setup(main.c)----&sock_init(/net/so cket.c)----&do_initcalls(main.c) void __init sock_init(void) { printk(KERN_INFO &Linux NET4.0 for Linux 2.4\n&); printk(KERN_INFO &Based upon Swansea University Computer Society NET3.039\n&); /* * Initialize all address (protocol) families. 每一项表示的是针对一个 地址族的操作集合,例如对于 ipv4 来说,在 net/ipv4/af_inet.c 文件中的函数 inet_proto_init()就调用 sock_register()函数将 inet_families_ops 初始化 到属于 IPV4 的 net_families 数组中的一项。 */ for (i = 0; i & NPROTO; i++) net_families[i] = NULL; /* * Initialize sock SLAB cache.初始化对于 sock 结构预留的内存的 slab 缓 存。 */ sk_init(); #ifdef SLAB_SKB /* * Initialize skbuff SLAB cache 初始化对于 skbuff 结构的 slab 缓存。以 后对于 skbuff 的申请可以通过函数 kmem_cache_alloc()在这个缓存中申请空 间。 */ skb_init(); #endif /* * Wan router layer. */ #ifdef CONFIG_WAN_ROUTER wanrouter_init(); #endif /* * Initialize the protocols module. 向系统登记 sock 文件系统,并且将其 安装到系统上来。 */ register_filesystem(&sock_fs_type); sock_mnt = kern_mount(&sock_fs_type); /* The real protocol initialization is performed when * do_initcalls is run. */ /* * The netlink device handler may be needed early. */ #ifdef CONFIG_NET rtnetlink_init(); #endif #ifdef CONFIG_NETLINK_DEV init_netlink(); #endif #ifdef CONFIG_NETFILTER netfilter_init(); #endif #ifdef CONFIG_BLUEZ bluez_init(); #endif /*yfhuang ipsec*/ #ifdef CONFIG_IPSEC pfkey_init(); #endif /*yfhuang ipsec*/ }2.2 do_initcalls() 中做了其它的初始化,其中包括 协议初始化,路由初始化,网络接口设备初始化 (例如 inet_init 函数以_init 开头表示是系统初始化时 做,函数结束后跟 module_init(inet_init),这是一个宏,在 include/linux/init.c 中定义,展开 为 _initcall(inet_init),表示这个函数在 do_initcalls 被调用了) 2.3 协议初始化 此处主要列举 inet 协议的初始化过程。 static int __init inet_init(void) { struct sk_buff *dummy_ struct inet_protocol *p; struct inet_protosw *q; struct list_head *r; printk(KERN_INFO &NET4: Linux TCP/IP 1.0 for NET4.0\n&); if (sizeof(struct inet_skb_parm) & sizeof(dummy_skb-&cb)) { printk(KERN_CRIT &inet_proto_init: panic\n&); return -EINVAL; } /* * Tell SOCKET that we are alive... 注册 socket,告诉 socket inet 类型 的地址族已经准备好了 */ (void) sock_register(&inet_family_ops); /* * Add all the protocols. 包括 arp,ip、ICMP、UPD、tcp_v4、tcp、igmp 的初始化,主要初始化各种协议对应的 inode 和 socket 变量。 其中 arp_init 完成系统中路由部分 neighbour 表的初始化 ip_init 完成 ip 协议的初始化。在这两个函数中,都通过定义一个 packet_type 结构的变量将这种数据包对应的协议发送数据、允许发送设备都做初始化。 */ printk(KERN_INFO &IP Protocols: &); for (p = inet_protocol_ p != NULL;) { struct inet_protocol *tmp = (struct inet_protocol *) p-& inet_add_protocol(p); printk(&%s%s&,p-&name,tmp?&, &:&\n&); p = } /* Register the socket-side information for inet_create. */ for(r = &inetsw[0]; r & &inetsw[SOCK_MAX]; ++r) INIT_LIST_HEAD(r); for(q = inetsw_ q & &inetsw_array[INETSW_ARRAY_LEN]; ++q) inet_register_protosw(q); /* * Set the ARP module up */ arp_init(); /* * Set the IP module up */ ip_init(); tcp_v4_init(&inet_family_ops); /* Setup TCP slab cache for open requests. */ tcp_init(); /* * Set the ICMP layer up */ icmp_init(&inet_family_ops); /* I wish inet_add_protocol had no constructor hook... I had to move IPIP from net/ipv4/protocol.c :-( --ANK */ #ifdef CONFIG_NET_IPIP ipip_init(); #endif #ifdef CONFIG_NET_IPGRE ipgre_init(); #endif /* * Initialise the multicast router */ #if defined(CONFIG_IP_MROUTE) ip_mr_init(); #endif /* * Create all the /proc entries. */ #ifdef CONFIG_PROC_FS proc_net_create (&raw&, 0, raw_get_info); proc_net_create (&netstat&, 0, netstat_get_info); proc_net_create (&snmp&, 0, snmp_get_info); proc_net_create (&sockstat&, 0, afinet_get_info); proc_net_create (&tcp&, 0, tcp_get_info); proc_net_create (&udp&, 0, udp_get_info); #endif /* CONFIG_PROC_FS */ ipfrag_init(); return 0; } module_init(inet_init);2.4 路由初始化(包括 neighbour 表、 表、 FIB 和路由缓存表的初始化工作) 2.4.1 rtcache 表 ip_rt_init()函数 在 net/ipv4/ip_output 中 调用,net/ipv4/route.c 中定义 2.4.2 FIB 初始化 在 ip_rt_init()中调用 在 net/ipv4/fib_front.c 中定义 2.4.3 neigbour 表初始化 arp_init()函数中定义 2.5 网络接口设备初始化 在系统中网络接口都是由一个 dev_base 链表进行管理的。通过 内核的启动方式也是通过这个链表进行操作的。在系统启动之初,将所有内核能 够支持的网络接口都初始化成这个链表中的 一个节点,并且每个节点都需要初 始化出 init 函数指针,用来检测网络接口设备。然后,系统遍历整个 dev_base 链表, 对每个节点分别调用 init 函 数指针, 如果成功, 证明网络接口设备可用, 那么这个节点就可以进一步初始化,如果返回失败,那么证明该网络设备不存在 或是不可用,只能将该节点删除。启动 结束之后,在 dev_base 中剩下的都是可 以用的网络接口设备。 2.5.1 do_initcalls----&net_dev_init()(net/core /dev.c)------&ethif_probe()(drivers/net/Space.c,在 netdevice{}结构的 init 中调 用,这边 ethif_probe 是以太网卡针对的调用)3.网络设备驱动程序(略)4.网络连接 4.1 连接的建立和关闭 tcp 连接建立的代码如下: server=gethostbyname(SERVER_NAME); sockfd=socket(AF_INET,SOCK_STREAM,0); address.sin_family=AF_INET; address.sin_port=htons(PORT_NUM); memcpy(&address.sin_addr,server-&h_addr,server-&h _length); connect(sockfd,&address,sizeof(address)); 连接的初始化与建立期间主要发生的事情如下: 1)sys_socket 调用:调用 socket_creat(),创建出一个满足传入参数 family、type、和 protocol 的 socket,调用 sock_map_fd()获取 一个未被使用 的文件描述符,并且申请并初始化对应的 file{}结构。 2) sock_creat(): 创建 socket 结 构, 针对每种不同的 family 的 socket 结构的初始化,就需要调用不同的 create 函数来完成。对应于 inet 类型的地址 来说,在网络协议初始化 时调用 sock_register()函数中完成注册的定义如下: struct net_proto_family inet_family_ops={ PF_INET; inet_create };所以 inet 协议最后会调用 inet_create 函数。 3)inet_create: 初始化 sock 的状态设置为 SS_UNCONNECTED,申请一个 新的 sock 结构,并且初始化 socket 的成员 ops 初始化为 inet_stream_ops,而 sock 的成员 prot 初始化为 tcp_prot。然后调用 sock_init_data,将该 socket 结构的变 量 sock 和 sock 类型的变量关联起来。 4)在系统初始化完毕后便是进行 connect 的工 作,系统调用 connect 将一个和 socket 结构关联的文件描述符和一个 sockaddr{}结构的地址对应的远 程机器相关联,并且调用各个协议自己 对应的 connect 连接函数。对应于 tcp 类型,则 sock-&ops-&connect 便为 inet_stream_connect。5)inet_stream_connect: 得到 sk,sk=sock-&sk,锁定 sk,对自动获取 sk 的端口号存放在 sk-&num 中,并且用 htons()函数转换存放在 sk-&sport 中。然后调用 sk-&prot-&connect()函数指针,对 tcp 协议来说就是 tcp_v4_connect()函 数。然后将 sock-&state 状态字设置为 SS_CONNECTING,等 待后面一系列的处理完成之后,就将状态改成 SS_CONNECTTED。 6) tcp_v4_connect():调用函数 ip_route_connect(),寻找合适的路 由存放在 rt 中。ip_route_connect 找两 次,第一次找到下一跳的 ip 地址,在 路由缓存或 fib 中找到,然后第二次找到下一跳的具体邻居,到 neigh_table 中找到。然后申请出 tcp 头的空 间存放在 buff 中。将 sk 中相关地址数据做一 些针对路由的变动,并且初始化一个 tcp 连接的序列号,调用函数 tcp_connect (),初始化 tcp 头,并设置 tcp 处理需要的定时器。一次 connect()建立的 过程就结束了。 连接的关闭主要如下: 1)close: 一个 socket 文件描述符对应的 file{}结构中,有一个 file_operations{}结构的成员 f_ops, 它的初始化关闭函数为 sock_close 函数。 2)sock_close:调用函数 sock_release(),参数为一个 socket{}结构 的指针。 3)sock_release:调用 inet_release,并释放 socket 的指针和文件 空间 4) inet_release: 调用和该 socket 对应协议的关闭函数 inet_release, 如果是 tcp 协议,那么调用的是 tcp_close;最后释放 sk。 4.2 数据发送流程图 各层主要函数以及位置功能说明: 1)sock_write:初始化 msghdr{}结构 net/socket.c 2)sock_sendmsg:net/socket.c 3)inet_sendmsg:net/ipv4/af_net.c 4)tcp_sendmsg:申请 sk_buff{}结构的空间,把 msghdr{}结构中的数 据填入 sk_buff 空间。net/ipv4/tcp.c 5)tcp_send_skb:net/ipv4/tcp_output.c 6)tcp_transmit_skb:net/ipv4/tcp_output.c 7)ip_queue_xmit:net/ipv4/ip_output.c 8)ip_queue_xmit2:net/ipv4/ip_output.c 9)ip_output:net/ipv4/ip_output.c 10)ip_finish_output:net/ipv4/ip_output.c 11)ip_finish_output2:net/ipv4/ip_output.c 12)neigh_resolve_output:net/core/neighbour.c 13)dev_queue_xmit:net/core/dev.c4.3 数据接收流程图各层主要函数以及位置功能说明: 1)sock_read:初始化 msghdr{}的结构类型变量 msg, 并且将需要接收的 数据存放的地址传给 msg.msg_iov-&iov_base. net/socket.c 2)sock_recvmsg: 调用函数指针 sock-&ops-&recvmsg()完成在 INET Socket 层的数据接收过程.其中 sock-&ops 被初始化为 inet_stream_ops,其成员 recvmsg 对应的函数实现为 inet_recvmsg()函数. net/socket.c 3)sys_recv()/sys_recvfrom():分别对应着面向连接和面向无连接的 协议两种情况. net/socket.c 4)inet_recvmsg:调用 sk-&prot-&recvmsg 函数完成数据接收,这个函 数对于 tcp 协议便是 tcp_recvmsg net/ipv4/af_net.c 5)tcp_recvmsg:从网络协议栈接收数 据的动作,自上而下的触发动作 一直到这个函数为止,出现了一次等待的过程.函数 tcp_recvmsg 可能会被动地 等待在 sk 的接收数据队列上,也就是 说,系统中肯定有其他地方会去修改这个 队列使得 tcp_recvmsg 可以进行下去.入口参数 sk 是这个网络连接对应的 sock{} 指针,msg 用于存放 接收到的数据.接收数据的时候会去遍历接收队列中的数据, 找到序列号合适的. 但读取队列为空时 tcp_recvmsg 就会调用 tcp_v4_do_rcv 使用 backlog 队列填充接收队列. 6)tcp_v4_rcv:tcp_v4_rcv 被 ip_local_deliver 函数调用,是从 IP 层 协议向 INET Socket 层提交的&数据到&请求,入口参数 skb 存放接收到的数 据,len 是接收的数据的长度,这个函数首先移动 skb-&data 指针,让它 指向 tcp 头,然后更新 tcp 层的一些数据统计,然后进行 tcp 的一些值的校验.再从 INET Socket 层中已经建立的 sock{}结构变量中查找正在等待当前到达数据的哪一项. 可能这个 sock{}结构已经建立,或者还处于监听端口、等待数据 连接的状态。 返回的 sock 结构指针存放在 sk 中。然后根据其他进程对 sk 的操作情况,将 skb 发送到合适的位置.调用如下: TCP 包接收器(tcp_v4_rcv)将 TCP 包投递到目的套接字进行接收处理. 当套接字正被用户锁定,TCP 包将暂时排入该套接字的后备队列 (sk_add_backlog).这时如果某一用户线程企图锁定该套接字 (lock_sock),该 线程被排入套接字的后备处理等待队列(sk-&lock.wq).当用户释放上锁的套接 字时 (release_sock,在 tcp_recvmsg 中调用),后备队列中的 TCP 包被立即注入 TCP 包处理器(tcp_v4_do_rcv)进行处 理,然后唤醒等待队列中最先的一个用户 来获得其锁定权. 如果套接字未被上锁,当用户正在读取该套接字时, TCP 包将 被排入套接字的预备队列(tcp_prequeue),将其传递到该用户线程上下文中进行 处理.如果添加到 sk-&prequeue 不成 功,便可以添加到 sk-&receive_queue 队 列中(用户线程可以登记到预备队列,当预备队列中出现第一个包时就唤醒等待 线程.) /net/tcp_ipv4.c 7)ip_rcv、ip_rcv_finish: 从以太网接收数据,放到 skb 里,作 ip 层的一些数据及选项检查,调用 ip_route_input()做路由处理,判断是进行 ip 转发还是将数据传递到 高一层的协议.调用 skb-&dst-&input 函数指针,这个指 针的实现可能有多种情况,如果路由得到的结果说明这个数据包应该转发到其 他主机,这里的 input 便是 ip_如果数据包是给本机的,那么 input 指针 初始化为 ip_local_deliver 函数./net /ipv4/ip_input.c 8)ip_local_deliver、 ip_local_deliver_finish:入口参数 skb 存放 需要传送到上层协议的数据,从 ip 头中获取是否已经分拆的信息,如果已经分拆, 则调 用函数 ip_defrag 将数据包重组。 然后通过调用 ip_prot-&handler 指针调 用 tcp_v4_rcv(tcp)。ip_prot 是 inet_protocol 结构指针,是用来 ip 层登记协 议的,比如由 udp,tcp,icmp 等协议。 /net/ipv4/ip_input.cinit();(xxx/main.c) | v xxx | v do_basic_setup();(xxx/main.c) | v xxx | v sock_init();(xxx/net/socket.c) | v for (i = 0; i & NPROTO; i++) net_families[i] = NULL; | v xxx | v sk_init(); | v skb_init(); | v xxx | --------/ | v start_context_thread(); | v do_initcalls();(xxx/main.c) 好像是 IP、ARP 等协议以及网卡驱动都是在 do_initcalls()这个函数中被调用的, 而这个函数就是一个循环: static void __init do_initcalls(void) { initcall_t * call = &__initcall_ do { (*call)(); call++; } while (call & &__initcall_end); /* Make sure there is no pending stuff from the initcall sequence */ flush_scheduled_tasks(); } 问题是,我找了半天也没有找到__initcall_start 这个冬冬指到了哪里? 看一下 include/linux/init.h 和 arch/*/vmlinux.lds 应该就比较清楚了虽然我知道对 IP 的初始化在 af_inet.c(xxx/net/ipv4)这个文件中被初始化, 可是它是怎样 被调用的昵? 还有底层通用的网络接口设备好像是在 xxx/drivers/block/genhd.c 这个文件中 作初始化的,那么具体的某种网卡的驱动在什么地方被调用呢? 对 于 直 接 编 译 到 内 核 中 的 网 卡 驱 动 , 内 核 中 构 造 了 一 个 struct net_device 的 链 表 (drivers/net/Space.c)( 表 头 为 dev_base),device_init(genhd.c) 中 调 用 net_dev_init(net/core /dev.c),net_dev_init 中遍历 dev_base,并分别调用各个 net_device 结构的 init 函数进行初始 化。 仔细看一下 Space.c 吧, 那个链表构造的很巧妙 在内核链接的时候,以__init 或者 module_init(?)修饰的代码会放到 __initcall_start 所标识的 segment。 因此所有这样的函数都会在初 始化的时候被调用到。 【 在 junky (从头再来) 的大作中提到: 】: 网卡作为外设,其驱动初始化是另外一套体系 : 一般用的是 module_init 宏,如果驱动静态链接,则该宏最终用的是 initcall, : 如果是动态模块,则最终用的是 init_module
内核协议栈数据包转发完全解析_互联网_IT/计算机_专业...函数来将其发送给 netif_receive_skb 驱动以及软...分配环形 DMA 缓冲区 Linux 内核中,用 skb 来描述...主要介绍Linux TCP/IP网络数据包的传送!Linux 网络协议栈之数据包处理过程 Linux...处理 IPv4 分组 下面以 IPv4 为例,讲解 IPv4 分组在高层的处理。 linux 内核...Linux内核IP Queue机制的分析(一)――用户态接收数据...流入网口名称 */ char indev_name[IFNAMSIZ]; ...三、一个实现接收内核态发送的 IP Queue 数据包的...Linux 内核网络协议栈笔记
15:54:07cnblogs.com-微型葡萄-点击数...我们这里所说的初始化过程指的是从硬件加电启动,到可以从网络接收或发送数据包...文档名称 文档密级 Linux 网络堆栈的排队机制在任何网络堆栈或设备中, 数据包的...为了避免因为MTU大小限制而出现的大量数据包,Linux内核对传输大小进行了多项优 ...Linux 内核 bridge 浅析 Linux 网桥模型: Linux 内核通过一个虚拟的网桥设备来...flood 数据包,向除接收网口外的其余网口发送该数据包 br_flood_forward(br, ...监视网络数据包信息并对数据包进行分析,来检查网络数据传输的状态,从而监控 网络...基本服务的核心程序 (kernel) 是由 Linus 带头开发出来的,Linux 这个名称便是...linux内核网络子模块学习笔记_计算机软件及应用_IT/计算机_专业资料。一. 网络...&func(skb, skb-&dev, pt_prev, orig_dev); 以上就把数据包传到了上层了...Linux内核驱动之视频基础(五)HDMI-DVI信号详解_计算机硬件及网络_IT/计算机_专业...个用于传输数据的 TMDS 通道, 以及 1 个独立的 TMDS 时钟通道,以保证传输时...本文以 Linux2.4.16 内核作为讲解的对象,内核源码...网络设备接口部份主要负责从物理介质接收和发送数据。...el_receive()首先检查接收的包是否 正确,如果是一...
All rights reserved Powered by
www.tceic.com
copyright &copyright 。文档资料库内容来自网络,如有侵犯请联系客服。

我要回帖

更多关于 网络数据传输 的文章

 

随机推荐