精华内容
下载资源
问答
  • Linux 内核系统架构

    千次阅读 多人点赞 2019-09-17 02:55:13
    描述Linux内核的文章已经有上亿字了但是对于初学者,还是应该多学习多看,毕竟上亿字不能一下子就明白的。即使看了所有的Linux 内核文章,估计也还不是很明白,这时候,还...
        

    描述Linux内核的文章已经有上亿字了

    但是对于初学者,还是应该多学习多看,毕竟上亿字不能一下子就明白的。

    即使看了所有的Linux 内核文章,估计也还不是很明白,这时候,还是需要fucking the code.


    28年前(1991年8月26日)Linus公开Linux的代码,开启了一个伟大的时代。这篇文章从进程调度,内存管理,设备驱动,文件系统,网络等方面讲解Linux内核系统架构。Linux的系统架构是一个经典的设计,它优秀的分层和模块化,融合了数量繁多的设备和不同的物理架构,让世界各地的内核开发者能够高效并行工作。先来看看Linus在多年前公开Linux的邮件。

    "Hello everybody out there using minix - I’m doing a (free) operating system (just a hobby, won’t be big and professional like gnu) for 386(486) AT clones. This has been brewing since april, and is starting to get ready. I’d like any feedback on things people like/dislike in minix, as my OS resembles it somewhat (same physical layout of the file-system (due to practical reasons) among other things).

    I’ve currently ported bash(1.08) and gcc(1.40), and things seem to work. This implies that I’ll get something practical within a few months, and I’d like to know what features most people would want. Any suggestions are welcome, but I won’t promise I’ll implement them :-)

    Linus (torv...@kruuna.helsinki[1].fi)"

    事实上,从那一天开始,Linux便是博采众长,融合了非常多的优秀设计。在了解操作系统的时候,我们至少需要知道:

    1.操作系统是如何管理各种资源的?

    2.软硬件如何协同工作?3.如何通过抽象化屏蔽差异4.软硬件如何分工?

    这篇文章通过对内核主要模块的介绍,希望能为大家寻找这些问题的答案起一个抛砖引玉的作用。实际上,建议每一个希望成为技术专家的人都读一遍Linux的源代码。

    先来看看Linux内核一个高阶架构图:

    640?wx_fmt=png

    Linux系统架构图

    架构非常清晰,从硬件层,硬件抽象层,内核基础模块(进程调度,内存管理,网络协议栈等)到应用层,这个基本上也是各类软硬件结合的系统架构的基础设计,例如物联网系统(从单片机,MCU等小型嵌入式系统,到智能家居,智慧社区甚至智慧城市)在接入端设备的可参考架构模型。

    Linux最初是运行在PC机上的,使用的x86架构处理器相对来说比较强大,各类指令和模式也比较齐全。例如我们看到的用户态和内核态,在一般的小型嵌入式处理器上是没有的,它的好处是通过将代码和数据段(segment)给予不同的权限,保护内核态的代码和数据(包括硬件资源)必须通过类似系统调用(SysCall)的方式才能访问,确保内核的稳定。

    想象一下,如果需要你写一个操作系统,有哪些因素需要考虑?

    进程管理:如何在多任务系统中按照调度算法分配CPU的时间片。

    内存管理:如何实现虚拟内存和物理内存的映射,分配和回收内存。

    文件系统:如何将硬盘的扇区组织成文件系统,实现文件的读写等操作。

    设备管理:如何寻址,访问,读,写设备配置信息和数据。

    这些概念是操作系统的核心概念,由于篇幅原因,本文章主要从高阶的角度来讲,更多细节不在本文覆盖。

    进程管理

    进程在不同的操作系统中有些称为process,有些称为task。操作系统中进程数据结构包含了很多元素,往往用链表连接。

    进程相关的内容主要包括:虚拟地址空间,优先级,生命周期(阻塞,就绪,运行等),占有的资源(例如信号量,文件等)。

    CPU在每个系统滴答(Tick)中断产生的时候检查就绪队列里面的进程(遍历链表中的进程结构体),如有符合调度算法的新进程需要切换,保存当前运行的进程的信息(包括栈信息等)后挂起当前进程,选择新的进程运行,这就是进程调度。

    进程的优先级差异是CPU调度的基本依据,调度的终极目标是让高优先级的活动能够即时得到CPU的计算资源(即时响应),低优先级的任务也能公平分配到CPU资源。因为需要保存进程运行的上下文(process context)等,进程的切换本身是有成本的,调度算法在进程切换频率上也需要考虑效率。

    在早期的Linux操作系统中,主要采用的是时间片轮转算法(Round-Robin),内核在就绪的进程队列中选择高优先级的进程运行,每次运行相等的时间。该算法简单直观,但仍然会导致某些低优先级的进程长时间无法得到调度。为了提高调度的公平性,在Linux 2.6.23之后,引入了称为完全公平调度器CFS(Completely Fair Scheduler)。

    CPU在任何时间点只能运行一个程序,用户在使用优酷APP看视频时,同时在微信中打字聊天,优酷和微信是两个不同的程序,为什么看起来像是在同时运行?CFS的目标就是让所有的程序看起来都是以相同的速度在多个并行的CPU上运行,即nr_running 个运行的进程,每个进程以1/nr_running的速度并发执行,例如如有2个可运行的任务,那么每个以50%的CPU物理能力并发执行。

    CFS引入了"虚拟运行时间"的概念,虚拟运行时间用p->se.vruntime (nanosec-unit) 表示,通过它记录和度量任务应该获得的"CPU时间"。在理想的调度情况下,任何时候所有的任务都应该有相同的p->se.vruntime值(上面提到的以相同的速度运行)。因为每个任务都是并发执行的,没有任务会超过理想状态下应该占有的CPU时间。CFS选择需要运行的任务的逻辑基于p->se.vruntime值,非常简单:它总是挑选p->se.vruntime值最小的任务运行(最少被调度到的任务)。

    CFS使用了基于时间排序的红黑树来为将来任务的执行排时间线。所有的任务按p->se.vruntime关键字排序。CFS从树中选择最左边的任务执行。随着系统运行,执行过的任务会被放到树的右边,逐步地地让每个任务都有机会成为最左边的任务,从而在一个可确定的时间内获得CPU资源。

    总结来说,CFS首先运行一个任务,当任务切换(或者Tick中断发生的时候)时,该任务使用的CPU时间会加到p->se.vruntime里,当p->se.vruntime的值逐渐增大到别的任务变成了红黑树最左边的任务时(同时在该任务和最左边任务间增加一个小的粒度距离,防止过度切换任务,影响性能),最左边的任务被选中执行,当前的任务被抢占。640?wx_fmt=png

    CFS红黑树

    一般来说,调度器处理单个任务,且尽可能为每个任务提供公平的CPU时间。某些时候,可能需要将任务分组,并为每个组提供公平的CPU时间。例如,系统可以为每个用户分配平均的CPU时间后,再为每个用户的每个任务分配平均的CPU时间。

    内存管理

    内存本身是一个外部存储设备,系统需要对内存区域寻址,找到对应的内存单元(memory cell),读写其中的数据。

    内存区域通过指针寻址,CPU的字节长度(32bit机器,64bit机器)决定了最大的可寻址地址空间。在32位机器上最大的寻址空间是4GBtyes。在64位机器上理论上有2^64Bytes

    最大的地址空间和实际系统有多少物理内存无关,所以称为虚拟地址空间。对系统中所有的进程来说,看起来每个进程都独立占有这个地址空间,且它无法感知其它进程的内存空间。事实上操作系统让应用程序无需关注其它应用程序,看起来每个任务都是这个电脑上运行的唯一进程。

    Linux将虚拟地址空间分为内核空间和用户空间。每个用户进程的虚拟空间范围从0到TASK_SIZE。从TASK_SIZE到2^32或2^64的区域保留给内核,不能被用户进程访问。TASK_SIZE可以配置,Linux系统默认配置3:1,应用程序使用3GB的空间,内核使用1GB的空间,这个划分并不依赖实际RAM的大小。在64位机器上,虚拟地址空间的范围可以非常大,但实际上只使用其中42位或47位(2^42 或 2^47)。640?wx_fmt=png

    虚拟地址空间

    绝大多数情况下,虚拟地址空间比实际系统可用的物理内存(RAM)大,内核和CPU必须考虑如何将实际可用的物理内存映射到虚拟地址空间。

    一个方法是通过页表(Page Table)将虚拟地址映射到物理地址。虚拟地址与进程使用的用户&内核地址相关,物理地址用来寻址实际使用的RAM。

    如下图所示,进程A和B的虚拟地址空间被分为大小相等的部分,称为页(page)。物理内存同样被分割为大小相等的页(page frame)。640?wx_fmt=png

    虚拟和物理地址空间映射

    进程A第1个内存页映射到物理内存(RAM)的第4页;进程B第1个内存页映射到物理内存第5页。进程A第5个内存页和进程B第1个内存页都映射到物理内存的第5页(内核可决定哪些内存空间被不同进程共享)。

    如图所示,并不是每个虚拟地址空间的页都与某个page frame关联,该页可能并未使用或者数据还没有被加载到物理内存(暂时不需要),也可能因为物理内存页被置换到了硬盘上,后续实际再需要的时候再被置换回内存。

    页表(page table)将虚拟地址空间映射到物理地址空间。最简单的做法是用一个数组将虚拟页和物理页一一对应,但是这样做可能需要消耗整个RAM本身来保存这个页表,假设每个页大小为4KB,虚拟地址空间大小为4GB,需要一个1百万个元素的数组来保存页表。

    因为虚拟地址空间的绝大多数区域实际并没有使用,这些页实际并没有和page frame关联,引入多级页表(multilevel paging)能极大降低页表使用的内存,提高查询效率。关于多级也表的细节描述可以参考xxx。

    内存映射(memory mapping)是一个重要的抽象方法,被运用在内核和用户应用程序等多个地方。映射是将来自某个数据源的数据(也可以是某个设备的I/O端口等)转移到某个进程的虚拟内存空间。对映射的地址空间的操作可以使用处理普通内存的方法(对地址内容直接进行读写)。任何对内存的改动会自动转移到原数据源,例如将某个文件的内容映射到内存中,只需要通过读该内存来获取文件的内容,通过将改动写到该内存来修改文件的内容,内核确保任何改动都会自动体现到文件里。

    另外,在内核中,实现设备驱动时,外设(外部设备)的输入和输出区域可以被映射到虚拟地址空间,读写这些空间会被系统重定向到设备,从而对设备进行操作,极大地简化了驱动的实现。

    内核必须跟踪哪些物理页已经被分配了,哪些还是空闲的,避免两个进程使用RAM中的同一个区域。内存的分配和释放是非常频繁的任务,内核必须确保完成的速度尽量快,内核只能分配整个page frame,它将内存分为更小的部分的任务交给了用户空间,用户空间的程序库可以将从内核收到的page frame分成更小的区域后分配给进程。

    虚拟文件系统

    Unix系统是建立在一些有见地的理念上的,一个非常重要的隐喻是:

    Everything is a file.

    即系统几乎所有的资源都可以看成是文件。为了支持不同的本地文件系统,内核在用户进程和文件系统实现间包含了一层虚拟文件系统(Virtual File System)。大多数的内核提供的函数都能通过VFS(Virtual File System)定义的文件接口访问。例如内核子系统:字符和块设备,管道,网络Socket,交互输入输出终端等。

    另外用于操作字符和块设备的设备文件是在/dev目录下的真实文件,当读写操作执行的时候,其的内容会被对应的设备驱动动态创建。640?wx_fmt=png

    VFS系统

    在虚拟文件系统中,inode用来表示文件和文件目录(对于系统来说,目录是一种特殊的文件)。inode的元素包含两类:1. Metadata用于描述文件的状态,例如读写权限。2. 用于保存文件内容的数据段。

    每个inode都有一个特别的号码用于唯一识别,文件名和inode的关联建立在该编号基础上。以内核查找/usr/bin/emacs为例,讲解inodes如何组成文件系统的目录结构。从根inode开始查找(即根目录‘/’),该目录使用一个inode表示,inode的数据段没有普通的数据,只包含了根目录存的一些文件/目录项,这些项可以表示文件或其它目录,每项包含两个部分:1. 下一个数据项所在的inode编号 2. 文件或目录名

    首先扫描根inode的数据区域直到找到一个名为‘usr’的项,查找子目录usr的inode。通过‘usr’ inode编号找到关联的inode。重复以上步骤,查找名为‘bin’的数据项,然后在其数据项的‘bin’对应的inode中搜索名字‘emacs’的数据项,最后返回的inode表示一个文件而不是一个目录。最后一个inode的文件内容不同于之前,前三个每个都表示了一个目录,包含了它的子目录和文件清单,和emacs文件关联的inode在它的数据段保存了文件的实际内容。

    尽管在VFS查找某个文件的步骤和上面的描述一样,但细节上还是有些差别。例如因为频繁打开文件是一个很慢的操作,引入缓存加速查找。640?wx_fmt=png

    通过inode机制查找某个文件

    设备驱动

    与外设通信往往指的是输入(input)和输出(output)操作,简称I/O。实现外设的I/O内核必须处理三个任务:第一,必须针对不同的设备类型采用不同的方法来寻址硬件。第二,内核必须为用户应用程序和系统工具提供操作不同设备的方法,且需要使用一个统一的机制来确保尽量有限的编程工作,和保证即使硬件方法不同应用程序也能互相交互。第三,用户空间需要知道在内核中有哪些设备。

    与外设通信的层级关系如下:

    640?wx_fmt=png

    设备通信层级图

    外部设备大多通过总线与CPU连接,系统往往不止一个总线,而是总线的集合。在很多PC设计中包含两个通过一个bridge相连的PCI总线。某些总线例如USB不能当作主总线使用,需要通过一个系统总线将数据传递给处理器。下图显示不同的总线是如何连接到系统的。640?wx_fmt=png

    系统总线拓扑图

    系统与外设交互主要有以下方式:

    I/O端口:使用I/O端口通信的情况下,内核通过一个I/O控制器发送数据,每个接收设备有唯一的端口号,且将数据转发给系统附着的硬件。有一个由处理器管理的单独的虚拟地址空间用来管理所有的I/O地址。

    I/O地址空间并不总是和普通的系统内存关联,考虑到端口能够映射到内存中,这往往不好理解。

    端口有不同的类型。一些是只读的,一些是只写的,一般情况下它们是可以双向操作的,数据能够在处理器和外设间双向交换。

    在IA-32架构体系中,端口的地址空间包含了2^16个不同的8位地址,这些地址可以通过从0x0到0xFFFFH间的数唯一识别。每个端口都有一个设备分配给它,或者空闲没有使用,多个外设不能共享一个端口。很多情况下,交换数据使用8位是不够用的,基于这个原因,可以将两个连续的8位端口绑定为一个16位的端口。两个连续的16位端口能够被当作一个32位的端口,处理器可以通过组装语句来做输入输出操作。

    不同处理器类型在实现操作端口时有所不同,内核必须提供一个合适的抽象层,例如outb(写一个字节),outw(写一个字)和inb(读一个字节)这些命令可以用来操作端口。

    I/O内存映射:必须能够像访问RAM内存一样寻址许多设备。因此处理器提供了将外设对应的I/O端口映射到内存中,这样就能像操作普通内存一样操作设备了。例如显卡使用这样的机制,PCI也往往通过映射的I/O地址寻址。

    为了实现内存映射,I/O端口必须首先被映射到普通系统内存中(使用处理器特有的函数)。因为平台间的实现方式差异比较大,所以内核提供了一个抽象层来映射和去映射I/O区域。

    除了如何访问外设,什么时候系统会知道是否外设有数据可以访问?主要通过两种方式:轮询和中断

    轮询周期性地访问查询设备是否有准备好的数据,如果有,便获取数据。这种方法需要处理器在设备没有数据的情况下也不断去访问设备,浪费了CPU时间片。

    另一种方式是中断,它的理念是外设把某件事情做完了后,主动通知CPU,中断的优先级最高,会中断CPU的当前进程运行。每个CPU都提供了中断线(可被不同的设备共享),每个中断由唯一的中断号识别,内核为每个使用的中断提供一个服务方法(ISR,Interrupt Service Routine,即中断发生后,CPU调用的处理函数),中断本身也可以设置优先级。

    中断会挂起普通的系统工作。当有数据已准备好可以给内核或者间接被一个应用程序使用的时候,外设出发一个中断。使用中断确保系统只有在外设需要处理器介入的时候才会通知处理器,有效提高了效率。

    通过总线控制设备:不是所有的设备都是直接通过I/O语句寻址操作的,很多情况下是通过某个总线系统。

    不是所有的设备类型都能直接挂接在所有的总线系统上,例如硬盘挂到SCSI接口上,但显卡不可以(显卡可以挂到PCI总线上)。硬盘必须通过IDE间接挂到PCI总线上。

    总线类型可分为系统总线和扩展总线。硬件上的实现差别对内核来说并不重要,只有总线和它附着的外设如何被寻址才相关。对于系统总线来说,例如PCI总线,I/O语句和内存映射用来与总线通信,也用于和它附着的设备通信。内核还提供了一些命令供设备驱动来调用总线函数,例如访问可用的设备列表,使用统一的格式读写配置信息。

    扩展总线例如USB,SCSI通过清晰定义的总线协议与附着的设备来交换数据和命令。内核通过I/O语句或内存映射来与总线通信,通过平台无关的函数来使总线与附着的设备通信。

    与总线附着的设备通信不一定需要通过在内核空间的驱动进行,在某些情况下也可以通过用户空间实现。一个主要的例子是SCSI Writer,通过cdrecord工具来寻址。这个工具产生所需要的SCSI命令,在内核的帮助下通过SCSI总线将命令发送到对应的设备,处理和回复设备产生或返回的信息。

    块设备(block)和字符设备(character)在3个方面显著不同:

    块设备中的数据能够在任何点操作,而字符设备不能也没这个要求。

    块设备数据传输的时候总是使用固定大小的块。即使只请求一个字节的情况下,设备驱动也总是从设备获取一个完整的块。相反,字符设备能够返回单个字节。

    读写块设备会使用缓存。读操作方面,数据缓存在内存中,能够在需要的时候重新访问。写操作方面,也会被缓存,延时写入设备。使用缓存对于字符设备(例如键盘)来说不合理,每个读请求都必须被可靠地交互到设备。

    块和扇区的概念:块是一个指定大小的字节序列,用于保存在内核和设备间传输的数据,块的大小可以被设置。扇区是固定大小的,能被设备传输的最小的数据量。块是一段连续的扇区,块大小是扇区的整数倍。

    网络

    Linux的网络子系统为互联网的发展提供了坚实的基础。网络模型基于ISO的OSI模型,如下图右半部分。但在具体应用中,往往会把相应层级结合以简化模型,下图左半部分为Linux运用的TCP/IP参考模型。(由于介绍Linux网络部分的资料比较多,在本文中只对大的层级简单介绍,不展开说明。)

    640?wx_fmt=png

    网络模型

    Host-to-host层(Physical Layer和Data link layer,即物理层和数据链路层)负责将数据从一个计算机传输到另一台计算机。这一层处理物理传输介质的电气和编解码属性,也将数据流拆分成固定大小的数据帧用于传输。如多个电脑共享一个传输路线,网络适配器(网卡等)必须有一个唯一的ID(即MAC地址)来区分。从内核的角度,这一层是通过网卡的设备驱动实现的。

    OSI模型的网络层在TCP/IP模型中称为网络层,网络层使网络中的计算机之间能交换数据,而这些计算机不一定是直接相连的。

    如下图,A和B之间物理上并没有直接相连,所以也没有直接的数据交换。网络层的任务是为网络中各机器之间通信找到路由。640?wx_fmt=png

    网络连接的电脑

    网络层也负责将要传输的包分成指定的大小,因为包在传输路径上每个电脑支持的最大的数据包大小可能不一样,在传输时,数据流被分割成不同的包,在接收端再被组合。

    网络层为网络中的电脑分配了唯一的网络地址以便他们能互相通信(不同于硬件的MAC地址,因为网络往往由子网络组成)。在互联网中,网络层由IP网络组成,有V4和V6版本。

    传输层的任务是规范在两个连接的电脑上运行的应用程序之间的数据传输。例如两台电脑上的客户端和服务端程序,包括TCP或UDP连接,通过端口号来识别通信的应用程序。例如端口号80用于web server,浏览器的客户端必须将请求发送到这个端口来获取需要的数据。而客户端也需要有一个唯一的端口号以便web server能将回复发送给它。

    这一层还负责为数据的传输提供一个可靠的连接(TCP情况下)。

    TCP/IP模型中的应用层在OSI模型中包含(session层,展现层,应用层)。当通信连接在两个应用之间建立起来后,这一层负责实际内容的传输。例如web server与它的客户端传输时的协议和数据,不同与mail server与它的客户端之间。

    大多数的网络协议在RFC(Request for Comments)中定义。

    网络实现分层模型:内核对网络层的实现类似TCP/IP参考模型。它是通过C代码实现的,每个层只能和它的上下层通信,这样的好处是可以将不同的协议和传输机制结合。如下图所示:640?wx_fmt=png

    网络实现分层图

    本文先介绍到这,对技术感兴趣的朋友可以关注 "从零开始学架构",后续也会继续推出对各类架构设计的介绍,希望和大家多多交流,也欢迎大家留言。(The End)

    参考资料:

    《Professional Linux Kernel Architecture》

    《Understanding Linux Kernel》

    《Architecture of the Linux Kernel》

    References

    [1] torv...@kruuna.helsinki: mailto:torv...@kruuna.helsinki

    扫码或长按关注

    回复「 加群 」进入技术群聊

    展开全文
  • Linux无线网络架构

    千次阅读 2016-09-08 15:55:25
    Android中无线网络的...要了解linux的无线网络,首先要了解linux网络架构,接着介绍无线网络架构,然后分析网络数据包的收、发流程。 1 Linux网络架构首先看一下linux网络架构系统调用接口 系统调用接

    简介

     Android中无线网络的软件涉及linux内核、supplicant、
    

    framework、wifi service,代码从c、c++、java都有,这一篇主
    要介绍linux内核中的无线网络。要了解linux的无线网络,首先要
    了解linux的网络架构,接着介绍无线网络的架构,然后分析网络数
    据包的收、发流程。

    1 Linux的网络架构

    首先看一下linux的网络架构
    这里写图片描述

     系统调用接口
    系统调用接口可以从两个角度进行描述。用户发起网络调用时,通过系统调用接口进入内核的过程应该是多路的。最后调用 ./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 结构及其关系如图所示。最初,会调用 inet_init 中的 inet_register_protosw 将这个数组中的每个协议都初始化为 inetsw。函数 inet_init 也会对各个 inet 模块进行初始化,例如 ARP、ICMP 和 IP 模块,以及 TCP 和 UDP 模块。
    这里写图片描述

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

     设备驱动程序
    网络栈底部是负责管理物理网络设备的设备驱动程序。例如,包串口使用的 SLIP 驱动程序以及以太网设备使用的以太网驱动程序都是这一层的设备。
    在进行初始化时,设备驱动程序会分配一个 net_device 结构,然后使用必须的程序对其进行初始化。这些程序中有一个是 dev->hard_start_xmit,它定义了上层应该如何对 sk_buff 排队进行传输。这个程序的参数为 sk_buff。这个函数的操作取决于底层硬件,但是通常 sk_buff 所描述的报文都会被移动到硬件环或队列中。

     网络接口的注册
    一个网卡,要能够被内核使用,必须通过register_netdev接口注册进内核。register_netdev的参数为net_device结构体,其中net_device结构中的netdev_ops成员涉及数据发送,网络操作的接口。下面给出了register_netdev和netdev_ops的结构体及boardcom的netdev_ops的定义。对于网络数据包接收,是直接调用内核接口netif_rx把数据包传给内核,在下面的章节在介绍。

    kernel\include\linux\netdevice.h
    struct net_device {
    
        /*
         * This is the first field of the "visible" part of this structure
         * (i.e. as seen by users in the "Space.c" file).  It is the name
         * of the interface.
         */
    ……..  /* 省略中间代码  */
    
        /* Management operations */
        const struct net_device_ops *netdev_ops;
        const struct ethtool_ops *ethtool_ops;
    kernel\include\linux\netdevice.h
    struct net_device_ops {
        int         (*ndo_init)(struct net_device *dev);
        void            (*ndo_uninit)(struct net_device *dev);
        int         (*ndo_open)(struct net_device *dev);
        int         (*ndo_stop)(struct net_device *dev);
        netdev_tx_t     (*ndo_start_xmit) (struct sk_buff *skb,
                               struct net_device *dev);
        u16         (*ndo_select_queue)(struct net_device *dev,
                                struct sk_buff *skb);
        void            (*ndo_change_rx_flags)(struct net_device *dev,
                                   int flags);
        void            (*ndo_set_rx_mode)(struct net_device *dev);
        int         (*ndo_set_mac_address)(struct net_device *dev,
                                   void *addr);
        int         (*ndo_validate_addr)(struct net_device *dev);
        int         (*ndo_do_ioctl)(struct net_device *dev,
                                struct ifreq *ifr, int cmd);
        int         (*ndo_set_config)(struct net_device *dev,
                                  struct ifmap *map);
        int         (*ndo_change_mtu)(struct net_device *dev,
                              int new_mtu);
        int         (*ndo_neigh_setup)(struct net_device *dev,
                               struct neigh_parms *);
        void            (*ndo_tx_timeout) (struct net_device *dev);
    kernel\drivers\net\wireless\actions\bcmdhd\dhd_linux.c
    static struct net_device_ops dhd_ops_pri = {
        .ndo_open = dhd_open,
        .ndo_stop = dhd_stop,
        .ndo_get_stats = dhd_get_stats,
        .ndo_do_ioctl = dhd_ioctl_entry,
        .ndo_start_xmit = dhd_start_xmit,
        .ndo_set_mac_address = dhd_set_mac_address,
    #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0))
        .ndo_set_rx_mode = dhd_set_multicast_list,
    #else
        .ndo_set_multicast_list = dhd_set_multicast_list,
    #endif
    };
    kernel\drivers\net\wireless\actions\bcmdhd\dhd_linux.c
    int
    dhd_register_if(dhd_pub_t *dhdp, int ifidx, bool need_rtnl_lock)
    {
        dhd_info_t *dhd = (dhd_info_t *)dhdp->info;
    
    ……..  /* 省略中间代码  */
    
        if (ifidx == 0) {
            /*
             * device functions for the primary interface only
             */
    #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31))
            net->open = dhd_open;
            net->stop = dhd_stop;
    #else
            net->netdev_ops = &dhd_ops_pri;
    #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31) */
            if (!ETHER_ISNULLADDR(dhd->pub.mac.octet))
                memcpy(temp_addr, dhd->pub.mac.octet, ETHER_ADDR_LEN);
        } 
    
    ……..  /* 省略中间代码  */
    
        if (need_rtnl_lock)
            err = register_netdev(net);
        else
            err = register_netdevice(net);

    2 Linux的无线架构

    对于Linux的无线架构,可以分成两部分来学习,第一部分为数据包的收发过程,这部分与1节所说的一样。第二部分为无线网络的控制部分,包括无线网络的扫描、连接、断开及无线网络的各种设置及查询。下图为一个较完整的无线架构。
    这里写图片描述
    上面的图看起来涉及比较多的内容,而且各种架构都存在,总的来说可以分成2大部分,无线网络控制部分及无线网络数据传输部分,数据传输部分与2.1节介绍的一样。控制部分中,应用层Wext方式的通道已经不使用,现在大部分都是使用libnl方式。对于驱动架构的方式,内核中3种使用方式都有使用。当从我们现在使用的驱动代码看,都是使用方式3的驱动架构。在这只介绍方式3的驱动架构,在方式3的驱动架构的代码中,mac80211已经看不到痕迹了,在这主要介绍一下nl80211及cfg80211。
     nl80211
    无线网络在应用层使用libnl(Netlink Library)对命令进行了一层封装,应用层对无线网络的操作全部是通过libnl提供的接口,而libnl与内核的交互最终会走到nl80211(nl80211.c)。nl80211的核心结构如下,包含了所有的无线网络操作命令。

    \kernel\include\net\genetlink.h
    /**
     * struct genl_ops - generic netlink operations
     * @cmd: command identifier
     * @internal_flags: flags used by the family
     * @flags: flags
     * @policy: attribute validation policy
     * @doit: standard command callback
     * @dumpit: callback for dumpers
     * @done: completion callback for dumps
     * @ops_list: operations list
     */
    struct genl_ops {
        u8          cmd;
        u8          internal_flags;
        unsigned int        flags;
        const struct nla_policy *policy;
        int            (*doit)(struct sk_buff *skb,
                           struct genl_info *info);
        int            (*dumpit)(struct sk_buff *skb,
                         struct netlink_callback *cb);
        int            (*done)(struct netlink_callback *cb);
        struct list_head    ops_list;
    };
    kernel\net\wireless\nl80211.c
    static struct genl_ops nl80211_ops[] = {
        {
            .cmd = NL80211_CMD_GET_WIPHY,
            .doit = nl80211_get_wiphy,
            .dumpit = nl80211_dump_wiphy,
            .policy = nl80211_policy,
            /* can be retrieved by unprivileged users */
            .internal_flags = NL80211_FLAG_NEED_WIPHY,
        },
        {
            .cmd = NL80211_CMD_SET_WIPHY,
            .doit = nl80211_set_wiphy,
            .policy = nl80211_policy,
            .flags = GENL_ADMIN_PERM,
            .internal_flags = NL80211_FLAG_NEED_RTNL,
        },
         ……        /* 省略中间部分代码 */
         {
            .cmd = NL80211_CMD_VENDOR,
            .doit = nl80211_vendor_cmd,
            .policy = nl80211_policy,
            .flags = GENL_ADMIN_PERM,
            .internal_flags = NL80211_FLAG_NEED_WIPHY |
                      NL80211_FLAG_NEED_RTNL,
        },
    };

     cfg80211_ops
    cfg80211是Linux 802.11配置API, 用于对无线设备进行配置管理,用于连接nl80211和硬件的操作,每一个不同的wifi驱动都有自己的一份cfg80211_ops代码,在初始化时通过wiphy_register注册进内核。下面为boardcom的cfg80211_ops代码:

    kernel\drivers\net\wireless\actions\bcmdhd\ wl_cfg80211.c
    static struct cfg80211_ops wl_cfg80211_ops = {
        .add_virtual_intf = wl_cfg80211_add_virtual_iface,
        .del_virtual_intf = wl_cfg80211_del_virtual_iface,
        .change_virtual_intf = wl_cfg80211_change_virtual_iface,
    #if defined(WL_CFG80211_P2P_DEV_IF)
        .start_p2p_device = wl_cfgp2p_start_p2p_device,
        .stop_p2p_device = wl_cfgp2p_stop_p2p_device,
    #endif /* WL_CFG80211_P2P_DEV_IF */
        .scan = wl_cfg80211_scan,
        .set_wiphy_params = wl_cfg80211_set_wiphy_params,
        .join_ibss = wl_cfg80211_join_ibss,
        .leave_ibss = wl_cfg80211_leave_ibss,
        .get_station = wl_cfg80211_get_station,
        .set_tx_power = wl_cfg80211_set_tx_power,
        .get_tx_power = wl_cfg80211_get_tx_power,
        .add_key = wl_cfg80211_add_key,
        .del_key = wl_cfg80211_del_key,
        .get_key = wl_cfg80211_get_key,
        .set_default_key = wl_cfg80211_config_default_key,
        .set_default_mgmt_key = wl_cfg80211_config_default_mgmt_key,
        .set_power_mgmt = wl_cfg80211_set_power_mgmt,
        .connect = wl_cfg80211_connect,
        .disconnect = wl_cfg80211_disconnect,
        .suspend = wl_cfg80211_suspend,
        .resume = wl_cfg80211_resume,
        .set_pmksa = wl_cfg80211_set_pmksa,
        .del_pmksa = wl_cfg80211_del_pmksa,
        .flush_pmksa = wl_cfg80211_flush_pmksa,
        .remain_on_channel = wl_cfg80211_remain_on_channel,
        .cancel_remain_on_channel = wl_cfg80211_cancel_remain_on_channel,
        .mgmt_tx = wl_cfg80211_mgmt_tx,
        .mgmt_frame_register = wl_cfg80211_mgmt_frame_register,
        .change_bss = wl_cfg80211_change_bss,
    #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0)) || defined(WL_COMPAT_WIRELESS)
        .set_channel = wl_cfg80211_set_channel,
    #endif /* ((LINUX_VERSION < VERSION(3, 6, 0)) || WL_COMPAT_WIRELESS */
    #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0)) && !defined(WL_COMPAT_WIRELESS)
        .set_beacon = wl_cfg80211_add_set_beacon,
        .add_beacon = wl_cfg80211_add_set_beacon,
    #else
        .change_beacon = wl_cfg80211_change_beacon,
        .start_ap = wl_cfg80211_start_ap,
        .stop_ap = wl_cfg80211_stop_ap,
    #endif /* LINUX_VERSION < KERNEL_VERSION(3,4,0) && !WL_COMPAT_WIRELESS */
    #ifdef WL_SCHED_SCAN
        .sched_scan_start = wl_cfg80211_sched_scan_start,
        .sched_scan_stop = wl_cfg80211_sched_scan_stop,
    #endif /* WL_SCHED_SCAN */
    #if defined(WL_SUPPORT_BACKPORTED_KPATCHES) || (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0))
        .del_station = wl_cfg80211_del_station,
        .change_station = wl_cfg80211_change_station,
        .mgmt_tx_cancel_wait = wl_cfg80211_mgmt_tx_cancel_wait,
    #endif /* WL_SUPPORT_BACKPORTED_KPATCHES || KERNEL_VERSION >= (3,2,0) */
    #if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 2, 0)) || defined(WL_COMPAT_WIRELESS)
        .tdls_oper = wl_cfg80211_tdls_oper,
    #endif /* LINUX_VERSION > VERSION(3, 2, 0) || WL_COMPAT_WIRELESS */
    #ifdef WL_SUPPORT_ACS
        .dump_survey = wl_cfg80211_dump_survey,
    #endif /* WL_SUPPORT_ACS */
    #ifdef WL_CFG80211_ACL
        .set_mac_acl = wl_cfg80211_set_mac_acl,
    #endif /* WL_CFG80211_ACL */
    };

    3 Linux内核网络数据发送流程

    网络协议繁多,但大体框架相同,下面以发送一个ipv4 tcp数据包为例,说明网络数据包的发送流程,下图为Linux tcp数据包的发送流程。
    这里写图片描述

    各层主要函数以及位置功能说明:
    1) SYSCALL_DEFINE4(send):kernel/net/socket.c;socket发送数据包系统调用接口;
    2) SYSCALL_DEFINE6(sendto):kernel/net/socket.c;send调用sendto(sendto也是系统调用),填充struct msghdr结构体;
    3) sock_sendmsg: kernel/net/socket.c;
    4) __sock_sendmsg: kernel/net/socket.c;
    5) __sock_sendmsg_nosec:kernel/net/socket.c;选择tcp、udp、raw data协议发送数据包。

    kernel/net/socket.c
    static inline int __sock_sendmsg_nosec(struct kiocb *iocb, struct socket *sock,
                           struct msghdr *msg, size_t size)
    {
        struct sock_iocb *si = kiocb_to_siocb(iocb);
    
        si->sock = sock;
        si->scm = NULL;
        si->msg = msg;
        si->size = size;
    
        return sock->ops->sendmsg(iocb, sock, msg, size);
    }
    
    const struct proto_ops inet_stream_ops = {   /* tcp数据包处理 */
        .family        = PF_INET,
        .owner         = THIS_MODULE,
        .release       = inet_release,
        .bind          = inet_bind,
        .connect       = inet_stream_connect,
        .socketpair    = sock_no_socketpair,
        .accept        = inet_accept,
        .getname       = inet_getname,
        .poll          = tcp_poll,
        .ioctl         = inet_ioctl,
        .listen        = inet_listen,
        .shutdown      = inet_shutdown,
        .setsockopt    = sock_common_setsockopt,
        .getsockopt    = sock_common_getsockopt,
        .sendmsg       = inet_sendmsg,
        .recvmsg       = inet_recvmsg,
    
    const struct proto_ops inet_dgram_ops = {    /* udp数据包处理 */
        .family        = PF_INET,
        .owner         = THIS_MODULE,
        .release       = inet_release,
        .bind          = inet_bind,
        .connect       = inet_dgram_connect,
        .socketpair    = sock_no_socketpair,
        .accept        = sock_no_accept,
        .getname       = inet_getname,
        .poll          = udp_poll,
        .ioctl         = inet_ioctl,
        .listen        = sock_no_listen,
        .shutdown      = inet_shutdown,
        .setsockopt    = sock_common_setsockopt,
        .getsockopt    = sock_common_getsockopt,
        .sendmsg       = inet_sendmsg,
        .recvmsg       = inet_recvmsg,
    
    static const struct proto_ops inet_sockraw_ops = {   /* raw数据包处理 */
        .family        = PF_INET,
        .owner         = THIS_MODULE,
        .release       = inet_release,
        .bind          = inet_bind,
        .connect       = inet_dgram_connect,
        .socketpair    = sock_no_socketpair,
        .accept        = sock_no_accept,
        .getname       = inet_getname,
        .poll          = datagram_poll,
        .ioctl         = inet_ioctl,
        .listen        = sock_no_listen,
        .shutdown      = inet_shutdown,
        .setsockopt    = sock_common_setsockopt,
        .getsockopt    = sock_common_getsockopt,
        .sendmsg       = inet_sendmsg,
        .recvmsg       = inet_recvmsg,

    6) inet_sendmsg:kernel\net\ipv4\ af_inet.c;选择ipv4处理数据发送

    kernel\net\ipv4\ af_inet.c
    int inet_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
             size_t size)
    {
        struct sock *sk = sock->sk;
    
        sock_rps_record_flow(sk);
    
        /* We may need to bind the socket. */
        if (!inet_sk(sk)->inet_num && !sk->sk_prot->no_autobind &&
            inet_autobind(sk))
            return -EAGAIN;
    
        return sk->sk_prot->sendmsg(iocb, sk, msg, size);
    }
    kernel\net\ipv4\tcp_ipv4.c
    struct proto tcp_prot = {
        .name           = "TCP",
        .owner          = THIS_MODULE,
        .close          = tcp_close,
        .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,
        .recvmsg        = tcp_recvmsg,
        .sendmsg        = tcp_sendmsg,

    7) tcp_sendmsg:kernel\net\ipv4\tcp.c;申请sk_buff{}结构的空间,把msghdr{}结构中的数据填入sk_buff空间;
    8) tcp_push_one:kernel\net\ipv4\ tcp_output.c;
    9) tcp_write_xmit:kernel\net\ipv4\ tcp_output.c;
    10) tcp_transmit_skb:kernel\net\ipv4\ tcp_output.c;进行tcp封包,传给ip层;

    kernel\net\ipv4\ tcp_output.c
    static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,
                    gfp_t gfp_mask)
    {
        const struct inet_connection_sock *icsk = inet_csk(sk);
        struct inet_sock *inet;
    
        ……        /* 省略中间部分代码 */
    
        err = icsk->icsk_af_ops->queue_xmit(skb, &inet->cork.fl);
    kernel\net\ipv4\tcp_ipv4.c
    const struct inet_connection_sock_af_ops ipv4_specific = {
        .queue_xmit    = ip_queue_xmit,
        .send_check    = tcp_v4_send_check,
        .rebuild_header    = inet_sk_rebuild_header,
        .sk_rx_dst_set     = inet_sk_rx_dst_set,
        .conn_request      = tcp_v4_conn_request,
        .syn_recv_sock     = tcp_v4_syn_recv_sock,
        .net_header_len    = sizeof(struct iphdr),
        .setsockopt    = ip_setsockopt,
        .getsockopt    = ip_getsockopt,
        .addr2sockaddr     = inet_csk_addr2sockaddr,
        .sockaddr_len      = sizeof(struct sockaddr_in),
        .bind_conflict     = inet_csk_bind_conflict,
    #ifdef CONFIG_COMPAT
        .compat_setsockopt = compat_ip_setsockopt,
        .compat_getsockopt = compat_ip_getsockopt,
    #endif
    };

    11) ip_queue_xmit:kernel\net\ipv4\ ip_output.c;ip层封包;
    12) ip_local_out:kernel\net\ipv4\ ip_output.c;
    13) __ip_local_out:kernel\net\ipv4\ ip_output.c;调用netfilter进行处理;
    14) dst_output:kernel\include\net\dst.h;
    15) ip_output:kernel\net\ipv4\ ip_output.c;
    16) ip_finish_output:kernel\net\ipv4\ ip_output.c;
    17) ip_finish_output2:kernel\net\ipv4\ ip_output.c;
    18) dst_neigh_output:kernel\include\net\ neighbour.h;
    19) neigh_hh_output或neigh_resolve_output; kernel\include\net\ neighbour.h 或kernel\net\core\neighbour.c;
    20) dev_queue_xm it:kernel\net\core\dev.c;
    21) dev_hard_start_xmit:kernel\net\core\dev.c;
    22) ndo_start_xmit:网卡驱动注册的发送接口。

    4 Linux内核网络数据接收流程

    Linux的网络数据包接收流程以接收一个ipv4 tcp数据包为例,接收流程如下图所示:
    这里写图片描述

    各层主要函数以及位置功能说明:
     从上往下调用接口如下:
    1) SYSCALL_DEFINE6(recvfrom):kernel/net/socket.c;socket接收数据包系统调用接口;
    2) sock_recvmsg:kernel/net/socket.c;
    3) __sock_recvmsg:kernel/net/socket.c;
    4) __sock_recvmsg_nosec::kernel/net/socket.c;调用函数指针sock->ops->recvmsg完成在INET Socket层的数据接收过程,其中sock->ops被初始化为inet_stream_ops。
    5) inet_recvmsg:kernel\net\ipv4\ af_inet.c;调用函数指针sk->sk_prot->recvmsg,recvmsg被初始化为tcp_recvmsg;
    6) tcp_recvmsg:kernel\net\ipv4\tcp.c;从网络协议栈接收数据的动作,自上而下的触发动作一直到这个函数为止,出现了一次等待的过程。函数tcp_recvmsg可能会被动地等待在sk的接收数据队列上,也就是说,系统中肯定有其他地方会去修改这个队列使得tcp_recvmsg可以进行下去,入口参数sk是这个网络连接对应的sock{}指针,msg用于存放接收到的数据。当有数据时,直接返回获取的数据,若没有数据时,则在sk_receive_queue上等待,等待底层收到数据后唤醒。

     下层接收到数据包后通知上层接口如下:
    1) netif_rx:kernel\net\core\dev.c; wifi驱动接收到数据包后调用接口把数据包传给内核,同时该函数调用enqueue_to_backlog 把数据包放入input_pkt_queue队列,同时ip_rcv函数从input_pkt_queue取出数据包,再推送给上层;
    2) ip_rcv:kernel\net\ipv4\ip_input.c;
    3) ip_rcv_finish:kernel\net\ipv4\ip_input.c;ip_rcv和ip_rcv_finish从以太网接收数据,放到skb里,作ip层的一些数据及选项检查,调用ip_route_input() 做路由处理,判断是进行ip转发还是将数据传递到高一层的协议。调用skb->dst->input函数指针,这个指针的实现可能有多种情况,如果路由得到的结果说明这个数据包应该转发到其他主机,这里的input便是ip_forward;如果数据包是给本机的,那么input指针初始化为ip_local_deliver;
    4) dst_input:kernel\include\net\dst.h;dst_input 函数调用skb_dst(skb)->input(skb),input函数指针被初始化为ip_local_deliver;
    5) ip_local_deliver:kernel\net\ipv4\ ip_input.c;
    6) ip_local_deliver_finish:kernel\net\ipv4\ ip_input.c; ip_local_deliver、ip_local_deliver_finish入口参数skb存放需要传送到上层协议的数据,从ip头中获取是否已经分拆的信息,如果已经分拆,则调用函数ip_defrag将数据包重组。然后通过调用ip_prot->handler指针调用 tcp_v4_rcv(tcp)。ip_prot是inet_protocol结构指针,是用来ip层登记协议的,比如由udp,tcp,icmp等协议;
    7) tcp_v4_rcv:kernel\net\ipv4\tcp_ipv4.c;
    8) tcp_v4_do_rcv:kernel\net\ipv4\tcp_ipv4.c;
    9) tcp_rcv_established:kernel\net\ipv4\tcp_ipv4.c;
    10) tcp_queue_rcv:kernel\net\ipv4\tcp_ipv4.c;tcp_v4_rcv被ip_local_deliver函数调用,是从IP层协议向INET Socket层提交的“数据到”请求,入口参数skb存放接收到的数据,len是接收的数据的长度,这个函数首先移动skb->data指针,让它指向tcp头,然后更新tcp层的一些数据统计,然后进行tcp的一些值的校验,再从INET Socket层中已经建立的sock{}结构变量中查找正在等待当前到达数据的哪一项,可能这个sock{}结构已经建立,或者还处于监听端口、等待数据连接的状态。返回的sock结构指针存放在sk中。然后根据其他进程对sk的操作情况,将skb发送到合适的位置,调用如下:TCP包接收器(tcp_v4_rcv)将TCP包投递到目的套接字进行接收处理。当套接字正被用户锁定,TCP包将暂时排入该套接字的后备队列(sk_add_backlog),这时如果某一用户线程企图锁定该套接字(lock_sock),该线程被排入套接字的后备处理等待队列(sk->lock.wq),当用户释放上锁的套接字时 (release_sock在tcp_recvmsg中调用),后备队列中的TCP包被立即注入TCP包处理器(tcp_v4_do_rcv)进行处理,然后唤醒等待队列中最先的一个用户来获得其锁定权。如果套接字未被上锁,当用户正在读取该套接字时,TCP包将被排入套接字的预备队列(tcp_prequeue),将其传递到该用户线程上下文中进行处理,如果添加到sk->prequeue不成功,便可以添加到sk_receive_queue队列中。

    展开全文
  • Linux系统架构

    千次阅读 2011-03-15 23:14:00
    Linux系统从应用角度来看,分为内核空间和用户空间两个部分。内核空间是Linux操作系统的主要部分,但是仅有内核的操作系统是不能完成用户任务。 一、 Linux内核的主要模块 1.进程调度 进程调度指的是系统对进程的...

    Linux系统从应用角度来看,分为内核空间和用户空间两个部分。内核空间是Linux操作系统的主要部分,但是仅有内核的操作系统是不能完成用户任务。

     

    一、 Linux内核的主要模块

     1.进程调度

          进程调度指的是系统对进程的多种状态之间转换的策略。Linux下的进程调度有3种策略:SCHED_OTHER、SCHED_FIFO和SCHED_RP。

     

          SCHED_OTHER是用于针对普通进程的时间片轮转调度策略。这种策略中,系统给所有的运行状态的进程分配时间片。在当前时间片用完之后,系统从进程中优等级最高的进程中选择进程运行。

          SCHED_FIFO是针对运行的实时性要求比较高、运行时间短的进程调度策略。这种策略中,系统按进入队列的先后进行进程的调度,在没有更高优先级进程到来或者当前进程没有因为等待资源而阻塞的情况下,会一直运行。

          SCHED_RR是针对运行的实时性要求比较高、运行经较长的进程调度策略。这种策略与SCHED_OTHER的策略类似,只不过SCHED_RR进程的优先级要高得多。 系统分配给SCHED_RR的进程时间片,然后轮循运行这些进程,将时间片用完的进程放入队列的末尾。

     

           Linux进程调度月票用的是“有条件可剥夺”的调度方式。普通进程中采用的是SCHED_OTHER的时间片轮循方式,实时进程可以剥夺普通进程。如果普通进程在用户空间运行,则普通进程立即停止运行,将资源让给实时进程vkjsuo通进程运行在内核空间,需要等系统调用返回用户空间后方可剥夺资源。

     

      2.内存管理MMU  

          内存管理是多个进程间的内存共享策略。在Linux系统中,内存管理的主要概念是虚拟内存。

          虚拟内存的分配策略是每个进程都可以公平地使用虚拟内存。虚拟内存的大小通常设置为物理内存的两倍。

     

      3.虚拟文件系统VFS

          在Linux下支持多种文件系统,如ext、ext2、minix、umsdos、msdos、vfat、ntfs、proc、smb、ncp、iso9660、sysv、hpfs、affs等。

     

       4、网络接口

          Linux是在Internet飞速发展的时期成长起来的,所以Linux支持多种网络接口和协议。网络接口分为网络协议和驱动程序。

     

       5、进程间通信

          Linux的进程间通信是从UNIX系统继承过来的。Linux下的进程间通信方式主要有管道方式、信号方式、消息队列方式、共享内存和套接字等方法。  

    二、Linux的文件结构

          与Windows下的文件组织结构不同,Linux不使用磁盘分区符号来访问文件系统,而是将整个文件系统表示成树状的结构,Linux系统每增加一个文件系统都会将其加入到这个树中。

     

          不同的Linux发行版本的目录结构和具体的实现功能存在一些细微的差别。但是主要的功能都是一致的。一些常用目录的作用如下:

     

          /etc: 包括绝大多数Linux系统引导所需要的配置文件,系统引导时读取配置文件,例如fstab、host.conf等。

     

          /lib: 包含C编译程序需要的函数库,是一组二进制文件,例如glibc等。

     

          /usr: 包括所有其他内容,如src、local。Linux的内核就在/usr/src中。其下有子目录/bin,存放所有安装语言的命令,如gcc、perl等。

     

          /var:包含系统定义表,以便在系统运行改变可以只备份该目录,如cache。

     

          /tmp: 用于临时性的存储。

     

          /bin: 大多数命令存放在这里。

     

          /home: 主要存放用户账号,并且可以支持ftp的用户管理。系统管理员增加用户时,系统在home目录下创建与用户同名的目录,此目录一般认有Desktop目录。

     

          /dev: 这个目录下存放一种设备文件的特殊文件,如fd0、had等。

     

          /mnt: 在Linux系统中,它是专门给外挂的文件系统使用的,里面有两个文件cdrom、floopy,登录光驱、软件时要用到。

     

    展开全文
  • Linux网络体系架构

    千次阅读 2013-04-21 16:27:21
    一,网络协议栈   linux的优点在于它丰富而稳定的协议栈。其范围从协议无关层(例如通用socket层接口或设备层)到各种具体的网络协议实现。   对于网络的理论介绍一般采用OSI(OpenSystem ...二,linux网络

    一,网络协议栈

     

    linux的优点在于它丰富而稳定的协议栈。其范围从协议无关层(例如通用socket层接口或设备层)到各种具体的网络协议实现

     

    对于网络的理论介绍一般采用OSIOpenSystem Interconnection)模型,但是Linux中网络栈的介绍一般分为四层Internet模型

    以下是OSI模型和linux网络模型的对比:

     

     

    二,linux网络子系统架构

     

     

    Systemcall interface(系统调用接口)

    为用户空间的应用程序提供了一种访问内核网络子系统的方法—socket系统调用。

     

    Protocolagnostic interface(协议无关接口)

    提供了一种通用方法来使用传输层协议。实现一组通用函数来访问各种不同的协议,通过socket实现。Linux中的socket使用struct sock来描述,这个结构包含了特定socket所需要的所有状态信息,还包括socket所使用的特定协议和在socket上可以执行的一些操作

     

    Network protocols(linux网络协议栈)

    用于实现各种具体的网络协议,如:TCPUDP

     

    Device agnosticinterface(设备无关接口)

    提供了协议与设备驱动通信的通用接口。将协议与各种网络设备驱动连接在一起。这一层提供一组通用函数供底层网络设备驱动程序使用,让它们对高层协议栈进行操作。首先,设备驱动程序可能通过调用register_netdeviceunregister_netdevice在内核中进行注册或注销。调用者先填写net_device结构,然后传递这个结构进行注册。内核调用它的init函数(如果定义了这种函数),然后执行一组健全性检查,并将新设备添加到设备列表中(内核中的活动设备链表)。

     

    Device drivers(设备驱动)

    网络体系结构的最底部是负责管理物理网路设备的设备驱动程序。

     

     

     

     

     

     

     

    展开全文
  • Linux网络驱动架构

    千次阅读 2015-08-10 22:54:39
    http://http://www.ibm.com/developerworks/cn/linux/l-cn-networkdriver/ 网络设备介绍 网络设备是计算机体系结构中必不可少的一部分,处理器如果想与外界通信,通常都会选择网络设备作为通信接口。...
  • 图解Linux系统的系统架构

    千次阅读 2016-01-05 12:53:16
    这篇文章主要介绍了图解Linux系统的系统架构,Linux系统由硬件、内核、系统调用、shell、库函数构成,本文分别对他们做了讲解,需要的朋友可以参考下我以下图为基础,说明Linux的架构(architecture)。(该图参考...
  • linux系统架构师需求

    千次阅读 2013-08-26 11:24:32
    1.在Linux/Unix系统下有三年以上php或C++开发经验。 2.熟悉并发计算、分布式处理、多核多CPU系统的软件架构模式、概念和技巧;2年以上的软件架构设计经验; 3.熟悉数据结构、常用算法、操作系统原理和编译原理; 4...
  • 什么是Linux系统架构

    千次阅读 2015-09-26 00:43:55
    什么是Linux系统架构?  要了解Linux系统,就要对其系统架构有一定的了解,下面小编就大家带来Linux系统架构的讲解,有兴趣的朋友不妨来了解下。  我以下图为基础,说明Linux的架构(architecture)。(该图...
  • Linux网络设备驱动架构

    千次阅读 2018-02-01 23:10:09
    Linux网络设备驱动程序体系结构分为四层:网络协议接口层、网络设备接口层、提供实际功能的设备驱动层以及网络设备与媒介层。 (1)网络协议接口层向网络层协议提供统一的数据包收发接口,不论上层协议是ARP还是...
  • linux的文件系统架构

    千次阅读 2010-08-18 22:41:00
    linux的文件系统是由若干个树形以及链表的结构组成的,其中众所周知,所有的目录和文件组成了视觉上“一棵”树,在这“一棵”树背后有一条挂载链表,如果说每一个文件系统都是一棵树的话(实际上就是如此),这...
  • linux内核整体架构

    千次阅读 2018-08-02 21:23:54
    本文是“Linux内核分析”系列文章的第一篇,会以内核的核心功能为出发点,描述Linux内核的整体架构,以及架构之下主要的软件子系统。之后,会介绍Linux内核源文件的目录结构,并和各个软件子系统对应。 2.linux内核...
  • Linux USB总线架构

    千次阅读 2017-08-07 12:21:49
    Linux USB总线架构
  • linux系统架构与目录解析

    千次阅读 2013-06-07 10:32:40
    1. 当有两个硬盘都装有linux时,居然可以直接跳进另一个操作系统,不用重启,使用命令chroot, 做一些简单的操作,因为linux kernel差不多!!! 2. linux本地启动过程为grub->kernel->initrd->init->inittab->rc....
  • Linux运维高级架构师实战视频教程

    万次阅读 2019-04-27 20:59:36
    其次就是涉及Linux系统接入网络的应用。你可以直接去搜一下简历上的说明。要找大公司的Linux运维工程师,小公司可能要求乱七八糟的,不是运维也叫运维 linux运维工程师职位描述: 岗位职责: 1.四年以上互联网应用...
  • linux体系架构

    千次阅读 2017-10-20 09:59:03
     如上图所示,从宏观上来看,Linux操作系统的体系架构分为用户态和内核态(或者用户空间和内核)。内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程序运行的环境。用户态即上层应用程序的活动...
  • Linux网络系统底层机制分析(2)

    千次阅读 2008-02-23 00:24:00
    Linux网络系统底层机制分析(2)----linux底层的若干机制暂且把报文的接收看作是上行处理,这一篇将总结linux是如何在底层处理从网络上接收到的报文。所有的源代码引自linux kernel 2.6.22。1)硬件通知驱动的方式...
  • linux C++通讯架构实战课程

    千次阅读 2018-12-19 23:36:07
    #linux C++通讯架构实战课程 极力推荐学习linux C++通讯架构实战 课程地址: https://study.163.com/course/introduction/1006470001.htm 20181219日,开始探索. 有关飞剑神的所有的虚拟机搭建 链接:...
  • Linux云计算架构

    千次阅读 2016-01-08 08:39:00
    Linux系统管理 科目 基础课程 Linux基本管理 Linux用户与权限管理 Linux文件系统管理 Shell编程 Python脚本编程 Linux系统管理 内容 计算机基础 网络基础 Windows ...
  • 因为,目前在做游戏的的后台开发。尽管最近在忙于开发游戏业务逻辑的的相关代码,也在不停思考着游戏后台的一些架构问题。...首先,我们知道目前后台所效率上受到限制的就是 数据库IO>磁盘》网络IO>cpu>内存
  • M. Tim Jones, 顾问工程师, Emulex Corp. 简介: Small Computer Systems Interface (SCSI) 是一组标准集,它定义了...Linux 是分层架构的一个很好的例子,它将高层的驱动器(比如磁盘驱动器或光驱)连接到物理接口,
  • Linux内核的整体架构简介

    万次阅读 多人点赞 2017-10-23 17:44:42
    本文是“Linux内核分析”系列文章的第一篇,会以内核的核心功能为出发点,描述Linux内核的整体架构,以及架构之下主要的软件子系统。之后,会介绍Linux内核源文件的目录结构,并和各个软件子系统对应。 注:本文和...
  • 安装 Linux 操作系统 Centos6.5 系统安装 Centos6.5 基本配置 创建新用户 网络配置 JAVA 配置 SSH免密码登陆配置 KVM下虚拟机网卡桥接配置 KVM克隆及在不同物理机下的移植 Centos6.5 安装软件 VNC 配置 Maven 安装...
  • Linux操作系统基础(完结)

    万次阅读 多人点赞 2016-03-09 21:00:45
    一、Linux操作系统概述 二、Linux操作系统安装 三、Linux文件系统及文件基础 四、Linux操作系统命令使用基础 五、Linux应用程序的安装与卸载基础 五、用户及进程 六、相关信息查询 七、网络配置 八、Linux...
  • linux与android架构基础

    千次阅读 2014-04-27 15:41:42
    我们以下图为基础,说明Linux架构(architecture)。   最内层是我们的硬件,最外层是我们常用的各种应用,比如说使用firefox浏览器,打开evolution查看邮件,运行一个计算流体模型等等。硬件是我们...
  • &...1.3查看系统和BIOS硬件时间。 1.4 Linux如何获得帮助,Linux关机命令:shutdown、init等。 1.5 Linux 7个启动级别,设置服务器在来电后自动开机。 单词整理: Terminal : 终端 &nbs...
  • Linux内核的整体架构

    万次阅读 多人点赞 2014-03-26 12:08:59
    本文是“Linux内核分析”系列文章的第一篇,会以内核的核心功能为出发点,描述Linux内核的整体架构,以及架构之下主要的软件子系统。之后,会介绍Linux内核源文件的目录结构,并和各个软件子系统对应。 注:本文...
  • Adnroid体系与系统架构

    千次阅读 2015-12-15 01:05:57
    Android体系与系统架构1.1 Google生态系统1.2 Android系统架构1.2.1 Linux1.2.2 Dalvik和ART1.2.3 Framework1.2.4 Standard libraries1.2.5 Application1.3 Android App 组件框架1.3.1 Android四大组件是如何协同...
  • Linux 二层协议架构组织

    千次阅读 2013-08-28 23:19:13
    本文主要讲解了Linux 二层协议架构组织,使用的内核的版本是2.6.32.27 为了方便理解,本文...Linux中1层2层标准化及在Linux系统中的实现位置如下所示 1层和2a层(MAC)层体现了不同网络介质的区别,在Li
  • 概述 在这个信息急剧膨胀的社会,我们不得不说人类正进入一个崭新的时代,那就是信息时代。信息时代的一个主要而显著...其中颇具争论和影响力的是C/S以及B/S架构,C/S是一种历史悠久且技术非常成熟的架构,B/S是新生
  • 基于arc架构cpu平台的Linux系统移植

    千次阅读 2015-03-28 20:06:04
    基于arc架构cpu平台的Linux系统移植 任务需求 项目总需求: 要求在杭州市的公司! 在规定时间内提供给我们一个可以基于我们芯片(ARC内核)的linux操作系统演示版本,系统正常启动,稳定运行,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 217,538
精华内容 87,015
关键字:

linux网络系统架构

linux 订阅