歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux資訊 >> 更多Linux

Linux最新穩定內核2.4.x的網絡接口源碼的結構(一)

作者:李元佳    一.前言       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;     ………     }




 

 



Copyright © Linux教程網 All Rights Reserved