Linux內(nèi)核源代碼:tcp/ip協(xié)議棧的調(diào)用
1 Linux概述
1.1 Linux操作系統(tǒng)架構(gòu)簡介
Linux操作系統(tǒng)總體上由Linux內(nèi)核和GNU系統(tǒng)構(gòu)成,具體來講由4個(gè)主要部分構(gòu)成,即Linux內(nèi)核、Shell、文件系統(tǒng)和應(yīng)用程序。內(nèi)核、Shell和文件系統(tǒng)構(gòu)成了操作系統(tǒng)的基本結(jié)構(gòu),使得用戶可以運(yùn)行程序、管理文件并使用系統(tǒng)。
內(nèi)核是操作系統(tǒng)的核心,具有很多最基本功能,如虛擬內(nèi)存、多任務(wù)、共享庫、需求加載、可執(zhí)行程序和TCP/IP網(wǎng)絡(luò)功能。我們所調(diào)研的工作,就是在Linux內(nèi)核層面進(jìn)行分析。
1.2 協(xié)議棧簡介
OSI(Open System Interconnect),即開放式系統(tǒng)互聯(lián)。一般都叫OSI參考模型,是ISO(國際標(biāo)準(zhǔn)化組織)組織在1985年研究的網(wǎng)絡(luò)互連模型。
ISO為了更好的使網(wǎng)絡(luò)應(yīng)用更為普及,推出了OSI參考模型。其含義就是推薦所有公司使用這個(gè)規(guī)范來控制網(wǎng)絡(luò)。這樣所有公司都有相同的規(guī)范,就能互聯(lián)了。
OSI定義了網(wǎng)絡(luò)互連的七層框架(物理層、數(shù)據(jù)鏈路層、網(wǎng)絡(luò)層、傳輸層、會(huì)話層、表示層、應(yīng)用層),即ISO開放互連系統(tǒng)參考模型。如下圖。
每一層實(shí)現(xiàn)各自的功能和協(xié)議,并完成與相鄰層的接口通信。OSI的服務(wù)定義詳細(xì)說明了各層所提供的服務(wù)。某一層的服務(wù)就是該層及其下各層的一種能力,它通過接口提供給更高一層。各層所提供的服務(wù)與這些服務(wù)是怎么實(shí)現(xiàn)的無關(guān)。
osi七層模型已經(jīng)成為了理論上的標(biāo)準(zhǔn),但真正運(yùn)用于實(shí)踐中的是TCP/IP五層模型。
TCP/IP五層協(xié)議和osi的七層協(xié)議對應(yīng)關(guān)系如下:
在每一層實(shí)現(xiàn)的協(xié)議也各不同,即每一層的服務(wù)也不同.下圖列出了每層主要的協(xié)議。
1.3 Linux內(nèi)核協(xié)議棧
Linux的協(xié)議棧其實(shí)是源于BSD的協(xié)議棧,它向上以及向下的接口以及協(xié)議棧本身的軟件分層組織的非常好。
Linux的協(xié)議;诜謱拥脑O(shè)計(jì)思想,總共分為四層,從下往上依次是:物理層,鏈路層,網(wǎng)絡(luò)層,應(yīng)用層。
物理層主要提供各種連接的物理設(shè)備,如各種網(wǎng)卡,串口卡等;鏈路層主要指的是提供對物理層進(jìn)行訪問的各種接口卡的驅(qū)動(dòng)程序,如網(wǎng)卡驅(qū)動(dòng)等;網(wǎng)路層的作用是負(fù)責(zé)將網(wǎng)絡(luò)數(shù)據(jù)包傳輸?shù)秸_的位置,最重要的網(wǎng)絡(luò)層協(xié)議當(dāng)然就是IP協(xié)議了,其實(shí)網(wǎng)絡(luò)層還有其他的協(xié)議如ICMP,ARP,RARP等,只不過不像IP那樣被多數(shù)人所熟悉;傳輸層的作用主要是提供端到端,說白一點(diǎn)就是提供應(yīng)用程序之間的通信,傳輸層最著名的協(xié)議非TCP與UDP協(xié)議末屬了;應(yīng)用層,顧名思義,當(dāng)然就是由應(yīng)用程序提供的,用來對傳輸數(shù)據(jù)進(jìn)行語義解釋的“人機(jī)界面”層了,比如HTTP,SMTP,F(xiàn)TP等等,其實(shí)應(yīng)用層還不是人們最終所看到的那一層,最上面的一層應(yīng)該是“解釋層”,負(fù)責(zé)將數(shù)據(jù)以各種不同的表項(xiàng)形式最終呈獻(xiàn)到人們眼前。
Linux網(wǎng)絡(luò)核心架構(gòu)Linux的網(wǎng)絡(luò)架構(gòu)從上往下可以分為三層,分別是:
用戶空間的應(yīng)用層。
內(nèi)核空間的網(wǎng)絡(luò)協(xié)議棧層。
物理硬件層。
其中最重要最核心的當(dāng)然是內(nèi)核空間的協(xié)議棧層了。
Linux網(wǎng)絡(luò)協(xié)議棧結(jié)構(gòu)Linux的整個(gè)網(wǎng)絡(luò)協(xié)議棧都構(gòu)建與Linux Kernel中,整個(gè)棧也是嚴(yán)格按照分層的思想來設(shè)計(jì)的,整個(gè)棧共分為五層,分別是 :
1,系統(tǒng)調(diào)用接口層,實(shí)質(zhì)是一個(gè)面向用戶空間應(yīng)用程序的接口調(diào)用庫,向用戶空間應(yīng)用程序提供使用網(wǎng)絡(luò)服務(wù)的接口。
2,協(xié)議無關(guān)的接口層,就是SOCKET層,這一層的目的是屏蔽底層的不同協(xié)議(更準(zhǔn)確的來說主要是TCP與UDP,當(dāng)然還包括RAW IP, SCTP等),以便與系統(tǒng)調(diào)用層之間的接口可以簡單,統(tǒng)一。簡單的說,不管我們應(yīng)用層使用什么協(xié)議,都要通過系統(tǒng)調(diào)用接口來建立一個(gè)SOCKET,這個(gè)SOCKET其實(shí)是一個(gè)巨大的sock結(jié)構(gòu),它和下面一層的網(wǎng)絡(luò)協(xié)議層聯(lián)系起來,屏蔽了不同的網(wǎng)絡(luò)協(xié)議的不同,只吧數(shù)據(jù)部分呈獻(xiàn)給應(yīng)用層(通過系統(tǒng)調(diào)用接口來呈獻(xiàn))。
3,網(wǎng)絡(luò)協(xié)議實(shí)現(xiàn)層,毫無疑問,這是整個(gè)協(xié)議棧的核心。這一層主要實(shí)現(xiàn)各種網(wǎng)絡(luò)協(xié)議,最主要的當(dāng)然是IP,ICMP,ARP,RARP,TCP,UDP等。這一層包含了很多設(shè)計(jì)的技巧與算法,相當(dāng)?shù)牟诲e(cuò)。
4,與具體設(shè)備無關(guān)的驅(qū)動(dòng)接口層,這一層的目的主要是為了統(tǒng)一不同的接口卡的驅(qū)動(dòng)程序與網(wǎng)絡(luò)協(xié)議層的接口,它將各種不同的驅(qū)動(dòng)程序的功能統(tǒng)一抽象為幾個(gè)特殊的動(dòng)作,如open,close,init等,這一層可以屏蔽底層不同的驅(qū)動(dòng)程序。
5,驅(qū)動(dòng)程序?qū)樱@一層的目的就很簡單了,就是建立與硬件的接口層。
可以看到,Linux網(wǎng)絡(luò)協(xié)議棧是一個(gè)嚴(yán)格分層的結(jié)構(gòu),其中的每一層都執(zhí)行相對獨(dú)立的功能,結(jié)構(gòu)非常清晰。
其中的兩個(gè)“無關(guān)”層的設(shè)計(jì)非常棒,通過這兩個(gè)“無關(guān)”層,其協(xié)議?梢苑浅]p松的進(jìn)行擴(kuò)展。在我們自己的軟件設(shè)計(jì)中,可以吸收這種設(shè)計(jì)方法。
2 代碼簡介
本文采用的測試代碼是一個(gè)非常簡單的基于socket的客戶端服務(wù)器程序,打開服務(wù)端并運(yùn)行,再開一終端運(yùn)行客戶端,兩者建立連接并可以發(fā)送hellohi的信息,server端代碼如下:
#include <stdio.h> perror
#include <stdlib.h> exit
#include <sys/types.h> WNOHANG
#include <sys/wait.h> waitpid
#include <string.h> memset
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netdb.h> gethostbyname
#define true 1
#define false 0
#define MYPORT 3490 監(jiān)聽的端口
#define BACKLOG 10 listen的請求接收隊(duì)列長度
#define BUF_SIZE 1024
int main()
{
int sockfd;
if ((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("socket");
exit(1);
}
struct sockaddr_in sa; 自身的地址信息
sa.sin_family = AF_INET;
sa.sin_port = htons(MYPORT); 網(wǎng)絡(luò)字節(jié)順序
sa.sin_addr.s_addr = INADDR_ANY; 自動(dòng)填本機(jī)IP
memset(&(sa.sin_zero), 0, 8); 其余部分置0
if (bind(sockfd, (struct sockaddr *)&sa, sizeof(sa)) == -1)
{
perror("bind");
exit(1);
}
struct sockaddr_in their_addr; 連接對方的地址信息
unsigned int sin_size = 0;
char buf[BUF_SIZE];
int ret_size = recvfrom(sockfd, buf, BUF_SIZE, 0, (struct sockaddr *)&their_addr, &sin_size);
if(ret_size == -1)
{
perror("recvfrom");
exit(1);
}
buf[ret_size] = '';
printf("recvfrom:%s", buf);
}
client端代碼如下:
#include <stdio.h> perror
#include <stdlib.h> exit
#include <sys/types.h> WNOHANG
#include <sys/wait.h> waitpid
#include <string.h> memset
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netdb.h> gethostbyname
#define true 1
#define false 0
#define PORT 3490 Server的端口
#define MAXDATASIZE 100 一次可以讀的最大字節(jié)數(shù)
int main(int argc, char *argv[])
{
int sockfd, numbytes;
char buf[MAXDATASIZE];
struct hostent *he; 主機(jī)信息
struct sockaddr_in server_addr; 對方地址信息
if (argc 。 2)
{
fprintf(stderr, "usage: client hostname");
exit(1);
}
get the host info
if ((he = gethostbyname(argv[1])) == NULL)
{
注意:獲取DNS信息時(shí),顯示出錯(cuò)需要用herror而不是perror
herror 在新的版本中會(huì)出現(xiàn)警告,已經(jīng)建議不要使用了
perror("gethostbyname");
exit(1);
}
if ((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("socket");
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT); short, NBO
server_addr.sin_addr = *((struct in_addr *)he->h_addr_list[0]);
memset(&(server_addr.sin_zero), 0, 8); 其余部分設(shè)成0
if ((numbytes = sendto(sockfd,
"Hello, world。, 14, 0,
(struct sockaddr *)&server_addr,
sizeof(server_addr))) == -1)
{
perror("sendto");
exit(1);
}
close(sockfd);
return true;
}
簡單來說,主要流程如下圖所示:
3 應(yīng)用層流程
3.1 發(fā)送端
網(wǎng)絡(luò)應(yīng)用調(diào)用Socket API socket (int family, int type, int protocol) 創(chuàng)建一個(gè) socket,該調(diào)用最終會(huì)調(diào)用 Linux system call socket() ,并最終調(diào)用 Linux Kernel 的 sock_create() 方法。該方法返回被創(chuàng)建好了的那個(gè) socket 的 file descriptor。對于每一個(gè) userspace 網(wǎng)絡(luò)應(yīng)用創(chuàng)建的 socket,在內(nèi)核中都有一個(gè)對應(yīng)的 struct socket和 struct sock。其中,struct sock 有三個(gè)隊(duì)列(queue),分別是 rx , tx 和 err,在 sock 結(jié)構(gòu)被初始化的時(shí)候,這些緩沖隊(duì)列也被初始化完成;在收據(jù)收發(fā)過程中,每個(gè) queue 中保存要發(fā)送或者接受的每個(gè) packet 對應(yīng)的 Linux 網(wǎng)絡(luò)棧 sk_buffer 數(shù)據(jù)結(jié)構(gòu)的實(shí)例 skb。
對于 TCP socket 來說,應(yīng)用調(diào)用 connect()API ,使得客戶端和服務(wù)器端通過該 socket 建立一個(gè)虛擬連接。在此過程中,TCP 協(xié)議棧通過三次握手會(huì)建立 TCP 連接。默認(rèn)地,該 API 會(huì)等到 TCP 握手完成連接建立后才返回。在建立連接的過程中的一個(gè)重要步驟是,確定雙方使用的 Maxium Segemet Size (MSS)。因?yàn)?UDP 是面向無連接的協(xié)議,因此它是不需要該步驟的。
應(yīng)用調(diào)用 Linux Socket 的 send 或者 write API 來發(fā)出一個(gè) message 給接收端sock_sendmsg 被調(diào)用,它使用 socket descriptor 獲取 sock struct,創(chuàng)建 message header 和 socket control message_sock_sendmsg 被調(diào)用,根據(jù) socket 的協(xié)議類型,調(diào)用相應(yīng)協(xié)議的發(fā)送函數(shù)。
對于 TCP ,調(diào)用 tcp_sendmsg 函數(shù)。對于 UDP 來說,userspace 應(yīng)用可以調(diào)用 send()/sendto()/sendmsg() 三個(gè) system call 中的任意一個(gè)來發(fā)送 UDP message,它們最終都會(huì)調(diào)用內(nèi)核中的 udp_sendmsg() 函數(shù)。
下面我們具體結(jié)合Linux內(nèi)核源碼進(jìn)行一步步仔細(xì)分析:
根據(jù)上述分析可知,發(fā)送端首先創(chuàng)建socket,創(chuàng)建之后會(huì)通過send發(fā)送數(shù)據(jù)。具體到源碼級(jí)別,會(huì)通過send,sendto,sendmsg這些系統(tǒng)調(diào)用來發(fā)送數(shù)據(jù),而上述三個(gè)函數(shù)底層都調(diào)用了sock_sendmsg。見下圖:
我們再跳轉(zhuǎn)到__sys_sendto看看這個(gè)函數(shù)干了什么:
我們可以發(fā)現(xiàn),它創(chuàng)建了兩個(gè)結(jié)構(gòu)體,分別是:struct msghdr msg和struct iovec iov,這兩個(gè)結(jié)構(gòu)體根據(jù)命名我們可以大致猜出是發(fā)送數(shù)據(jù)和io操作的一些信息,如下圖:
我們再來看看__sys_sendto調(diào)用的sock_sendmsg函數(shù)執(zhí)行了什么內(nèi)容:
發(fā)現(xiàn)調(diào)用了sock_sendmsg_nosec函數(shù):
發(fā)現(xiàn)調(diào)用了inet_sendmsg函數(shù):
至此,發(fā)送端調(diào)用完畢。我們可以通過gdb進(jìn)行調(diào)試驗(yàn)證:
剛好符合我們的分析。
3.2 接收端
每當(dāng)用戶應(yīng)用調(diào)用 read 或者 recvfrom 時(shí),該調(diào)用會(huì)被映射為/net/socket.c 中的 sys_recv 系統(tǒng)調(diào)用,并被轉(zhuǎn)化為 sys_recvfrom 調(diào)用,然后調(diào)用 sock_recgmsg 函數(shù)。
對于 INET 類型的 socket,/net/ipv4/af inet.c 中的 inet_recvmsg 方法會(huì)被調(diào)用,它會(huì)調(diào)用相關(guān)協(xié)議的數(shù)據(jù)接收方法。
對 TCP 來說,調(diào)用 tcp_recvmsg。該函數(shù)從 socket buffer 中拷貝數(shù)據(jù)到 user buffer。
對 UDP 來說,從 user space 中可以調(diào)用三個(gè) system call recv()/recvfrom()/recvmsg() 中的任意一個(gè)來接收 UDP package,這些系統(tǒng)調(diào)用最終都會(huì)調(diào)用內(nèi)核中的 udp_recvmsg 方法。
請輸入評論內(nèi)容...
請輸入評論/評論長度6~500個(gè)字
最新活動(dòng)更多
-
10月31日立即下載>> 【限時(shí)免費(fèi)下載】TE暖通空調(diào)系統(tǒng)高效可靠的組件解決方案
-
即日-11.13立即報(bào)名>>> 【在線會(huì)議】多物理場仿真助跑新能源汽車
-
11月28日立即報(bào)名>>> 2024工程師系列—工業(yè)電子技術(shù)在線會(huì)議
-
12月19日立即報(bào)名>> 【線下會(huì)議】OFweek 2024(第九屆)物聯(lián)網(wǎng)產(chǎn)業(yè)大會(huì)
-
即日-12.26火熱報(bào)名中>> OFweek2024中國智造CIO在線峰會(huì)
-
即日-2025.8.1立即下載>> 《2024智能制造產(chǎn)業(yè)高端化、智能化、綠色化發(fā)展藍(lán)皮書》
推薦專題
- 1 【一周車話】沒有方向盤和踏板的車,你敢坐嗎?
- 2 特斯拉發(fā)布無人駕駛車,還未迎來“Chatgpt時(shí)刻”
- 3 特斯拉股價(jià)大跌15%:Robotaxi離落地還差一個(gè)蘿卜快跑
- 4 馬斯克給的“驚喜”夠嗎?
- 5 大模型“新星”開啟變現(xiàn)競速
- 6 海信給AI電視打樣,12大AI智能體全面升級(jí)大屏體驗(yàn)
- 7 打完“價(jià)格戰(zhàn)”,大模型還要比什么?
- 8 馬斯克致敬“國產(chǎn)蘿卜”?
- 9 神經(jīng)網(wǎng)絡(luò),誰是盈利最強(qiáng)企業(yè)?
- 10 比蘋果偉大100倍!真正改寫人類歷史的智能產(chǎn)品降臨
- 高級(jí)軟件工程師 廣東省/深圳市
- 自動(dòng)化高級(jí)工程師 廣東省/深圳市
- 光器件研發(fā)工程師 福建省/福州市
- 銷售總監(jiān)(光器件) 北京市/海淀區(qū)
- 激光器高級(jí)銷售經(jīng)理 上海市/虹口區(qū)
- 光器件物理工程師 北京市/海淀區(qū)
- 激光研發(fā)工程師 北京市/昌平區(qū)
- 技術(shù)專家 廣東省/江門市
- 封裝工程師 北京市/海淀區(qū)
- 結(jié)構(gòu)工程師 廣東省/深圳市