天天看點

linux0.99網絡子產品-網絡子產品初始化

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 };

對應下圖:

linux0.99網絡子產品-網絡子產品初始化

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協定的逾時計時器的處理函數。

到此我們就把網絡子產品的初始化過程分析完了。

整個過程大緻圖示如下:

linux0.99網絡子產品-網絡子產品初始化

總結:

網絡子產品初始化工作是在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網絡子產品-網絡裝置初始化》,最後注冊了中斷下半部處理函數和逾時處理函數。