linux0.11版核心的啟動過程我們已經分析過了,具體可以參考《系統引導源碼分析bootsect.s》《setup.s源碼分析》《head.s源碼分析》《init/main.c源碼分析》四篇文章,對于0.99版稍有不同。我們看一下head.S中的改變。
linux0.11核心head.s:
135 after_page_tables:
136 pushl $0 # These are the parameters to main :-)
137 pushl $0
138 pushl $0
139 pushl $L6 # return address for main, if it decides to.
140 pushl $_main
141 jmp setup_paging
linux0.99核心head.s
81 lss _stack_start,%esp
82 pushl $0 # These are the parameters to main :-)
83 pushl $0
84 pushl $0
85 cld # gcc2 wants the direction flag cleared at all times
86 call _start_kernel
87 L6:
88 jmp L6 # main should never return here, but
89 # just in case, we know what happens.
可見linux0.99核心head.S中調用的不再是main函數,而是start_kernel.我們來看一下它的源碼。
202 void start_kernel(void)
203 { ..................
226 trap_init();
227 init_IRQ();
228 sched_init();
229 parse_options(command_line);
42 buffer_init();
243 inode_init();
244 time_init();
245 floppy_init();
246 sock_init();
247 sti();
........................
260 for(;;)
261 idle();
262 }
可以看到246行正是初始化網絡子產品的。我們來看一下:
net/socket.c
944 void
945 sock_init(void)
946 {
947 struct socket *sock;
948 int i, ok;
949
950 for (sock = sockets; sock <= last_socket; ++sock)
951 sock->state = SS_FREE;
952 for (i = ok = 0; i < NPROTO; ++i) {
953 printk("sock_init: initializing family %d (%s)\n",
954 proto_table[i].family, proto_table[i].name);
955 if ((*proto_table[i].ops->init)() < 0) {
956 printk("sock_init: init failed.\n",
957 proto_table[i].family);
958 proto_table[i].family = -1;
959 }
960 else
961 ++ok;
962 }
963 if (!ok)
964 printk("sock_init: warning: no protocols initialized\n");
965 return;
966 }
下面我們逐漸來分析該函數:
945 sock_init(void)
946 {
947 struct socket *sock;
948 int i, ok;
950 for (sock = sockets; sock <= last_socket; ++sock)
951 sock->state = SS_FREE;
75 static struct socket sockets[NSOCKETS]; 其中NSOCKETS是net/kern_sock.h中的宏定義,值為128
net/kern_sock.h:4:#define NSOCKETS 128
也就是說sockets是一個大小為128的socket數組。
950行-951行的for循環用于初始化sockets中的每個socket為空閑狀态。
繼續回到sock_init(void)函數
952 for (i = ok = 0; i < NPROTO; ++i) {
953 printk("sock_init: initializing family %d (%s)\n",
954 proto_table[i].family, proto_table[i].name);
955 if ((*proto_table[i].ops->init)() < 0) {
956 printk("sock_init: init failed.\n",
957 proto_table[i].family);
958 proto_table[i].family = -1;
959 }
960 else
961 ++ok;
962 }
其中NRPROTO定義于net/socket.c:35:#define NPROTO (sizeof(proto_table) / sizeof(proto_table[0]))
來看一下proto_table的定義:
25 static struct {
26 short family;
27 char *name;
28 struct proto_ops *ops;
29 } proto_table[] = {
30 {AF_UNIX, "AF_UNIX", &unix_proto_ops},
31 #ifdef CONFIG_TCPIP
32 {AF_INET, "AF_INET", &inet_proto_ops},
33 #endif
34 };
對應下圖:

NRPROTO對應的是協定的數目。
是以這裡的for循環周遊每一個協定,953行列印正在周遊的協定族編号(名稱),955行調用目前協定的初始化函數,如果初始化失敗,列印出錯資訊,并将其family指派為-1.如果初始化正常,則遞增ok來計數。
繼續
963 if (!ok)
964 printk("sock_init: warning: no protocols initialized\n");
965 return;
966 }
963行如果沒有一個協定被初始化,965行列印資訊。
到這裡初始化函數就分析完了。
接下來,我們就TCP/IP協定來具體看一下它的初始化過程。上面proto_table對應的第2項就是TCP/IP協定,它注冊的操作函數為inet_proto_ops。我們看一下它的init方法。
net/tcp/sock.c
156 struct proto_ops inet_proto_ops =
157 {
158 ip_proto_init,
159 ip_proto_create,
160 ip_proto_dup,
161 ip_proto_release,
162 ip_proto_bind,
163 ip_proto_connect,
164 ip_proto_socketpair,
165 ip_proto_accept,
166 ip_proto_getname,
167 ip_proto_read,
168 ip_proto_write,
169 ip_proto_select,
170 ip_proto_ioctl,
171 ip_proto_listen,
172 ip_proto_send,
173 ip_proto_recv,
174 ip_proto_sendto,
175 ip_proto_recvfrom,
176 ip_proto_shutdown,
177 ip_proto_setsockopt,
178 ip_proto_getsockopt,
179 ip_proto_fcntl
180 };
首先我們看到inet_proto_ops是一個方法的集合,這與驅動編寫中的file_operations非常相似。現在還是重點來看它的init方法,它對應的就是158行的ip_proto_init。
net/tcp/sock.c
我們分段來看
835
836 static int ip_proto_init(void)
837 {
838 int i;
839 struct device *dev, *dev2;
840 struct ip_protocol *p;
841 seq_offset = CURRENT_TIME*250;
842
843 for (i = 0; i < SOCK_ARRAY_SIZE; i++)
844 {
845 tcp_prot.sock_array[i] = NULL;
846 udp_prot.sock_array[i] = NULL;
847 raw_prot.sock_array[i] = NULL;
848 }
845-848行初始化了幾個相關數組,元素值均設為NULL。關于這裡的tcp_prot,udp_prot,raw_prot。以tcp_prot為例,它的定義于net/tcp/tcp.c:3185 struct proto tcp_prot。我們看一下proto結構體:
100 struct proto
101 {
102 void *(*wmalloc)(volatile struct sock *sk, unsigned long size, int force,
103 int priority);
.........省略.....
139 unsigned short max_header;
140 unsigned long retransmits;
141 volatile struct sock *sock_array[SOCK_ARRAY_SIZE];
142 };
根據名字就可以知道proto是協定的結構體,它包含大量的函數指針,這裡隻看一下sock_array,它是一個sock數組,可以先大緻說一下(詳細分析看傳輸層源碼,比如《linux0.99網絡子產品-傳輸層(TCP接收)》),sock對應一個TCP連接配接(它是一個四元組源IP:源端口 目的IP:目的端口),為什麼要以數組的形式存在呢?這也是從效率和管理角度考慮的,我們知道TCP連接配接會指明目的端口,這裡就根據目的端口号進行哈希後儲存到相應的索引對應的連結清單上。
繼續回到前面分析:
850 for (p = ip_protocol_base; p != NULL;)
851 {
852 struct ip_protocol *tmp;
853
854 tmp = p->next;
855 add_ip_protocol (p);
856 p = tmp;
857 }
先來看一下ip_protocol_base:
net/tcp/protocols.c:
86 struct ip_protocol *ip_protocol_base = &icmp_protocol;
76 static struct ip_protocol icmp_protocol =
77 {
........省略........
80 &udp_protocol, ........省略........
84 };
再來看一下ip_protocol的定義
net/tcp/ip.h
130 struct ip_protocol
131 {
.......省略.......
138 struct ip_protocol *next;
.......省略.......
142 };
通過上面可以知道,ip_protocol_base指向了icmp_protocol的位址,icmp_protocol中又通過next指針指向了udp_protocol,而udp_protocol通過next指向了tcp_protocol(上面沒有給出).
是以,850行開始的for循環,依次對icmp_protocol,udp_protocol和tcp_protocol調用add_ip_protocol。
我們來看一下這個方法:
net/tcp/ip.c
100 void
101 add_ip_protocol (struct ip_protocol *prot)
102 {
103 unsigned char hash;
104 struct ip_protocol *p2;
105 hash = prot->protocol & (MAX_IP_PROTOS-1);
106 prot ->next = ip_protos[hash];
107 ip_protos[hash] = prot; 這裡就是把prot添加到ip_protos之中合适的位置,如果該位置上已經存在就連結成鍊
108 prot->copy = 0;
109
110 for (p2 = prot->next; p2 != NULL; p2=p2->next)
111 {
112 if (p2->protocol == prot->protocol)
113 {
114 prot->copy = 1;
115 break;
116 }
117 } 如果這條鍊上有其他元素與剛剛添加的元素屬于同一個協定(protocol字段相同),就置位它的copy标記,然後中斷循環(因為後面的元素已經在其他元素插入時進行過同樣的設定了)。那麼這裡的copy是什麼用呢?分析到網絡層的時候我們發現它是用來指明是否要為上層協定複制資料報的。也就是說,IP層收到資料報後應該向上層傳輸,但是如果有多個上層協定注冊到了IP層(copy==1),這時就需要為它們複制資料報(更嚴謹的說,如果有n個上層協定注冊到IP層,那麼需要複制n-1個資料報)。
119 }
這裡的ip_protos其實是用于儲存注冊到網絡層中的傳輸層(也包括ICMP)協定,網絡層在接收到資料報後會根據這裡注冊的協定向相應的協定傳輸資料報。《linux0.99網絡子產品-網絡層(接收)》中的分析可以印證這句話,另外多說一句,鍊路層也是用了同樣的思想,ip協定和arp協定注冊到鍊路層來接收鍊路層的資料報,隻不過那裡是簡單的連結清單,并沒有使用hash。copy字段是用來表明是否需要拷貝一個資料報的副本來傳給相應的協定,如果隻有一個對象對此資料報感興趣就不需要拷貝,如果存在多個就需要給每一個對象拷貝一個副本。
繼續ip_proto_init的分析:
859
860
863
864 dev2 = NULL;
865 for (dev = dev_base; dev != NULL; dev=dev->next)
866 {
867 if (dev->init && dev->init(dev))
868 { //下面是初始化失敗的情況
869 if (dev2 == NULL)
870 dev_base = dev->next;
871 else
872 dev2->next = dev->next; //删除該裝置
873 }
874 else
875 {
876 dev2 = dev;
877 }
878 }
如果裝置初始化失敗就從鍊上删除它(注意dev->init成功傳回0,失敗傳回1),直到下次boot才會重新初始化它。
我們具體來看一下這裡的dev_base,它定義于net/tcp/Space.c:
100 struct device *dev_base = &loopback_dev;
loopback_dev通過next指針連結了wd8003_dev,其中loopback_dev就是回環裝置,而wd8003_dev就是網卡裝置了。關于裝置的初始化請看《linux0.99網絡子產品-網絡裝置初始化》。
繼續:
879 bh_base[INET_BH].routine = inet_bh;
880 timer_table[NET_TIMER].fn = net_timer;
881 return (0);
882 }
bh_base定義于kernel/irq.c:42:struct bh_struct bh_base[32]; 879行設定了TCP協定的下半部處理函數。
timer_table定義于kernel/sched.c:350:struct timer_struct timer_table[32];
include/linux/timer.h
37 struct timer_struct {
38 unsigned long expires;
39 void (*fn)(void);
40 };
880行是設定的TCP協定的逾時計時器的處理函數。
到此我們就把網絡子產品的初始化過程分析完了。
整個過程大緻圖示如下:
總結:
網絡子產品初始化工作是在sock_init函數中開始的。對于linux0.99來說它是init/main.c中的start_kernel函數中調用的。它首先把sockets數組中的所有socket的狀态都初始化為空閑狀态,然後周遊所有網絡協定,并依次調用它們的init方法進行初始化。所有的網絡協定都存在一個proto_table數組中,它的元素指明了協定族,協定名,以及操作函數(proto_ops),這裡就是通過它來找到對應init方法的。TCP協定對應的初始化方法為ip_proto_init,在這個方法中初始化了序列号(seq_offset),并且把相關協定的sock_array(關于sock_array會在傳輸層會看到它的用處)每個元素初始化NULL,随後周遊所有的傳輸層協定(這麼說不是很準确,總之就是準備注冊到IP層的協定),并把它們加入到ip_protos數組中,這個數組類似與Java中的HashMap,如果加入到ip_protos中的協定被hash到了同一個位置,就會被連結成鍊,并且其後所有與其具有相同protocol字段的元素的copy屬性都被置位(因為如果有多個對象對一個資料報感興趣的話,就必須給每一個對象一份資料報拷貝),最後初始化裝置.對于linux0.99來說,初始化了兩個裝置loopback和wd8003。關于初始化裝置的具體過程見《linux0.99網絡子產品-網絡裝置初始化》,最後注冊了中斷下半部處理函數和逾時處理函數。