深入講解linux內(nèi)核學(xué)習(xí)筆記------鄰居子系統(tǒng)(一)
鄰居子系統(tǒng)的結(jié)構(gòu)由多個(gè)數(shù)據(jù)結(jié)構(gòu)組成:
1、neigh_table結(jié)構(gòu):該結(jié)構(gòu)主要用來(lái)存儲(chǔ)與鄰居協(xié)議相關(guān)的參數(shù),功能函數(shù),以及鄰居項(xiàng)散列表;一個(gè)neigh_table結(jié)構(gòu)實(shí)例對(duì)應(yīng)一個(gè)鄰居協(xié)議(例如:arp,arp_tbl),所有的實(shí)例都鏈接在全局鏈表neigh_tables中。linux內(nèi)核源碼剖析——tcp/ip實(shí)現(xiàn)對(duì)這個(gè)結(jié)構(gòu)體做了很詳細(xì)的描述,大家可以去看看
2、neighbor結(jié)構(gòu):鄰居項(xiàng)就是使用該結(jié)構(gòu)來(lái)描述,該結(jié)構(gòu)存儲(chǔ)了鄰居的相關(guān)信息,包括狀態(tài),二層和三層協(xié)議地址,提供給三層協(xié)議的函數(shù)指針,還有定時(shí)器和緩存的二層首部等。注:一個(gè)鄰居并不代表一個(gè)主機(jī),而是一個(gè)三層協(xié)議地址,對(duì)于配置了多接口的主機(jī),一個(gè)主機(jī)將對(duì)應(yīng)多個(gè)三層協(xié)議地址。
3、neigh_ops結(jié)構(gòu):該結(jié)構(gòu)相當(dāng)于是鄰居項(xiàng)函數(shù)指針表,由在鄰居的生存周期中不同時(shí)期被調(diào)用的多個(gè)函數(shù)指針組成。其中有多個(gè)函數(shù)指針是實(shí)現(xiàn)三層與dev_queue_xmit之間的調(diào)用橋梁,適用于不同狀態(tài);
struct neigh_ops
{
/*
* 標(biāo)識(shí)所屬的地址族,比如ARP為AF_INET等。
*/
int family;
/*
* 發(fā)送請(qǐng)求報(bào)文函數(shù)。在發(fā)送第一個(gè)報(bào)文時(shí),需要
* 新的鄰居項(xiàng),發(fā)送報(bào)文被緩存到arp_queue隊(duì)列中,
* 然后會(huì)調(diào)用solicit()發(fā)送請(qǐng)求報(bào)文。
*/
void (*solicit)(struct neighbour *, struct sk_buff*);
/*
* 當(dāng)鄰居項(xiàng)緩存著未發(fā)送的報(bào)文,而該鄰居項(xiàng)又不可達(dá)時(shí),
* 被調(diào)用來(lái)向三層報(bào)告錯(cuò)誤的函數(shù)。ARP中為arp_error_report(),
* 最終會(huì)給報(bào)文發(fā)送方發(fā)送一個(gè)主機(jī)不可達(dá)的ICMP差錯(cuò)報(bào)文。
*/
void (*error_report)(struct neighbour *, struct sk_buff*);
/*
* 最通用的輸出函數(shù),可用于所有情況。此輸出函數(shù)實(shí)現(xiàn)了
* 完整的輸出過(guò)程,因此存在較多的校驗(yàn)與操作,以確保
* 報(bào)文的輸出,因此該函數(shù)相對(duì)較消耗資源。此外,不要
* 將neigh_ops->output()與neighbour->output()混淆。
*/
int (*output)(struct sk_buff*);
/*
* 在確定鄰居可達(dá)時(shí),即狀態(tài)為NUD_CONNECTED時(shí)使用的輸出函數(shù)。
* 由于所有輸出所需要的信息都已具備,因此該函數(shù)只是簡(jiǎn)單
* 地添加二層首部,也因此比output()快得多。
*/
int (*connected_output)(struct sk_buff*);
/*
* 在已緩存了二層首部的情況下使用的輸出函數(shù)。
*/
int (*hh_output)(struct sk_buff*);
/*
* 實(shí)際上,以上幾個(gè)輸出接口,除了hh_output外,并不真正傳輸
* 數(shù)據(jù)包,只是在準(zhǔn)備好二層首部之后,調(diào)用queue_xmit接口。
*/
int (*queue_xmit)(struct sk_buff*);
};
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)? ??


注意solicit,output,connected_output,hh_output以及queue_xmit的區(qū)別以及聯(lián)系,注釋中也講了很明顯。 4、neigh_parms結(jié)構(gòu):該結(jié)構(gòu)是鄰居協(xié)議參數(shù)的配置塊,用于存儲(chǔ)可調(diào)節(jié)的鄰居協(xié)議參數(shù),如重傳超時(shí)時(shí)間,proxy_queue隊(duì)列長(zhǎng)度等。一個(gè)鄰居協(xié)議對(duì)應(yīng)一個(gè)參數(shù)配置塊,而每一個(gè)網(wǎng)絡(luò)設(shè)備的ipv4的配置塊中也存在一個(gè)存放默認(rèn)值的鄰居配置塊。
5、pneigh_entry結(jié)構(gòu):該結(jié)構(gòu)用來(lái)保存允許代理的條件,只有和結(jié)構(gòu)中的接收設(shè)備以及目標(biāo)地址想匹配才能代理。所有的pneigh_entry實(shí)例都存儲(chǔ)在鄰居表的phash_buckets散列表中。稱之為代理向,可通過(guò)ip neigh add proxy命令添加。
6、hh_cache結(jié)構(gòu):該結(jié)構(gòu)用來(lái)緩存二層首部,這樣就可以復(fù)制而不是逐個(gè)域的設(shè)置二層首部,從而加速報(bào)文的輸出,但也并非所有的設(shè)備驅(qū)動(dòng)程序都需要緩存二層首部。
鄰居表是由neigh_table_init初始化,對(duì)于arp_tbl的初始化,在arp模塊初始化時(shí)由arp_init
void __init arp_init(void)
{
neigh_table_init(&arp_tbl);
dev_add_pack(&arp_packet_type);
arp_proc_init();
#ifdef CONFIG_SYSCTL
neigh_sysctl_register(NULL, &arp_tbl.parms, NET_IPV4,
? ? ?NET_IPV4_NEIGH, "ipv4", NULL, NULL);
#endif
register_netdevice_notifier(&arp_netdev_notifier);
}
void neigh_table_init(struct neigh_table *tbl)
{
struct neigh_table *tmp;
neigh_table_init_no_netlink(tbl);
write_lock(&neigh_tbl_lock);
for (tmp = neigh_tables; tmp; tmp = tmp->next) {
if (tmp->family == tbl->family)
break;
}
tbl->next = neigh_tables;
neigh_tables = tbl;
write_unlock(&neigh_tbl_lock);
if (unlikely(tmp)) {
printk(KERN_ERR "NEIGH: Registering multiple tables for "
? ? ? "family %d\n", tbl->family);
dump_stack();
}
}
從上面的源碼看出,arp初始化首先就會(huì)調(diào)用neigh_table_init對(duì)鄰居表進(jìn)行初始化,而neigh_table_init也比較簡(jiǎn)單,主要就是從neigh_tables中查找,看此鄰居表是否已經(jīng)存在,如果存在會(huì)報(bào)錯(cuò),否則就會(huì)把該鄰居表插入到neigh_tables中;在neigh_table_init_no_netlink這個(gè)函數(shù)中會(huì)做一些初始化的工作,分配緩存,初始化定時(shí)器的功能,創(chuàng)建存儲(chǔ)鄰居項(xiàng)和代理項(xiàng)的hash_buckets,phash_buckets; 鄰居項(xiàng)存在一種狀態(tài)機(jī),鄰居項(xiàng)都有一個(gè)對(duì)于管理和維護(hù)鄰居表來(lái)說(shuō)非常重要的成員,nud_state,用來(lái)表示該鄰居項(xiàng)當(dāng)前所處的狀態(tài)。具體的狀態(tài)遷移圖在上面提到的那本書中有,并且在第二篇博客中博主結(jié)合arp協(xié)議進(jìn)行描繪。下面依依介紹這幾個(gè)狀態(tài):
1、NUD_NONE:鄰居項(xiàng)剛建立時(shí)處于的狀態(tài),在該狀態(tài)下,還沒(méi)有硬件地址可以用,所以還不能發(fā)送請(qǐng)求報(bào)文。一旦有報(bào)文要輸出到該鄰居,便會(huì)出發(fā)對(duì)該鄰居硬件地址的請(qǐng)求,進(jìn)入NUD_INCOMPLETE狀態(tài),并緩存發(fā)送的報(bào)文
2、NUD_INCOMPLETE:該狀態(tài)是請(qǐng)求報(bào)文已發(fā)送,但尚未收到應(yīng)答的狀態(tài)。該狀態(tài)下還沒(méi)解析到硬件地址,因此尚無(wú)可用硬件地址,如果有報(bào)文要輸出到該鄰居,會(huì)將其緩存起來(lái)。這個(gè)狀態(tài)會(huì)啟動(dòng)一個(gè)定時(shí)器,如果在定時(shí)器到期時(shí)還沒(méi)有接收到鄰居的回應(yīng),則會(huì)重復(fù)發(fā)送請(qǐng)求報(bào)文,進(jìn)入NUD_REACHABLE,否則發(fā)送請(qǐng)求報(bào)文的次數(shù)打到上限,便會(huì)進(jìn)入NUD_FAILED
3、NUD_REACHABEL:該狀態(tài)以及得到并緩存了鄰居的硬件地址。進(jìn)入該狀態(tài)首先設(shè)置鄰居項(xiàng)相關(guān)的output函數(shù)(該狀態(tài)使用neighbors_ops結(jié)構(gòu)的connectd_outpt),然后查看是否存在要發(fā)送給該鄰居的報(bào)文。如果在該狀態(tài)下閑置時(shí)間達(dá)到上限,便會(huì)進(jìn)入NUD_STATLE
4、NUD_STATLE:該狀態(tài)一旦有報(bào)文要輸出到該鄰居,則會(huì)進(jìn)入NUD_DELAY并將該報(bào)文輸出。如果在該狀態(tài)下閑置時(shí)間達(dá)到上限,且此時(shí)的引用計(jì)數(shù)為1,則通過(guò)垃圾回收機(jī)制將其刪除,在該狀態(tài)下,報(bào)文的輸出不收限制,使用慢速發(fā)送過(guò)程
5、NUD_DELAY:該狀態(tài)下表示NUD_STATE狀態(tài)下發(fā)送的報(bào)文已經(jīng)發(fā)出,需得到鄰居的可達(dá)性確認(rèn)的狀態(tài)。在為接收到鄰居的應(yīng)答或確認(rèn)時(shí)也會(huì)定時(shí)地重發(fā)請(qǐng)求,如果發(fā)送請(qǐng)求報(bào)文的次數(shù)到上限,如果收到鄰居的應(yīng)答,進(jìn)入NUD_REACHABEL,否則進(jìn)入NUD_FAILED,在該狀態(tài)下,報(bào)文的輸出不收限制,使用慢速發(fā)送過(guò)程
還有幾種狀態(tài)比較容易理解,也就不特意列出了。
