Linux操作系统原理—内核网络协议栈

一口Linux 2021-09-24 11:50

推荐关注👇下方公众号学习更多Linux、驱动知识!



前言

本文主要记录 Linux 内核网络协议栈的运行原理

数据报文的封装与分用


封装:当应用程序用 TCP 协议传送数据时,数据首先进入内核网络协议栈中,然后逐一通过 TCP/IP 协议族的每层直到被当作一串比特流送入网络。对于每一层而言,对收到的数据都会封装相应的协议首部信息(有时还会增加尾部信息)。TCP 协议传给 IP 协议的数据单元称作 TCP 报文段,或简称 TCP 段(TCP segment)。IP 传给数据链路层的数据单元称作 IP 数据报(IP datagram),最后通过以太网传输的比特流称作帧(Frame)。


分用:当目的主机收到一个以太网数据帧时,数据就开始从内核网络协议栈中由底向上升,同时去掉各层协议加上的报文首部。每层协议都会检查报文首部中的协议标识,以确定接收数据的上层协议。这个过程称作分用。


Linux 内核网络协议栈

协议栈的全景图


协议栈的分层结构



逻辑抽象层级:

物理层:主要提供各种连接的物理设备,如各种网卡,串口卡等。

链路层:主要提供对物理层进行访问的各种接口卡的驱动程序,如网卡驱动等。

网路层:是负责将网络数据包传输到正确的位置,最重要的网络层协议是 IP 协议,此外还有如 ICMP,ARP,RARP 等协议。

传输层:为应用程序之间提供端到端连接,主要为 TCP 和 UDP 协议。

应用层:顾名思义,主要由应用程序提供,用来对传输数据进行语义解释的 “人机交互界面层”,比如 HTTP,SMTP,FTP 等协议。

协议栈实现层级:

硬件层(Physical device hardware):又称驱动程序层,提供连接硬件设备的接口。

设备无关层(Device agnostic interface):又称设备接口层,提供与具体设备无关的驱动程序抽象接口。这一层的目的主要是为了统一不同的接口卡的驱动程序与网络协议层的接口,它将各种不同的驱动程序的功能统一抽象为几个特殊的动作,如 open,close,init 等,这一层可以屏蔽底层不同的驱动程序。

网络协议层(Network protocols):对应 IP layer 和 Transport layer。毫无疑问,这是整个内核网络协议栈的核心。这一层主要实现了各种网络协议,最主要的当然是 IP,ICMP,ARP,RARP,TCP,UDP 等。

协议无关层(Protocol agnostic interface),又称协议接口层,本质就是 SOCKET 层。这一层的目的是屏蔽网络协议层中诸多类型的网络协议(主要是 TCP 与 UDP 协议,当然也包括 RAW IP, SCTP 等等),以便提供简单而同一的接口给上面的系统调用层调用。简单的说,不管我们应用层使用什么协议,都要通过系统调用接口来建立一个 SOCKET,这个 SOCKET 其实是一个巨大的 sock 结构体,它和下面的网络协议层联系起来,屏蔽了不同的网络协议,通过系统调用接口只把数据部分呈献给应用层。

BSD(Berkeley Software Distribution)socket:BSD Socket 层,提供统一的 SOCKET 操作接口,与 socket 结构体关系紧密。

INET(指一切支持 IP 协议的网络) socket:INET socket 层,调用 IP 层协议的统一接口,与 sock 结构体关系紧密。

系统调用接口层(System call interface),实质是一个面向用户空间(User Space)应用程序的接口调用库,向用户空间应用程序提供使用网络服务的接口。


协议栈的数据结构


msghdr:描述了从应用层传递下来的消息格式,包含有用户空间地址,消息标记等重要信息。

iovec:描述了用户空间地址的起始位置。

file:描述文件属性的结构体,与文件描述符一一对应。

file_operations:文件操作相关结构体,包括 read()、write()、open()、ioctl() 等。

socket:向应用层提供的 BSD socket 操作结构体,协议无关,主要作用为应用层提供统一的 Socket 操作。

sock:网络层 sock,定义与协议无关操作,是网络层的统一的结构,传输层在此基础上实现了 inet_sock。

sock_common:最小网络层表示结构体。

inet_sock:表示层结构体,在 sock 上做的扩展,用于在网络层之上表示 inet 协议族的的传输层公共结构体。

udp_sock:传输层 UDP 协议专用 sock 结构,在传输层 inet_sock 上扩展。

proto_ops:BSD socket 层到 inet_sock 层接口,主要用于操作 socket 结构。

proto:inet_sock 层到传输层操作的统一接口,主要用于操作 sock 结构。

net_proto_family:用于标识和注册协议族,常见的协议族有 IPv4、IPv6。

softnet_data:内核为每个 CPU 都分配一个这样的 softnet_data 数据空间。每个 CPU 都有一个这样的队列,用于接收数据包。

sk_buff:描述一个帧结构的属性,包含 socket、到达时间、到达设备、各层首部大小、下一站路由入口、帧长度、校验和等等。

sk_buff_head:数据包队列结构。

net_device:这个巨大的结构体描述一个网络设备的所有属性,数据等信息。

inet_protosw:向 IP 层注册 socket 层的调用操作接口。

inetsw_array:socket 层调用 IP 层操作接口都在这个数组中注册。

sock_type:socket 类型。

IPPROTO:传输层协议类型 ID。

net_protocol:用于传输层协议向 IP 层注册收包的接口。

packet_type:以太网数据帧的结构,包括了以太网帧类型、处理方法等。

rtable:路由表结构,描述一个路由表的完整形态。

rt_hash_bucket:路由表缓存。

dst_entry:包的去向接口,描述了包的去留,下一跳等路由关键信息。

napi_struct:NAPI 调度的结构。NAPI 是 Linux 上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收服务,然后采用 poll 的方法来轮询数据。NAPI 技术适用于高速率的短长度数据包的处理。

网络协议栈初始化流程

这需要从内核启动流程说起。当内核完成自解压过程后进入内核启动流程,这一过程先在 arch/mips/kernel/head.S 程序中,这个程序负责数据区(BBS)、中断描述表(IDT)、段描述表(GDT)、页表和寄存器的初始化,程序中定义了内核的入口函数 kernel_entry()、kernel_entry() 函数是体系结构相关的汇编代码,它首先初始化内核堆栈段为创建系统中的第一过程进行准备,接着用一段循环将内核映像的未初始化的数据段清零,最后跳到 start_kernel() 函数中初始化硬件相关的代码,完成 Linux Kernel 环境的建立。

start_kenrel() 定义在 init/main.c 中,真正的内核初始化过程就是从这里才开始。函数 start_kerenl() 将会调用一系列的初始化函数,如:平台初始化,内存初始化,陷阱初始化,中断初始化,进程调度初始化,缓冲区初始化,完成内核本身的各方面设置,目的是最终建立起基本完整的 Linux 内核环境。

start_kernel() 中主要函数及调用关系如下:


start_kernel() 的过程中会执行 socket_init() 来完成协议栈的初始化,实现如下:

void sock_init(void)//网络栈初始化
{
int i;

printk("Swansea University Computer Society NET3.019\n");

/*
* Initialize all address (protocol) families.
*/


for (i = 0; i < NPROTO; ++i) pops[i] = NULL;

/*
* Initialize the protocols module.
*/


proto_init();

#ifdef CONFIG_NET
/*
* Initialize the DEV module.
*/


dev_init();

/*
* And the bottom half handler
*/


bh_base[NET_BH].routine= net_bh;
enable_bh(NET_BH);
#endif
}


sock_init() 包含了内核协议栈的初始化工作:

sock_init:Initialize sk_buff SLAB cache,注册 SOCKET 文件系统。

net_inuse_init:为每个 CPU 分配缓存。

proto_init:在 /proc/net 域下建立 protocols 文件,注册相关文件操作函数。

net_dev_init:建立 netdevice 在 /proc/sys 相关的数据结构,并且开启网卡收发中断;为每个 CPU 初始化一个数据包接收队列(softnet_data),包接收的回调;注册本地回环操作,注册默认网络设备操作。

inet_init:注册 INET 协议族的 SOCKET 创建方法,注册 TCP、UDP、ICMP、IGMP 接口基本的收包方法。为 IPv4 协议族创建 proc 文件。此函数为协议栈主要的注册函数:

rc = proto_register(&udp_prot, 1);:注册 INET 层 UDP 协议,为其分配快速缓存。

(void)sock_register(&inet_family_ops);:向 static const struct net_proto_family *net_families[NPROTO] 结构体注册 INET 协议族的操作集合(主要是 INET socket 的创建操作)。

inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0;:向 externconst struct net_protocol *inet_protos[MAX_INET_PROTOS] 结构体注册传输层 UDP 的操作集合。

static struct list_head inetsw[SOCK_MAX]; for (r = &inetsw[0]; r < &inetsw[SOCK_MAX];++r) INIT_LIST_HEAD(r);:初始化 SOCKET 类型数组,其中保存了这是个链表数组,每个元素是一个链表,连接使用同种 SOCKET 类型的协议和操作集合。

for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q):

inet_register_protosw(q);:向 sock 注册协议的的调用操作集合。

arp_init();:启动 ARP 协议支持。

ip_init();:启动 IP 协议支持。

udp_init();:启动 UDP 协议支持。

dev_add_pack(&ip_packet_type);:向 ptype_base[PTYPE_HASH_SIZE]; 注册 IP 协议的操作集合。

socket.c 提供的系统调用接口。



协议栈初始化完成后再执行 dev_init(),继续设备的初始化。

Socket 创建流程


协议栈收包流程概述

硬件层与设备无关层:硬件监听物理介质,进行数据的接收,当接收的数据填满了缓冲区,硬件就会产生中断,中断产生后,系统会转向中断服务子程序。在中断服务子程序中,数据会从硬件的缓冲区复制到内核的空间缓冲区,并包装成一个数据结构(sk_buff),然后调用对驱动层的接口函数 netif_rx() 将数据包发送给设备无关层。该函数的实现在 net/inet/dev.c 中,采用了 bootom half 技术,该技术的原理是将中断处理程序人为的分为两部分,上半部分是实时性要求较高的任务,后半部分可以稍后完成,这样就可以节省中断程序的处理时间,整体提高了系统的性能。

NOTE:在整个协议栈实现中 dev.c 文件的作用重大,它衔接了其下的硬件层和其上的网络协议层,可以称它为链路层模块,或者设备无关层的实现。

网络协议层:就以 IP 数据报为例,从设备无关层向网络协议层传递时会调用 ip_rcv()。该函数会根据 IP 首部中使用的传输层协议来调用相应协议的处理函数。UDP 对应 udp_rcv()、TCP 对应 tcp_rcv()、ICMP 对应 icmp_rcv()、IGMP 对应 igmp_rcv()。以 tcp_rcv() 为例,所有使用 TCP 协议的套接字对应的 sock 结构体都被挂入 tcp_prot 全局变量表示的 proto 结构之 sock_array 数组中,采用以本地端口号为索引的插入方式。所以,当 tcp_rcv() 接收到一个数据包,在完成必要的检查和处理后,其将以 TCP 协议首部中目的端口号为索引,在 tcp_prot 对应的 sock 结构体之 sock_array 数组中得到正确的 sock 结构体队列,再辅之以其他条件遍历该队列进行对应 sock 结构体的查询,在得到匹配的 sock 结构体后,将数据包挂入该 sock 结构体中的缓存队列中(由 sock 结构体中的 receive_queue 字段指向),从而完成数据包的最终接收。

NOTE:虽然这里的 ICMP、IGMP 通常被划分为网络层协议,但是实际上他们都封装在 IP 协议里面,作为传输层对待。

协议无关层和系统调用接口层:当用户需要接收数据时,首先根据文件描述符 inode 得到 socket 结构体和 sock 结构体,然后从 sock 结构体中指向的队列 recieve_queue 中读取数据包,将数据包 copy 到用户空间缓冲区。数据就完整的从硬件中传输到用户空间。这样也完成了一次完整的从下到上的传输。

协议栈发包流程概述

1、应用层可以通过系统调用接口层或文件操作来调用内核函数,BSD socket 层的 sock_write() 会调用 INET socket 层的 inet_wirte()。INET socket 层会调用具体传输层协议的 write 函数,该函数是通过调用本层的 inet_send() 来实现的,inet_send() 的 UDP 协议对应的函数为 udp_write()。

2、在传输层 udp_write() 调用本层的 udp_sendto() 完成功能。udp_sendto() 完成 sk_buff 结构体相应的设置和报头的填写后会调用 udp_send() 来发送数据。而在 udp_send() 中,最后会调用 ip_queue_xmit() 将数据包下放的网络层。

3、在网络层,函数 ip_queue_xmit() 的功能是将数据包进行一系列复杂的操作,比如是检查数据包是否需要分片,是否是多播等一系列检查,最后调用 dev_queue_xmit() 发送数据。

4、在链路层中,函数调用会调用具体设备提供的发送函数来发送数据包,e.g. dev->hard_start_xmit(skb, dev);。具体设备的发送函数在协议栈初始化的时候已经设置了。这里以 8390 网卡为例来说明驱动层的工作原理,在 net/drivers/8390.c 中函数 ethdev_init() 的设置如下:

/* Initialize the rest of the 8390 device structure. */  
int ethdev_init(struct device *dev)
{
if (ei_debug > 1)
printk(version);

if (dev->priv == NULL) { //申请私有空间
struct ei_device *ei_local; //8390 网卡设备的结构体

dev->priv = kmalloc(sizeof(struct ei_device), GFP_KERNEL); //申请内核内存空间
memset(dev->priv, 0, sizeof(struct ei_device));
ei_local = (struct ei_device *)dev->priv;
#ifndef NO_PINGPONG
ei_local->pingpong = 1;
#endif
}

/* The open call may be overridden by the card-specific code. */
if (dev->open == NULL)
dev->open = &ei_open; // 设备的打开函数
/* We should have a dev->stop entry also. */
dev->hard_start_xmit = &ei_start_xmit; // 设备的发送函数,定义在 8390.c 中
dev->get_stats = get_stats;
#ifdef HAVE_MULTICAST
dev->set_multicast_list = &set_multicast_list;
#endif

ether_setup(dev);

return 0;
}

UDP 的收发包流程总览


内核中断收包流程


UDP 收包流程


UDP 发包流程





- END -


关注,回复【1024】海量Linux资料赠送

 精彩文章合集

linux入门

C语言
Linux驱动
ARM
计算机网络
粉丝问答
所有原创
点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看
一口Linux 写点代码,写点人生!
评论
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 68浏览
  • TOF多区传感器: ND06   ND06是一款微型多区高集成度ToF测距传感器,其支持24个区域(6 x 4)同步测距,测距范围远达5m,具有测距范围广、精度高、测距稳定等特点。适用于投影仪的无感自动对焦和梯形校正、AIoT、手势识别、智能面板和智能灯具等多种场景。                 如果用ND06进行手势识别,只需要经过三个步骤: 第一步&
    esad0 2024-12-04 11:20 58浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 119浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 116浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 111浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 102浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 73浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 98浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 87浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 120浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 103浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 125浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦