Linux的TCP/IP協議棧閱讀筆記__Linux

來源:互聯網
上載者:User

以下的文章假定你有TCP/IP的知識,看過TCPv1或者Comer的Vol1

然後編過一些Linux下面的socket程式,好,Come on

Linux核心的啟動我就不說了,反正我的彙編也快忘光了,也沒有
學習過GAS。

1 從init/main.c的start_kernel函數說起。在這個函數裡面調用kernel_thread啟動了init進程,這個進程對應的函數是同一個檔案裡面的init函數,在init函數裡面調用了一個叫do_basic_setup的在同一個檔案裡面的函數,這個函數調用了net/socket.c裡面的sock_init函數,這個函數就是TCP/IP協議棧,也包括ipx等的入口。

首先sock_init函數裡面有很多ifdef這樣的東東,我覺得對於一個普通的主機來說,這些都不會配置的,它們包括:SLAB_SKB,CONFIG_WAN_ROUTER,CONFIG_FIREWALL,CONFIG_RTNETLINK, CONFIG_NETLINK_DEV不過說實話除了CONFIG_WAN_ROUTER和CONFIG_FIREWALL可以看出來是什麼意思以外,其它的我都是因為沒有見過所以認為不會配置的。
去掉了這些編譯選項以後就剩下這樣的代碼:
for (i = 0; i < NPROTO; i++)
net_families = NULL;
sk_init();
proto_init();

其中net_families在include/linux/net.h裡面定義,是這樣的:
struct net_proto_family
{
int family;
int (*create)(struct socket *sock, int protocol);
/* These are counters for the number of different methods of
each we support */
short authentication;
short encryption;
short encrypt_net;
};
其中有用的只有前兩項,那個create的callback函數是每個協議,例如AF_INET等初始化上層協議如TCP/ICMP協議需要的,以後還會遇到的,這裡先放著把sk_init函數在net/core/sock.c裡面,沒什麼說的..
struct sock *sk_alloc(int family, int priority, int zero_it)
{
struct sock *sk = kmem_cache_alloc(sk_cachep, priority);
if(sk) {
if (zero_it)
memset(sk, 0, sizeof(struct sock));
sk->family = family;
}
return sk;
}

proto_init函數在同一個檔案裡面:
void __init proto_init(void)
{
extern struct net_proto protocols[];
struct net_proto *pro;
pro = protocols;
while (pro->name != NULL)
{
(*pro->init_func)(pro);
pro++;
}
}

struct net_proto在include/linux/net.h裡面是這樣的:
struct net_proto
{
const char *name; /* Protocol name */
void (*init_func)(struct net_proto *); /* Bootstrap */
};
這個protocols的數組是在net/protocols.c裡面定義的,包含了一堆的協議初始化結構體,其中我只注意兩個:AF_INET和AF_PACKET它們的初始化函數分別是inet_proto_init和packet_proto_init

2 首先看PACKET協議,首先我們假定PACKET協議是編譯在核心裏面的,而不是一個MODULE,這樣得到packet_proto_init函數在net/packet/af_packet.c

裡面是這樣的: 

void __init packet_proto_init(struct net_proto *pro) 


sock_register(& packet_family_ops);
register_netdevice_notifier(& packet_netdev_notifier);




其中sock_register函數在net/socket.c裡面,就是簡單的 設定前面說的net_families數組中間對應的值:
int sock_register(struct net_proto_family *ops) 

if (ops->family >= NPROTO) { 

printk(KERN_CRIT "protocol %d >= NPROTO(%d)\n", ops->family, NPROTO); 
return -ENOBUFS; 


net_families[ops->family]=ops;

return 0; 




這裡要說明的是 packet_netdev_notifier是一個struct notifier_block類型,這個struct是在include/linux/notifier.h裡面的:

struct notifier_block 

int (*notifier_call)(struct notifier_block *self, unsigned long, void *); 

struct notifier_block *next; 
int priority; 
}; 


而register_netdevice_notifier函數在net/core/dev.c裡面,是這樣的: 
int register_netdevice_notifier(struct notifier_block *nb) 

return notifier_chain_register(&netdev_chain, nb); 


而 notifier_chain_register函數在include/linux/notifier.h裡面,是這樣的: 

extern __inline__ int notifier_chain_register(struct notifier_block **list, struct notifier_block *n)


while(*list) 

if(n->priority > (*list)->priority) 
break; 

list= &((*list)->next); 


n->next = *list; 

*list=n; 

return 0; 


顯然就是根據每個block的優先順序把這個block排列在一個block的鏈表裡面,在notifier_chain_register函數裡面我們可以發現這個鏈表是netdev_chain。實際上這個鏈表的作用就是在每個interface開啟,關閉
狀態改變或者外界調用相應的ioctl的時候通知這個鏈表上面的所有相關的裝置,而每一個協議都調用register_netdevice_notifier註冊了一個netdev_notifier的結構體,這樣就可以在interface改變的時候得到通知了(通過調用每個notifier_call函數)。


下面來看 inet_proto_init函數,這個函數在net/ipv4/af_inet.c中間,裡面也有很多ifdef的編譯選項,假定下面幾個是沒有定義的:

CONFIG_NET_IPIP,CONFIG_NET_IPGRE,CONFIG_IP_FIREWALL,
CONFIG_IP_MASQUERADE,CONFIG_IP_MROUTE 

假定下面幾個是定義了的: 
CONFIG_INET_RARP,CONFIG_PROC_FS 
下面是整理後的代碼: 

(void) sock_register(&inet_family_ops); 
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; 


arp_init(); 
ip_init(); 
tcp_v4_init(&inet_family_ops); 
tcp_init(); 

icmp_init(&inet_family_ops); 
rarp_ioctl_hook = rarp_ioctl; 

proc_net_register(&proc_net_rarp); 
proc_net_register(&proc_net_raw); 
proc_net_register(&proc_net_snmp); 

proc_net_register(&proc_net_netstat); 
proc_net_register(&proc_net_sockstat); 
proc_net_register(&proc_net_tcp); 
proc_net_register(&proc_net_udp);

其中的sock_register函數的作用已經在前面說了,現在來看看struct inet_protocol和inet_add_protocol函數。前面的結構體是在include/net/protocol.h裡面:
struct inet_protocol 
{
int (*handler)(struct sk_buff *skb, unsigned short len);
void (*err_handler)(struct sk_buff *skb, unsigned char *dp, int len);
struct inet_protocol *next;
unsigned char protocol;
unsigned char copy:1;
void *data;
const char *name;
};
第一個函數是用來接收資料的callback函數,第二個是錯誤處理函數,
其它的copy是用來協議共用的,這個以後再說,data當然就是這個結構體的私人資料了。

inet_add_protocol函數是在net/ipv4/protocol.c裡面的: 
void inet_add_protocol(struct inet_protocol *prot)
{
unsigned char hash;
struct inet_protocol *p2;

hash = prot->protocol & (MAX_INET_PROTOS - 1);
prot ->next = inet_protos[hash];
inet_protos[hash] = prot;
prot->copy = 0;

p2 = (struct inet_protocol *) prot->next;
while(p2 != NULL) 
{
if (p2->protocol == prot->protocol) 
{
prot->copy = 1;
break;
}
p2 = (struct inet_protocol *) p2->next;
}
}
顯然這個函數就是作樂一個hash表,然後每個hash表項都是一個鏈表頭,然後通過這個hash表加鏈表的方式訪問每個協議結構體。在這裡你也見到了copy成員的用法了把。

arp_init函數是在net/ipv4/arp.c裡面的(假定沒有定義CONFIG_SYSCTL):

neigh_table_init(&arp_tbl); 
dev_add_pack(&arp_packet_type);
proc_net_register(&proc_net_arp);

不知道是不是有人眼睛一亮啊,呵呵,看到了dev_add_pack函數。 
還是一步步來把。
neigh_table_init函數在net/core/neighbour.c中間:
void neigh_table_init(struct neigh_table *tbl)
{
unsigned long now = jiffies;

tbl->parms.reachable_time = neigh_rand_reach_time(tbl->parms.base_reachable_time);
init_timer(&tbl->gc_timer);
tbl->gc_timer.data = (unsigned long)tbl;
tbl->gc_timer.function = neigh_periodic_timer;
tbl->gc_timer.expires = now + tbl->gc_interval + tbl->parms.reachable_time;
add_timer(&tbl->gc_timer);
init_timer(&tbl->proxy_timer);
tbl->proxy_timer.data = (unsigned long)tbl;
tbl->proxy_timer.function = neigh_proxy_process;
skb_queue_head_init(&tbl->proxy_queue);
tbl->last_flush = now;
tbl->last_rand = now + tbl->parms.reachable_time*20;
tbl->next = neigh_tables;
neigh_tables = tbl;
}
jiffies是當前系統的時間,在i386系統上面好象一個jiffies代表50ms,顯然這個函數就是產生兩個timer將一個放在系統的timerlist裡面。那個gc_timer的意思是garbage collect timer,因為每過一段時間arp
的cache就應該更新,所以要有一個expires時間,這段時間過了以後就要更新arp地址了,那個proxy_timer還沒有看是什麼,不過我假 定我的機器不使用proxy也不做成proxy,所以proxy相關的都沒有管:P那個timer的function顯然是時鐘到期的回呼函數,data 是這個回呼函數要使用的私人資料了。

下面是dev_add_pack函數,它在net/core/dev.c裡面:
void dev_add_pack(struct packet_type *pt)
{
int hash;
#ifdef CONFIG_NET_FASTROUTE
/* Hack to detect packet socket */
if (pt->data) {
netdev_fastroute_obstacles++;
dev_clear_fastroute(pt->dev);
}
#endif
if(pt->type==htons(ETH_P_ALL))
{
netdev_nit++;
pt->next=ptype_all;
ptype_all=pt;
}
else

hash=ntohs(pt->type)&15;
pt->next = ptype_base[hash];
ptype_base[hash] = pt;
}
}
顯然系統保留了兩個表,一個是ptype_all,用來接收所有類型的包的鏈表,一個是一個hash數組+鏈表的結構,用來接收特定類型的包。那個fastroute不知道是什麼東西。

struct packet_type的定義在include/linux/netdevice.h裡面,我保留原來的注釋,這樣就不用我多說了:) 
{
unsigned short type; 
/* This is really htons(ether_type). */
struct device *dev; 
/* NULL is wildcarded here */
int (*func) (struct sk_buff *, 
struct device *, struct packet_type *);
void *data; 
/* Private to the packet type */
struct packet_type *next;
};
其中的func當然是回呼函數了,舉個例子來說,arp_packet_type是這樣的:
static struct packet_type arp_packet_type =
{
__constant_htons(ETH_P_ARP),
NULL, /* All devices */
arp_rcv,
NULL,
NULL

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.