整站优化

查看: 92|回复: 6

TCP/IP协议栈在Linux内核中的运行时序分析

[复制链接]

154

主题

155

帖子

476

积分

中级会员

Rank: 3Rank: 3

积分
476
 楼主| 发表于 2021-6-19 13:13:40| 字数 26,441 | 显示全部楼层 |阅读模式
TCP/IP和谈栈正在Linux内乱核中的运转时序剖析整站劣化,本文次要是解说TCP/IP和谈栈正在Linux内乱核中的运转时序,文章较少,内里有配套的视频解说,倡议珍藏寓目。) w0 S) k$ H- o; ^, c* C1 A# I, I
1 Linux概述
" [: W( n9 k7 }% |, c  @
  1.1 Linux操纵体系架构简介

. M% I2 Z' ^: l* L. R0 P$ n  T% z
Linux操纵体系整体上由Linux内乱核战GNU体系组成,详细来说由4个次要部门组成,即Linux内乱核、Shell、文件体系战使用法式。内乱核、Shell战文件体系组成了操纵体系的根本构造,使得用户能够运转法式、治理文件并利用体系。
) z3 h0 w" m2 p
内乱核是操纵体系的焦点,具有许多最根本功效,如假造内乱存、多使命、同享库、需供减载、可施行法式战TCP/IP收集功效。我们所调研的事情,便是正在Linux内乱核层里举行剖析。
2 d" E4 q$ H& [

0 P5 H- j. g- U) j$ {) X: u7 P

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

3 o, d2 l0 a; e# H3 A
  Linux内乱核和谈栈相干视频解说:「链接」
4 c/ P  t- F0 a1 g

8 a1 R* r2 W7 i" u
底层道理到徒脚完成 TCP/IP收集和谈栈视频解说:「链接」* F8 A' {: x' ?1 A5 H# {/ R/ i1 d/ e

/ n/ X" y$ w5 R5 t
C/C++ Linux效劳器开辟初级架构进修视频:C/C++Linux效劳器开辟/背景架构师【整声教诲】-进修视频教程-腾讯教室
# B" W( I! e1 A7 v) t( `0 T

. I8 e0 o% m* Z0 o
1.2 和谈栈简介

/ D& E5 V9 X  E# H# o; g  OSI(Open System Interconnect),即开放式体系互联。 一样平常皆叫OSI参考模子,是ISO(国际尺度化构造)构造正在1985年研讨的收集互连模子。/ P$ I' A( m* i$ y  P2 u
ISO为了更好的使收集使用更加提高,推出了OSI参考模子。其寄义便是保举一切公司利用那个标准去掌握收集。如许一切公司皆有不异的标准,就可以互联了。
# G  O0 U" o6 j  U+ rOSI界说了收集互连的七层框架(物理层、数据链路层、收集层、传输层、会话层、表现层、使用层),即ISO开放互连络统参考模子。以下图。
- E& b9 n3 S: f! R3 \
3 j% [* q% @! M6 n

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
8 m$ |! F$ j4 A' @/ s
每层完成各自的功效战和谈,并完成取相邻层的接心通讯。OSI的效劳界说具体阐明了各层所供给的效劳。某一层的效劳便是该层及其下各层的一种才能,它经由过程接心供给给更下一层。各层所供给的效劳取那些效劳是怎样完成的无闭。! p( K) X# A2 \" r1 L0 ^
  osi七层模子曾经成了实际上的尺度,但实正使用于理论中的是TCP/IP五层模子。
/ [2 W' V9 x" f; K+ h% u0 S5 U/ \1 s# H  TCP/IP五层和谈战osi的七层和谈对应干系以下:
8 Q' p& `" X6 K9 T& `  W
5 y* F) Z( I2 d, ~% s$ g6 J

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
) y- o: H' _# z/ p+ J$ s9 x
正在每层完成的和谈也各差别,即每层的效劳也差别.下图列出了每层次要的和谈。9 r2 r1 _. _4 E/ @- `) L2 P) m
% z$ X1 J8 j+ r' Q

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

! C2 J. n7 n0 Y4 ~4 p  `
  1.3 Linux内乱核和谈栈
/ I; o( S  G0 k0 l' ]- ^9 I' h/ w
  Linux的和谈栈实际上是源于BSD的和谈栈,它背上和背下的接心和和谈栈自己的硬件分层构造的很是好。
( ?$ H4 B7 m0 D, g6 I  Linux的和谈栈基于分层的设想头脑,统共分为四层,从下往上顺次是:物理层,链路层,收集层,使用层。
" E/ m; F/ k/ T) [  物理层次要供给种种毗连的物理装备,如种种网卡,串心卡等;链路层次要指的是供给对物理层举行会见的种种接心卡的驱动法式,如网卡驱动等;网路层的感化是卖力将收集数据包传输到准确的地位,最主要的收集层和谈固然便是IP和谈了,实在收集层另有其他的和谈如ICMP,ARP,RARP等,只不外没有像IP那样被大都人所熟习;传输层的感化次要是供给端到端,道利剑一面便是供给使用法式之间的通讯,传输层最出名的和谈非TCP取UDP和谈终属了;使用层,望文生义,固然便是由使用法式供给的,用去对传输数据举行语义诠释的“人机界里”层了,好比HTTP,SMTP,FTP等等,实在使用层借没有是人们终极所看到的那一层,最上里的一层该当是“诠释层”,卖力将数据以种种差别的表项情势终极呈献到人们长远。/ x. b) t! ?7 Y
  Linux收集焦点架构Linux的收集架构从上往下能够分为三层,划分是:
$ g: N$ G' f6 E  用户空间的使用层。& R+ P/ h5 ]) I# [$ g
  内乱核空间的收集和谈栈层。0 T& H7 \3 O0 f  r  \- S* t4 L
  物理硬件层。
' F, X% J+ {2 `7 e7 T- F  此中最主要最焦点确当然是内乱核空间的和谈栈层了。. r& t6 U8 ]# E$ j' V
  Linux收集和谈栈构造Linux的全部收集和谈栈皆构建取Linux Kernel中,全部栈也是严酷根据分层的头脑去设想的,全部栈共分为五层,划分是 :
- a/ A: O" C3 c- J& ^  1,体系挪用接心层,本质是一个里背用户空间使用法式的接心挪用库,背用户空间使用法式供给利用收集效劳的接心。6 B! [& V7 U; H8 j
  2,和谈无闭的接心层,便是SOCKET层,那一层的目标是屏障底层的差别和谈(更精确的来讲次要是TCP取UDP,固然借包罗RAW IP, SCTP等),以便取体系挪用层之间的接心能够简朴,同一。简朴的道,不论我们使用层利用甚么和谈,皆要经由过程体系挪用接心去建设一个SOCKET,那个SOCKET实际上是一个庞大的sock构造,它战上面一层的收集和谈层联络起去,屏障了差别的收集和谈的差别,只吧数据部门呈献给使用层(经由过程体系挪用接心去呈献)。! ]# {# e0 X% E, m2 g
  3,收集和谈完成层,毫无疑问,那是全部和谈栈的焦点。那一层次要完成种种收集和谈,最次要确当然是IP,ICMP,ARP,RARP,TCP,UDP等。那一层包罗了许多设想的本领取算法,相称的没有错。: R* m) H1 i( p$ I- w! N7 h% M
  4,取详细装备无闭的驱动接心层,那一层的目标次要是为了同一差别的接心卡的驱动法式取收集和谈层的接心,它将种种差别的驱动法式的功效同一笼统为几个特别的行动,如open,close,init等,那一层能够屏障底层差别的驱动法式。
- s0 X) ^/ A4 C  5,驱动法式层,那一层的目标便很简朴了,便是建设取硬件的接心层。
7 V" Y/ E. [1 B  能够看到,Linux收集和谈栈是一个严酷分层的构造,此中的每层皆施行相对自力的功效,构造很是明晰。+ k3 X# i+ I3 B. O0 s& K  h7 i$ G
  此中的两个“无闭”层的设想很是棒,经由过程那两个“无闭”层,其和谈栈能够很是沉紧的举行扩大。正在我们本人的硬件设想中,能够吸取这类设想要领。
/ h0 ~5 G1 t7 h) q4 q5 B8 p

! G6 {  l) d! k! }, C/ j

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
+ n: v+ r. a- Q; N$ o8 Z3 w
2 代码简介

8 M* F2 F" [8 X7 E9 g. d
本文彩用的测试代码是一个很是简朴的基于socket的客户端效劳器法式,翻开效劳端并运转,再开一末端运转客户端,二者建设毗连并能够收收hello\hi的疑息,server端代码以下:
7 @) L1 Y& s4 U; K* K+ v7 H
#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    /* 监听的端心 */#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);     /* 收集字节挨次 */    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端代码以下:
7 A$ z  x6 g/ c  p; O* Y
#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 /* 一次能够读的最年夜字节数 */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 正在新的版本中会泛起忠告,曾经倡议没有要利用了 */        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); /* 其他部门设成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;}
简朴来讲,次要流程以下图所示:

, ?/ g3 N: X% v8 a
  s" o1 f& l! {" \, \- \

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
$ ^  L7 D. D2 A- z  c8 w0 }
专攻Linux内乱核,处置内乱核开辟的朋侪,需求进修材料能够看看上面的视频能否需求进修,面击:正正在跳转 获得
7 R. Z% D/ Y: e  ~4 K; U

4 \% W- n# R/ `3 v+ i3 q2 W

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
/ ^- A5 d% f4 v8 U" l
Linux内乱核开辟进修门路纲领:
: B$ |6 r8 _. {2 m

8 ?" V6 l: l. Q- m

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

/ c( h" r8 N9 x; ?- U& L6 D
3 使用层流程

# D% g& f- ^- m1 a
  3.1 收收端
4 H; B6 F6 E/ K3 D
  • 收集使用挪用Socket API socket (int family, int type, int protocol) 建立一个 socket,该挪用终极会挪用 Linux system call socket() ,并终极挪用 Linux Kernel 的 sock_create() 要领。该要领返回被建立好了的谁人 socket 的 file descriptor。关于每个 userspace 收集使用建立的 socket,正在内乱核中皆有一个对应的 struct socket战 struct sock。此中,struct sock 有三个行列(queue),划分是 rx , tx 战 err,正在 sock 构造被初初化的时间,那些缓冲行列也被初初化完成;正在收条支收过程当中,每一个 queue 中生存要收收大概承受的每一个 packet 对应的 Linux 收集栈 sk_buffer 数据构造的真例 skb。关于 TCP socket 来讲,使用挪用 connect()API ,使得客户端战效劳器端经由过程该 socket 建设一个假造毗连。正在此过程当中,TCP 和谈栈经由过程三次握脚会建设 TCP 毗连。默许天,该 API 会比及 TCP 握脚完成毗连建设后才返回。正在建设毗连的过程当中的一个主要步调是,肯定单方利用的 Maxium Segemet Size (MSS)。由于 UDP 是里背无毗连的和谈,因而它是没有需求该步调的。 使用挪用 Linux Socket 的 send 大概 write API 去收回一个 message 给吸收端 sock_sendmsg 被挪用,它利用 socket descriptor 获得 sock struct,建立 message header 战 socket control message _sock_sendmsg 被挪用,凭据 socket 的和谈范例,挪用响应和谈的收收函数。
    ) E! w' t' x1 c1 {

      / D: w8 @9 ]& o; P0 f" B3 Z关于 TCP ,挪用 tcp_sendmsg 函数。 关于 UDP 来讲,userspace 使用能够挪用 send()/sendto()/sendmsg() 三个 system call 中的恣意一个去收收 UDP message,它们终极都市挪用内乱核中的 udp_sendmsg() 函数。 + [" t) y3 l. f/ B0 I( i

    * @- {2 y9 X* k, G, y" f% O/ E
* r7 [* j, E! l6 {, F9 C

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

, B7 H$ w5 ?& g& v9 @
上面我们详细联合Linux内乱核源码举行一步步认真剖析:

. M  A  u- E. l7 Q
凭据上述剖析可知,收收端起首建立socket,建立以后会经由过程send收收数据。详细到源码级别,会经由过程send,sendto,sendmsg那些体系挪用去收收数据,而上述三个函数底层皆挪用了sock_sendmsg。睹下图:
3 B4 [- s' S0 g' V. z  Q4 m

( D( C% i4 k. c6 |" i3 c+ E

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
$ k' Z/ N3 U! F; g
我们再跳转到__sys_sendto看看那个函数干了甚么:
: g( F0 N( }" r3 a# v  f3 o, I

5 m" z2 }# M) N! p% v$ T: e$ |

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
6 V/ l# p( C3 Q/ b% B% j' a
我们能够发明,它建立了两个构造体,划分是:struct msghdr msg战struct iovec iov,那两个构造体凭据定名我们能够大抵猜出是收收数据战io操纵的一些疑息,以下图:
  W9 J) E& q& L
7 U1 p( p/ ]5 g0 \

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

1 L' |2 J6 Z) s. }& o
( |1 `2 s# M( \0 ]9 G* I4 N  P

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

0 r: U) v* |& y; ~
我们再去看看__sys_sendto挪用的sock_sendmsg函数施行了甚么内乱容:
9 B5 v; `3 p8 Q1 t/ P# D

7 {- J7 G) e, q) L. t' ]+ Y! i

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

7 Y% f% S- T0 T$ {
发明挪用了sock_sendmsg_nosec函数:

$ F9 M9 h* r% ?4 A; A9 z( F1 q. \; ~) y. @) i) d' h1 ]

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
; d# p) O2 X# h
发明挪用了inet_sendmsg函数:
& ?* m4 L- `- s" ~  M% t

* B3 `6 x' f3 t/ O- ~

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
+ V, c3 g$ x: v1 |  O# z: x
至此,收收端挪用终了。我们能够经由过程gdb举行调实验证:

. \1 j8 N0 _$ h( s/ ?6 `9 i% a1 W6 s- I: x( ?+ m

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

6 k; N% j: P  a; k7 o
恰好切合我们的剖析。

0 X# P! m1 I7 u# y! N' @
  3.2 吸收端
! M' e5 E0 T* x* i) O

    2 @1 ?& `; B2 g) c7 W+ c! m  L" W每当用户使用挪用 read 大概 recvfrom 时,该挪用会被映照为/net/socket.c 中的 sys_recv 体系挪用,并被转化为 sys_recvfrom 挪用,然后挪用 sock_recgmsg 函数。 关于 INET 范例的 socket,/net/ipv4/af inet.c 中的 inet_recvmsg 要领会被挪用,它会挪用相干和谈的数据吸收要领。 对 TCP 来讲,挪用 tcp_recvmsg。该函数从 socket buffer 中拷贝数据到 user buffer。 对 UDP 来讲,从 user space 中能够挪用三个 system call recv()/recvfrom()/recvmsg() 中的恣意一个去吸收 UDP package,那些体系挪用终极都市挪用内乱核中的 udp_recvmsg 要领。 ' Y" A7 T  T! y; \
我们联合源码举行认真剖析:
0 Y: t) {1 w( R! T$ f5 N$ d% i
吸收端挪用的是__sys_recvfrom函数:

2 }: O6 Y0 N/ g- U% e4 u! @/ [( d8 P1 A5 X

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
# K! v, b" s) @; H
__sys_recvfrom函数详细以下:
1 D7 v0 d' u4 m& L1 V

0 a, S/ L; i: F# f- e. q

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

, F, H' i  `, z& C3 t& [) U) d
发明它挪用了sock_recvmsg函数:

- m% ^1 B4 Z7 K- K/ p9 M& e' i7 S/ z2 d' Q# x8 ~

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

# }  c- D" g/ V2 L* y
发明它挪用了sock_recvmsg_nosec函数:

& T$ O. S) D6 e8 N4 R5 M$ N/ m9 F
6 t+ k! `) D0 L" }4 h9 Z) E8 y6 F

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
8 X  }0 Q) c) J1 B+ v  e4 A
发明它挪用了inet_recvmsg函数:

; T( n4 n, m4 A) D1 V& K" c% n6 K3 C/ ~4 Z

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
: z* N0 y3 j: `# K! z% e- \# m+ K& h
最初挪用的是tcp_recvmsg那个体系挪用。至此吸收端挪用剖析终了。

- z( u6 g: D! P. d' \
上面用gdb挨断面举行考证:

9 u+ v" x3 V! a9 [1 Q7 a2 v( _( t' h
1 W) J; A7 J8 y) F6 I) C( K* `

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
0 V' P- \3 u( A. `
考证效果恰好切合我们的调研。
& m. `% p( i5 a& d6 q# f1 a
4 传输层流程

) G4 T. X( `5 `. u- |
  4.1 收收端
8 W, D0 P8 P& e. k& W4 d) D/ t
传输层的终极目标是背它的用户供给下效的、牢靠的战本钱有用的数据传输效劳,次要功效包罗 (1)结构 TCP segment (2)盘算 checksum (3)收收复兴(ACK)包 (4)滑动窗心(sliding windown)等包管牢靠性的操纵。TCP 和谈栈的大抵处置惩罚历程以下图所示:

* p3 S6 N' B7 G' b  y* e1 ]
. W9 {* ~: j8 E: j* @

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

. b: ?4 P0 x6 E" Y! F# L
TCP 栈扼要历程:
, K$ o! _7 h$ `) y" p
    , X! G6 c% `# `) r, W) B  q1 ~9 j
    tcp_sendmsg 函数会起首查抄曾经建设的 TCP connection 的形态,然后获得该毗连的 MSS,最先 segement 收收流程。 结构 TCP 段的 playload:它正在内乱核空间中建立该 packet 的 sk_buffer 数据构造的真例 skb,从 userspace buffer 中拷贝 packet 的数据到 skb 的 buffer。 结构 TCP header。 盘算 TCP 校验战(checksum)战 挨次号 (sequence number)。
      ) w0 X! @( q" }: E; p7 @+ C) f, y/ g
      TCP 校验战是一个端到真个校验战,由收收端盘算,然后由吸收端考证。其目标是为了发明TCP尾部战数据正在收收端到吸收端之间发作的任何窜改。若是吸收圆检测到校验战有不对,则TCP段会被间接抛弃。TCP校验战笼罩 TCP 尾部战 TCP 数据。 TCP的校验战是必须的
      7 g5 e8 J, M: R3 j
  • 收到 IP 层处置惩罚:挪用 IP handler 句柄 ip_queue_xmit,将 skb 传进 IP 处置惩罚流程。& ?; E0 H0 e1 p  p/ j
闭于C/C++ Linux后端开辟收集底层道理常识 面击 正正在跳转 获得,内乱容常识面包罗Linux,Nginx,ZeroMQ,MySQL,Redis,线程池,MongoDB,ZK,Linux内乱核,CDN,P2P,epoll,Docker,TCP/IP,协程,DPDK等等。
2 G& ]2 h( s# `+ ?! Q  a6 U  ]  W" n  l0 m4 w/ }4 ]* W

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

, r3 r- o- g' ]6 X
UDP 栈扼要历程:

. \+ n5 C- }! R  t, G( i

    : _9 o% }4 j+ [& r; e- G/ FUDP 将 message 启拆成 UDP 数据报 挪用 ip_append_data() 要领将 packet 收到 IP 层举行处置惩罚。 " d1 H) S$ P0 Y1 y  ?- m4 t* l
 上面我们联合代码顺次剖析:

7 f& K0 V6 `6 Q/ T( T+ ^, s
凭据我们对使用层的清查能够发明,传输层也是先挪用send()->sendto()->sys_sento->sock_sendmsg->sock_sendmsg_nosec,我们看下sock_sendmsg_nosec那个函数:
: S" \0 z( S8 N7 z! I
0 x- K  N4 i- o6 W' a4 T# e1 Z+ A

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
  s" }1 e+ }  R4 L! `: o
正在使用层挪用的是inet_sendmsg函数,正在传输层凭据前面的断面能够明白,挪用的是sock->ops-sendmsg那个函数。而sendmsg为一个宏,挪用的是tcp_sendmsg,以下;
' m% o0 ^9 e- N+ o! h5 I
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现实上挪用的是

2 j% G: T; L& s2 T$ Dint tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
那个函数以下:

' `5 F# I8 X  A6 A6 Vint tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size){    struct tcp_sock *tp = tcp_sk(sk);/*举行了强迫范例转换*/    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中,完成的是将一切的数据构造成收收行列,那个收收行列是struct sock构造中的一个域sk_write_queue,那个行列的每个元素是一个skb,内里寄存的便是待收收的数据。然后挪用了tcp_push()函数。构造体struct sock以下:
. s5 j" `! E; x- S% A
struct sock{    ...    struct sk_buff_head    sk_write_queue;/*指背skb行列的第一个元素*/    ...    struct sk_buff    *sk_send_head;/*指背行列第一个借出有收收的元素*/}
正在tcp和谈的头部有几个标记字段:URG、ACK、RSH、RST、SYN、FIN,tcp_push中会判定那个skb的元素能否需求push,若是需求便将tcp头部字段的push置一,置一的历程以下:
* [) n- a" Q9 J. g! P
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构造体寄存的便是tcp的头部,头部的掌握位为tcp_flags,经由过程tcp_mark_push会将skb中的cb,也便是48个字节的数组,范例转换为struct tcp_skb_cb,如许位于skb的cb便成了tcp的头部。tcp_mark_push以下:
- D& F+ C6 i8 l( ^3 O+ T
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个字节tcp[13])    */    ......};
然后,tcp_push挪用了__tcp_push_pending_frames(sk, mss_now, nonagle);函数收收数据:
3 ]- o& R/ l& o% ]
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);}
发明它挪用了tcp_write_xmit函数去收收数据:

* C: z+ i; Q  lstatic 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;    /*统计已收收的报文总数*/    sent_pkts = 0;    ......    /*若收收行列已谦,则筹办收收报文*/    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);        /*查抄收收窗心的巨细*/        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中,它完成了tcp的堵塞掌握,然后挪用了tcp_transmit_skb(sk, skb, 1, gfp)传输数据,现实上挪用的是__tcp_transmit_skb:
3 [1 G; S. ?1 \' e# w( [- H
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);    ......    /* 构建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收收数据位于传输层的最初一步,那里起首对TCP数据段的头部举行了处置惩罚,然后挪用了收集层供给的收收接心icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);完成了数据的收收,自此,数据脱离了传输层,传输层的使命也便竣事了。

# t0 J/ \  m' k! j0 l) t- s2 A
gdb调实验证以下:
1 w. n  Y' R+ T0 g) W) D  f
, x8 V( ^$ x' Z5 `- \# N

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

$ N' T8 K- U* q) P1 I
  4.2 吸收端
! p& F$ s- U/ Y/ w6 {2 x) [( M
    . ^6 O9 c/ U' a; a" H
    传输层 TCP 处置惩罚进口正在 tcp_v4_rcv 函数(位于 linux/net/ipv4/tcp ipv4.c 文件中),它会做 TCP header 查抄等处置惩罚。 挪用 _tcp_v4_lookup,查找该 package 的 open socket。若是找没有到,该 package 会被抛弃。接下去查抄 socket 战 connection 的形态。 若是socket 战 connection 统统一般,挪用 tcp_prequeue 使 package 从内乱核进进 user space,放进 socket 的 receive queue。然后 socket 会被叫醒,挪用 system call,并终极挪用 tcp_recvmsg 函数来从 socket recieve queue 中获得 segment。 1 F) {7 D7 H+ U
关于传输层的代码阶段,我们需求剖析recv函数,那个取send相似,挪用的是__sys_recvfrom,全部函数的挪用途径取send很是相似:
5 _2 C" @8 w, ~1 m1 k0 I& W: a
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挪用了sock_recvmsg去吸收数据,全部函数现实挪用的是sock->ops->recvmsg(sock, msg, msg_data_left(msg), flags);,一样,凭据tcp_prot构造的初初化,挪用的实际上是tcp_rcvmsg
. S* \/ i2 ^/ V& ~! X; ?3 m: c/ M
承受函数比收收函数要庞大很多,由于数据吸收不单单只是吸收,tcp的三次握脚也是正在吸收函数完成的,以是支到数据后要判定当前的形态,能否正正在建设毗连等,凭据收去的疑息思量形态能否要改动,正在那里,我们仅仅思量正在毗连建设后数据的吸收。

1 M; G1 q; u+ V! \6 v0 T5 {
tcp_rcvmsg函数以下:
9 j  O! h! I& X/ ?8 ^$ j7 Q
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,划分为预处置惩罚行列,后备行列战吸收行列,正在毗连建设后,若出无数据到去,吸收行列为空,历程会正在sk_busy_loop函数内乱轮回等候,明白吸收行列没有为空,并挪用函数数skb_copy_datagram_msg将吸收到的数据拷贝到用户态,现实挪用的是__skb_datagram_iter,那里一样用了struct msghdr *msg去完成。__skb_datagram_iter函数以下:

+ a: q' I! _& }: Oint __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;    }    /* 拷贝数据部门 */    for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {        int end;        const skb_frag_t *frag = &skb_shinfo(skb)->frags;        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;    }
拷贝完成后,函数返回,全部吸收的历程也便完成了。, g8 T* }* `2 C1 D
用一张函数间的互相挪用图能够表现:

# f  p2 |# H0 ^/ N
, w! s  w' D& U9 U# {( I- W4 t$ T6 R

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
4 h4 M5 c  ^- W0 [
经由过程gdb调实验证以下:
+ r& r2 E% p+ A2 o5 t+ n
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) cBreakpoint 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    {
切合我们之前的剖析。
" W0 a' L5 c  K* ?" C9 J. I9 N. u
5 IP层流程
3 a+ `/ n. O  R, X* F' i
  5.1 收收端
9 `, c: C( D9 I3 K5 A/ S
收集层的使命便是挑选适宜的网间路由战交流结面, 确保数据实时传收。收集层将数据链路层供给的帧构成数据包,包中启拆有收集层包头,此中露有逻辑天址疑息- -源站面战目标站面天址的收集天址。其次要使命包罗 (1)路由处置惩罚,即挑选下一跳 (2)增加 IP header(3)盘算 IP header checksum,用于检测 IP 报文头部正在流传过程当中能否堕落 (4)能够的话,举行 IP 分片(5)处置惩罚终了,获得下一跳的 MAC 天址,设置链路层报文头,然后转进链路层处置惩罚。

$ g+ c! F* O7 y7 k2 I% p
IP 头:
" |# A! S5 Y/ H1 C

8 a( ]* X# K* D% k$ q% X

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
3 K! m* T/ q6 Y# s$ _
IP 栈根本处置惩罚历程以下图所示:
3 I) l; e& x0 i( O

9 }4 r8 Z, A# E* b+ ]5 Z; ^( ]% ~

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
" h' Q, _! [% W

    / p: }  P( T3 c: ]7 _( r起首,ip_queue_xmit(skb)会查抄skb->dst路由疑息。若是出有,好比套接字的第一个包,便利用ip_route_output()挑选一个路由。 接着,添补IP包的各个字段,好比版本、包头少度、TOS等。 中心的一些分片等,可参阅相干文档。根本头脑是,当报文的少度年夜于mtu,gso的少度没有为0便会挪用 ip_fragment 举行分片,不然便会挪用ip_finish_output2把数据收收进来。ip_fragment 函数中,会查抄 IP_DF 标记位,若是待分片IP数据包克制分片,则挪用 icmp_send()背收收圆收收一个缘故原由为需求分片而设置了没有分片标记的目标不行达ICMP报文,并抛弃报文,即设置IP形态为分片失利,开释skb,返回新闻太长毛病码。 接下去便用 ip_finish_ouput2 设置链路层报文头了。若是,链路层报头缓存有(即hh没有为空),那便拷贝到skb里。若是出,那末便挪用neigh_resolve_output,利用 ARP 获得。
    . i* n' D! l4 e6 k
详细代码剖析以下:

8 t3 m1 q$ S& c! E$ w8 X
进口函数是ip_queue_xmit,函数以下:
) t) [9 D* v- @& L9 \

' Y& s- l! b  u* c1 _' ?' g8 E. j

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
( K  V# |$ }8 X! a
发明挪用了__ip_queue_xmit函数:

" i+ |; P0 V7 ~
! H) N3 E7 i' e: ?& J. r- w

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
6 `3 L% x9 w/ R5 ~$ V& x4 J
发明挪用了skb_rtable函数,现实上是最先找路由缓存,持续看:
$ R% E2 b( K: }# U

3 O" Q- y  _" |6 Q2 Q  l( x- A4 ]

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

+ m( @6 K3 l# s- _
  c4 t  [% `- [7 H2 @0 S( ~+ Z( ?0 r

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

" U/ X1 R# A! d: @2 u4 z% {
发明挪用ip_local_out举行数据收收:
+ c8 [; u0 o0 W; n

* ~) @6 g( j8 P

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

, V. R1 Y. L8 W& ?% M6 N% d
发明挪用__ip_local_out函数:

' X& e& _3 l/ o* a& P& d4 j
3 q/ e' g, h8 Q# R4 J

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
+ P" n( z+ _* }1 [
发明返回一个nf_hook函数,内里挪用了dst_output,那个函数本质上是挪用ip_finish__output函数:

0 o; e0 b3 `9 d+ M5 D8 w6 d
" ^) L: \3 J' A1 b, X

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

" S" s  S8 c& r% R* d
发明挪用__ip_finish_output函数:
$ V% Q5 V, ]# n2 i
* ^1 D/ B3 M' J& \2 v

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

! g) U# F/ `  {0 ~7 v
若是分片便挪用ip_fragment,不然便挪用IP_finish_output2函数:

* L! G! g1 W6 R  v% v- e8 V; U) y- G3 O# e" U1 @

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
2 |% m3 J- ~! w4 s" k; }+ `6 q6 Q5 |
正在结构好 ip 头,查抄完分片以后,会挪用邻人子体系的输出函数 neigh_output 举行输 出。neigh_output函数以下:
, B6 ]  ^5 C: v  S5 z, @' L0 D
! ^5 q6 y, R! R2 S) {( ^

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

' G+ w; t$ }+ }# P0 C" L+ w% j
输出分为有两层头缓存战出有两种情形,有缓存时挪用 neigh_hh_output 举行快速输 出,出有缓存时,则挪用邻人子体系的输出回调函数举行缓速输出。那个函数以下:

# _/ ~5 o& F* g: [' l9 x  L$ o+ I% g: \% b2 b. _, M+ B6 Y

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
8 Z. A+ V; [% o1 T1 `
最初挪用dev_queue_xmit函数举行背链路层收收包,到此竣事。gdb考证以下:
1 u& n  v, G( B5 S! l! A; N% W

1 B/ p$ d% M$ b. ^3 h

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

3 D# r+ e0 X& ^; d! e! n! e
* t$ P8 s( k0 U2 P

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

% c1 ?( ^4 ?2 Y! ]  v; G0 a
  5.2 吸收端
5 J$ }- Z9 v9 J' c7 _; m
    . w" T7 _" ~. q2 S# }. ~
    IP 层的进口函数正在 ip_rcv 函数。该函数起首会做包罗 package checksum 正在内乱的种种查抄,若是需求的话会做 IP defragment(将多个分片兼并),然后 packet 挪用曾经注册的 Pre-routing netfilter hook ,完成后终极抵达 ip_rcv_finish 函数。 ip_rcv_finish 函数会挪用 ip_router_input 函数,进进路由处置惩罚环节。它起首会挪用 ip_route_input 去更新路由,然后查找 route,决议该 package 将会被收到本机照旧会被转发回是抛弃:

      - w- o: T+ h, ~- O' Y1 v  B若是是收到本机的话,挪用 ip_local_deliver 函数,能够会做 de-fragment(兼并多个 IP packet),然后挪用 ip_local_deliver 函数。该函数凭据 package 的下一个处置惩罚层的 protocal number,挪用下一层接心,包罗 tcp_v4_rcv (TCP), udp_rcv (UDP),icmp_rcv (ICMP),igmp_rcv(IGMP)。关于 TCP 来讲,函数 tcp_v4_rcv 函数会被挪用,从而处置惩罚流程进进 TCP 栈。 若是需求转收 (forward),则进进转收流程。该流程需求处置惩罚 TTL,再挪用 dst_input 函数。该函数会 (1)处置惩罚 Netfilter Hook (2)施行 IP fragmentation (3)挪用 dev_queue_xmit,进进链路层处置惩罚流程。 / R7 v, x: M  v2 b# j5 n5 g
    6 h, v5 @1 y6 a$ _* t
吸收相对简朴,进口正在ip_rcv,那个函数以下:
0 l# B/ }( D9 W& I0 {) C7 k

# ~* a( l) |+ `7 q6 r/ o% E6 t3 e

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
1 C+ y% K. T  k) M0 s
内里挪用ip_rcv_finish函数:

: @) w6 I5 I1 [3 y% U! R' R% u8 c; K' x9 ^9 e

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
: f9 Q$ c& C- A8 R" R- X8 O% r+ ?6 q0 p2 H
发明挪用dst_input函数,现实上是挪用ip_local_deliver函数:

: X+ f4 a7 \4 N3 Q/ ^* Q: H; A; I# a
& H: v/ O! f, N1 u

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

# l7 K+ _* E' E8 Q) b
若是分片,便挪用ip_defrag函数,出有则挪用ip_local_deliver_finish函数:
* @( g+ A) z7 a* U. ]( B

6 h8 O, Z! ?& y

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

; a" o& o* K) ^; w
发明挪用ip_protocol_deliver_rcu函数:

9 |, |( U. f+ @* p7 G; s- L, U
& S5 z' |; L0 x+ x. N* F" R

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

. O' B" [( t3 D1 O: J+ @! j
挪用终了以后进进tcp栈,挪用终了,经由过程gdb考证以下:

/ w3 x" c) h6 E, R. T1 J0 ?- c) e( X

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
2 N" r; I: G, M* k; n
6 数据链路层流程
6 g0 [- ^1 ^4 L$ j/ q% }6 J
  6.1 收收端

: y3 w- T2 e+ O: D# O
功效上,正在物理层供给比特流效劳的根底上,建设相邻结面之间的数据链路,经由过程不对掌握供给数据帧(Frame)正在疑讲上无不对的传输,并举行各电路上的行动系列。数据链路层正在不行靠的物理介量上供给牢靠的传输。该层的感化包罗:物理天址觅址、数据的成帧、流量掌握、数据的检错、重收等。正在那一层,数据的单元称为帧(frame)。数据链路层和谈的代表包罗:SDLC、HDLC、PPP、STP、帧中继等。
6 R* N, V5 @0 k7 U
完成上,Linux 供给了一个 Network device 的笼统层,实在如今 linux/net/core/dev.c。详细的物理收集装备正在装备驱动中(driver.c)需求完成此中的实函数。Network Device 笼统层挪用详细收集装备的函数。
( p, i3 M: @, l8 |4 l. K

4 }' J6 p- g9 [& U

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

: P, z: C# P. {
收收端挪用dev_queue_xmit,那个函数现实上挪用__dev_queue_xmit:
/ F! c6 W! F8 I
1 {' I7 b8 T9 m9 U

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
* }8 w1 C. ]2 }7 h
发明它挪用了dev_hard_start_xmit函数:
7 N. Z' Q) ]+ T$ ^8 N# w& U
) J* C- _) D- |+ K

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

9 V8 [. z5 z2 p/ s
挪用xmit_one:

& P) }" T, z% g  E) v9 f9 G7 m; M/ C1 Z

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
7 m' R2 S( O5 R
挪用trace_net_dev_start_xmit,现实上挪用__net_dev_start_xmit函数:

) P8 K6 J& A3 q! `& I# a' G3 t0 _0 t' H; M

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
5 {0 f* W& S1 ?
到此,挪用链竣事。gdb调试以下:

1 ~4 G$ H( W" j: S! M: l6 |
7 Q' Q( @4 Y5 B7 m

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
& X+ |7 k7 a8 J: a  o
  6.2 吸收端

; W$ h- @. N1 e" I6 O) q: Y4 X
扼要历程:

" O4 s; B8 S2 Q! i
    # ^  X" Z2 p; \5 s! {
    一个 package 抵达机械的物理收集适配器,当它吸收到数据帧时,便会触收一其中断,并将经由过程 DMA 传收到位于 linux kernel 内乱存中的 rx_ring。 网卡收回中止,告诉 CPU 有个 package 需求它处置惩罚。中止处置惩罚法式次要举行以下一些操纵,包罗分派 skb_buff 数据构造,并将吸收到的数据帧从收集适配器I/O端心拷贝到skb_buff 缓冲区中;从数据帧中提掏出一些疑息,并设置 skb_buff 响应的参数,那些参数将被上层的收集和谈利用,比方skb->protocol; 末端处置惩罚法式经由简朴处置惩罚后,收回一个硬中止(NET_RX_SOFTIRQ),告诉内乱核吸收到新的数据帧。 内乱核 2.5 中引进一组新的 API 去处置惩罚吸收的数据帧,即 NAPI。以是,驱动有两种方法告诉内乱核:(1) 经由过程从前的函数netif_rx;(2)经由过程NAPI机造。该中止处置惩罚法式挪用 Network device的 netif_rx_schedule 函数,进进硬中止处置惩罚流程,再挪用 net_rx_action 函数。 该函数封闭中止,获得每一个 Network device 的 rx_ring 中的一切 package,终极 pacakage 从 rx_ring 中被删除,进进 netif _receive_skb 处置惩罚流程。 netif_receive_skb 是链路层吸收数据报的最初一站。它凭据注册正在齐局数组 ptype_all 战 ptype_base 里的收集层数据报范例,把数据报递交给差别的收集层和谈的吸收函数(INET域中次要是ip_rcv战arp_rcv)。该函数次要便是挪用第三层和谈的吸收函数处置惩罚该skb包,进进第三层收集层处置惩罚。
    6 q1 o- h8 s& c# K
进口函数是net_rx_action:

, m) d0 m# P" e% d+ l( I, B: d+ Y$ a/ K& Z

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
9 |, c5 S+ B" S- v
发明挪用napi_poll,本质上挪用napi_gro_receive函数:
. T3 C3 j" G! T) W/ }

* E( L8 p0 T: O3 G

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

. x0 ~0 u1 d$ D$ e1 P. [, v$ F  c
napi_gro_receive 会间接挪用 netif_receive_skb_core。而它会挪用__netif_receive_skb_one_core,将数据包交给上层 ip_rcv 举行处置惩罚。

9 a* G/ M1 n" ?& u, U0 u2 _* p( t$ N2 }- a2 A: j, {

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

/ S, F8 f2 z! r$ C  x' @
挪用竣事以后,经由过程硬中止告诉CPU,至此,挪用链竣事。gdb考证以下:

  l# o, ?# ]  Z. u8 @8 c; d% N) W) O: v* H) @. Z9 G

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
9 _8 V2 s) R  A' [
7 物理层流程
# I  B% t/ H1 u% \: j
  7.1 收收端

  v- u3 z, o' x- C

    ' k5 V) q4 l9 b# g物理层正在支到收收恳求以后,经由过程 DMA 将该主存中的数据拷贝至内乱部RAM(buffer)当中。正在数据拷贝中,同时参加切合以太网和谈的相干header,IFG、前导符战CRC。关于以太网收集,物理层收收接纳CSMA/CD,即正在收收过程当中侦听链路抵触。 一旦网卡完成报文收收,将发生中止告诉CPU,然后驱动层中的中止处置惩罚法式就能够删除生存的 skb 了。
    8 F$ P( {( V( n% {0 e, z
9 v3 y9 \' ?+ D

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

( o; ]2 g  L- a! x1 N6 F% {: X
  7.2 吸收端
) i% _( J/ X$ N/ b0 U" g9 j) }. g
    ; a. o5 G9 S+ D( r
    一个 package 抵达机械的物理收集适配器,当它吸收到数据帧时,便会触收一其中断,并将经由过程 DMA 传收到位于 linux kernel 内乱存中的 rx_ring。 网卡收回中止,告诉 CPU 有个 package 需求它处置惩罚。中止处置惩罚法式次要举行以下一些操纵,包罗分派 skb_buff 数据构造,并将吸收到的数据帧从收集适配器I/O端心拷贝到skb_buff 缓冲区中;从数据帧中提掏出一些疑息,并设置 skb_buff 响应的参数,那些参数将被上层的收集和谈利用,比方skb->protocol; 末端处置惩罚法式经由简朴处置惩罚后,收回一个硬中止(NET_RX_SOFTIRQ),告诉内乱核吸收到新的数据帧。 内乱核 2.5 中引进一组新的 API 去处置惩罚吸收的数据帧,即 NAPI。以是,驱动有两种方法告诉内乱核:(1) 经由过程从前的函数netif_rx;(2)经由过程NAPI机造。该中止处置惩罚法式挪用 Network device的 netif_rx_schedule 函数,进进硬中止处置惩罚流程,再挪用 net_rx_action 函数。 该函数封闭中止,获得每一个 Network device 的 rx_ring 中的一切 package,终极 pacakage 从 rx_ring 中被删除,进进 netif _receive_skb 处置惩罚流程。 netif_receive_skb 是链路层吸收数据报的最初一站。它凭据注册正在齐局数组 ptype_all 战 ptype_base 里的收集层数据报范例,把数据报递交给差别的收集层和谈的吸收函数(INET域中次要是ip_rcv战arp_rcv)。该函数次要便是挪用第三层和谈的吸收函数处置惩罚该skb包,进进第三层收集层处置惩罚。
    ) h" T! l. X( F5 \& k2 t
8 时序图展现战总结

+ a3 I& v' }4 T4 [; |* m
时序图以下:
6 u8 M  Z) J& Z' H0 H
. F- \/ D: [; X4 J2 C

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析

TCP/IP和谈栈正在Linux内乱核中的运转时序剖析
% z( l, a, q' b8 I* ?& X
本次尝试次要是经由过程剖析Linux内乱核源代码,一步步天经由过程gdb举行调试函数挪用链,终极清晰了tcp/ip和谈栈的挪用历程。由于工夫有限,部门细节能够会有毛病,期望读者多减斧正。

! c2 g. S, X8 i/ B: J" h, N, G4 Z+ ]! C
本文链接:lee哥的效劳器开辟|存眷整站劣化网 进修更多seo相干要领...
回复

使用道具 举报

0

主题

24

帖子

54

积分

注册会员

Rank: 2

积分
54
发表于 2021-6-19 13:18:52| 字数 1 | 显示全部楼层
回复

使用道具 举报

0

主题

23

帖子

54

积分

注册会员

Rank: 2

积分
54
发表于 2021-6-19 13:59:25| 字数 3 | 显示全部楼层
转发了
回复

使用道具 举报

0

主题

33

帖子

70

积分

注册会员

Rank: 2

积分
70
发表于 2021-6-19 14:51:54| 字数 3 | 显示全部楼层
转发了
回复

使用道具 举报

0

主题

27

帖子

57

积分

注册会员

Rank: 2

积分
57
发表于 2021-6-19 15:07:25| 字数 3 | 显示全部楼层
转发了
回复

使用道具 举报

0

主题

25

帖子

57

积分

注册会员

Rank: 2

积分
57
发表于 2021-6-19 15:12:52| 字数 3 | 显示全部楼层
转发了
回复

使用道具 举报

0

主题

28

帖子

60

积分

注册会员

Rank: 2

积分
60
发表于 2021-6-19 15:34:58| 字数 3 | 显示全部楼层
转发了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

  • 发布新帖

  • 在线客服

  • 微信

  • 客户端

  • 返回顶部

  • QQ|Archiver|手机版|小黑屋|整站优化 — 整站SEO优化,万维网 SEO 整站优化网 ( 粤ICP备19158344号 ) |

    GMT+8, 2021-7-31 06:48 , Processed in 0.148722 second(s), 33 queries , Gzip On, Redis On.

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表