性无码一区二区三区在线观看,少妇被爽到高潮在线观看,午夜精品一区二区三区,无码中文字幕人妻在线一区二区三区,无码精品国产一区二区三区免费

一口Linux
認證:優(yōu)質(zhì)創(chuàng)作者
所在專題目錄 查看專題
【粉絲問答7】局域網(wǎng)內(nèi)終端是如何訪問外網(wǎng)?答案在最后
教你如何抓取網(wǎng)絡(luò)中的數(shù)據(jù)包!黑客必備技能
1萬字30張圖說清TCP協(xié)議
TCP/IP協(xié)議漏洞如此之多?
TCP/IP協(xié)議棧在Linux內(nèi)核中的運行時序分析
linux服務(wù)器編程之網(wǎng)絡(luò)連接斷掉之后,究竟會發(fā)生什么···
作者動態(tài) 更多
linux系統(tǒng)監(jiān)控工具小神器:btop
2天前
有沒有權(quán)貴開后門讓子女做軟件開發(fā)人員?
1星期前
一文包你學會網(wǎng)絡(luò)數(shù)據(jù)抓包
03-15 09:26
C語言初學者編程水平上不來?不妨嘗試這10個C語言例子
03-14 20:31
Linux rootfs:如何開機就自動添加某個用戶?
03-09 22:18

TCP/IP協(xié)議棧在Linux內(nèi)核中的運行時序分析

1 Linux概述

  1.1 Linux操作系統(tǒng)架構(gòu)簡介

Linux操作系統(tǒng)總體上由Linux內(nèi)核和GNU系統(tǒng)構(gòu)成,具體來講由4個主要部分構(gòu)成,即Linux內(nèi)核、Shell、文件系統(tǒng)和應(yīng)用程序。內(nèi)核、Shell和文件系統(tǒng)構(gòu)成了操作系統(tǒng)的基本結(jié)構(gòu),使得用戶可以運行程序、管理文件并使用系統(tǒng)。

內(nèi)核是操作系統(tǒng)的核心,具有很多最基本功能,如虛擬內(nèi)存、多任務(wù)、共享庫、需求加載、可執(zhí)行程序和TCP/IP網(wǎng)絡(luò)功能。我們所調(diào)研的工作,就是在Linux內(nèi)核層面進行分析。

1.2 協(xié)議棧簡介

  OSI(Open System Interconnect),即開放式系統(tǒng)互聯(lián)。一般都叫OSI參考模型,是ISO(國際標準化組織)組織在1985年研究的網(wǎng)絡(luò)互連模型。

ISO為了更好的使網(wǎng)絡(luò)應(yīng)用更為普及,推出了OSI參考模型。其含義就是推薦所有公司使用這個規(guī)范來控制網(wǎng)絡(luò)。這樣所有公司都有相同的規(guī)范,就能互聯(lián)了。

OSI定義了網(wǎng)絡(luò)互連的七層框架(物理層、數(shù)據(jù)鏈路層、網(wǎng)絡(luò)層、傳輸層、會話層、表示層、應(yīng)用層),即ISO開放互連系統(tǒng)參考模型。如下圖。

每一層實現(xiàn)各自的功能和協(xié)議,并完成與相鄰層的接口通信。OSI的服務(wù)定義詳細說明了各層所提供的服務(wù)。某一層的服務(wù)就是該層及其下各層的一種能力,它通過接口提供給更高一層。各層所提供的服務(wù)與這些服務(wù)是怎么實現(xiàn)的無關(guān)。

  osi七層模型已經(jīng)成為了理論上的標準,但真正運用于實踐中的是TCP/IP五層模型。

  TCP/IP五層協(xié)議和osi的七層協(xié)議對應(yīng)關(guān)系如下:

在每一層實現(xiàn)的協(xié)議也各不同,即每一層的服務(wù)也不同.下圖列出了每層主要的協(xié)議。

  1.3 Linux內(nèi)核協(xié)議棧

  Linux的協(xié)議棧其實是源于BSD的協(xié)議棧,它向上以及向下的接口以及協(xié)議棧本身的軟件分層組織的非常好。

  Linux的協(xié)議?;诜謱拥脑O(shè)計思想,總共分為四層,從下往上依次是:物理層,鏈路層,網(wǎng)絡(luò)層,應(yīng)用層。

  物理層主要提供各種連接的物理設(shè)備,如各種網(wǎng)卡,串口卡等;鏈路層主要指的是提供對物理層進行訪問的各種接口卡的驅(qū)動程序,如網(wǎng)卡驅(qū)動等;網(wǎng)路層的作用是負責將網(wǎng)絡(luò)數(shù)據(jù)包傳輸?shù)秸_的位置,最重要的網(wǎng)絡(luò)層協(xié)議當然就是IP協(xié)議了,其實網(wǎng)絡(luò)層還有其他的協(xié)議如ICMP,ARP,RARP等,只不過不像IP那樣被多數(shù)人所熟悉;傳輸層的作用主要是提供端到端,說白一點就是提供應(yīng)用程序之間的通信,傳輸層最著名的協(xié)議非TCP與UDP協(xié)議末屬了;應(yīng)用層,顧名思義,當然就是由應(yīng)用程序提供的,用來對傳輸數(shù)據(jù)進行語義解釋的“人機界面”層了,比如HTTP,SMTP,F(xiàn)TP等等,其實應(yīng)用層還不是人們最終所看到的那一層,最上面的一層應(yīng)該是“解釋層”,負責將數(shù)據(jù)以各種不同的表項形式最終呈獻到人們眼前?! inux網(wǎng)絡(luò)核心架構(gòu)Linux的網(wǎng)絡(luò)架構(gòu)從上往下可以分為三層,分別是:  用戶空間的應(yīng)用層?! ?nèi)核空間的網(wǎng)絡(luò)協(xié)議棧層?! ∥锢碛布??! ∑渲凶钪匾詈诵牡漠斎皇莾?nèi)核空間的協(xié)議棧層了。  Linux網(wǎng)絡(luò)協(xié)議棧結(jié)構(gòu)Linux的整個網(wǎng)絡(luò)協(xié)議棧都構(gòu)建與Linux Kernel中,整個棧也是嚴格按照分層的思想來設(shè)計的,整個棧共分為五層,分別是 :  1,系統(tǒng)調(diào)用接口層,實質(zhì)是一個面向用戶空間應(yīng)用程序的接口調(diào)用庫,向用戶空間應(yīng)用程序提供使用網(wǎng)絡(luò)服務(wù)的接口?! ?,協(xié)議無關(guān)的接口層,就是SOCKET層,這一層的目的是屏蔽底層的不同協(xié)議(更準確的來說主要是TCP與UDP,當然還包括RAW IP, SCTP等),以便與系統(tǒng)調(diào)用層之間的接口可以簡單,統(tǒng)一。簡單的說,不管我們應(yīng)用層使用什么協(xié)議,都要通過系統(tǒng)調(diào)用接口來建立一個SOCKET,這個SOCKET其實是一個巨大的sock結(jié)構(gòu),它和下面一層的網(wǎng)絡(luò)協(xié)議層聯(lián)系起來,屏蔽了不同的網(wǎng)絡(luò)協(xié)議的不同,只吧數(shù)據(jù)部分呈獻給應(yīng)用層(通過系統(tǒng)調(diào)用接口來呈獻)?! ?,網(wǎng)絡(luò)協(xié)議實現(xiàn)層,毫無疑問,這是整個協(xié)議棧的核心。這一層主要實現(xiàn)各種網(wǎng)絡(luò)協(xié)議,最主要的當然是IP,ICMP,ARP,RARP,TCP,UDP等。這一層包含了很多設(shè)計的技巧與算法,相當?shù)牟诲e。  4,與具體設(shè)備無關(guān)的驅(qū)動接口層,這一層的目的主要是為了統(tǒng)一不同的接口卡的驅(qū)動程序與網(wǎng)絡(luò)協(xié)議層的接口,它將各種不同的驅(qū)動程序的功能統(tǒng)一抽象為幾個特殊的動作,如open,close,init等,這一層可以屏蔽底層不同的驅(qū)動程序。  5,驅(qū)動程序?qū)樱@一層的目的就很簡單了,就是建立與硬件的接口層?! 】梢钥吹?,Linux網(wǎng)絡(luò)協(xié)議棧是一個嚴格分層的結(jié)構(gòu),其中的每一層都執(zhí)行相對獨立的功能,結(jié)構(gòu)非常清晰?! ∑渲械膬蓚€“無關(guān)”層的設(shè)計非常棒,通過這兩個“無關(guān)”層,其協(xié)議??梢苑浅]p松的進行擴展。在我們自己的軟件設(shè)計中,可以吸收這種設(shè)計方法。

2 代碼簡介

本文采用的測試代碼是一個非常簡單的基于socket的客戶端服務(wù)器程序,打開服務(wù)端并運行,再開一終端運行客戶端,兩者建立連接并可以發(fā)送hello\hi的信息,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的請求接收隊列長度 */#define BUF_SIZE    1024int 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; /* 自動填本機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] = '\0';    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;            /* 主機信息 */    struct sockaddr_in server_addr; /* 對方地址信息 */    if (argc != 2)    {        fprintf(stderr, "usage: client hostname\n");        exit(1);    }    /* get the host info */    if ((he = gethostbyname(argv[1])) == NULL)    {        /* 注意:獲取DNS信息時,顯示出錯需要用herror而不是perror */        /* herror 在新的版本中會出現(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!\n", 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)建一個 socket,該調(diào)用最終會調(diào)用 Linux system call socket() ,并最終調(diào)用 Linux Kernel 的 sock_create() 方法。該方法返回被創(chuàng)建好了的那個 socket 的 file descriptor。對于每一個 userspace 網(wǎng)絡(luò)應(yīng)用創(chuàng)建的 socket,在內(nèi)核中都有一個對應(yīng)的 struct socket和 struct sock。其中,struct sock 有三個隊列(queue),分別是 rx , tx 和 err,在 sock 結(jié)構(gòu)被初始化的時候,這些緩沖隊列也被初始化完成;在收據(jù)收發(fā)過程中,每個 queue 中保存要發(fā)送或者接受的每個 packet 對應(yīng)的 Linux 網(wǎng)絡(luò)棧 sk_buffer 數(shù)據(jù)結(jié)構(gòu)的實例 skb。

對于 TCP socket 來說,應(yīng)用調(diào)用 connect()API ,使得客戶端和服務(wù)器端通過該 socket 建立一個虛擬連接。在此過程中,TCP 協(xié)議棧通過三次握手會建立 TCP 連接。默認地,該 API 會等到 TCP 握手完成連接建立后才返回。在建立連接的過程中的一個重要步驟是,確定雙方使用的 Maxium Segemet Size (MSS)。因為 UDP 是面向無連接的協(xié)議,因此它是不需要該步驟的。

應(yīng)用調(diào)用 Linux Socket 的 send 或者 write API 來發(fā)出一個 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() 三個 system call 中的任意一個來發(fā)送 UDP message,它們最終都會調(diào)用內(nèi)核中的 udp_sendmsg() 函數(shù)。

下面我們具體結(jié)合Linux內(nèi)核源碼進行一步步仔細分析:

根據(jù)上述分析可知,發(fā)送端首先創(chuàng)建socket,創(chuàng)建之后會通過send發(fā)送數(shù)據(jù)。具體到源碼級別,會通過send,sendto,sendmsg這些系統(tǒng)調(diào)用來發(fā)送數(shù)據(jù),而上述三個函數(shù)底層都調(diào)用了sock_sendmsg。見下圖:

我們再跳轉(zhuǎn)到__sys_sendto看看這個函數(shù)干了什么:

我們可以發(fā)現(xiàn),它創(chuàng)建了兩個結(jié)構(gòu)體,分別是:struct msghdr msg和struct iovec iov,這兩個結(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進行調(diào)試驗證:

剛好符合我們的分析。

  3.2 接收端

每當用戶應(yīng)用調(diào)用 read 或者 recvfrom 時,該調(diào)用會被映射為/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 方法會被調(diào)用,它會調(diào)用相關(guān)協(xié)議的數(shù)據(jù)接收方法。

對 TCP 來說,調(diào)用 tcp_recvmsg。該函數(shù)從 socket buffer 中拷貝數(shù)據(jù)到 user buffer。

對 UDP 來說,從 user space 中可以調(diào)用三個 system call recv()/recvfrom()/recvmsg() 中的任意一個來接收 UDP package,這些系統(tǒng)調(diào)用最終都會調(diào)用內(nèi)核中的 udp_recvmsg 方法。

我們結(jié)合源碼進行仔細分析:

接收端調(diào)用的是__sys_recvfrom函數(shù):

__sys_recvfrom函數(shù)具體如下:

發(fā)現(xiàn)它調(diào)用了sock_recvmsg函數(shù):

發(fā)現(xiàn)它調(diào)用了sock_recvmsg_nosec函數(shù):

發(fā)現(xiàn)它調(diào)用了inet_recvmsg函數(shù):

最后調(diào)用的是tcp_recvmsg這個系統(tǒng)調(diào)用。至此接收端調(diào)用分析完畢。

下面用gdb打斷點進行驗證:

驗證結(jié)果剛好符合我們的調(diào)研。

4 傳輸層流程

  4.1 發(fā)送端

傳輸層的最終目的是向它的用戶提供高效的、可靠的和成本有效的數(shù)據(jù)傳輸服務(wù),主要功能包括 (1)構(gòu)造 TCP segment (2)計算 checksum (3)發(fā)送回復(fù)(ACK)包 (4)滑動窗口(sliding windown)等保證可靠性的操作。TCP 協(xié)議棧的大致處理過程如下圖所示:

TCP 棧簡要過程:

tcp_sendmsg 函數(shù)會首先檢查已經(jīng)建立的 TCP connection 的狀態(tài),然后獲取該連接的 MSS,開始 segement 發(fā)送流程。

構(gòu)造 TCP 段的 playload:它在內(nèi)核空間中創(chuàng)建該 packet 的 sk_buffer 數(shù)據(jù)結(jié)構(gòu)的實例 skb,從 userspace buffer 中拷貝 packet 的數(shù)據(jù)到 skb 的 buffer。

構(gòu)造 TCP header。

計算 TCP 校驗和(checksum)和 順序號 (sequence number)。

TCP 校驗和是一個端到端的校驗和,由發(fā)送端計算,然后由接收端驗證。其目的是為了發(fā)現(xiàn)TCP首部和數(shù)據(jù)在發(fā)送端到接收端之間發(fā)生的任何改動。如果接收方檢測到校驗和有差錯,則TCP段會被直接丟棄。TCP校驗和覆蓋 TCP 首部和 TCP 數(shù)據(jù)。

TCP的校驗和是必需的

發(fā)到 IP 層處理:調(diào)用 IP handler 句柄 ip_queue_xmit,將 skb 傳入 IP 處理流程。

UDP 棧簡要過程:

UDP 將 message 封裝成 UDP 數(shù)據(jù)報

調(diào)用 ip_append_data() 方法將 packet 送到 IP 層進行處理。

下面我們結(jié)合代碼依次分析:

根據(jù)我們對應(yīng)用層的追查可以發(fā)現(xiàn),傳輸層也是先調(diào)用send()->sendto()->sys_sento->sock_sendmsg->sock_sendmsg_nosec,我們看下sock_sendmsg_nosec這個函數(shù):

在應(yīng)用層調(diào)用的是inet_sendmsg函數(shù),在傳輸層根據(jù)后面的斷點可以知道,調(diào)用的是sock->ops-sendmsg這個函數(shù)。而sendmsg為一個宏,調(diào)用的是tcp_sendmsg,如下;

struct proto tcp_prot = {    .name            = "TCP",    .owner            = THIS_MODULE,    .close            = tcp_close,    .pre_connect        = tcp_v4_pre_connect,    .connect        = tcp_v4_connect,    .disconnect        = tcp_disconnect,    .accept            = inet_csk_accept,    .ioctl            = tcp_ioctl,    .init            = tcp_v4_init_sock,    .destroy        = tcp_v4_destroy_sock,    .shutdown        = tcp_shutdown,    .setsockopt        = tcp_setsockopt,    .getsockopt        = tcp_getsockopt,    .keepalive        = tcp_set_keepalive,    .recvmsg        = tcp_recvmsg,    .sendmsg        = tcp_sendmsg,    ......

而tcp_sendmsg實際上調(diào)用的是

int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)

這個函數(shù)如下:

int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size){    struct tcp_sock *tp = tcp_sk(sk);/*進行了強制類型轉(zhuǎn)換*/    struct sk_buff *skb;    flags = msg->msg_flags;    ......        if (copied)            tcp_push(sk, flags & ~MSG_MORE, mss_now,                 TCP_NAGLE_PUSH, size_goal);}

在tcp_sendmsg_locked中,完成的是將所有的數(shù)據(jù)組織成發(fā)送隊列,這個發(fā)送隊列是struct sock結(jié)構(gòu)中的一個域sk_write_queue,這個隊列的每一個元素是一個skb,里面存放的就是待發(fā)送的數(shù)據(jù)。然后調(diào)用了tcp_push()函數(shù)。結(jié)構(gòu)體struct sock如下:

struct sock{    ...    struct sk_buff_head    sk_write_queue;/*指向skb隊列的第一個元素*/    ...    struct sk_buff    *sk_send_head;/*指向隊列第一個還沒有發(fā)送的元素*/}

在tcp協(xié)議的頭部有幾個標志字段:URG、ACK、RSH、RST、SYN、FIN,tcp_push中會判斷這個skb的元素是否需要push,如果需要就將tcp頭部字段的push置一,置一的過程如下:

static void tcp_push(struct sock *sk, int flags, int mss_now,             int nonagle, int size_goal){    struct tcp_sock *tp = tcp_sk(sk);    struct sk_buff *skb;    skb = tcp_write_queue_tail(sk);    if (!skb)        return;    if (!(flags & MSG_MORE) || forced_push(tp))        tcp_mark_push(tp, skb);    tcp_mark_urg(tp, flags);    if (tcp_should_autocork(sk, skb, size_goal)) {        /* avoid atomic op if TSQ_THROTTLED bit is already set */        if (!test_bit(TSQ_THROTTLED, &sk->sk_tsq_flags)) {            NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTOCORKING);            set_bit(TSQ_THROTTLED, &sk->sk_tsq_flags);        }        /* It is possible TX completion already happened         * before we set TSQ_THROTTLED.         */        if (refcount_read(&sk->sk_wmem_alloc) > skb->truesize)            return;    }    if (flags & MSG_MORE)        nonagle = TCP_NAGLE_CORK;    __tcp_push_pending_frames(sk, mss_now, nonagle);}

首先struct tcp_skb_cb結(jié)構(gòu)體存放的就是tcp的頭部,頭部的控制位為tcp_flags,通過tcp_mark_push會將skb中的cb,也就是48個字節(jié)的數(shù)組,類型轉(zhuǎn)換為struct tcp_skb_cb,這樣位于skb的cb就成了tcp的頭部。tcp_mark_push如下:

static inline void tcp_mark_push(struct tcp_sock *tp, struct sk_buff *skb){    TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_PSH;    tp->pushed_seq = tp->write_seq;}...#define TCP_SKB_CB(__skb)    ((struct tcp_skb_cb *)&((__skb)->cb[0]))...struct sk_buff {    ...        char            cb[48] __aligned(8);    ...
struct tcp_skb_cb {    __u32        seq;        /* Starting sequence number    */    __u32        end_seq;    /* SEQ + FIN + SYN + datalen    */    __u8        tcp_flags;    /* tcp頭部標志,位于第13個字節(jié)tcp[13])    */    ......};

然后,tcp_push調(diào)用了__tcp_push_pending_frames(sk, mss_now, nonagle);函數(shù)發(fā)送數(shù)據(jù):

void __tcp_push_pending_frames(struct sock *sk, unsigned int cur_mss,                   int nonagle){    if (tcp_write_xmit(sk, cur_mss, nonagle, 0,               sk_gfp_mask(sk, GFP_ATOMIC)))        tcp_check_probe_timer(sk);}

發(fā)現(xiàn)它調(diào)用了tcp_write_xmit函數(shù)來發(fā)送數(shù)據(jù):

static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,               int push_one, gfp_t gfp){    struct tcp_sock *tp = tcp_sk(sk);    struct sk_buff *skb;    unsigned int tso_segs, sent_pkts;    int cwnd_quota;    int result;    bool is_cwnd_limited = false, is_rwnd_limited = false;    u32 max_segs;    /*統(tǒng)計已發(fā)送的報文總數(shù)*/    sent_pkts = 0;    ......    /*若發(fā)送隊列未滿,則準備發(fā)送報文*/    while ((skb = tcp_send_head(sk))) {        unsigned int limit;        if (unlikely(tp->repair) && tp->repair_queue == TCP_SEND_QUEUE) {            /* "skb_mstamp_ns" is used as a start point for the retransmit timer */            skb->skb_mstamp_ns = tp->tcp_wstamp_ns = tp->tcp_clock_cache;            list_move_tail(&skb->tcp_tsorted_anchor, &tp->tsorted_sent_queue);            tcp_init_tso_segs(skb, mss_now);            goto repair; /* Skip network transmission */        }        if (tcp_pacing_check(sk))            break;        tso_segs = tcp_init_tso_segs(skb, mss_now);        BUG_ON(!tso_segs);        /*檢查發(fā)送窗口的大小*/        cwnd_quota = tcp_cwnd_test(tp, skb);        if (!cwnd_quota) {            if (push_one == 2)                /* Force out a loss probe pkt. */                cwnd_quota = 1;            else                break;        }        if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now))) {            is_rwnd_limited = true;            break;        ......        limit = mss_now;        if (tso_segs > 1 && !tcp_urg_mode(tp))            limit = tcp_mss_split_point(sk, skb, mss_now,                            min_t(unsigned int,                              cwnd_quota,                              max_segs),                            nonagle);        if (skb->len > limit &&            unlikely(tso_fragment(sk, TCP_FRAG_IN_WRITE_QUEUE,                      skb, limit, mss_now, gfp)))            break;        if (tcp_small_queue_check(sk, skb, 0))            break;        if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))            break;    ......

tcp_write_xmit位于tcpoutput.c中,它實現(xiàn)了tcp的擁塞控制,然后調(diào)用了tcp_transmit_skb(sk, skb, 1, gfp)傳輸數(shù)據(jù),實際上調(diào)用的是__tcp_transmit_skb:

static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,                  int clone_it, gfp_t gfp_mask, u32 rcv_nxt){        skb_push(skb, tcp_header_size);    skb_reset_transport_header(skb);    ......    /* 構(gòu)建TCP頭部和校驗和 */    th = (struct tcphdr *)skb->data;    th->source        = inet->inet_sport;    th->dest        = inet->inet_dport;    th->seq            = htonl(tcb->seq);    th->ack_seq        = htonl(rcv_nxt);    tcp_options_write((__be32 *)(th + 1), tp, &opts);    skb_shinfo(skb)->gso_type = sk->sk_gso_type;    if (likely(!(tcb->tcp_flags & TCPHDR_SYN))) {        th->window      = htons(tcp_select_window(sk));        tcp_ecn_send(sk, skb, th, tcp_header_size);    } else {        /* RFC1323: The window in SYN & SYN/ACK segments         * is never scaled.         */        th->window    = htons(min(tp->rcv_wnd, 65535U));    }    ......    icsk->icsk_af_ops->send_check(sk, skb);    if (likely(tcb->tcp_flags & TCPHDR_ACK))        tcp_event_ack_sent(sk, tcp_skb_pcount(skb), rcv_nxt);    if (skb->len != tcp_header_size) {        tcp_event_data_sent(tp, sk);        tp->data_segs_out += tcp_skb_pcount(skb);        tp->bytes_sent += skb->len - tcp_header_size;    }    if (after(tcb->end_seq, tp->snd_nxt) || tcb->seq == tcb->end_seq)        TCP_ADD_STATS(sock_net(sk), TCP_MIB_OUTSEGS,                  tcp_skb_pcount(skb));    tp->segs_out += tcp_skb_pcount(skb);    /* OK, its time to fill skb_shinfo(skb)->gso_{segs|size} */    skb_shinfo(skb)->gso_segs = tcp_skb_pcount(skb);    skb_shinfo(skb)->gso_size = tcp_skb_mss(skb);    /* Leave earliest departure time in skb->tstamp (skb->skb_mstamp_ns) */    /* Cleanup our debris for IP stacks */    memset(skb->cb, 0, max(sizeof(struct inet_skb_parm),                   sizeof(struct inet6_skb_parm)));    err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);    ......}

tcp_transmit_skb是tcp發(fā)送數(shù)據(jù)位于傳輸層的最后一步,這里首先對TCP數(shù)據(jù)段的頭部進行了處理,然后調(diào)用了網(wǎng)絡(luò)層提供的發(fā)送接口icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);實現(xiàn)了數(shù)據(jù)的發(fā)送,自此,數(shù)據(jù)離開了傳輸層,傳輸層的任務(wù)也就結(jié)束了。

gdb調(diào)試驗證如下:

  4.2 接收端

傳輸層 TCP 處理入口在 tcp_v4_rcv 函數(shù)(位于 linux/net/ipv4/tcp ipv4.c 文件中),它會做 TCP header 檢查等處理。

調(diào)用 _tcp_v4_lookup,查找該 package 的 open socket。如果找不到,該 package 會被丟棄。接下來檢查 socket 和 connection 的狀態(tài)。

如果socket 和 connection 一切正常,調(diào)用 tcp_prequeue 使 package 從內(nèi)核進入 user space,放進 socket 的 receive queue。然后 socket 會被喚醒,調(diào)用 system call,并最終調(diào)用 tcp_recvmsg 函數(shù)去從 socket recieve queue 中獲取 segment。

對于傳輸層的代碼階段,我們需要分析recv函數(shù),這個與send類似,調(diào)用的是__sys_recvfrom,整個函數(shù)的調(diào)用路徑與send非常類似:

int __sys_recvfrom(int fd, void __user *ubuf, size_t size, unsigned int flags,           struct sockaddr __user *addr, int __user *addr_len){    ......    err = import_single_range(READ, ubuf, size, &iov, &msg.msg_iter);    if (unlikely(err))        return err;    sock = sockfd_lookup_light(fd, &err, &fput_needed);    .....    msg.msg_control = NULL;    msg.msg_controllen = 0;    /* Save some cycles and don't copy the address if not needed */    msg.msg_name = addr ? (struct sockaddr *)&address : NULL;    /* We assume all kernel code knows the size of sockaddr_storage */    msg.msg_namelen = 0;    msg.msg_iocb = NULL;    msg.msg_flags = 0;    if (sock->file->f_flags & O_NONBLOCK)        flags |= MSG_DONTWAIT;    err = sock_recvmsg(sock, &msg, flags);    if (err >= 0 && addr != NULL) {        err2 = move_addr_to_user(&address,                     msg.msg_namelen, addr, addr_len);    .....}

__sys_recvfrom調(diào)用了sock_recvmsg來接收數(shù)據(jù),整個函數(shù)實際調(diào)用的是sock->ops->recvmsg(sock, msg, msg_data_left(msg), flags);,同樣,根據(jù)tcp_prot結(jié)構(gòu)的初始化,調(diào)用的其實是tcp_rcvmsg

接受函數(shù)比發(fā)送函數(shù)要復(fù)雜得多,因為數(shù)據(jù)接收不僅僅只是接收,tcp的三次握手也是在接收函數(shù)實現(xiàn)的,所以收到數(shù)據(jù)后要判斷當前的狀態(tài),是否正在建立連接等,根據(jù)發(fā)來的信息考慮狀態(tài)是否要改變,在這里,我們僅僅考慮在連接建立后數(shù)據(jù)的接收。

tcp_rcvmsg函數(shù)如下:

int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock,        int flags, int *addr_len){    ......    if (sk_can_busy_loop(sk) && skb_queue_empty(&sk->sk_receive_queue) &&        (sk->sk_state == TCP_ESTABLISHED))        sk_busy_loop(sk, nonblock);    lock_sock(sk);    .....        if (unlikely(tp->repair)) {        err = -EPERM;        if (!(flags & MSG_PEEK))            goto out;        if (tp->repair_queue == TCP_SEND_QUEUE)            goto recv_sndq;        err = -EINVAL;        if (tp->repair_queue == TCP_NO_QUEUE)            goto out;    ......        last = skb_peek_tail(&sk->sk_receive_queue);        skb_queue_walk(&sk->sk_receive_queue, skb) {            last = skb;    ......            if (!(flags & MSG_TRUNC)) {            err = skb_copy_datagram_msg(skb, offset, msg, used);            if (err) {                /* Exception. Bailout! */                if (!copied)                    copied = -EFAULT;                break;            }        }        *seq += used;        copied += used;        len -= used;        tcp_rcv_space_adjust(sk);    

這里共維護了三個隊列:prequeue、backlog、receive_queue,分別為預(yù)處理隊列,后備隊列和接收隊列,在連接建立后,若沒有數(shù)據(jù)到來,接收隊列為空,進程會在sk_busy_loop函數(shù)內(nèi)循環(huán)等待,知道接收隊列不為空,并調(diào)用函數(shù)數(shù)skb_copy_datagram_msg將接收到的數(shù)據(jù)拷貝到用戶態(tài),實際調(diào)用的是__skb_datagram_iter,這里同樣用了struct msghdr *msg來實現(xiàn)。__skb_datagram_iter函數(shù)如下:

int __skb_datagram_iter(const struct sk_buff *skb, int offset,            struct iov_iter *to, int len, bool fault_short,            size_t (*cb)(const void *, size_t, void *, struct iov_iter *),            void *data){    int start = skb_headlen(skb);    int i, copy = start - offset, start_off = offset, n;    struct sk_buff *frag_iter;    /* 拷貝tcp頭部 */    if (copy > 0) {        if (copy > len)            copy = len;        n = cb(skb->data + offset, copy, data, to);        offset += n;        if (n != copy)            goto short_copy;        if ((len -= copy) == 0)            return 0;    }    /* 拷貝數(shù)據(jù)部分 */    for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {        int end;        const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];        WARN_ON(start > offset + len);        end = start + skb_frag_size(frag);        if ((copy = end - offset) > 0) {            struct page *page = skb_frag_page(frag);            u8 *vaddr = kmap(page);            if (copy > len)                copy = len;            n = cb(vaddr + frag->page_offset +                offset - start, copy, data, to);            kunmap(page);            offset += n;            if (n != copy)                goto short_copy;            if (!(len -= copy))                return 0;        }        start = end;    }

拷貝完成后,函數(shù)返回,整個接收的過程也就完成了。用一張函數(shù)間的相互調(diào)用圖可以表示:

通過gdb調(diào)試驗證如下:

Breakpoint 1, __sys_recvfrom (fd=5, ubuf=0x7ffd9428d960, size=1024, flags=0,     addr=0x0 <fixed_percpu_data>, addr_len=0x0 <fixed_percpu_data>)    at net/socket.c:19901990    {(gdb) cContinuing.Breakpoint 2, sock_recvmsg (sock=0xffff888006df1900, msg=0xffffc900001f7e28,     flags=0) at net/socket.c:891891    {(gdb) cContinuing.Breakpoint 3, tcp_recvmsg (sk=0xffff888006479100, msg=0xffffc900001f7e28,     len=1024, nonblock=0, flags=0, addr_len=0xffffc900001f7df4)    at net/ipv4/tcp.c:19331933    {(gdb) c
Breakpoint 1, __sys_recvfrom (fd=5, ubuf=0x7ffd9428d960, size=1024, flags=0,     addr=0x0 <fixed_percpu_data>, addr_len=0x0 <fixed_percpu_data>)    at net/socket.c:19901990    {(gdb) cContinuing.Breakpoint 2, sock_recvmsg (sock=0xffff888006df1900, msg=0xffffc900001f7e28,     flags=0) at net/socket.c:891891    {(gdb) cContinuing.Breakpoint 3, tcp_recvmsg (sk=0xffff888006479100, msg=0xffffc900001f7e28,     len=1024, nonblock=0, flags=0, addr_len=0xffffc900001f7df4)    at net/ipv4/tcp.c:19331933    {(gdb) cContinuing.Breakpoint 4, __skb_datagram_iter (skb=0xffff8880068714e0, offset=0,     to=0xffffc900001efe38, len=2, fault_short=false,     cb=0xffffffff817ff860 <simple_copy_to_iter>, data=0x0 <fixed_percpu_data>)    at net/core/datagram.c:414414    {

符合我們之前的分析。

5 IP層流程

  5.1 發(fā)送端

網(wǎng)絡(luò)層的任務(wù)就是選擇合適的網(wǎng)間路由和交換結(jié)點, 確保數(shù)據(jù)及時傳送。網(wǎng)絡(luò)層將數(shù)據(jù)鏈路層提供的幀組成數(shù)據(jù)包,包中封裝有網(wǎng)絡(luò)層包頭,其中含有邏輯地址信息- -源站點和目的站點地址的網(wǎng)絡(luò)地址。其主要任務(wù)包括 (1)路由處理,即選擇下一跳 (2)添加 IP header(3)計算 IP header checksum,用于檢測 IP 報文頭部在傳播過程中是否出錯 (4)可能的話,進行 IP 分片(5)處理完畢,獲取下一跳的 MAC 地址,設(shè)置鏈路層報文頭,然后轉(zhuǎn)入鏈路層處理。

IP 頭:

IP 棧基本處理過程如下圖所示:

首先,ip_queue_xmit(skb)會檢查skb->dst路由信息。如果沒有,比如套接字的第一個包,就使用ip_route_output()選擇一個路由。

接著,填充IP包的各個字段,比如版本、包頭長度、TOS等。

中間的一些分片等,可參閱相關(guān)文檔?;舅枷胧?,當報文的長度大于mtu,gso的長度不為0就會調(diào)用 ip_fragment 進行分片,否則就會調(diào)用ip_finish_output2把數(shù)據(jù)發(fā)送出去。ip_fragment 函數(shù)中,會檢查 IP_DF 標志位,如果待分片IP數(shù)據(jù)包禁止分片,則調(diào)用 icmp_send()向發(fā)送方發(fā)送一個原因為需要分片而設(shè)置了不分片標志的目的不可達ICMP報文,并丟棄報文,即設(shè)置IP狀態(tài)為分片失敗,釋放skb,返回消息過長錯誤碼。

接下來就用 ip_finish_ouput2 設(shè)置鏈路層報文頭了。如果,鏈路層報頭緩存有(即hh不為空),那就拷貝到skb里。如果沒,那么就調(diào)用neigh_resolve_output,使用 ARP 獲取。

具體代碼分析如下:

入口函數(shù)是ip_queue_xmit,函數(shù)如下:

發(fā)現(xiàn)調(diào)用了__ip_queue_xmit函數(shù):

發(fā)現(xiàn)調(diào)用了skb_rtable函數(shù),實際上是開始找路由緩存,繼續(xù)看:

發(fā)現(xiàn)調(diào)用ip_local_out進行數(shù)據(jù)發(fā)送:

發(fā)現(xiàn)調(diào)用__ip_local_out函數(shù):

發(fā)現(xiàn)返回一個nf_hook函數(shù),里面調(diào)用了dst_output,這個函數(shù)實質(zhì)上是調(diào)用ip_finish__output函數(shù):

發(fā)現(xiàn)調(diào)用__ip_finish_output函數(shù):

如果分片就調(diào)用ip_fragment,否則就調(diào)用IP_finish_output2函數(shù):

在構(gòu)造好 ip 頭,檢查完分片之后,會調(diào)用鄰居子系統(tǒng)的輸出函數(shù) neigh_output 進行輸 出。neigh_output函數(shù)如下:

輸出分為有二層頭緩存和沒有兩種情況,有緩存時調(diào)用 neigh_hh_output 進行快速輸 出,沒有緩存時,則調(diào)用鄰居子系統(tǒng)的輸出回調(diào)函數(shù)進行慢速輸出。這個函數(shù)如下:

最后調(diào)用dev_queue_xmit函數(shù)進行向鏈路層發(fā)送包,到此結(jié)束。gdb驗證如下:

  5.2 接收端

IP 層的入口函數(shù)在 ip_rcv 函數(shù)。該函數(shù)首先會做包括 package checksum 在內(nèi)的各種檢查,如果需要的話會做 IP defragment(將多個分片合并),然后 packet 調(diào)用已經(jīng)注冊的 Pre-routing netfilter hook ,完成后最終到達 ip_rcv_finish 函數(shù)。

ip_rcv_finish 函數(shù)會調(diào)用 ip_router_input 函數(shù),進入路由處理環(huán)節(jié)。它首先會調(diào)用 ip_route_input 來更新路由,然后查找 route,決定該 package 將會被發(fā)到本機還是會被轉(zhuǎn)發(fā)還是丟棄:

如果是發(fā)到本機的話,調(diào)用 ip_local_deliver 函數(shù),可能會做 de-fragment(合并多個 IP packet),然后調(diào)用 ip_local_deliver 函數(shù)。該函數(shù)根據(jù) package 的下一個處理層的 protocal number,調(diào)用下一層接口,包括 tcp_v4_rcv (TCP), udp_rcv (UDP),icmp_rcv (ICMP),igmp_rcv(IGMP)。對于 TCP 來說,函數(shù) tcp_v4_rcv 函數(shù)會被調(diào)用,從而處理流程進入 TCP 棧。

如果需要轉(zhuǎn)發(fā) (forward),則進入轉(zhuǎn)發(fā)流程。該流程需要處理 TTL,再調(diào)用 dst_input 函數(shù)。該函數(shù)會 

(1)處理 Netfilter Hook 

(2)執(zhí)行 IP fragmentation 

(3)調(diào)用 dev_queue_xmit,進入鏈路層處理流程。

接收相對簡單,入口在ip_rcv,這個函數(shù)如下:

里面調(diào)用ip_rcv_finish函數(shù):

發(fā)現(xiàn)調(diào)用dst_input函數(shù),實際上是調(diào)用ip_local_deliver函數(shù):

如果分片,就調(diào)用ip_defrag函數(shù),沒有則調(diào)用ip_local_deliver_finish函數(shù):

發(fā)現(xiàn)調(diào)用ip_protocol_deliver_rcu函數(shù):

調(diào)用完畢之后進入tcp棧,調(diào)用完畢,通過gdb驗證如下:

6 數(shù)據(jù)鏈路層流程

  6.1 發(fā)送端

功能上,在物理層提供比特流服務(wù)的基礎(chǔ)上,建立相鄰結(jié)點之間的數(shù)據(jù)鏈路,通過差錯控制提供數(shù)據(jù)幀(Frame)在信道上無差錯的傳輸,并進行各電路上的動作系列。

數(shù)據(jù)鏈路層在不可靠的物理介質(zhì)上提供可靠的傳輸。

該層的作用包括:物理地址尋址、數(shù)據(jù)的成幀、流量控制、數(shù)據(jù)的檢錯、重發(fā)等。在這一層,數(shù)據(jù)的單位稱為幀(frame)。數(shù)據(jù)鏈路層協(xié)議的代表包括:SDLC、HDLC、PPP、STP、幀中繼等。

實現(xiàn)上,Linux 提供了一個 Network device 的抽象層,其實現(xiàn)在 linux/net/core/dev.c。具體的物理網(wǎng)絡(luò)設(shè)備在設(shè)備驅(qū)動中(driver.c)需要實現(xiàn)其中的虛函數(shù)。Network Device 抽象層調(diào)用具體網(wǎng)絡(luò)設(shè)備的函數(shù)。

發(fā)送端調(diào)用dev_queue_xmit,這個函數(shù)實際上調(diào)用__dev_queue_xmit:

發(fā)現(xiàn)它調(diào)用了dev_hard_start_xmit函數(shù):

調(diào)用xmit_one:

調(diào)用trace_net_dev_start_xmit,實際上調(diào)用__net_dev_start_xmit函數(shù):

到此,調(diào)用鏈結(jié)束。gdb調(diào)試如下:

  6.2 接收端

簡要過程:

一個 package 到達機器的物理網(wǎng)絡(luò)適配器,當它接收到數(shù)據(jù)幀時,就會觸發(fā)一個中斷,并將通過 DMA 傳送到位于 linux kernel 內(nèi)存中的 rx_ring。

網(wǎng)卡發(fā)出中斷,通知 CPU 有個 package 需要它處理。中斷處理程序主要進行以下一些操作,包括分配 skb_buff 數(shù)據(jù)結(jié)構(gòu),并將接收到的數(shù)據(jù)幀從網(wǎng)絡(luò)適配器I/O端口拷貝到skb_buff 緩沖區(qū)中;

從數(shù)據(jù)幀中提取出一些信息,并設(shè)置 skb_buff 相應(yīng)的參數(shù),這些參數(shù)將被上層的網(wǎng)絡(luò)協(xié)議使用,例如skb->protocol;

終端處理程序經(jīng)過簡單處理后,發(fā)出一個軟中斷(NET_RX_SOFTIRQ),通知內(nèi)核接收到新的數(shù)據(jù)幀。

內(nèi)核 2.5 中引入一組新的 API 來處理接收的數(shù)據(jù)幀,即 NAPI。所以,驅(qū)動有兩種方式通知內(nèi)核:(1) 通過以前的函數(shù)netif_rx;(2)通過NAPI機制。該中斷處理程序調(diào)用 Network device的 netif_rx_schedule 函數(shù),進入軟中斷處理流程,再調(diào)用 net_rx_action 函數(shù)。

該函數(shù)關(guān)閉中斷,獲取每個 Network device 的 rx_ring 中的所有 package,最終 pacakage 從 rx_ring 中被刪除,進入 netif _receive_skb 處理流程。

netif_receive_skb 是鏈路層接收數(shù)據(jù)報的最后一站。它根據(jù)注冊在全局數(shù)組 ptype_all 和 ptype_base 里的網(wǎng)絡(luò)層數(shù)據(jù)報類型,把數(shù)據(jù)報遞交給不同的網(wǎng)絡(luò)層協(xié)議的接收函數(shù)(INET域中主要是ip_rcv和arp_rcv)。該函數(shù)主要就是調(diào)用第三層協(xié)議的接收函數(shù)處理該skb包,進入第三層網(wǎng)絡(luò)層處理。

入口函數(shù)是net_rx_action:

發(fā)現(xiàn)調(diào)用napi_poll,實質(zhì)上調(diào)用napi_gro_receive函數(shù):

napi_gro_receive 會直接調(diào)用 netif_receive_skb_core。而它會調(diào)用__netif_receive_skb_one_core,將數(shù)據(jù)包交給上層 ip_rcv 進行處理。

調(diào)用結(jié)束之后,通過軟中斷通知CPU,至此,調(diào)用鏈結(jié)束。gdb驗證如下:

7 物理層流程

  7.1 發(fā)送端

物理層在收到發(fā)送請求之后,通過 DMA 將該主存中的數(shù)據(jù)拷貝至內(nèi)部RAM(buffer)之中。在數(shù)據(jù)拷貝中,同時加入符合以太網(wǎng)協(xié)議的相關(guān)header,IFG、前導(dǎo)符和CRC。對于以太網(wǎng)網(wǎng)絡(luò),物理層發(fā)送采用CSMA/CD,即在發(fā)送過程中偵聽鏈路沖突。

一旦網(wǎng)卡完成報文發(fā)送,將產(chǎn)生中斷通知CPU,然后驅(qū)動層中的中斷處理程序就可以刪除保存的 skb 了。

  7.2 接收端

一個 package 到達機器的物理網(wǎng)絡(luò)適配器,當它接收到數(shù)據(jù)幀時,就會觸發(fā)一個中斷,并將通過 DMA 傳送到位于 linux kernel 內(nèi)存中的 rx_ring。

網(wǎng)卡發(fā)出中斷,通知 CPU 有個 package 需要它處理。中斷處理程序主要進行以下一些操作,包括分配 skb_buff 數(shù)據(jù)結(jié)構(gòu),并將接收到的數(shù)據(jù)幀從網(wǎng)絡(luò)適配器I/O端口拷貝到skb_buff 緩沖區(qū)中;從數(shù)據(jù)幀中提取出一些信息,并設(shè)置 skb_buff 相應(yīng)的參數(shù),這些參數(shù)將被上層的網(wǎng)絡(luò)協(xié)議使用,例如skb->protocol;

終端處理程序經(jīng)過簡單處理后,發(fā)出一個軟中斷(NET_RX_SOFTIRQ),通知內(nèi)核接收到新的數(shù)據(jù)幀。

內(nèi)核 2.5 中引入一組新的 API 來處理接收的數(shù)據(jù)幀,即 NAPI。所以,驅(qū)動有兩種方式通知內(nèi)核:(1) 通過以前的函數(shù)netif_rx;(2)通過NAPI機制。該中斷處理程序調(diào)用 Network device的 netif_rx_schedule 函數(shù),進入軟中斷處理流程,再調(diào)用 net_rx_action 函數(shù)。

該函數(shù)關(guān)閉中斷,獲取每個 Network device 的 rx_ring 中的所有 package,最終 pacakage 從 rx_ring 中被刪除,進入 netif _receive_skb 處理流程。

netif_receive_skb 是鏈路層接收數(shù)據(jù)報的最后一站。它根據(jù)注冊在全局數(shù)組 ptype_all 和 ptype_base 里的網(wǎng)絡(luò)層數(shù)據(jù)報類型,把數(shù)據(jù)報遞交給不同的網(wǎng)絡(luò)層協(xié)議的接收函數(shù)(INET域中主要是ip_rcv和arp_rcv)。該函數(shù)主要就是調(diào)用第三層協(xié)議的接收函數(shù)處理該skb包,進入第三層網(wǎng)絡(luò)層處理。

8 時序圖展示和總結(jié)

時序圖如下:

本次實驗主要是通過分析Linux內(nèi)核源代碼,一步步地通過gdb進行調(diào)試函數(shù)調(diào)用鏈,最終清楚了tcp/ip協(xié)議棧的調(diào)用過程。因為時間有限,部分細節(jié)可能會有錯誤,希望讀者多加指正。

聲明:本內(nèi)容為作者獨立觀點,不代表電子星球立場。未經(jīng)允許不得轉(zhuǎn)載。授權(quán)事宜與稿件投訴,請聯(lián)系:editor@netbroad.com
覺得內(nèi)容不錯的朋友,別忘了一鍵三連哦!
贊 5
收藏 4
關(guān)注 181
成為作者 賺取收益
全部留言
0/200
成為第一個和作者交流的人吧