本文分析基於核心Linux Kernel 1.2.13
原創作品,轉載請標明http://blog.csdn.net/yming0221/article/details/7488828
更多請看專欄,地址http://blog.csdn.net/column/details/linux-kernel-net.html
作者:閆明
以後的系列博文將深入分析Linux核心的網路棧實現原理,這裡看到曹桂平博士的分析後,也決定選擇Linux核心1.2.13版本進行分析。
原因如下:
1.功能和網路棧層次已經非常清晰
2.該版本與其後續版本的銜接性較好
3.複雜度相對新的核心版本較小,複雜度低,更容易把握網路核心的實質
4.該核心版本比較系統資料可以查詢
下面開始零基礎分析Linux核心網路部分的初始化過程。
經過系統加電後執行的bootsect.S,setup.S,head.S,可以參考以前分析的0.11核心。原理相同。
- Linux0.11核心--啟動引導程式碼分析bootsect.s
- Linux0.11核心--啟動引導程式碼分析setup.s
- Linux0.11核心--idt(中斷描述符表的初始化)head.s分析
進行前期的準備工作後,系統跳轉到init/main.c下的start_kernel函數執行。
網路棧的階層如:(註:該圖片摘自《Linux核心網路棧原始碼情景分析》)
物理層主要提供各種串連的物理裝置,如各種網卡,串口卡等;
鏈路層主要指的是提供對物理層進行訪問的各種介面卡的驅動程式,如網卡驅動等;
網路層的作用是負責將網路資料包傳輸到正確的位置,最重要的網路層協議當然就是IP協議了,其實網路層還有其他的協議如ICMP,ARP,RARP等,只不過不像IP那樣被多數人所熟悉;
傳輸層的作用主要是提供端到端,說白一點就是提供應用程式之間的通訊,傳輸層最著名的協議非TCP與UDP協議末屬了;
應用程式層,顧名思義,當然就是由應用程式提供的,用來對傳輸資料進行語義解釋的“人機介面”層了,比如HTTP,SMTP,FTP等等,其實應用程式層還不是人們最終所看到的那一層,最上面的一層應該是“解釋層”,負責將資料以各種不同的表項形式最終呈獻到人們眼前。
Linux網路通訊協定棧結構
1,系統調用介面層,實質是一個面向使用者空間應用程式的介面調用庫,向使用者空間應用程式提供使用網路服務的介面。
2,協議無關的介面層,就是SOCKET層,這一層的目的是屏蔽底層的不同協議(更準確的來說主要是TCP與UDP,當然還包括RAW IP, SCTP等),以便與系統調用層之間的介面可以簡單,統一。簡單的說,不管我們應用程式層使用什麼協議,都要通過系統調用介面來建立一個SOCKET,這個SOCKET其實是一個巨大的sock結構,它和下面一層的網路通訊協定層聯絡起來,屏蔽了不同的網路通訊協定的不同,只吧資料部分呈獻給應用程式層(通過系統調用介面來呈獻)。
3,網路通訊協定實現層,毫無疑問,這是整個協議棧的核心。這一層主要實現各種網路通訊協定,最主要的當然是IP,ICMP,ARP,RARP,TCP,UDP等。這一層包含了很多設計的技巧與演算法,相當的不錯。
4,與具體裝置無關的驅動介面層,這一層的目的主要是為了統一不同的介面卡的驅動程式與網路通訊協定層的介面,它將各種不同的驅動程式的功能統一抽象為幾個特殊的動作,如open,close,init等,這一層可以屏蔽底層不同的驅動程式。
5,驅動程式層,這一層的目的就很簡單了,就是建立與硬體的介面層。
start_kernel函數經過平台初始化,記憶體初始化,陷阱初始化,中斷初始化,進程調度初始化,緩衝區初始化等,然後執行socket_init(),最後開中斷執行init()。
核心的網路戰初始化函數socket_init()函數的實現在net/socket.c中
下面是該函數的實現
void sock_init(void)//網路棧初始化{int i;printk("Swansea University Computer Society NET3.019\n");/* *Initialize all address (protocol) families. */ for (i = 0; i < NPROTO; ++i) pops[i] = NULL;/* *Initialize the protocols module. */proto_init();#ifdef CONFIG_NET/* *Initialize the DEV module. */dev_init(); /* *And the bottom half handler */bh_base[NET_BH].routine= net_bh;enable_bh(NET_BH);#endif }
其中的地址族協議初始化語句for (i = 0; i < NPROTO; ++i) pops[i] = NULL;
這裡檔案中定義的NPROTO為16
#define NPROTO16/* should be enough for now..*/
而pop[i]是如何定義的呢?
static struct proto_ops *pops[NPROTO];
proto_ops結構體是什麼呢?該結構體的定義在include/linux/net.h中,該結構體是具體的操作函數集合,是聯絡BSD通訊端和INET通訊端的介面,可以把BSD通訊端看做是INET通訊端的抽象,結構如下:
具體定義在net.h中
struct proto_ops { intfamily; int(*create)(struct socket *sock, int protocol); int(*dup)(struct socket *newsock, struct socket *oldsock); int(*release)(struct socket *sock, struct socket *peer); int(*bind)(struct socket *sock, struct sockaddr *umyaddr, int sockaddr_len); int(*connect)(struct socket *sock, struct sockaddr *uservaddr, int sockaddr_len, int flags); int(*socketpair)(struct socket *sock1, struct socket *sock2); int(*accept)(struct socket *sock, struct socket *newsock, int flags); int(*getname)(struct socket *sock, struct sockaddr *uaddr, int *usockaddr_len, int peer); int(*read)(struct socket *sock, char *ubuf, int size, int nonblock); int(*write)(struct socket *sock, char *ubuf, int size, int nonblock); int(*select)(struct socket *sock, int sel_type, select_table *wait); int(*ioctl)(struct socket *sock, unsigned int cmd, unsigned long arg); int(*listen)(struct socket *sock, int len); int(*send)(struct socket *sock, void *buff, int len, int nonblock, unsigned flags); int(*recv)(struct socket *sock, void *buff, int len, int nonblock, unsigned flags); int(*sendto)(struct socket *sock, void *buff, int len, int nonblock, unsigned flags, struct sockaddr *, int addr_len); int(*recvfrom)(struct socket *sock, void *buff, int len, int nonblock, unsigned flags, struct sockaddr *, int *addr_len); int(*shutdown)(struct socket *sock, int flags); int(*setsockopt)(struct socket *sock, int level, int optname, char *optval, int optlen); int(*getsockopt)(struct socket *sock, int level, int optname, char *optval, int *optlen); int(*fcntl)(struct socket *sock, unsigned int cmd, unsigned long arg);};
可以看到,這裡實際上就是一系列操作的函數,有點類似於檔案系統中的file_operations。通過參數傳遞socket完成操作。
接下來是proto_init()協議初始化。
void proto_init(void){extern struct net_proto protocols[];/* Network protocols 全域變數,定義在protocols.c中 */struct net_proto *pro;/* Kick all configured protocols. */pro = protocols;while (pro->name != NULL) {(*pro->init_func)(pro);pro++;}/* We're all done... */}
全域的protocols定義如下:
struct net_proto protocols[] = {#ifdefCONFIG_UNIX { "UNIX",unix_proto_init},#endif#if defined(CONFIG_IPX)||defined(CONFIG_ATALK) { "802.2",p8022_proto_init }, { "SNAP",snap_proto_init },#endif#ifdef CONFIG_AX25 { "AX.25",ax25_proto_init },#endif #ifdefCONFIG_INET { "INET",inet_proto_init},#endif#ifdef CONFIG_IPX { "IPX",ipx_proto_init },#endif#ifdef CONFIG_ATALK { "DDP",atalk_proto_init },#endif { NULL,NULL}};
而結構體net_proto的定義net.h中為
struct net_proto {char *name;/* Protocol name */void (*init_func)(struct net_proto *);/* Bootstrap */};
以後注重討論標準的INET域
讓我們回到proto_init()函數
接下來會執行inet_proto_init()函數,進行INET域協議的初始化。該函數的實現在net/inet/af_inet.c中
其中的
(void) sock_register(inet_proto_ops.family, &inet_proto_ops);
int sock_register(int family, struct proto_ops *ops){int i;cli();//關中斷for(i = 0; i < NPROTO; i++) //尋找一個可用的空閑表項{if (pops[i] != NULL) continue;//如果不空,則跳過pops[i] = ops;//進行賦值pops[i]->family = family;sti();//開中斷return(i);//返回用於剛剛註冊的協議向量號}sti();//出現異常,也要開中斷return(-ENOMEM);}
參數中的inet_proto_ops定義如下:
static struct proto_ops inet_proto_ops = {AF_INET,inet_create,inet_dup,inet_release,inet_bind,inet_connect,inet_socketpair,inet_accept,inet_getname, inet_read,inet_write,inet_select,inet_ioctl,inet_listen,inet_send,inet_recv,inet_sendto,inet_recvfrom,inet_shutdown,inet_setsockopt,inet_getsockopt,inet_fcntl,};
其中AF_INET宏定義為2,即INET協議族號為2,後面是函數指標,INET域的操作函數。
然後
printk("IP Protocols: ");for(p = inet_protocol_base; p != NULL;) //將inet_protocol_base指向的一個inet_protocol結構體加入數組inet_protos中{struct inet_protocol *tmp = (struct inet_protocol *) p->next;inet_add_protocol(p);printk("%s%s",p->name,tmp?", ":"\n");p = tmp;}/* *Set the ARP module up */arp_init();//對位址解析層進行初始化 /* *Set the IP module up */ip_init();//對IP層進行初始化
協議初始化完成後再執行dev_init()裝置的初始化。
這是大體的一個初始化流程,討論的不是很詳細,後續會進行Linux核心網路棧原始碼的詳細分析。