Linux網路介面的原始碼分析

2021-04-27 18:07:18 字數 4865 閱讀 8077

二.網路介面程式的結構五.網路協議部分

協議層是真正實現是在這一層。在linux/include/linux/socket.h裡面,linux的bsd socket 定義了多至32支援的協議族,其中pf_inet就是我們最熟悉的tcp/ip協議族(ipv4, 以下沒有特別宣告都指ipv4)。以這個協議族為例,看看這層是怎麼工作的。實現tcp/ip協議族的主要檔案在inux/net/ipv4/目錄下面, linux/net/ipv4/af_inet.c為主要的管理檔案。

在linux2.4.16裡面,實現了tcp/ip協議族裡面的的igmp,tcp,udp,icmp,arp,ip。我們先討論一下這些協議之間的關係。ip和arp協議是需要直接和網路裝置介面打交道的協議,也就是需要從網路核心模組(core) 接收資料和傳送資料的。而其它協議tcp,udp,igmp,icmp是需要直接利用ip協議的,需要從ip協議接收資料,以及利用ip協議傳送資料,同時還要向上層socket層提供直接的呼叫介面。可以看到ip層是乙個核心的協議,向下需要和下層打交道,又要向上層提供所以的傳輸和接收的服務。

先來看看ip協議層。網路核心模組(core) 如果接收到ip層的資料,通過ptype_base[eth_p_ip] 陣列的ip層的項指向的ip協議的ip_packet_type->ip_rcv()函式把資料報傳遞給ip層,也就是說ip層通過這個函式 ip_rcv()(linux/net/ipv4/ip_input.c)接收資料的。ip_rcv()這個函式只對ip資料保做了一些checksum 的檢查工作,如果包是正確的就把包交給了下乙個處理函式ip_rcv_finish()(注意呼叫是通過nf_hook這個巨集實現的)。現在, ip_rcv_finish()這個函式真正要完成一些ip層的工作了。ip層要做的主要工作就是路由,要決定把資料報往那裡送。路由的工作是通過函式 ip_route_input()(/linux/net/ipv4/route.c,line 1622)實現的。對於進來的包可能的路由有這些:

我們現在關心的是如果資料是本地資料的時候怎麼處理。ip_route_input()呼叫ip_route_input_slow() (net/ipv4/route.c, line 1312),在ip_route_input_slow()裡面的1559行rth->u.dst.input= ip_local_deliver,這就是判斷到ip包是本地的資料報,並把本地資料報處理函式的位址返回。好了,路由工作完成了,返回到 ip_rcv_finish()。ip_rcv_finish()最後呼叫拉skb->dst->input(skb),從上面可以看到,這其實就是呼叫了ip_local_deliver()函式,而ip_local_deliver(),接著就呼叫了 ip_local_deliver_finish()。現在真正到了往上層傳遞資料報的時候了。

現在的情形和網路核心模組層 (core) 往上層傳遞資料報的情形非常相似,怎麼從多個協議選擇合適的協議,並且往這個協議傳遞資料呢?網路網路核心模組層(core) 通過乙個陣列ptype_base[16]儲存了註冊了的所有可以接收資料的協議,同樣網路協議層也定義了這樣乙個陣列struct net_protocol*inet_protos[max_inet_protos](/linux/net/ipv4/protocol.c#l102), 它儲存了所有需要從ip協議層接收資料的上層協議(igmp,tcp,udp,icmp)的接收處理函式的位址。我們來看看tcp協議的資料結構是怎麼樣的:

linux/net/ipv4/protocol.c line67

static struct inet_protocol tcp_protocol = ;

第一項就是我們最關心的了,ip層可以通過這個函式把資料報往tcp層傳的。在linux/net/ipv4/protocol.c的上部,我們可以看到其它協議層的處理函式是igmp_rcv(), udp_rcv(), icmp_rcv()。同樣在linux/net/ipv4/protocol.c,往陣列inet_protos[max_inet_protos] 裡面新增協議是通過函式inet_add_protocol()實現的,刪除協議是通過 inet_del_protocol()實現的。inet_protos[max_inet_protos]初始化的過程在 linux/net/ipv4/af_inet.c inet_init()初始化函式裡面。

inet_init()

如果你在linux啟動的時候有留意啟動的資訊, 或者在linux下打命令dmesg就可以看到這一段程式輸出的資訊:

ip protocols: icmp,udp,tcp,igmp也就是說現在陣列inet_protos裡面有了icmp,udp,tcp,igmp四個協議的inet_protocol資料結構,資料結構包含了它們接收資料的處理函式。

linux 2.4.16在linux/include/linux/socket.h裡定義了32種支援的bsdsocket協議,常見的有tcp/ip, ipx/spx,x.25等,而每種協議還提供不同的服務,例如tcp/ip協議通過tcp協議支援連線服務,而通過udp協議支援無連線服務,面對這麼多的協議,向使用者提供統一的介面是必要的,這種統一是通過socket來進行的。

在bsd socket網路程式設計的模式下,利用一系列的統一的函式來利用通訊的服務。例如乙個典型的利用tcp協議通訊程式是這樣:

sock_descriptor = socket(af_inet,sock_stream,0);

connect(sock_descriptor, 位址,) ;

send(sock_descriptor,」hello world」);

recv(sock_descriptor,buffer,1024,0);

第乙個函式指定了協議inet協議,即tcp/ip協議,同時是利用面向連線的服務,這樣就對應到tcp協議,以後的操作就是利用socket的標準函式進行的。

從上面我們可以看到兩個問題,首先socket層需要根據使用者指定的協議族(上面是af_inet) 從下面32種協議中選擇一種協議來完成使用者的要求,當協議族確定以後,還要把特定的服務對映到協議族下的具體協議,例如當使用者指定的是面向連線的服務時,inet協議族會對映到tcp協議。

從多個協議中選擇使用者指定的協議,並把具體的出理交給選中的協議,這和一起網路核心層向上和向下銜接的問題本質上是一樣的,所以解決的方法也是一樣的,同樣還是通過陣列。在linux/net/socket.c定義了這個陣列staticstruct net_proto_family *net_families[nproto] 。陣列的元素已經確定了,net_families[2] 是tcp/ip協議,net_families[3] 是x.25協議,具體那一項對應什麼協議,在 include/linux/socket.h有定義。但是每一項的資料結構net_proto_family的ops是空的,也就是具體協議處理函式的位址是不知道的。協議的處理函式和ops建立聯絡是通過sock_register()(linux/net/socket.c)這個函式建立的,例如 tcp/ip協議的是這樣建立關係的:

int __init inet_init(void) (net/ipv4/af_inet.c)

只要給出af_inet(在巨集裡定義是2),就可以找到net_failies[2] 裡面的處理函式了。

協議的對映完成了,現在要進行服務的映**。上層當然不可能知道下層的什麼協議能對應特定的服務,所以這種對映自然由協議族自己完成。在tcp/ip協議族裡,這種對映是通過struct

list_head inetsw[sock_max]( net/ipv4/af_inet.c)

這個陣列進行對映的,在談論這個陣列之前我們來看另外乙個陣列inetsw_array(net/ipv4/af_inet.c)

static struct inet_protosw inetsw_array =

, ,

}; 我們看到,sock_stream對映到了tcp協議,sock_dgram對映到了udp協議,sock_raw對映到了ip協議。現在只要把 inetsw_array裡的三項新增到陣列inetsw[sock_max]就可以了,新增是通過函式inet_register_protosw() 實現的。在inet_init() (net/ipv4/af_inet.c) 裡完成了這些工作。

還有乙個需要對映的就是socket其它諸如accept,send(),

connect(),release(),bind()等的操作函式是怎麼對映的呢?我們來看一下上面的陣列的tcp的項

, 我們看到這種對映是通過ops,和prot來對映的,我們再來看看 tcp_prot這一項:

struct proto tcp_prot = ;

所以的對映都已經完成了,使用者呼叫connect()函式,其實就是呼叫了tcp_v4_connect()函式,按照這幅圖,讀起原始碼來就簡單了很多了。

六 socket層

上一節把socket層大多數要討論的東西都談論了,現在只講講socket 層和使用者的銜接。

系統呼叫socket(),bind(),connect(),accept,send(),release()等是在linux/net/socket.c裡面的實現的,系統呼叫實現的函式是相應的函式名加上sys_的字首。

現在看看當使用者呼叫socket()這個函式,到底下面發生了什麼。

socket(af_inet,sock_stream,0)呼叫了sys_socket(),sys_socket()接著呼叫 socket_creat(),socket_creat()就要根據使用者提供的協議族引數在net_families裡尋找合適的協議族,如果協議族沒有被安裝就要請求安裝該協議族的模組,然後就呼叫該協議族的create()函式的處理控制代碼。根據引數af_inet,inet_creat()就被呼叫了,在inet_creat()根據服務型別在inetsw[sock_max] 選擇合適的協議,並把協議的操作集賦給socket就是了,根據sock_stream,tcp協議被選中,

inet_creat()

到此為止,上下都打通了,該是大家讀原始碼的時候了。

Linux網路驅動原始碼分析 一

網路驅動是一種典型的pci裝置驅動,無論在嵌入式平台還是在pc領域,網路相關的專案開發有著比較廣闊的前景,因此,分析當前linux核心中網路裝置的驅動,不但能了解網路相關的基本原理,而且可以借鑑linux核心的先進的技術,將其應用到嵌入式或其他領域。本文以linux核心中的rtl8139網路驅動為例...

原始碼 callable介面的底層實現

在使用執行緒池的時候,我們需要使用到callable介面,那我們來看一下calllable的底層是怎麼實現,並且有返回值的。首先我們看一下呼叫。threadpoolexcutor並沒有實現 submit 方法,那麼肯定是它的父類實現的。如願以償在abstractexecutorservice中找到了...

Linux核心 fork 原始碼分析

核心版本 linux 4.4.18 原始碼位置 這裡 接著 呼叫copy process 它設定了程序描述符以及子程序所需的任何其他核心資料結構。ftrace graph init task 初始化ftrace,核心追蹤函式呼叫。rt mutex init task 初始化鎖。copy creds ...