|
|
| | -文章搜索 - 最新文章 - | |
[摘录]sk_buff读书笔记(转) |
| 发布时间:2006年10月15日 点击次数:1828 |
| 来源: 作者: |
sk_buff读书笔记 第一部分: skb - Linux network buffers skb——Linux网络缓存 Harald Welte laforge@gnumonks.org 1.3, 2000/10/14 21:27:02 --------------------------------------------------------------------- Short description about the linux network buffers (skb's) --------------------------------------------------------------------- 1. Introduction At the time I wanted to know more about the Linux network stack, I always wanted a document like this to exist. But unfortunately I never found one. After I gained some basic knowledge about the Linux network stack internals, I wrote one. I'm happy if this document is of any use for other people trying to learn about the Linux kernel. Please let me know of any bugs in this document. It should resemble kernel revision 2.4.0-test4 2. skbuff's skbuffs are the buffers in which the linux kernel handles network packets. The packet is received by the network card, put into a skbuff and then passed to the network stack, which uses the skbuff all the time. skbuffs是那些linux内核处理网络分组的缓存。网卡收到分组后,将它们放进skbuff,然后再传送给网络堆栈。网络堆栈一直要用到skbuff。 2.1 struct sk_buff The struct sk_buff is defined in <linux/skbuff.h> as follows: 在<linux/skbuff.h>中定义了sk_buff struct,如下: next next buffer in list 链表中下一个缓存 prev previous buffer in list 链表中前一个缓存 list list we are on 当前链表 sk socket we belong to 所属socket stamp timeval we arrived at (分组)到达的时间 dev device we are leaving by 分组离开的设备 rx_dev device we arrived at 分组到达的设备 h transport layer header (tcp,udp,icmp,igmp,spx,raw) 传输层头标(tcp,udp,icmp,igmp,spx,raw) nh network layer header (ip,ipv6,arp,ipx,raw) 网络层头标(ip,ipv6,arp,ipx,raw) mac link layer header 链路层头标 dst FIXME: (目的地??) cb control buffer, used internally 控制缓存,后台使用 len length of actual data 实际数据长度 csum checksum 校验和 used FIXME: data moved to user and not MSG_PEEK 已经传递给用户的数据,未经MSG_PEEK is_clone we are a clone 为克隆副本 cloned head may be cloned 头标可被克隆 pkt_type packet class 分组等级分类 ip_summed driver fed us ip checksum 驱动器将IP校验和反馈给我们 priority packet queuing priority 分组排队优先级 users user count 用户计数 protocol packet protocol from driver 驱动器的分组协议 security security level of packet 分组的安全级别 truesize real size of the buffer 缓存的真实尺寸 head pointer to head of buffer 缓存头指针 data data head pointer 数据头指针 tail tail pointer 尾指针 end end pointer 结束指针 destructor destructor function 拆除功能 nfmark netfilter mark 网络过滤器标志 nfcache netfilter internal caching info 网络过滤器内在高速缓存信息 nfct associated connection, if any 相关联的连接 tc_index traffic control index 流量控制索引 2.2 skb support functions skb支持的功能函数 There are a bunch of skb support functions provided by the sk_buff layer. I briefly describe the most important ones in this section. 主要描述一下sk_buff层最重要的一些功能函数。 allocation / free / copy / clone and expansion functions 分配、释放、复制、克隆、扩展等功能函数 struct sk_buff *alloc_skb(unsigned int size, int gfp_mask) This function allocates a new skb. This is provided by the skb layer to initialize some private data and do memory statistics. The returned buffer has no headroom and a tailroom of /size/ bytes. 此函数分配了一个新的skb,skb层提供此函数来初始化一些私有数据,同时作内存统计。返回的缓存没有头空间,有一个尺寸/字节尾空间; 《Linux设备驱动程序》一书描述为: struct sk_buff *alloc_skb(unsigned int len,int priority) struct sk_buff *dev_alloc_skb(unsigned int len) 分配一个缓冲区。alloc_skb分配一个缓冲区并初始化skb->data和skb->tail到skb->head。 dev_alloc_skb函数是以GFP_ATOMIC优先级调用alloc_skb,并保存skb->head和skb->data之间16个字节的一个快捷方式。这个数据空间可以用来“推(push)”硬件包头。 void kfree_skb(struct sk_buff *skb) Decrement the skb's usage count by one and free the skb if no references left. skb使用计数减一,如果没有任何可参照的说明,释放skb; 《Linux设备驱动程序》一书描述为: 释放一个缓冲区。kfree_skb被内核内部使用。kree_skb被内核内部使用。 struct sk_buff *skb_get(struct sk_buff *skb) Increments the skb's usage count by one and returns a pointer to it. skb使用计数加一,返回一个指向skb的指针。这个skb估计是新使用的skb。 struct sk_buff *skb_clone(struct sk_buff *skb, int gfp_mask) This function clones a skb. Both copies share the packet data but have their own struct sk_buff. The new copy is not owned by any socket, reference count is 1. 这个函数克隆一个skb。两份拷贝共享分组数据,但分别拥有自己的sk_buff结构。新拷贝不属于任何socket,参考计数为1。 struct sk_buff *skb_copy(const struct sk_buff *skb, int gfp_mask) Makes a real copy of the skb, including packet data. This is needed, if You wish to modify the packet data. Reference count of the new skb is 1. 产生一个实实在在的skb拷贝,包含分组数据。但你想要修改分组数据时,需要这么做。新拷贝的参考计数为1。 struct skb_copy_expand(const struct sk_buff *skb, int new_headroom, int new_tailroom, int gfp_mask) Make a copy of the skb, including packet data. Additionally the new skb has a headroom of /new_headroom/ bytes size and a tailroom of /new_tailroom/ bytes. 产生一个skb拷贝,包含分组数据。另外,新skb还拥有一个/new_headroom/字节大小的头空间,和一个/new_tailroom/字节大小的尾空间。 anciliary functions ~l 辅助函数: int skb_cloned(struct sk_buff *skb) Is the skb a clone? 判断skb是否是克隆 int skb_shared(struct sk_Buff *skb) Is this skb shared? (is the reference count > 1)? 判断此skb是否共享(参考计数是不是大于1)。 operations on lists of skb's 对skb链表的操作。 struct sk_buff *skb_peek(struct sk_buff_head *list_) peek a skb from front of the list; does not remove skb from the list 偷窥链表起始的一个skb;不把skb从链表中移除。 struct sk_buff *skb_peek_tail(struct sk_buff_head *list_) peek a skb from tail of the list; does not remove sk from the list 偷窥链表尾部的一个skb;不把skb从链表中移除。 __u32 skb_queue_len(sk_buff_head *list_) return the length of the given skb list 返回给定skb链表的长度 void skb_queue_head(struct sk_buff_head *list_, struct sk_buff *newsk) enqueue a skb at the head of a given list 将一个skb排入给定链表头部。 void skb_queue_tail(struct sk_buff_head *list_, struct sk_buff *newsk) enqueue a skb at the end of a given list. 将一个skb排入给定链表尾部。 struct sk_buff *skb_dequeue(struct sk_buff_head *list_) dequeue a skb from the head of the given list. 将一个skb从给定链表头部移除。 struct sk_buff *sbk_dequeue_tail(struct sk_buff_head *list_) dequeue a skb from the tail of the given list 将一个skb从给定链表尾部移除。 operations on skb data 对skb数据的操作 unsigned char *skb_put(struct sk_buff *sbk, int len) extends the data area of the skb. if the total size exceeds the size of the skb, the kernel will panic. A pointer to the first byte of new data is returned. 扩展skb的数据域。如果总长度超出了skb的尺寸大小,内核将陷入慌乱。此函数将返回一个指向新数据第一个字节的指针。 unsigned char *skb_push(struct sk_buff *skb, int len) extends the data area of the skb. if the total size exceeds the size of the skb, the kernel will panic. A pointer to the first byte of new data is returned. 扩展skb的数据域。如果。。。。与上一个函数解释一样,不知是不是发布者的错误。 unsigned char *skb_pull(struct sk_buff *skb, int len) remove data from the start of a buffer, returning the bytes to headroom. A pointr to the next data in the buffer is returned. 将数据从缓存起始处移除,回收头空间字节。返回一个指向缓存中下一个数据的指针。 int skb_headroom(struct sk_buff *skb) return the amount of bytes of free space at the head of skb 返回skb头部空闲空间的字节数。 int skb_tailroom(struct sk_buff *skb) return the amount of bytes of free space at the end of skb 返回skb尾部空闲空间的字节数。 struct sk_buff *skb_cow(struct sk_buff *skb, int headroom) if the buffer passed lacks sufficient headroom or is a clone it is copied and additional headroom made available. 如果缓存缺乏头空间或者是一个克隆,则缓存将被拷贝,激活附加头空间。 第二部分: 标题: Linux网络代码导读v0.2 作者: yawl<yawl@nsfocus.com> 主页: www.nsfocus.com 时间: 11/2000 1 前言 许多人在分析linux代码时对网络部分(主要是src/linux/net,src/linux/include/net及 src/linux/include/linux目录下的文件)比较感兴趣,确实,尽管已经从书本上学到了大 量的TCP/IP原理,不读源码的话,头脑中还是建立不起具体的印象。而分析这部分代码的 一个问题便是代码众多而资料很少。这篇文章的目的就是勾勒出一个框架,让读者能够大致 能够了解TCP/IP究竟是怎么工作的。以前见到的许多代码分析都是基于2.0内核的,在新的 内核中许多函数变了名字,这尤其给初学者带来了困难,本文是以2.4.0-test9的代码作例子, 这样对照代码时可能更清晰些。 其实网络部分的代码我只对防火墙部分一行行仔细分析过,其他许多地方也只是一知半解, 如果理解有误,欢迎指正。 建议在看本文的同时,用source insight(www.soucedyn.com)建立一个项目,同时看代码, 这样可能效果更好点。我也用过其他的一些工具,但在分析大量的代码的时候,没有一个工 具比它更方便的了。 2 正文 ISO的七层模型都非常熟悉了,当然,对于internet,用四层模型更为适合。在这两份模型里, 网络协议以层次的形式出现。而LINUX的内核代码中,严格分出清楚的层次却比较困难,因为 除了一些"内核线程(kernel thread外)",整个内核其实是个单一的进程。因此所谓"网络层" ,只是一组相关的函数,而各层之间大多通过一般的函数调用的方式完成交互。 而从逻辑上,网络部分的代码更应该这样分层更为合理: .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目录下。 网络部分还有很多其他文件,如防火墙,路由等,一般根据看到名字便能猜测出相应的处理,此处不再赘述。 现在我要给出一张表,全文的内容就是为了说明这张表(如果你觉得我在文章中的语言比较乏味,尽可 抛掉他们,结合这张表自己看代码)。在我最初看网络部分代码时,比较喜欢《linux kernel internals》 的第八章的一段,其中有一个进程A通过网络远程向另一进程B发包的例子,详细介绍了一个数据包如何 从网络堆栈中走过的过程。我觉得这样可以更迅速的帮助读者看清森林的全貌,因此本文参照这种结构来 叙述。 ^ 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 fs/read_write.c sock_writev net/socket.c sock_sendmsg net/socket.c inet_sendmsg net/ipv4/af_inet.c udp_sendmsg net/ipv4/udp.c ip_build_xmit net/ipv4/ip_output.c output_maybe_reroute net/ipv4/ip_output.c ip_output net/ipv4/ip_output.c ip_finish_output net/ipv4/ip_output.c dev_queue_xmit net/dev.c -------------------------------------------- el3_start_xmit driver/net/3c309.c V 我们假设的环境如下:有两台主机通过互联网联在一起,其中一台机子运行这一个进程A, 另外一台运行进程B,进程A将向进程B发出一条信息,比如"Hello",而B接受此信息。 TCP处理本身非常复杂,为了便于叙述,在后面我们将用UDP作为例子。 2.1 建立套接字 在数据发送之前,要建立一个套接字(socket),在两边的程序中都会调用如下语句: ... int sockfd; sockfd=socket(AF_INET,SOCK_DGRAM,0); ... 这是个系统调用,因此会通过0x80中断进入系统内核,调用内核中的相应函数.当寻找 系统调用在内核中的对应流程时,一般前面加入"sys_"再找就是了,如对fork来说,就是 调用sys_fork。但是socket相关调用有些特殊,所有的这类调用都是通过一个入口,即 sys_socketcall进入系统内核,然后再通过参数调用具体的sys_socket,socket_bind等函数。 sys_socket会调用sock_create产生一个struct socket结构(见include/linux/net.h), 每个套接字在内核中都有一个这样的结构对应,在初始化了此结构的一些通用成员后(如 分配inode,根据第二个参数为type项赋值等),会根据其一个参数作响应的调度,即这 一句: ... net_families[family]->create(sock, protocol); ... 我们的程序的第一个参数是AF_INET,所以此函数指针会指向inet_create();(net_families 是个数组,保留了网络协议族(net families)的信息,而这些协议族用sock_register加载。) 在struct socket结构结构中最重要的信息保留在struct sock结构中,这个结构在网络代码中经常 使用,建议把它和其他常见结构(如struct sk_buff)打印出来放在手边。在inet_create会为此 结构分配内存,并根据套接字类型(其实就是socket函数的第二个参数),作各自不同的初始化: ... if (sk->prot->init) sk->prot->init(sk); ... 如果类型是SOCK_STREAM的话会调用tcp_v4_init_sock,而SOCK_DGRAM类型的socket没有额外的初始化了, 到此socket调用结束。 还有一个值得注意的地方是当inet_create()调用完后,会接着调用sock_map_fd函数,这个 函数中会为套接字分配一个文件描述符并分配一个file文件。在应用层便可象处理文件一样 处理套接字了。 开始的时候可能有些流程难以跟下去,主要便是这些函数指针的实际指向会根据类型变化。 2.2 发送数据 当进程A想发送数据时,程序中会调用如下语句(如果用send函数的话会走类似的流程,略): ... write(sockfd,"Hello",strlen("Hello")); ... write在内核中对应的函数就是sys_write,此函数首先根据文件描述符找到struct file结构,如果此文件 存在(file指针非空)且可写(file->f_mode & FMODE_WRITE为true),便调用此文件结构的写操作: ... if (file->f_op && (write = file->f_op->write) != NULL) ret = write(file, buf, count, &file->f_pos); ... 其中f_op是个struct file_operations结构指针,在sock_map_fd中将其指向socket_file_ops,其 定义如下(/net/socket.c): static struct file_operations socket_file_ops = { llseek: sock_lseek, read: sock_read, write: sock_write, poll: sock_poll, ioctl: sock_ioctl, mmap: sock_mmap, open: sock_no_open, /* special open code to disallow open via /proc */ release: sock_close, fasync: sock_fasync, readv: sock_readv, writev: sock_writev }; 此时wirte函数指针显然指向了sock_write,我们跟下去看,此函数将一个字符串缓冲整理成struct msghdr, 最后调用了sock_sendmsg. sock_sendmsg中的scm_send我不了解(scm是Socket level control messages的简写),好在它也不是很关键, 我们注意到这句: ... sock->ops->sendmsg(sock, msg, size, &scm); ... 又是个函数指针,sock->ops在inet_create()函数中被初始化,由于我们我们是UDP的套接字,sock->ops 指向了inet_dgram_ops(即sock->ops = &inet_dgram_ops;),其定义在net/ipv4/Af_inet.c中: struct proto_ops inet_dgram_ops = { family: PF_INET, release: inet_release, bind: inet_bind, connect: inet_dgram_connect, socketpair: sock_no_socketpair, accept: sock_no_accept, getname: inet_getname, poll: datagram_poll, ioctl: inet_ioctl, listen: sock_no_listen, shutdown: inet_shutdown, setsockopt: inet_setsockopt, getsockopt: inet_getsockopt, sendmsg: inet_sendmsg, recvmsg: inet_recvmsg, mmap: sock_no_mmap, }; 因此我们要看得便是inet_sendmsg()函数了,而马上,这个函数又通过函数指针调用了另一函数: ... sk->prot->sendmsg(sk, msg, size); ... 我们不得不再次寻找其具体指向。看到这里,说点题外话,怎么才能找到其具体定义呢?我一般是这样: 对上例而言,sk是个struct sock结构,到其定义(linux/net/sock.h中)出看到prot是个struct proto 结构,此时我们便在源代码树中寻找所有此结构的实例(这些诸如跳到定义,寻找引用等工作在source insight中实在太方便快速了^_^),很快便会发现诸如udp_prot,tcp_prot,raw_prot等,猜测是用了 udp_prot,便再找一下它在源代码中的引用情况,果然发现在inet_create中有这么一句: ... prot=&udp_prot; ... 其实如果前面看inet_create函数时仔细一点会早点发现了,但我总没有这么细心:)。 我们顺着udp_sendmsg往下走: 在这个函数的主要作用是填充UDP头(源端口,目的端口等),接着调用了 ip_route_output,作用是查找出去的路由,而后: ... ip_build_xmit(sk, (sk->no_check == UDP_CSUM_NOXMIT ? udp_getfrag_nosum : udp_getfrag), &ufh, ulen, &ipc, rt, msg->msg_flags); ... ip_build_xmit函数的很大比例是生成sk_buff,并为数据包加入IP头。 后面有这么一句: ... NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,output_maybe_reroute); ... 简单的说,在没有防火墙代码干预的情况下,你可以将此处理解为直接调用output_maybe_reroute, (具体可参看绿盟月刊14期中的《内核防火墙netfilter入门 》) 而output_maybe_reroute中只有一句: return skb->dst->output(skb); 依旧照上面的方法(不过这个确实不太好找),发现其实这个指针是在ip_route_output中指定的, (提示:ip_route_output_slow中:rth->u.dst.output=ip_output;),ip_route_output的作用 便是查找路由,并将结果记录到skb->dst中。 于是,我们开始看ip_output函数了,而它马上又走向了ip_finish_output~~。 每个网络设备,如网卡,在内核中由一个net_device表示,在ip_finish_output中找到其用到的设备 (也是在ip_route_output中初始化的),这个参数在会传给netfilter在NF_IP_POST_ROUTING点登记 的函数,结束后调用ip_finish_output2,而这个函数中又会调用: ... hh->hh_output(skb); ... 闲话少叙,实际调用了dev_queue_xmit,到此我们完成了TCP/IP层的工作,开始数据链路层的处理。 在做了一些判断之后,实际的调用是这句: ... dev->hard_start_xmit(skb, dev); ... 这个函数是在网卡的驱动程序中定义的,每个不同的网卡有不同的处理,我的网卡是比较通用的3c509 (其驱动程序是3c509.c),在网卡处理化的时候(el3_probe),有: ... dev->hard_start_xmit = &el3_start_xmit; ... 再往下便是IO操作,将数据包真正的发到网络上去,至此发送过程结束。 中间我说的有些草率,完全没顾的上中间的如出错,阻塞,分片等特殊处理,只是将理想的过程描述出来。 这篇短文的目的也只是帮助大家建立个大致的印象,其实每个地方的都有非常复杂的处理(尤其是TCP部分)。 2.3 接受数据 当有数据到达网卡的时候,会产生一个硬件中断,然后调用网卡驱动程序中的函数来处理,对我的3c509网卡来说, 其处理函数为:el3_interrupt。(相应的IRQ号是在系统启动,网卡初始化时通过request_irq函数决定的。) 这个中断处理程序首先要做的当然就是进行一些IO操作将数据读入(读IO用inw函数),当数据帧成功接受后, 执行el3_rx(dev)进一步处理。 在el3_rx中,收到的数据报会被封装成struct sk_buff,并脱离驱动程序,转到通用的处理函数netif_rx (dev.c)中。为了CPU的效率,上层的处理函数的将采用软中断的方式激活,netif_rx的一个重要工作就 是将传入的sk_buff放到等候队列中,并置软中断标志位,然后便可放心返回,等待下一次网络数据包的到来: ... __skb_queue_tail(&queue->input_pkt_queue,skb); __cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ); ... 这个地方在2.2内核中一直被称为"底半"处理--bottom half,其内部实现基本类似,目的是快速的从中断中返回。 过了一段时间后,一次CPU调度会由于某些原因会发生(如某进程的时间片用完)。在进程调度函数即schedule() 中,会检查有没有软中断发生,若有则运行相应的处理函数: ... if (softirq_active(this_cpu) & softirq_mask(this_cpu)) goto handle_softirq; handle_softirq_back: ... ... handle_softirq: do_softirq(); goto handle_softirq_back; ... 在系统初始化的时候,具体说是在net_dev_init中,此软中断的处理函数被定为net_rx_action: ... open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL); ... 当下一次进程调度被执行的时候,系统会检查是否发生NET_TX_SOFTIRQ软中断,若有则调用net_rx_action。 net_tx_action函数既是2.2版本中的net_bh函数,在内核中有两个全局变量用来登记网络层的, 一个是链表ptype_all,另外一个是数组ptype_base[16],他们记载了所有内核能够处理 的第三层(按照OSI7层模型)协议。每个网络层的接收处理由一个 struct packet_type表示,而这个结构将通dev_add_pack函数将他们登记到ptype_all或 ptype_base中。只有packet_type中的type项为ETH_P_ALL时,才会登记到ptype_all链表 中,否则如ip_packet_type,会在数组ptype_base[16]找到相应的位置。两者不同点是 如果是以ETH_P_ALL类型登记,那么处理函数会受到所有类型的包,否则只能处理自己登记 的类型的。 skb->protocol是在el3_rx中赋值的,其实就是以太帧头信息中提取出的上层协议名,对 于我们的例子来说,这个值是ETH_P_IP,所以在net_tx_action中,会选择IP层的接收处理 函数,而从ip_packet_type 不难看出,这个函数便是ip_recv()。 pt_prev->func(实际指向ip_recv)前面有一个atomic_inc(&skb->users)操作(在2.2 内核中这个地方是一句skb_clone,原理类似),目的是增加这个sk_buff的引用数。网络层 的接收函数在处理完或因为某些原因要丢弃此sk_buff时(如防火墙)会调用kfree_skb, 而kfree_skb中首先会检查是否还有其他地方需要此函数,如果没有地方再用,才真正释放 此内存(__kfree_skb),否则只是计数器减一。 现在我们便来看看ip_recv(net/ipv4/ip_input.c)。这个函数的操作是非常清晰的: 首先检查这个包的合法性(版本号,长度,校验和等是否正确),如果合法则进行接下来 的处理。在2.4内核中,为了灵活处理防火墙代码,将原来的一个ip_recv分成了两部分, 即将将原来的的ip_recv的后半段独立出一个ip_rcv_finish函数。在ip_rcv_finish中, 一部分是带有IP选项(如源路由等)的IP包,例外就是通过ip_route_input查找路由, 并将结果记录到skb->dst中。此时接收到的包有两种,发往本地进程(需要传往上层协议) 或转发(用作网关时),此时需要的处理函数也不相同,如果传往本地,则调用ip_local_deliver (/net/ipv4/ip_input.c),否则调用ip_forward(/net/ipv4/ip_forward.c).skb->dst->input 这个函数指针会将数据报领上正确的道路。 对我们的例子而言,此时应该是调用ip_local_deliver的时候了。 发来的包很有可能是碎片包,这样的话则首先应该把它们组装好再传给上层协议,这当然也是 ip_local_deliver函数所做的第一份工作,如果组装成功(返回的sk_buff不为空),则继续处 理(详细的组装算法可参见绿盟月刊13期中的《IP分片重组的分析和常见碎片攻击》)。 但此时代码又被netfilter一分为二了,象前面一样,我们直接到后半段,即ip_local_deliver_finish (/net/ipv4/ip_input.c)中去。 传输层(如TCP,UDP,RAW)的处理被登记到了inet_protos中(通过inet_add_protocol)。 ip_local_deliver_finish会根据IP头信息中的上层协议信息(即iph->protocol),调用相应的处理函数。为了简便,我们采用了udp,此时的ipprot->handler实际便是udp_rcv了。 前面已经提到,在应用程序中建立的每个socket在内核中有一个struct socket/struct sock对应。udp_rcv会通过udp_v4_lookup首先找到在内核中的sock,然后将其作参数调用 udp_queue_rcv_skb(/net/ipv4/udp.c)。马上,sock_queue_rcv_skb函数被调用,此函数将sk_buff放入等待队列,然后通知上层数据到达: ... kb_set_owner_r(skb, sk); skb_queue_tail(&sk->receive_queue, skb); if (!sk->dead) sk->data_ready(sk,skb->len); return 0; ... sk->data_ready的定义在sock结构初始化的时候(sock_init_data): ... sk->data_ready=sock_def_readable; ... 现在我们便要从上往下看起了: 进程B要接收数据报,在程序里调用: ... read(sockfd,buff,sizeof(buff)); ... 此系统调用在内核中的函数是sys_read(fs/read_write.c)以下的处理类似write的操作,不再详述.udp_recvmsg函数会调用skb_recv_datagram,如果数据还没有到达,且socket设为阻塞模式时,进程会挂起(signal_pending(current)),直到data_ready通知进程资源得到满足后继续处理(wake_up_interruptible(sk->sleep);)。 2.4 skbuff 网络代码中有大量的处理涉及对sk_buff的操作,尽管此文中尽量将其回避了,但在仔细分析的时候则必须对此作分析,数据包在网络协议层是以sk_buff的形式传送处理的,可以说它是网络部分最重要的数据结构。具体分析建议参看alan cox的《Network Buffers And Memory Management》,这片发表 在1996年10月的linux journal上。 这里引用phrack 55-12期中的一幅图,尽管它只描绘了sk_buff的极小的一个侧面,但却非常有用,尤其是当你像我一样总忘记了skb_put是向前还是向后调指针的时候:) --- -----------------hand ^ ^ skb_push -----------------data--- --- ^ true v skb_pull size len ^ skb_trim v -----------------tail--- --- v skb_put v --- -----------------end linux网络层效率:在linux的网络层代码中指针被大量应用,其目的就是避免数据拷贝这类耗费系统资源的操作。 一个数据包的数据段部分在读入或发出时只经过两次拷贝,即从网卡中考到核心态内存,和从核心态内存考到 用户态内存。前些天看到,在一些提高sniffer抓包效率的尝试中,turbo packet(一个内核补丁)采用了核心态和 用户态共享一段内存的办法,又减少了一次数据拷贝,进一步提高了效率。 3 后记: 这次的投稿又是到了最后关头仓促写出来的,看着里面拙劣的文笔,实在觉得有点对不住观众~~如果有时间 我会把这部分好好重写的,其实这也是我一直的愿望:) 4 参考文献: [1.] phrack 55-12期 [2.] <Linux Kernel Internals> 2nd Edition [3.] Network Buffers And Memory Management Alan Cox http://www2.linuxjournal.com/lj-issues/issue30/1312.html [4.] 浙大源码分析报告《Linux网络设备分析》潘纲 [5.] Linux IP Networking--A Guide to the Implementation and Modification of theLinux Poptocol Stack Glenn Herrin May 31,2000 http://www.movement.uklinux.net/linux-net.pdf 第三部分:Alan Cox的妙文: Network Buffers: Introduction An article on Linux by Alan Cox. Reproduced and updated from the Kernel Hackers Guide. Introduction The Linux operating system implements the industry standard Berkeley socket API, which has its origins in the BSD unix developments (4.2/4.3/4.4 BSD). In this article we will look at the way the memory management and buffering is implemented for network layers and network device drivers under the existing Linux kernel as well as explaining why some things have changed over time. 本文中讲述了linux内核有关网络层、网络设备的内存管理,缓存执行;同时也介绍了随着时间的推移而改变的一些内容。 Network Buffers: Implementation 网络缓存:执行 Implementation The primary goal of the sk_buff routines is to provide a consistent and efficient buffer handling method for all of the network layers, and by being consistent to make it possible to provide higher level sk_buff and socket handling facilties to all the protocols. An sk_buff is a control structure with a block of memory attached. There are two primary sets of functions provided in the sk_buff library. Firstly routines to manipulate doubly linked lists of sk_buffs, secondly functions for controlling the attached memory. The buffers are held on linked lists optimised for the common network operations of append to end and remove from start. As so much of the networking functionality occurs during interrupts these routines are written to be atomic. The small extra overhead this causes is well worth the pain it saves in bug hunting. 一个sk_buff是附带一块内存的控制结构,sk_buff库主要提供两个系列的功能函数。首先是控制双倍链接的sk_buffs链表的规程,其次便是控制附属内存的函数。 We use the list operations to manage groups of packets as they arrive from the network, and as we send them to the physical interfaces. We use the memory manipulation routines for handling the contents of packets in a standardised and efficient manner. 我们使用链表操作来管理从网络而来或者向物理接口而去的分组群。我们使用内存管理规则来标准且高效地处理分组的内容 Network Buffers: Functions Provided 网络缓存:提供的函数 Functions Provided The following block of functions allow the user to manipulate the sk_buff lists. While it is possible to manipulate the lists directly by hand, this is strongly frowned upon unless neccessary. The list itself is a ring of buffers including the list header. Each has a prev and next pointer, which point back and forward to the previous and next member of the list. 用户可以使用下面一系列的函数来控制sk_buff链表。不提倡在可能的情况自己手动直接去修改链表,除非必须。链表本身是一个缓存的ring,包括链表头。每个都包含一个“前一个”或“下一个”的指针。 In order to optimise performance the header has its previous and next fields aligned to match those of the sk_buff header so that from both structures it is legal to look at the prev and next fields. 为了优化操作,头标有它自己的“前”或“后”域,用来与相应的sk_buff头标匹配。 struct sk_buff *skb_dequeue(struct skb_buff_head *list) This takes the first buffer from a list. If the list is empty a NULL pointer is returned. This is used to pull buffers off queues. The buffers are added with the routines skb_queue_head and skb_queue_tail. If you know that any locks are already held you can use __skb_dequeue in the same way. This variant of the function avoids interrupt locking overhead. 将一个缓存从链表中移除。如果链表本空,则返回一个NULL指针。这个函数用来把缓存从队列中拉出去。按skb_queue_head 和 skb_queue_tail 的规程,可以加入新的缓存。如果你确信所有的锁定(lock?)都被保持,则你可以按同样的方法使用__skb_dequeue。 函数的变量避免了突发性的上层lock。 int skb_peek(struct sk_buff_head *list) Returns a pointer to the first item on the list. Since the list is not locked by this routine anything using skb_peek must be careful to have the list locked before it does the peek and not to unlock it until it has finished with the data. In an ideal world skb_peek would not exist. For for some things it is however just too useful. Programmers should treat skb_peek as an invitation to bugs in their code and be very aware of race conditions. 返回指向链表第一个条目的指针。因为链表并不因这条规程而锁定,任何skb_peekde的使用都需要注意在peek之前都需要锁定链表,并且一直到相应数据使用结束前都不应该解锁。在一个理想世界中skb_peek并不应该存在,然而遇到有些事情时它又很有用。编程者可以使用skb_peek来发现代码中的bug,以及清楚地了解特征环境。 int skb_queue_empty(struct sk_buff_head *list) Returns true if the list is empty. 如果链表为空,则返回true。 void skb_queue_head(struct sk_buff *skb) This function places a buffer at the start of a list. As with all the list operations it is atomic. The faster __skb_queue_head form exists if you know the list operation is safe from pre-emption. 这个函数将一个缓存放到一个链表的起始。如果你知晓自上一次清空后链表操作是安全的,你可以使用更快的__skb_queue_head函数。 void skb_queue_head_init(struct sk_buff_head *list) Initialise an sk_buff_head structure. This must be done before the list is ever used. It must not be called again, nor the list head freed until the list is empty. 初始化一个sk_buff_head结构。这个必须在链表被使用之前完成,并且不能再被调用;在链表为空之前,链表头不能被释放。 __u32 skb_queue_len(struct sk_buff_head *list) Reports the number of sk_buffs sitting on the queue. 报告队列中sk_buff的数目。 void skb_queue_tail(struct sk_buff *skb) Place a buffer at the end of a list, which is the most commonly used function. Almost all the queues are handled with one set of routines queueing data with this function and another set removing items from the same queues with skb_dequeue. The faster __skb_queue_tail form exists if you know the list operation is safe from pre-emption. 将一个缓存放置到链表的尾部。这是一个经常用到的函数。几乎所有的队列使用这个函数来处理一系列队列数据,同时使用skb_dequeue来将另一系列的条目从同一队列中移除。如果你知晓自上一次清空后链表操作是安全的,你可以使用更快的__skb_queue_tail函数。 void skb_unlink(struct sk_buff *skb) This function removes a buffer from whatever list it was on. The buffer is not freed merely removed from the list. To make some operations easier you can always called skb_unlink() on a buffer not in a list, and you need not know what list it is on. This enables network code to pull a buffer out of use even when the network protocol has no idea who is currently using it. A seperate locking mechanism is provided so device drivers do not find someone removing a buffer they are using at that moment. If the list holding the sk_buff is known to be safe from pre-emption the faster void __skb_unlink(struct sk_buff *skb) form can be used. 这个函数无条件地将一个缓存从任意链表上移除。缓存并未被释放,而只是从链表上移除。为了使某些操作更简便,我们常常可以对一个缓存调用skb_unlink(),而不必知道这个缓存具体处在哪个链表上。这样可以使网络代码让一个缓存退出使用,尽管此时网络协议也许并不清楚谁正在使用这个缓存。我们提供了一个独立的锁定机制,这样设备驱动器就不会发现他们在那一时刻正在使用的缓存被某人移走。如果你知晓持有sk_buff的链表自上一次清空后是安全可靠的,你可以使用更快捷的__skb_unlink(struct sk_buff *skb)。 Some more complex protocols like TCP keep frames in order and re-order their input as data is received. Two functions void skb_append(struct sk_buff *entry, struct sk_buff *new_entry) and void skb_insert(struct sk_buff *entry, struct sk_buff *new_entry) exist to allow users to place sk_buff's before or after a specific buffer in a list. 一些更复杂的协议比如TCP保持帧的顺序,当数据接收后会重新排列它们的顺序。有两个函数可以帮助用户将sk_buff放在链表中某一特定缓存之前或之后,这两个函数是: void skb_append(struct sk_buff *entry, struct sk_buff *new_entry) void skb_insert(struct sk_buff *entry, struct sk_buff *new_entry) Network Buffers: Higher Level Support Routines 网络缓存:高层支持程序 Higher Level Support Routines The semantics of allocating and queueing buffers for sockets also involve flow control rules and for sending a whole list of interactions with signals and optional settings such as non blocking. Two routines are designed to make this easy for most protocols. 套结字分配、排列缓存的句法同时也包含流控规则,以及发送相互作用的包括信号、可选设置比如非模块化的链表的规则。设计了两套规程来使这些操作对绝大多数协议来说变得简便。 The sock_queue_rcv_skb function is used to handle incoming data flow control and is normally used in the form sock_queue_rcv_skb函数用来处理对输入数据的流控,一般按下述形式使用: sk=my_find_socket(whatever); if(sock_queue_rcv_skb(sk,skb)==-1) { myproto_stats.dropped++; kfree_skb(skb,FREE_READ); return; } This function uses the socket read queue counters to prevent vast amounts of data being queued to a socket. After a limit is hit data is discarded. It is up to the application to read fast enough or as in TCP for the protocol to do flow control over the network. TCP actually tells the sending machine to shut up when it can no longer queue data. 这个函数使用套结字来读取队列计数器,避免过多的数据被送入一个Socket。当达到限制数目时,数据将被丢弃。这样一个应用就有必要以足够快的速度读取数据,或者在TCP中协议要在网络中进行流控。当不能再列入数据时,TCP会告诉发送者停止发送数据, On the sending side sock_alloc_send_skb handles all the semantics of blocking until there is space in the send queue so you cannot tie up all of memory with data queued for a slow interface, signal handling and the non blocking flags. The semantics and subtleties of this function are quite complex, and its strongly recommended protocol writers try very hard to use it. Many protocol send routines have this function doing almost all the work. 在发送方,由sock_alloc_send_skb处理模块的所有语义,直到发送队列中有了可用空间,这样你便不能对队列中输出到一个较慢接口的带有非模块化标记、需要进行信号处理的数据,使用所有的内存。这个函数的语义及精妙之处非常复杂,以致极力推荐它的协议作者都要非常辛苦的去使用它,呵呵~~ 很多协议发送规程几乎在所有的工作中都要使用到这个函数。
第四部分:Linux Kernel核心中文手册 |
|
|
|
|
[另类其他] 相关文章: A51 4.4.1 DBIT简介:
伪指令 DBIT 在 bit 或 ebit 段中预留空间。伪指令 DBIT 的格式如下: label : DBIT expression 其中 label 是被赋予了预留内存的地址的符号。 label 是一个 BIT 型的符号,它获得了活动段的当前地址值和存储类型。 label 只能用在这种类型的符号允许使用的位置。 expression 是要预留的位数。expression 不能包含前向引用、可重定位的符号或外部符...... A51 4.3.3 DD (只适用于AX51 和 A251)
A51 4.3.2 DW
传感器的发展方向
哈佛结构和冯·诺伊曼结构的区别
用于单片机系统的干扰抑制元件有哪些
SD卡接口的完整规范(zz) (转)
SD卡的总线概念zz (转)
硬件设计中一些术语的简称
用单片机实现的汉字OSD模块 |
|
|
|