百度空间 | 百度首页 
               
 
查看文章
 
[转载] Linux内核2.4.x的网络接口源码的结构
2008-08-04 10:00

一.前言

   Linux的源码里,网络接口的实现部份是非常值得一读的,通过读源码,不仅对网络协议会有更深的了解,也有助于在网络编程的时候,对应用函数有更精确的了解和把握。

   本文把重点放在网络接口程序的总体结构上,希望能作为读源码时一些指导性的文字。

   本文以Linux2.4.16内核作为讲解的对象,内核源码可以在http://www.kernel.org上下载。我读源码时参考的是http://lxr.linux.no/这个交差参考的网站,我个人认为是一个很好的工具,如果有条件最好上这个网站。

   二.网络接口程序的结构

   Linux的网络接口分为四部份:网络设备接口部份,网络接口核心部份,网络协议族部份,以及网络接口socket层。
   网络设备接口部份主要负责从物理介质接收和发送数据。实现的文件在linu/driver/net目录下面。

   网络接口核心部份是整个网络接口的关键部位,它为网络协议提供统一的发送接口,屏蔽各种各样的物理介质,同时有负责把来自下层的包向合适的协议配送。它是网络接口的中枢部份。它的主要实现文件在linux/net/core目录下,其中linux/net/core/dev.c为主要管理文件。

   网络协议族部份是各种具体协议实现的部份。Linux支持TCP/IP,IPX,X.25,AppleTalk等的协议,各种具体协议实现的源码在linux/net/目录下相应的名称。在这里主要讨论TCP/IP(IPv4)协议,实现的源码在linux/net/ipv4,其中linux/net/ipv4/af_inet.c是主要的管理文件。

   网络接口Socket层为用户提供的网络服务的编程接口。主要的源码在linux/net/socket.c

   三.网络设备接口部份

   物理层上有许多不同类型的网络接口设备, 在文件include/linux/if_arp.h的28行里定义了ARP能处理的各种的物理设备的标志符。网络设备接口要负责具体物理介质的控制,从物理介质接收以及发送数据,并对物理介质进行诸如最大数据包之类的各种设置。这里我们以比较简单的3Com3c501 太网卡的驱动程序为例,大概讲一下这层的工作原理。源码在Linux/drivers/net/3c501.c。

   我们从直觉上来考虑,一个网卡当然最主要的是完成数据的接收和发送,在这里我们来看看接收和发送的过程是怎么样的。

   发送相对来说比较简单,在Linux/drivers/net/3c501.c的行475 开始的el_start_xmit()这个函数就是实际向3Com3c501以太网卡发送数据的函数,具体的发送工作不外乎是对一些寄存器的读写,源码的注释很清楚,大家可以看看。

   接收的工作相对来说比较复杂。通常来说,一个新的包到了,或者一个包发送完成了,都会产生一个中断。Linux/drivers/net/3c501.c的572开始el_interrupt()的函数里面,前半部份处理的是包发送完以后的汇报,后半部份处理的是一个新的包来的,就是说接收到了新的数据。el_interrupt()函数并没有对新的包进行太多的处理,就交给了接收处理函数el_receive()。el_receive()首先检查接收的包是否正确,如果是一个“好”包就会为包分配一个缓冲结构(dev_alloc_skb()),这样驱动程序对包的接收工作就完成了,通过调用上层的函数netif_rx()(net/core/dev.c1214行) ,把包交给上层。

   现在驱动程序有了发送和接收数据的功能了,驱动程序怎么样和上层建立联系呢?就是说接收到包以后怎么送给上层,以及上层怎么能调用驱动程序的发送函数呢?

   由下往上的关系,是通过驱动程序调用上层的netif_rx()(net/core/dev.c 1214行)函数实现的,驱动程序通过这个函数把接到的数据交给上层,请注意所有的网卡驱动程序都需要调用这个函数的,这是网络接口核心层和网络接口设备联系的桥梁。

   由上往下的关系就复杂点。网络接口核心层需要知道有多少网络设备可以用,每个设备的函数的入口地址等都要知道。网络接口核心层会大声喊,“嘿,有多少设备可以帮我发送数据包?能发送的请给我排成一队!”。这一队就由dev_base开始,指针structnet_device *dev_base (Linux/include/linux/netdevice.h 436行)就是保存了网络接口核心层所知道的所有设备。对于网络接口核心层来说,所有的设备都是一个net_device结构,它在include/linux/netdevice.h,line 233里被定义,这是从网络接口核心层的角度看到的一个抽象的设备,我们来看看网络接口核心层的角度看到的网络设备具有的功能:

   struct net_device {
   ………
   open()
   stop()
   hard_start_xmit()
   hard_header()
   rebuild_header()
   set_mac_address()
   do_ioctl()
   set_config()
   hard_header_cache()
   header_cache_update()
   change_mtu()
   tx_timeout()
   hard_header_parse()
   neigh_setup()
   accept_fastpath()
   ………
   }

   如果网络接口核心层需要由下层发送数据的时候,在dev_base找到设备以后,就直接调dev->hard_start_xmit()的这个函数来让下层发数据包。

   驱动程序要让网络接口核心层知道自己的存在,当然要加入dev_base所指向的指针链,然后把自己的函数以及各种参数和net_device里的相应的域对应起来。加入dev_base所指向的指针链是通过函数register_netdev(&dev_3c50)(linux/drivers/net/net_init.c, line 532)

   建立的。而把自己的函数以和net_device里的相应的域及各种参数关系的建立是在el1_probe1()(Linux/drivers/net/3c501.c)里进行的:

   el1_probe1(){
   ………
   dev->open = &el_open;
   dev->hard_start_xmit = &el_start_xmit;
   dev->tx_timeout = &el_timeout;
   dev->watchdog_timeo = HZ;
   dev->stop = &el1_close;
   dev->get_stats = &el1_get_stats;
   dev->set_multicast_list = &set_multicast_list;
   ………
   ether_setup(dev);
   ………

   }

   进一步的对应工作在ether_setup(dev) (drivers/net/net_init.c, line 405 )里进行。我们注意到dev->hard_start_xmit =&el_start_xmit,这样发送函数的关系就建立了,上层只知道调用dev->hard_start_xmit这个来发送数据,上面的语句就把驱动程序实际的发送函数告诉了上层。

   四.网络接口核心部分

   刚才谈论了驱动程序怎么和网络接口核心层衔接的。网络接口核心层知道驱动程序以及驱动程序的函数的入口是通过*dev_base指向的设备链的,而下层是通过调用这一层的函数netif_rx()(net/core/dev.c
1214行) 把数据传递个这一层的。

   网络接口核心层的上层是具体的网络协议,下层是驱动程序,我们以及解决了下层的关系,但和上层的关系没有解决。先来讨论一下网络接口核心层和网络协议族部份的关系,这种关系不外乎也是接收和发送的关系。

   网络协议,例如IP,ARP等的协议要发送数据包的时候会把数据包传递给这层,那么这种传递是通过什么函数来发生的呢?网络接口核心层通过dev_queue_xmit()(net/core/dev.c,line975)这个函数向上层提供统一的发送接口,也就是说无论是IP,还是ARP协议,通过这个函数把要发送的数据传递给这一层,想发送数据的时候就调用这个函数就可以了。dev_queue_xmit()做的工作最后会落实到dev->hard_start_xmit(),而dev->hard_start_xmit()会调用实际的驱动程序来完成发送的任务。例如上面的例子中,调用dev->hard_start_xmit()实际就是调用了el_start_xmit()。

   现在讨论接收的情况。网络接口核心层通过的函数netif_rx()(net/core/dev.c 1214行)接收了上层发送来的数据,这时候当然要把数据包往上层派送。所有的协议族的下层协议都需要接收数据,TCP/IP的IP协议和ARP协议,SPX/IPX的IPX协议,AppleTalk的DDP和AARP协议等都需要直接从网络接口核心层接收数据,网络接口核心层接收数据是如何把包发给这些协议的呢?这时的情形和于下层的关系很相似,网络接口核心层的下面可能有许多的网卡的驱动程序,为了知道怎么向这些驱动程序发数据,前面以及讲过时,是通过*dev_base这个指针指向的链解决的,现在解决和上层的关系是通过static struct packet_ptype_base[16]( net/core/dev.c line 164)这个数组解决的。这个数组包含了需要接收数据包的协议,以及它们的接收函数的入口。

   从上面可以看到,IP协议接收数据是通过ip_rcv()函数的,而ARP协议是通过arp_rcv()的,网络接口核心层只要通过这个数组就可以把数据交给上层函数了。

   如果有协议想把自己添加到这个数组,是通过dev_add_pack()(net/core/dev.c, line233)函数,从数组删除是通过dev_remove_pack()函数的。Ip层的注册是在初始化函数进行的void __init ip_init(void) (net/ipv4/ip_output.c, line 1003)

   {
   ………
   dev_add_pack(&ip_packet_type);
   ………

   }

   重新到回我们关于接收的讨论,网络接口核心层通过的函数netif_rx()(net/core/dev.c 1214行)接收了上层发送来的数据,看看这个函数做了些什么。

   由于现在还是在中断的服务里面,所有并不能够处理太多的东西,剩下的东西就通过cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ)

   交给软中断处理, 从open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL)可以知道NET_RX_SOFTIRQ软中断的处理函数是net_rx_action()(net/core/dev.c, line 1419),net_rx_action()根据数据包的协议类型在数组ptype_base[16]里找到相应的协议,并从中知道了接收的处理函数,然后把数据包交给处理函数,这样就交给了上层处理,实际调用处理函数是通过net_rx_action()里的pt_prev->func()这一句。例如如果数据包是IP协议的话,ptype_base[ETH_P_IP]->func()(ip_rcv()),这样就把数据包交给了IP协议。

   五.网络协议部分

   协议层是真正实现是在这一层。在linux/include/linux/socket.h里面,Linux的BSD
Socket定义了多至32支持的协议族,其中PF_INET就是我们最熟悉的TCP/IP协议族(IPv4, 以下没有特别声明都指IPv4)。以这个协议族为例,看看这层是怎么工作的。实现TCP/IP协议族的主要文件在inux/net/ipv4/目录下面,Linux/net/ipv4/af_inet.c为主要的管理文件。

   在Linux2.4.16里面,实现了TCP/IP协议族里面的的IGMP,TCP,UDP,ICMP,ARP,IP。我们先讨论一下这些协议之间的关系。IP和ARP协议是需要直接和网络设备接口打交道的协议,也就是需要从网络核心模块(core)
接收数据和发送数据的。而其它协议TCP,UDP,IGMP,ICMP是需要直接利用IP协议的,需要从IP协议接收数据,以及利用IP协议发送数据,同时还要向上层Socket层提供直接的调用接口。可以看到IP层是一个核心的协议,向下需要和下层打交道,又要向上层提供所以的传输和接收的服务。

   先来看看IP协议层。网络核心模块(core) 如果接收到IP层的数据,通过ptype_base[ETH_P_IP] 数组的IP层的项指向的IP协议的ip_packet_type->ip_rcv()函数把数据包传递给IP层,也就是说IP层通过这个函数ip_rcv()(linux/net/ipv4/ip_input.c)接收数据的。ip_rcv()这个函数只对IP数据保做了一些checksum的检查工作,如果包是正确的就把包交给了下一个处理函数ip_rcv_finish()(注意调用是通过NF_HOOK这个宏实现的)。现在,ip_rcv_finish()这个函数真正要完成一些IP层的工作了。IP层要做的主要工作就是路由,要决定把数据包往那里送。路由的工作是通过函数ip_route_input()(/linux/net/ipv4/route.c,line 1622)实现的。对于进来的包可能的路由有这些:

   属于本地的数据(即是需要传递给TCP,UDP,IGMP这些上层协议的) ;
   需要要转发的数据包(网关或者NAT服务器之类的);
   不可能路由的数据包(地址信息有误);

   我们现在关心的是如果数据是本地数据的时候怎么处理。ip_route_input()调用ip_route_input_slow()(net/ipv4/route.c, line 1312),在ip_route_input_slow()里面的1559行rth->u.dst.input=

   ip_local_deliver,这就是判断到IP包是本地的数据包,并把本地数据包处理函数的地址返回。好了,路由工作完成了,返回到ip_rcv_finish()。ip_rcv_finish()最后调用拉skb->dst->input(skb),从上面可以看到,这其实就是调用了ip_local_deliver()函数,而ip_local_deliver(),接着就调用了ip_local_deliver_finish()。现在真正到了往上层传递数据包的时候了。

   现在的情形和网络核心模块层(core) 往上层传递数据包的情形非常相似,怎么从多个协议选择合适的协议,并且往这个协议传递数据呢?网络网络核心模块层(core) 通过一个数组ptype_base[16]保存了注册了的所有可以接收数据的协议,同样网络协议层也定义了这样一个数组struct net_protocol*inet_protos[MAX_INET_PROTOS](/linux/net/ipv4/protocol.c#L102),它保存了所有需要从IP协议层接收数据的上层协议(IGMP,TCP,UDP,ICMP)的接收处理函数的地址。我们来看看TCP协议的数据结构是怎么样的:

   linux/net/ipv4/protocol.c line67
   static struct inet_protocol tcp_protocol = {
   handler: tcp_v4_rcv,// 接收数据的函数
   err_handler: tcp_v4_err,// 出错处理的函数
   next: IPPROTO_PREVIOUS,
   protocol: IPPROTO_TCP,
   name: "TCP"
   };

   第一项就是我们最关心的了,IP层可以通过这个函数把数据包往TCP层传的。在linux/net/ipv4/protocol.c的上部,我们可以看到其它协议层的处理函数是igmp_rcv(),
udp_rcv(), icmp_rcv()。同样在linux/net/ipv4/protocol.c,往数组inet_protos[MAX_INET_PROTOS] 里面添加协议是通过函数inet_add_protocol()实现的,删除协议是通过 inet_del_protocol()实现的。inet_protos[MAX_INET_PROTOS]初始化的过程在linux/net/ipv4/af_inet.c inet_init()初始化函数里面。

   inet_init(){
   ……
   printk(KERN_INFO "IP Protocols: ");
   for (p = inet_protocol_base; p != NULL;) {
   struct inet_protocol *tmp = (struct inet_protocol *) p->next;
   inet_add_protocol(p);// 添加协议
   printk("%s%s",p->name,tmp?", ":"\n");
   p = tmp;
   ………
   }


   如果你在Linux启动的时候有留意启动的信息, 或者在linux下打命令dmesg就可以看到这一段程序输出的信息:
   IP Protocols: ICMP,UDP,TCP,IGMP也就是说现在数组inet_protos[]里面有了ICMP,UDP,TCP,IGMP四个协议的inet_protocol数据结构,数据结构包含了它们接收数据的处理函数。

   Linux 2.4.16在linux/include/linux/socket.h里定义了32种支持的BSDsocket协议,常见的有TCP/IP,IPX/SPX,X.25等,而每种协议还提供不同的服务,例如TCP/IP协议通过TCP协议支持连接服务,而通过UDP协议支持无连接服务,面对这么多的协议,向用户提供统一的接口是必要的,这种统一是通过socket来进行的。

   在BSD socket网络编程的模式下,利用一系列的统一的函数来利用通信的服务。例如一个典型的利用TCP协议通信程序是这样:

   sock_descriptor = socket(AF_INET,SOCK_STREAM,0);
   connect(sock_descriptor, 地址,) ;
   send(sock_descriptor,”hello world”);
   recv(sock_descriptor,buffer,1024,0);

   第一个函数指定了协议Inet协议,即TCP/IP协议,同时是利用面向连接的服务,这样就对应到TCP协议,以后的操作就是利用socket的标准函数进行的。

   从上面我们可以看到两个问题,首先socket层需要根据用户指定的协议族(上面是AF_INET)
从下面32种协议中选择一种协议来完成用户的要求,当协议族确定以后,还要把特定的服务映射到协议族下的具体协议,例如当用户指定的是面向连接的服务时,Inet协议族会映射到TCP协议。

   从多个协议中选择用户指定的协议,并把具体的出理交给选中的协议,这和一起网络核心层向上和向下衔接的问题本质上是一样的,所以解决的方法也是一样的,同样还是通过数组。在Linux/net/socket.c定义了这个数组staticstruct net_proto_family *net_families[NPROTO] 。数组的元素已经确定了,net_families[2] 是TCP/IP协议,net_families[3]

   是X.25协议,具体那一项对应什么协议,在include/linux/socket.h有定义。但是每一项的数据结构net_proto_family的ops是空的,也就是具体协议处理函数的地址是不知道的。协议的处理函数和ops建立联系是通过sock_register()(Linux/net/socket.c)这个函数建立的,例如TCP/IP协议的是这样建立关系的:

   int __init inet_init(void) (net/ipv4/af_inet.c)
   {
   (void) sock_register(&inet_family_ops);

   }

   只要给出AF_INET(在宏里定义是2),就可以找到net_failies[2] 里面的处理函数了。

   协议的映射完成了,现在要进行服务的映射了。上层当然不可能知道下层的什么协议能对应特定的服务,所以这种映射自然由协议族自己完成。在TCP/IP协议族里,这种映射是通过struct
list_head inetsw[SOCK_MAX]( net/ipv4/af_inet.c)

   这个数组进行映射的,在谈论这个数组之前我们来看另外一个数组inetsw_array[](net/ipv4/af_inet.c)

   static struct inet_protosw inetsw_array[] =
   {
   {
   type: SOCK_STREAM,
   protocol: IPPROTO_TCP,
   prot: &tcp_prot,
   ops: &inet_stream_ops,
   capability: -1,
   no_check: 0,
   flags: INET_PROTOSW_PERMANENT,
   },

   {
   type: SOCK_DGRAM,
   protocol: IPPROTO_UDP,
   prot: &udp_prot,
   ops: &inet_dgram_ops,
   capability: -1,
   no_check: UDP_CSUM_DEFAULT,
   flags: INET_PROTOSW_PERMANENT,
   },

   {
   type: SOCK_RAW,
   protocol: IPPROTO_IP, /* wild card */
   prot: &raw_prot,
   ops: &inet_dgram_ops,
   capability: CAP_NET_RAW,
   no_check: UDP_CSUM_DEFAULT,
   flags: INET_PROTOSW_REUSE,
   }
   };


   我们看到,SOCK_STREAM映射到了TCP协议,SOCK_DGRAM映射到了UDP协议,SOCK_RAW映射到了IP协议。现在只要把inetsw_array里的三项添加到数组inetsw[SOCK_MAX]就可以了,添加是通过函数inet_register_protosw()实现的。在inet_init()

   (net/ipv4/af_inet.c) 里完成了这些工作。

   还有一个需要映射的就是socket其它诸如accept,send(),

   connect(),release(),bind()等的操作函数是怎么映射的呢?我们来看一下上面的数组的TCP的项
   {
   type: SOCK_STREAM,
   protocol: IPPROTO_TCP,
   prot: &tcp_prot,
   ops: &inet_stream_ops,
   capability: -1,
   no_check: 0,
   flags: INET_PROTOSW_PERMANENT,
   },

   我们看到这种映射是通过ops,和prot来映射的,我们再来看看 tcp_prot这一项:

   struct proto tcp_prot = {
   name: "TCP",
   close: tcp_close,
   connect: tcp_v4_connect,
   disconnect: tcp_disconnect,
   accept: tcp_accept,
   ioctl: tcp_ioctl,
   init: tcp_v4_init_sock,
   destroy: tcp_v4_destroy_sock,
   shutdown: tcp_shutdown,
   setsockopt: tcp_setsockopt,
   getsockopt: tcp_getsockopt,
   sendmsg: tcp_sendmsg,
   recvmsg: tcp_recvmsg,
   backlog_rcv: tcp_v4_do_rcv,
   hash: tcp_v4_hash,
   unhash: tcp_unhash,
   get_port: tcp_v4_get_port,
   };


   所以的映射都已经完成了,用户调用connect()函数,其实就是调用了tcp_v4_connect()函数,按照这幅图,读起源码来就简单了很多了。

   六 Socket层

   上一节把socket层大多数要讨论的东西都谈论了,现在只讲讲socket 层和用户的衔接。

   系统调用socket(),bind(),connect(),accept,send(),release()等是在Linux/net/socket.c里面的实现的,系统调用实现的函数是相应的函数名加上sys_的前缀。

   现在看看当用户调用socket()这个函数,到底下面发生了什么。

   Socket(AF_INET,SOCK_STREAM,0)调用了sys_socket(),sys_socket()接着调用socket_creat(),socket_creat()就要根据用户提供的协议族参数在net_families[]里寻找合适的协议族,如果协议族没有被安装就要请求安装该协议族的模块,然后就调用该协议族的create()函数的处理句柄。根据参数AF_INET,inet_creat()就被调用了,在inet_creat()根据服务类型在inetsw[SOCK_MAX]

   选择合适的协议,并把协议的操作集赋给socket就是了,根据SOCK_STREAM,TCP协议被选中,
   inet_creat(){
   answer=inetsw [用户要求服务服务] ;
   sock->ops = answer->ops;
   sk->prot = answer->prot
   }

   到此为止,上下都打通了,该是大家都源码的时候了。

博客文章汇总:

如何科学饮水   供应WIFI(提供 wince linux下驱动)  

合肥市政府原副秘书长受贿240万被判13   百度C2C支付平台定名百付宝 域名地址昨曝光

LHC 的加速过程   什么是百度蜘蛛?百度爬虫是什么?    百度确定推C2C支付平台 电子商务大战一触即发   

无线音箱(出售)  

LINUX下无线网卡驱动程式安装的一般步骤  

云时代来临之云计算的四个显著特点 云计算时代来临 云计算到底指什么 随着安全云计算的热潮 您准备好了吗  

云计算的基本原理和概念  

发现一个靠点击广告就能赚美元的网站

Writing Linux LCD drivers

WinCELCD驱动程序编写指南

关于MODULE_PARM()  

Linux环境下USB的原理、驱动和配置2

Linux环境下USB的原理、驱动和配置1

腾讯发布QQ for Linux 1.0 Preview  

搜索引擎正在逐步抹杀人类的记忆和思考

[转载] Linux内核2.4.x的网络接口源码的结构  

Zigbee入门开发  

中国会面临金融战争的灭顶之灾吗?    不断增长的外汇储备,是中国政府的心病  

两粒电子的爱情  

zigbee简介  

银监会令深上报断供真相MTK是什么?  

[出售]WIFI模块,WIFI方案VT6656+RFUSB接口,MINIPCI接口   

鸟巢山寨机,玩的就是心跳   

ARM Bootloader的共性  

Jennic-zigbee开发套件

供应jennic无线模块 > jn5139高功率模块

吴宇森首度回应质疑萌萌是可爱的错误   

《赤壁》中的精彩与搞笑  

日本第一钻石王老五”--青山光司 日本第一钻王老五”--青山光司  

谁来拯救中国3760亿美元财富  

什么是无线网卡?

电磁辐射会失忆

中国产山寨手机热销巴基斯坦 难定位恐怖分子  

Zigbee标准可能成为自动化的未来动力

无线网络知识收集 无线增益天线认识和选购  

功率放大器"增益放大器"的区别   

无线问题面面观

噪声与信噪比  

zigbee软件设计  

什么是ZigBee无线网络?  

PLC 电力载波通信  

Introduction to IT800D  

超外差和超再生模块有何区别?  

飞弹发射失败怎么办?用 Photoshop 补一枚上去!   

Linux Driver Model()  

Power   Management in the Linux Driver Model(2.6.14)()  

WDM(Windows Driver Model)学习()  

Google问:手机还能拿来做什么?   

USB Dongle VT6656   

IEEE1394  

接收灵敏度   

JN5139 Wireless   Microcontroller  

JN5139-xxx-M02 高功率模块   

ZigBee架构WG议长:ZigBee与蓝牙可以共存  

基于ZigBee的无线网络技术及其应用  

Zigbee可能成为自动化的未来动力  

zigbee简介  

JN51xx开发套件  

ZigBee网络层


类别:杂项 | 添加到搜藏 | 浏览() | 评论 (0)
 
最近读者:
 
网友评论:
发表评论:
姓 名:
网址或邮箱: (选填)
内 容:
验证码: 请点击后输入四位验证码,字母不区分大小写
      

     

©2009 Baidu