转至繁体中文版     | 网站首页 | 图文教程 | 资源下载 | 站长博客 | 图片素材 | 武汉seo | 武汉网站优化 | 
最新公告:     敏韬网|教学资源学习资料永久免费分享站!  [mintao  2008年9月2日]        
您现在的位置: 学习笔记 >> 图文教程 >> 数据库 >> MySql >> 正文
Linux网络代码导读v0.2         ★★★★

Linux网络代码导读v0.2

作者:闵涛 文章来源:闵涛的学习笔记 点击数:1906 更新时间:2009/4/22 20:47:25
                          Linux网络代码导读v0.2

发布日期:


作者:yawl < yawl@nsfocus.com > 
主页:http://www.nsfocus.com 


    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想发送数据时,程序中会调用如下语句(如果用sendto函数的话会走类似的流程,
略):
...
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链表
中

[1] [2]  下一页


[系统软件]Win2K/XP SDT Restore 0.2 (Proof-Of-Concept)  [MySql]Linux网络服务软件安装备忘录 ver 0.2
教程录入:mintao    责任编辑:mintao 
  • 上一篇教程:

  • 下一篇教程:
  • 【字体: 】【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
      注:本站部分文章源于互联网,版权归原作者所有!如有侵权,请原作者与本站联系,本站将立即删除! 本站文章除特别注明外均可转载,但需注明出处! [MinTao学以致用网]
      网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)

    同类栏目
    · Sql Server  · MySql
    · Access  · ORACLE
    · SyBase  · 其他
    更多内容
    热门推荐 更多内容
  • 没有教程
  • 赞助链接
    更多内容
    闵涛博文 更多关于武汉SEO的内容
    500 - 内部服务器错误。

    500 - 内部服务器错误。

    您查找的资源存在问题,因而无法显示。

    | 设为首页 |加入收藏 | 联系站长 | 友情链接 | 版权申明 | 广告服务
    MinTao学以致用网

    Copyright @ 2007-2012 敏韬网(敏而好学,文韬武略--MinTao.Net)(学习笔记) Inc All Rights Reserved.
    闵涛 投放广告、内容合作请Q我! E_mail:admin@mintao.net(欢迎提供学习资源)

    站长:MinTao ICP备案号:鄂ICP备11006601号-18

    闵涛站盟:医药大全-武穴网A打造BCD……
    咸宁网络警察报警平台