Linux 网络设备驱动开发(二) —— Linux 网络栈剖析

一、协议简介

    虽然对于网络的正式介绍一般都参考了 OSI(Open Systems Interconnection)模型,但是本文对 Linux 中基本网络栈的介绍分为四层的 Internet 模型(如图 1 所示)。

图 1. 网络栈的 Internet 模型
网络栈的 Internet 模型


    这个栈的最底部是链路层。链路层是指提供对物理层访问的设备驱动程序,这可以是各种介质,例如串口链路或以太网设备。链路层上面是网络层,它负责将报文定向到目标位置。再上一层称为传输层,负责端到端的通信(例如,在一台主机内部)。尽管网络层负责管理主机之间的通信,但是传输层需要负责管理主机内部各端之间的通信。最后一层是应用层,它通常是一个语义层,能够理解要传输的数据。例如,超文本传输协议(HTTP)就负责传输服务器和客户机之间对 Web 内容的请求与响应。

   实际来说,网络栈的各个层次有一些更为人所熟知的名字。在链路层上,可以找到以太网,这是最常用的一种高速介质。更早的链路层协议包括一些串口协议,例如 SLIP(Serial Line Internet Protocol)、CSLIP(Compressed SLIP)和PPP(Point-to-Point Protocol)。最常见的网络层协议是 IP(Internet Protocol),但是网络层中还存在一些满足其他需求的协议,例如 ICMP(Internet Control Message Protocol)和ARP( Address Resolution Protocol)。在传输层上是 TCP(Transmission Control Protocol)和 UDP(User Datagram Protocol)。最后,应用层中包含很多大家都非常熟悉的协议,包括标准的 Web 协议 HTTP 和电子邮件协议 SMTP(Simple Mail Transfer Protocol)。


二、核心网络架构

     现在继续了解 Linux 网络栈的架构以及如何实现这种 Internet 模型。图 2 提供了 Linux 网络栈的高级视图。最上面是用户空间层,或称为应用层,其中定义了网络栈的用户。底部是物理设备,提供了对网络的连接能力(串口或诸如以太网之类的高速网络)。中间是内核空间,即网络子系统,也是本文介绍的重点。流经网络栈内部的是 socket 缓冲区(sk_buffs),它负责在源和汇点之间传递报文数据。您很快就将看到sk_buff 的结构。

图 2. Linux 高级网络栈架构

Linux 高级网络栈架构


     首先,让我们来快速浏览一下 Linux 网络子系统的核心元素,后续章节中会更详细进行介绍。顶部(请参阅图 2)是系统调用接口。它简单地为用户空间的应用程序提供了一种访问内核网络子系统的方法。位于其下面的是一个协议无关层,它提供了一种通用方法来使用底层传输层协议。然后是实际协议,在 Linux 中包括内嵌的协议 TCP、UDP,当然还有 IP。然后是另外一个协议无关层,提供了与各个设备驱动程序通信的通用接口,最下面是设备驱动程序本身。


三、系统调用接口

   系统调用接口可以从两个角度进行描述。用户发起网络调用时,通过系统调用接口进入内核的过程应该是多路的。最后调用 ./net/socket.c 中的sys_socketcall 结束该过程,然后进一步将调用分路发送到指定目标。系统调用接口的另一种描述是使用普通文件操作作为网络 I/O。例如,典型的读写操作可以在网络 socket 上执行(socket 使用一个文件描述符表示,与一个普通文件一样)。因此,尽管有很多操作是网络专用的(使用 socket 调用创建一个 socket,使用 connect 调用连接一个收信方,等等),但是也有一些标准的文件操作可以应用于网络对象,就像操作普通文件一样。最后,系统调用接口提供了在用户空间应用程序和内核之间转移控制的方法。


四、协议无关接口

     socket 层是一个协议无关接口,它提供了一组通用函数来支持各种不同协议。socket 层不但可以支持典型的 TCP 和 UDP 协议,而且还可以支持 IP、裸以太网和其他传输协议,例如 SCTP(Stream Control Transmission Protocol)。

     通过网络栈进行的通信都需要对 socket 进行操作。Linux 中的 socket 结构是 struct sock,这个结构是在 linux/include/net/sock.h 中定义的。这个巨大的结构中包含了特定 socket 所需要的所有状态信息,其中包括 socket 所使用的特定协议和在 socket 上可以执行的一些操作。

     网络子系统可以通过一个定义了自己功能的特殊结构来了解可用协议。每个协议都维护了一个名为 proto 的结构(可以在 linux/include/net/sock.h 中找到)。这个结构定义了可以在从 socket 层到传输层中执行特定的 socket 操作(例如,如何创建一个 socket,如何使用 socket 建立一个连接,如何关闭一个 socket 等等)。


五、网络协议

     网络协议这一节对一些可用的特定网络协议作出了定义(例如 TCP、UDP 等)。它们都是在 linux/net/ipv4/af_inet.c 文件中一个名为inet_init 的函数中进行初始化的(因为 TCP 和 UDP 都是 inet 簇协议的一部分)。 inet_init 函数使用 proto_register 函数来注册每个内嵌协议。这个函数是在 linux/net/core/sock.c 中定义的,除了可以将这个协议添加到活动协议列表中之外,如果需要,该函数还可以选择分配一到多个 slab 缓存。

    通过 linux/net/ipv4/ 目录中 udp.c 和 raw.c 文件中的 proto 接口,您可以了解各个协议是如何标识自己的。这些协议接口每个都按照类型和协议映射到 inetsw_array,该数组将内嵌协议与操作映射到一起。inetsw_array 结构及其关系如图 3 所示。最初,会调用 inet_init 中的inet_register_protosw 将这个数组中的每个协议都初始化为 inetsw。函数 inet_init 也会对各个 inet 模块进行初始化,例如 ARP、ICMP 和 IP 模块,以及 TCP 和 UDP 模块。

图 3. Internet 协议数组结构

Internet 协议数组结构

Socket 协议的相互关系

回想以下在创建 socket 时,需要指定类型和协议,例如my_sock = socket( AF_INET, SOCK_STREAM, 0 )AF_INET 表示一个 Internet 地址簇,它使用的是一个流 socket,定义为 SOCK_STREAM(如此处的inetsw_array 所示)。

       注意在 图 3 中,proto 结构定义了传输特有的方法,而 proto_ops 结构则定义了通用的 socket 方法。可以通过调用inet_register_protosw 将其他协议加入到 inetsw 协议中。例如,SCTP 就是通过调用 linux/net/sctp/protocol.c 中的 sctp_init 加入其中的。有关 SCTP 的更多信息,请参阅 参考资料 一节的内容。

      socket 中的数据移动是使用一个所谓的 socket 缓冲区(sk_buff)的核心结构实现的。sk_buff 中包含了报文数据,以及涉及协议栈中多个层次的状态数据。所发送或接收的每个报文都是使用一个 sk_buff 表示的。sk_buff 结构是在 linux/include/linux/skbuff.h 中定义的,如图 4 所示。

图 4. Socket 缓冲区及其与其他结构的关系

Socket 缓冲区及其与其他结构的关系


      如图所示,多个 sk_buff 可以针对某个给定连接链接在一起。每个 sk_buff 都在设备结构(net_device)中标识报文发送的目的地,或者接收报文的来源地。由于每个报文都是使用一个 sk_buff 表示的,因此报文头都可以通过一组指针(thiph 和 mac[用于 Media Access Control 或者 MAC 头])方便地进行定位。由于 sk_buff 是 socket 数据管理的中心,因此创建了很多支持函数来对它们进行管理。其中有些函数用于创建和销毁 sk_buff 结构,或对它进行克隆或排队管理。

     针对给定的 socket,Socket 缓冲区可以链接在一起,这样可以包含众多信息,包括到协议头的链接、时间戳(报文是何时发送或接收的),以及与这个报文相关的设备。


六、设备无关接口

      协议层下面是另外一个无关接口层,它将协议与具有很多各种不同功能的硬件设备连接在一起。这一层提供了一组通用函数供底层网络设备驱动程序使用,让它们可以对高层协议栈进行操作。

     首先,设备驱动程序可能会通过调用 register_netdevice 或 unregister_netdevice 在内核中进行注册或注销。调用者首先填写net_device 结构,然后传递这个结构进行注册。内核调用它的 init 函数(如果定义了这种函数),然后执行一组健全性检查,并创建一个sysfs 条目,然后将新设备添加到设备列表中(内核中的活动设备链表)。在 linux/include/linux/netdevice.h 中可以找到这个 net_device 结构。这些函数都是在 linux/net/core/dev.c 中实现的。

     要从协议层向设备中发送 sk_buff,就需要使用 dev_queue_xmit 函数。这个函数可以对 sk_buff 进行排队,从而由底层设备驱动程序进行最终传输(使用 sk_buff 中引用的 net_device 或 sk_buff->dev 所定义的网络设备)。dev 结构中包含了一个名为hard_start_xmit 的方法,其中保存有发起 sk_buff 传输所使用的驱动程序函数。

     报文的接收通常是使用 netif_rx 执行的。当底层设备驱动程序接收一个报文(包含在所分配的 sk_buff 中)时,就会通过调用 netif_rx将 sk_buff 上传至网络层。然后,这个函数通过 netif_rx_schedule 将 sk_buff 在上层协议队列中进行排队,供以后进行处理。可以在 linux/net/core/dev.c 中找到 dev_queue_xmit 和 netif_rx 函数。

    最近,内核中引入了一种新的应用程序编程接口(NAPI),该接口允许驱动程序与设备无关层(dev)进行交互。有些驱动程序使用的是 NAPI,但是大多数驱动程序仍然在使用老式的帧接收接口(比例大约是 6 比 1)。NAPI 在高负载的情况下可以产生更好的性能,它避免了为每个传入的帧都产生中断。


七、设备驱动程序

      网络栈底部是负责管理物理网络设备的设备驱动程序。例如,包串口使用的 SLIP 驱动程序以及以太网设备使用的以太网驱动程序都是这一层的设备。

     在进行初始化时,设备驱动程序会分配一个 net_device 结构,然后使用必须的程序对其进行初始化。这些程序中有一个是 dev->hard_start_xmit,它定义了上层应该如何对 sk_buff 排队进行传输。这个程序的参数为 sk_buff。这个函数的操作取决于底层硬件,但是通常 sk_buff 所描述的报文都会被移动到硬件环或队列中。就像是设备无关层中所描述的一样,对于 NAPI 兼容的网络驱动程序来说,帧的接收使用了 netif_rx 和 netif_receive_skb 接口。NAPI 驱动程序会对底层硬件的能力进行一些限制。

    设备驱动程序在 dev 结构中配置好自己的接口之后,调用 register_netdevice 便可以使用该配置。在 linux/drivers/net 中可以找出网络设备专用的驱动程序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/401876.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 网络设备驱动开发(三) —— 网络设备驱动基本原理和框架

一、协议栈层次对比 二、Linux网络子系统 Linux网络子系统的顶部是系统调用接口层。它为用户空间提供的应用程序提供了一种访问内核网络子系统的方法(socket)。位于其下面是一个协议无关层,它提供一种通用的方法来使用传输层协议。然后是具…

国产杀毒软件也开始支持虚拟化

今天不小心看到一个较老的“新”闻,国产安全软件厂商瑞星已经推出支持VMware vshield Endpoint技术的防病毒产品,也就是说如果客户今天使用的是VMware的虚拟化软件,上面运行的所有虚拟机就不需要安装传统的防病毒软件程序,而可以直…

VRP平台基本操作

一、显示系统信息 <Huawei>display version 图上所示可以知道VRP平台信息&#xff0c;运行的版本&#xff0c;运行的时间 二、修改和查看设备系统时间参数 1.查看时间 <Huawei>display clock 2.修改系统日期和时间 三、进入系统视图界面 <Huawei>system-view…

Android中获取应用程序(包)的信息-----PackageManager的使用(一)

本节内容是如何获取Android系统中应用程序的信息&#xff0c;主要包括packagename、label、icon、占用大小等。具体分为两个 部分&#xff0c;计划如下&#xff1a; 第一部分&#xff1a; 获取应用程序的packagename、label、icon等 &#xff1b; 第二部分&#xff1a; 获取应用…

VRP平台总体介绍及基础配置

前言 1、VRP软件系统基础 VRP系统在启动时需要加载“系统软件”和“配置文件”两部分&#xff0c;这与其它品牌网络交换机的操作系统是一样的。如果指定了下次启动的补丁文件&#xff0c;还需加载补丁文件。修改VRP系统启动的场景一般有以下几种&#xff1a; a-- 对交换机进行升…

小强的HTML5移动开发之路(43)——JqueryMobile页眉、工具栏和标签栏导航

一、页眉1、添加页眉和页脚<div data-role"header"><h1>第 1 页</h1></div><div data-role"footer"><h4>页面脚注</h4></div>默认的页眉在屏幕的顶部边缘显示&#xff0c;而且在在屏幕滚动时&#xff0c;…

交换机开发(二)—— 三层交换机报文转发过程

如图所示&#xff0c;假如主机A想访问主机B&#xff0c;首先主机A会将自己的IP地址和子网掩码做与操作,得出网路地址(如:Host-A的IP地址100.1.1.2与自身掩码255.255.255.0做与操作后,得到的网络号是100.1.1.0).然后判断目的IP地址(即Host-B的IP地址)与自己的网络地址是不是在同…

分布式搜索elasticsearch配置文件详解

2019独角兽企业重金招聘Python工程师标准>>> elasticsearch的config文件夹里面有两个配置文件&#xff1a;elasticsearch.yml和logging.yml&#xff0c;第一个是es的基本配置文件&#xff0c;第二个是日志配置文件&#xff0c;es也是使用log4j来记录日志的&#xff…

交换机开发(三)—— 深入分析三层网络交换机的原理和设计

引言传统路由器在网络中起到隔离网络、隔离广播、路由转发以及防火墙的作业&#xff0c;并且随着网络的不断发展&#xff0c;路由器的负荷也在迅速增长。其中一个重要原因是出于安全和管理方便等方面的考虑&#xff0c;VLAN(虚拟局域网)技术在网络中大量应用。VLAN技术可以逻辑…

XML 命名空间(XML Namespaces)

为什么80%的码农都做不了架构师&#xff1f;>>> XML 应用程序 XML CDATA XML 命名空间提供避免元素命名冲突的方法。 命名冲突 在 XML 中&#xff0c;元素名称是由开发者定义的&#xff0c;当两个不同的文档使用相同的元素名时&#xff0c;就会发生命名冲突。 这个…

Linux 下挂载新硬盘方法

Linux的硬盘识别: 一般使用”fdisk -l”命令可以列出系统中当前连接的硬盘 设备和分区信息.新硬盘没有分区信息,则只显示硬盘大小信息. 1.关闭服务器加上新硬盘 2.启动服务器&#xff0c;以root用户登录 3.查看硬盘信息 #fdisk -l [cpp] view plaincopy Disk /dev/sda: 42.9 GB…

C++ 学习基础篇(一)—— C++与C 的区别

编程的学习学无止境&#xff0c;只掌握一门语言是远远不够的&#xff0c;现在我们开始C的学习之路&#xff0c;下面先看下C 与C 的区别 一、C概述 1、发展历史 1980年&#xff0c;Bjarne Stroustrup博士开始着手创建一种模拟语言&#xff0c;能够具有面向对象的程序设计特色。在…

C++学习基础篇 —— 引用()的用法和应用

一、引用简介 引用就是某一变量&#xff08;目标&#xff09;的一个别名&#xff0c;对引用的操作与对变量直接操作完全一样。 引用的声明方法&#xff1a;类型标识符&引用名目标变量名&#xff1b; 【例1】&#xff1a; [cpp] view plaincopy int a; int &raa; //定义…

C++基础知识(二)—— 变量和数据类型

你可能觉得这个“Hellow World”程序用处不大。我们写了好几行代码&#xff0c;编译&#xff0c;然后执行生成的程序只是为了在屏幕上看到一句话。的确&#xff0c;我们直接在屏幕上打出这句话会更快。但是编程并不仅限于在屏幕上打出文字这么简单的工作。为了能够进一步写出可…

C++基础知识(四)—— 操作符/运算符

前面已经学习了变量和常量&#xff0c;我们可以开始对它们进行操作&#xff0c;这就要用到C的操作符。有些语言&#xff0c;很多操作符都是一些关键字&#xff0c; 比如add, equals等等。C的操作符主要是由符号组成的。这些符号不在字母表中&#xff0c;但是在所有键盘上都可以…

Java中如何克隆集合——ArrayList和HashSet深拷贝

2019独角兽企业重金招聘Python工程师标准>>> 编程人员经常误用各个集合类提供的拷贝构造函数作为克隆List&#xff0c;Set&#xff0c;ArrayList&#xff0c;HashSet或者其他集合实现的方法。需要记住的是&#xff0c;Java集合的拷贝构造函数只提供浅拷贝而不是深拷…

C++ 控制结构和函数(一) —— 控制结构

一个程序的语句往往并不仅限于线性顺序结构。在程序的执行过程中它可能被分成两支执行&#xff0c;可能重复某些语句&#xff0c;也可能根据一些判断结果而执行不同的语句。因此C 提供一些控制结构语句 (control structures) 来实现这些执行顺序。 为了介绍程序的执行顺序&…

C++ 控制结构和函数(二) —— 函数I(Functions I)

通过使用函数(functions)我们可以把我们的程序以更模块化的形式组织起来&#xff0c;从而利用C所能提供的所有结构化编程的潜力。 一个函数(function)是一个可以从程序其它地方调用执行的语句块。以下是它的格式&#xff1a; type name ( argument1, argument2, ...) statement…

条件概率的几何解释 由定义计算条件概率 由条件概率公式计算条件概率

A发生&#xff0c;则去掉圈A以外的区域&#xff0c;形成新的样本空间 &#xff08;如果是概率质量函数&#xff0c;则称为归一化&#xff09;然后在A发生的前提下&#xff0c;B的概率为 圈A与圈B的公共区域/圈A 这就是条件概率的几何解释~~~ 甲乙两人各抛一个骰子&#xff0c…

C++ 控制结构和函数(三)—— 函数II(Functions II)

参数按数值传递和按地址传递(Arguments passed by value and by reference) 到目前为止&#xff0c;我们看到的所有函数中&#xff0c;传递到函数中的参数全部是按数值传递的(by value)。也就是说&#xff0c;当我们调用一个带有参数的函数时&#xff0c;我们传递到函数中的是变…