[ZLG-ARM] Linux网络代码导读v0.2

[复制链接]
1272|1
 楼主| reeper 发表于 2009-4-6 17:08 | 显示全部楼层 |阅读模式
1&nbsp;前言<br />许多人在分析linux代码时对网络部分(主要是src/linux/net,src/linux/include/net及src/linux/include/linux目录下的文件)比较感兴趣,确实,尽管已经从书本上学到了大量的TCP/IP原理,不读源码的话,头脑中还是建立不起具体的印象。而分析这部分代码的一个问题便是代码众多而资料很少。这篇**的目的就是勾勒出一个框架,让读者能够大致能够了解TCP/IP究竟是怎么工作的。以前见到的许多代码分析都是基于2.0内核的,在新的内核中许多函数变了名字,这尤其给初学者带来了困难,本文是以2.4.0-test9的代码作例子,这样对照代码时可能更清晰些。<br /><br />其实网络部分的代码我只对防火墙部分一行行仔细分析过,其他许多地方也只是一知半解,如果理解有误,欢迎指正。<br /><br />建议在看本文的同时,用source&nbsp;insight(www.soucedyn.com)建立一个项目,同时看代码,这样可能效果更好点。我也用过其他的一些工具,但在分析大量的代码的时候,没有一个工具比它更方便的了。<br /><br /><br />2&nbsp;正文<br /><br />ISO的七层模型都非常熟悉了,当然,对于internet,用四层模型更为适合。在这两份模型里,网络协议以层次的形式出现。而LINUX的内核代码中,严格分出清楚的层次却比较困难,因为除了一些'内核线程(kernel&nbsp;thread外)',整个内核其实是个单一的进程。因此所谓'网络层',只是一组相关的函数,而各层之间大多通过一般的函数调用的方式完成交互。<br /><br />而从逻辑上,网络部分的代码更应该这样分层更为合理:<br />.BSD&nbsp;socket层:这一部分处理BSD&nbsp;socket相关操作,每个socket在内核中以struct&nbsp;socket结构体现。<br />&nbsp;&nbsp;&nbsp;&nbsp;这一部分的文件主要有:/net/socket.c&nbsp;/net/protocols.c&nbsp;etc<br /><br />.INET&nbsp;socket层:BSD&nbsp;socket是个可以用于各种网络协议的接口,而当用于tcp/ip,即建立了AF_INET形式的socket时,还需要保留些额外的参数,于是就有了struct&nbsp;sock结构。<br />&nbsp;&nbsp;&nbsp;&nbsp;文件主要有:/net/ipv4/protocol.c&nbsp;/net/ipv4/af_inet.c&nbsp;/net/core/sock.c&nbsp;etc<br /><br />.TCP/UDP层:处理传输层的操作,传输层用struct&nbsp;inet_protocol和struct&nbsp;proto两个结构表示。<br />&nbsp;&nbsp;&nbsp;&nbsp;文件主要有:/net/ipv4/udp.c&nbsp;/net/ipv4/datagram.c&nbsp;/net/ipv4/tcp.c&nbsp;/net/ipv4/tcp_input.c<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/net/ipv4//tcp_output.c&nbsp;/net/ipv4/tcp_minisocks.c&nbsp;/net/ipv4/tcp_output.c&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/net/ipv4/tcp_timer.c&nbsp;etc&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />.IP层:处理网络层的操作,网络层用struct&nbsp;packet_type结构表示。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;文件主要有:/net/ipv4/ip_forward.c&nbsp;ip_fragment.c&nbsp;ip_input.c&nbsp;ip_output.c&nbsp;etc.<br /><br />.数据链路层和驱动程序:每个网络设备以struct&nbsp;net_device表示,通用的处理在dev.c中,<br />&nbsp;&nbsp;&nbsp;&nbsp;驱动程序都在/driver/net目录下。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />网络部分还有很多其他文件,如防火墙,路由等,一般根据看到名字便能猜测出相应的处理,此处不再赘述。&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /><br />现在我要给出一张表,全文的内容就是为了说明这张表(如果你觉得我在**中的语言比较乏味,尽可抛掉他们,结合这张表自己看代码)。在我最初看网络部分代码时,比较喜欢《linux&nbsp;kernel&nbsp;internals》的第八章的一段,其中有一个进程A通过网络远程向另一进程B发包的例子,详细介绍了一个数据包如何从网络堆栈中走过的过程。我觉得这样可以更迅速的帮助读者看清森林的全貌,因此本文参照这种结构来<br />叙述。<br /><br />^<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sys_read&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fs/read_write.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_read&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/socket.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_recvmsg&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/socket.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inet_recvmsg&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/ipv4/af_inet.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;udp_recvmsg&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/ipv4/udp.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;skb_recv_datagram&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/core/datagram.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-------------------------------------------<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_queue_rcv_skb&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;include/net/sock.h<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;udp_queue_rcv_skb&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/ipv4/udp.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;udp_rcv&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/ipv4/udp.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ip_local_deliver_finish&nbsp;net/ipv4/ip_input.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ip_local_deliver&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/ipv4/ip_input.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ip_recv&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/ipv4/ip_input.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net_rx_action&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/dev.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-------------------------------------------<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;netif_rx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/dev.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;el3_rx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;driver/net/3c309.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;el3_interrupt&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;driver/net/3c309.c<br /><br />==========================<br /><br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sys_write&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fs/read_write.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_writev&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/socket.c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_sendmsg&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/socket.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inet_sendmsg&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/ipv4/af_inet.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;udp_sendmsg&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/ipv4/udp.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ip_build_xmit&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/ipv4/ip_output.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;output_maybe_reroute&nbsp;&nbsp;&nbsp;&nbsp;net/ipv4/ip_output.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ip_output&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/ipv4/ip_output.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ip_finish_output&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/ipv4/ip_output.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dev_queue_xmit&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;net/dev.c<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--------------------------------------------<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;el3_start_xmit&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;driver/net/3c309.c<br />V<br /><br /><br /><br />我们假设的环境如下:有两台主机通过互联网联在一起,其中一台机子运行这一个进程A,另外一台运行进程B,进程A将向进程B发出一条信息,比如'Hello',而B接受此信息。<br />TCP处理本身非常复杂,为了便于叙述,在后面我们将用UDP作为例子。<br /><br /><br />2.1&nbsp;建立套接字<br /><br />在数据发送之前,要建立一个套接字(socket),在两边的程序中都会调用如下语句:<br /><br />...<br />int&nbsp;sockfd;<br />sockfd=socket(AF_INET,SOCK_DGRAM,0);<br />...<br /><br />这是个系统调用,因此会通过0x80中断进入系统内核,调用内核中的相应函数.当寻找系统调用在内核中的对应流程时,一般前面加入'sys_'再找就是了,如对fork来说,就是调用sys_fork。但是socket相关调用有些特殊,所有的这类调用都是通过一个入口,即sys_socketcall进入系统内核,然后再通过参数调用具体的sys_socket,socket_bind等函数。<br /><br />sys_socket会调用sock_create产生一个struct&nbsp;socket结构(见include/linux/net.h),每个套接字在内核中都有一个这样的结构对应,在初始化了此结构的一些通用成员后(如分配inode,根据第二个参数为type项赋值等),会根据其一个参数作响应的调度,即这<br />一句:<br />...<br />net_families[family]-&gtcreate(sock,&nbsp;protocol);<br />...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />我们的程序的第一个参数是AF_INET,所以此函数指针会指向inet_create();(net_families是个数组,保留了网络协议族(net&nbsp;families)的信息,而这些协议族用sock_register加载。)<br /><br />在struct&nbsp;socket结构结构中最重要的信息保留在struct&nbsp;sock结构中,这个结构在网络代码中经常使用,建议把它和其他常见结构(如struct&nbsp;sk_buff)打印出来放在手边。在inet_create会为此结构分配内存,并根据套接字类型(其实就是socket函数的第二个参数),作各自不同的初始化:<br />...<br />if&nbsp;(sk-&gtprot-&gtinit)&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sk-&gtprot-&gtinit(sk);<br />...<br /><br />如果类型是SOCK_STREAM的话会调用tcp_v4_init_sock,而SOCK_DGRAM类型的socket没有额外的初始化了,到此socket调用结束。<br /><br />还有一个值得注意的地方是当inet_create()调用完后,会接着调用sock_map_fd函数,这个函数中会为套接字分配一个文件描述符并分配一个file文件。在应用层便可象处理文件一样处理套接字了。<br /><br />开始的时候可能有些流程难以跟下去,主要便是这些函数指针的实际指向会根据类型变化。<br /><br /><br />2.2&nbsp;发送数据<br /><br />当进程A想发送数据时,程序中会调用如下语句(如果用sendto函数的话会走类似的流程,略):<br />...<br />write(sockfd,'Hello',strlen('Hello'));<br />...<br /><br />write在内核中对应的函数就是sys_write,此函数首先根据文件描述符找到struct&nbsp;file结构,如果此文件存在(file指针非空)且可写(file-&gtf_mode&nbsp;&&nbsp;FMODE_WRITE为true),便调用此文件结构的写操作:<br />...<br />if&nbsp;(file-&gtf_op&nbsp;&&&nbsp;(write&nbsp;=&nbsp;file-&gtf_op-&gtwrite)&nbsp;!=&nbsp;NULL)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ret&nbsp;=&nbsp;write(file,&nbsp;buf,&nbsp;count,&nbsp;&file-&gtf_pos);<br />...&nbsp;<br /><br />其中f_op是个struct&nbsp;file_operations结构指针,在sock_map_fd中将其指向socket_file_ops,其定义如下(/net/socket.c):<br />static&nbsp;struct&nbsp;file_operations&nbsp;socket_file_ops&nbsp;=&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;llseek:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_lseek,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;read:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_read,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;write:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_write,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;poll:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_poll,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ioctl:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_ioctl,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mmap:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_mmap,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;open:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_no_open,&nbsp;&nbsp;&nbsp;/*&nbsp;special&nbsp;open&nbsp;code&nbsp;to&nbsp;disallow&nbsp;open&nbsp;via&nbsp;/proc&nbsp;*/<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;release:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_close,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fasync:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_fasync,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;readv:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_readv,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;writev:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_writev<br />};<br /><br />此时wirte函数指针显然指向了sock_write,我们跟下去看,此函数将一个字符串缓冲整理成struct&nbsp;msghdr,最后调用了sock_sendmsg.<br /><br />sock_sendmsg中的scm_send我不了解(scm是Socket&nbsp;level&nbsp;control&nbsp;messages的简写),好在它也不是很关键,我们注意到这句:<br />...<br />sock-&gtops-&gtsendmsg(sock,&nbsp;msg,&nbsp;size,&nbsp;&scm);<br />...<br /><br />又是个函数指针,sock-&gtops在inet_create()函数中被初始化,由于我们我们是UDP的套接字,sock-&gtops指向了inet_dgram_ops(即sock-&gtops&nbsp;=&nbsp;&inet_dgram_ops;),其定义在net/ipv4/Af_inet.c中:<br />struct&nbsp;proto_ops&nbsp;inet_dgram_ops&nbsp;=&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;family:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PF_INET,<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;release:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inet_release,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bind:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inet_bind,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;connect:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inet_dgram_connect,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;socketpair:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_no_socketpair,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;accept:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_no_accept,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getname:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inet_getname,&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;poll:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;datagram_poll,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ioctl:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inet_ioctl,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;listen:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_no_listen,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;shutdown:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inet_shutdown,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setsockopt:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inet_setsockopt,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getsockopt:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inet_getsockopt,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sendmsg:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inet_sendmsg,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;recvmsg:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inet_recvmsg,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mmap:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sock_no_mmap,<br />};<br /><br />因此我们要看得便是inet_sendmsg()函数了,而马上,这个函数又通过函数指针调用了另一函数:<br />...<br />sk-&gtprot-&gtsendmsg(sk,&nbsp;msg,&nbsp;size);<br />...<br /><br />我们不得不再次寻找其具体指向。看到这里,说点题外话,怎么才能找到其具体定义呢?我一般是这样:对上例而言,sk是个struct&nbsp;sock结构,到其定义(linux/net/sock.h中)出看到prot是个struct&nbsp;proto结构,此时我们便在源代码树中寻找所有此结构的实例(这些诸如跳到定义,寻找引用等工作在source&nbsp;insight中实在太方便快速了^_^),很快便会发现诸如udp_prot,tcp_prot,raw_prot等,猜测是用了udp_prot,便再找一下它在源代码中的引用情况,果然发现在inet_create中有这么一句:<br />...<br />prot=&udp_prot;<br />...<br /><br />其实如果前面看inet_create函数时仔细一点会早点发现了,但我总没有这么细心。<br /><br />我们顺着udp_sendmsg往下走:<br />在这个函数的主要作用是填充UDP头(源端口,目的端口等),接着调用了<br />ip_route_output,作用是查找出去的路由,而后:<br />...<br />ip_build_xmit(sk,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(sk-&gtno_check&nbsp;==&nbsp;UDP_CSUM_NOXMIT&nbsp;?<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;udp_getfrag_nosum&nbsp;:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;udp_getfrag),<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&ufh,&nbsp;ulen,&nbsp;&ipc,&nbsp;rt,&nbsp;msg-&gtmsg_flags);<br />...<br /><br />ip_build_xmit函数的很大比例是生成sk_buff,并为数据包加入IP头。<br />后面有这么一句:<br />...<br />NF_HOOK(PF_INET,&nbsp;NF_IP_LOCAL_OUT,&nbsp;skb,&nbsp;NULL,&nbsp;rt-&gtu.dst.dev,output_maybe_reroute);<br />...<br /><br />简单的说,在没有防火墙代码干预的情况下,你可以将此处理解为直接调用output_maybe_reroute,(具体可参看绿盟月刊14期中的《内核防火墙netfilter入门&nbsp;》)<br />而output_maybe_reroute中只有一句:<br />return&nbsp;skb-&gtdst-&gtoutput(skb);<br /><br />依旧照上面的方法(不过这个确实不太好找),发现其实这个指针是在ip_route_output中指定的,(提示:ip_route_output_slow中:rth-&gtu.dst.output=ip_output;),ip_route_output的作用便是查找路由,并将结果记录到skb-&gtdst中。<br /><br />于是,我们开始看ip_output函数了,而它马上又走向了ip_finish_output~~。<br />每个网络设备,如网卡,在内核中由一个net_device表示,在ip_finish_output中找到其用到的设备(也是在ip_route_output中初始化的),这个参数在会传给netfilter在NF_IP_POST_ROUTING点登记的函数,结束后调用ip_finish_output2,而这个函数中又会调用:<br />...<br />hh-&gthh_output(skb);<br />...<br /><br />闲话少叙,实际调用了dev_queue_xmit,到此我们完成了TCP/IP层的工作,开始数据链路层的处理。<br /><br />在做了一些判断之后,实际的调用是这句:<br />...<br />dev-&gthard_start_xmit(skb,&nbsp;dev);<br />...<br /><br />这个函数是在网卡的驱动程序中定义的,每个不同的网卡有不同的处理,我的网卡是比较通用的3c509(其驱动程序是3c509.c),在网卡处理化的时候(el3_probe),有:<br />...<br />dev-&gthard_start_xmit&nbsp;=&nbsp;&el3_start_xmit;<br />...<br /><br />再往下便是IO操作,将数据包真正的发到网络上去,至此发送过程结束。<br /><br />中间我说的有些草率,完全没顾的上中间的如出错,阻塞,分片等特殊处理,只是将理想的过程描述出来。<br />这篇短文的目的也只是帮助大家建立个大致的印象,其实每个地方的都有非常复杂的处理(尤其是TCP部分)。<br /><br /><br />2.3&nbsp;接受数据<br /><br />当有数据到达网卡的时候,会产生一个硬件中断,然后调用网卡驱动程序中的函数来处理,对我的3c509网卡来说,其处理函数为:el3_interrupt。(相应的IRQ号是在系统启动,网卡初始化时通过request_irq函数决定的。)这个中断处理程序首先要做的当然就是进行一些IO操作将数据读入(读IO用inw函数),当数据帧成功接受后,执行el3_rx(dev)进一步处理。<br /><br />在el3_rx中,收到的数据报会被封装成struct&nbsp;sk_buff,并脱离驱动程序,转到通用的处理函数netif_rx(dev.c)中。为了CPU的效率,上层的处理函数的将采用软中断的方式激活,netif_rx的一个重要工作就是将传入的sk_buff放到等候队列中,并置软中断标志位,然后便可放心返回,等待下一次网络数据包的到来:<br />...<br />__skb_queue_tail(&queue-&gtinput_pkt_queue,skb);<br />__cpu_raise_softirq(this_cpu,&nbsp;NET_RX_SOFTIRQ);<br />...<br /><br />这个地方在2.2内核中一直被称为'底半'处理--bottom&nbsp;half,其内部实现基本类似,目的是快速的从中断中返回。<br /><br />过了一段时间后,一次CPU调度会由于某些原因会发生(如某进程的时间片用完)。在进程调度函数即schedule()中,会检查有没有软中断发生,若有则运行相应的处理函数:<br />...<br />if&nbsp;(softirq_active(this_cpu)&nbsp;&&nbsp;softirq_mask(this_cpu))<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;goto&nbsp;handle_softirq;<br />handle_softirq_back:<br />...<br />...<br />handle_softirq:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;do_softirq();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;goto&nbsp;handle_softirq_back;<br />...<br /><br />在系统初始化的时候,具体说是在net_dev_init中,此软中断的处理函数被定为net_rx_action:<br />...<br />open_softirq(NET_TX_SOFTIRQ,&nbsp;net_tx_action,&nbsp;NULL);<br />...<br /><br />当下一次进程调度被执行的时候,系统会检查是否发生NET_TX_SOFTIRQ软中断,若有则调用net_rx_action。<br /><br />net_tx_action函数既是2.2版本中的net_bh函数,在内核中有两个全局变量用来登记网络层的,一个是链表ptype_all,另外一个是数组ptype_base[16],他们记载了所有内核能够处理的第三层(按照OSI7层模型)协议。每个网络层的接收处理由一个<br />struct&nbsp;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类型登记,那么处理函数会受到所有类型的包,否则只能处理自己登记的类型的。<br /><br />skb-&gtprotocol是在el3_rx中赋值的,其实就是以太帧头信息中提取出的上层协议名,对于我们的例子来说,这个值是ETH_P_IP,所以在net_tx_action中,会选择IP层的接收处理函数,而从ip_packet_type&nbsp;不难看出,这个函数便是ip_recv()。<br />pt_prev-&gtfunc(实际指向ip_recv)前面有一个atomic_inc(&skb-&gtusers)操作(在2.2内核中这个地方是一句skb_clone,原理类似),目的是增加这个sk_buff的引用数。网络层的接收函数在处理完或因为某些原因要丢弃此sk_buff时(如防火墙)会调用kfree_skb,而kfree_skb中首先会检查是否还有其他地方需要此函数,如果没有地方再用,才真正释放此内存(__kfree_skb),否则只是计数器减一。<br /><br />现在我们便来看看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-&gtdst中。此时接收到的包有两种,发往本地进程(需要传往上层协议)或转发(用作网关时),此时需要的处理函数也不相同,如果传往本地,则调用ip_local_deliver(/net/ipv4/ip_input.c),否则调用ip_forward(/net/ipv4/ip_forward.c).skb-&gtdst-&gtinput这个函数指针会将数据报领上正确的道路。<br /><br />对我们的例子而言,此时应该是调用ip_local_deliver的时候了。<br />发来的包很有可能是碎片包,这样的话则首先应该把它们组装好再传给上层协议,这当然也是ip_local_deliver函数所做的第一份工作,如果组装成功(返回的sk_buff不为空),则继续处理(详细的组装算法可参见绿盟月刊13期中的《IP分片重组的分析和常见碎片攻击》)。<br />但此时代码又被netfilter一分为二了,象前面一样,我们直接到后半段,即ip_local_deliver_finish(/net/ipv4/ip_input.c)中去。<br /><br />传输层(如TCP,UDP,RAW)的处理被登记到了inet_protos中(通过inet_add_protocol)。ip_local_deliver_finish会根据IP头信息中的上层协议信息(即iph-&gtprotocol),调用相应的处理函数。为了简便,我们采用了udp,此时的ipprot-&gthandler实际便是udp_rcv了。<br /><br />前面已经提到,在应用程序中建立的每个socket在内核中有一个struct&nbsp;socket/struct&nbsp;sock对应。udp_rcv会通过udp_v4_lookup首先找到在内核中的sock,然后将其作参数调用udp_queue_rcv_skb(/net/ipv4/udp.c)。马上,sock_queue_rcv_skb函数被调用,此函数将sk_buff放入等待队列,然后通知上层数据到达:<br />...<br />kb_set_owner_r(skb,&nbsp;sk);<br />skb_queue_tail(&sk-&gtreceive_queue,&nbsp;skb);<br />if&nbsp;(!sk-&gtdead)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sk-&gtdata_ready(sk,skb-&gtlen);<br />return&nbsp;0;<br />...<br /><br />sk-&gtdata_ready的定义在sock结构初始化的时候(sock_init_data):<br />...<br />sk-&gtdata_ready=sock_def_readable;<br />...<br /><br />现在我们便要从上往下看起了:<br />进程B要接收数据报,在程序里调用:<br />...<br />read(sockfd,buff,sizeof(buff));<br />...<br /><br />此系统调用在内核中的函数是sys_read(fs/read_write.c)以下的处理类似write的操作,不再详述.udp_recvmsg函数会调用skb_recv_datagram,如果数据还没有到达,且socket设为阻塞模式时,进程会挂起(signal_pending(current)),直到data_ready通知进程资源得到满足后继续处理(wake_up_interruptible(sk-&gtsleep);)。<br /><br />2.4&nbsp;skbuff<br /><br />网络代码中有大量的处理涉及对sk_buff的操作,尽管此文中尽量将其回避了,但在仔细分析的时候则必须对此作分析,数据包在网络协议层是以sk_buff的形式传送处理的,可以说它是网络部分最重要的数据结构。具体分析建议参看alan&nbsp;cox的《Network&nbsp;Buffers&nbsp;And&nbsp;Memory&nbsp;Management》,这篇发表在1996年10月的linux&nbsp;journal上。<br /><br />这里引用phrack&nbsp;55-12期中的一幅图,尽管它只描绘了sk_buff的极小的一个侧面,但却非常有用,尤其是当你像我一样总忘记了skb_put是向前还是向后调指针的时候:)<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;---&nbsp;-----------------hand<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;^&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;^&nbsp;skb_push<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;-----------------data---&nbsp;---&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;^&nbsp;&nbsp;&nbsp;|<br />&nbsp;&nbsp;&nbsp;true&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;v&nbsp;skb_pull<br />&nbsp;&nbsp;&nbsp;size&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;len<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;^&nbsp;skb_trim&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;v&nbsp;&nbsp;&nbsp;|<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;-----------------tail---&nbsp;---<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;v&nbsp;&nbsp;skb_put<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;v&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />&nbsp;&nbsp;&nbsp;&nbsp;---&nbsp;-----------------end<br /><br />linux网络层效率:在linux的网络层代码中指针被大量应用,其目的就是避免数据拷贝这类耗费系统资源的操作。一个数据包的数据段部分在读入或发出时只经过两次拷贝,即从网卡中考到核心态内存,和从核心态内存考到用户态内存。前些天看到,在一些提高sniffer抓包效率的尝试中,turbo&nbsp;packet(一个内核补丁)采用了核心态和<br />用户态共享一段内存的办法,又减少了一次数据拷贝,进一步提高了效率。<br /><br /><br />3&nbsp;后记:<br />这次的投稿又是到了最后关头仓促写出来的,看着里面拙劣的文笔,实在觉得有点对不住观众~~如果有时间我会把这部分好好重写的,其实这也是我一直的愿望:)<br /><br /><br />4&nbsp;参考文献:<br /><br />[1.]&nbsp;phrack&nbsp;55-12期<br />[2.]&nbsp;2nd&nbsp;Edition<br />[3.]&nbsp;Network&nbsp;Buffers&nbsp;And&nbsp;Memory&nbsp;Management&nbsp;&nbsp;Alan&nbsp;Cox<br />http://www2.linuxjournal.com/lj-issues/issue30/1312.html<br />[4.]&nbsp;浙大源码分析报告《Linux网络设备分析》潘纲&nbsp;<br />[5.]&nbsp;Linux&nbsp;IP&nbsp;Networking--A&nbsp;Guide&nbsp;to&nbsp;the&nbsp;Implementation&nbsp;and&nbsp;Modification&nbsp;of&nbsp;theLinux&nbsp;Poptocol&nbsp;Stack<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Glenn&nbsp;Herrin&nbsp;May&nbsp;31,2000&nbsp;http://www.movement.uklinux.net/linux-net.pdf&nbsp;<br /><br /><br />[zz]http://www.nsfocus.net/index.php?act=sec_self&do=view&doc_id=507<br />&nbsp;<br /> &nbsp;&nbsp;<br />
ddpxy 发表于 2009-4-6 17:09 | 显示全部楼层

恩!不错。收藏了

  
您需要登录后才可以回帖 登录 | 注册

本版积分规则

139

主题

185

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部