当前位置: 首页 > 科技观察

Linux系统架构分析

时间:2023-03-13 22:31:40 科技观察

历史1991年,还在芬兰赫尔辛基大学读书的LinusTorvalds在他的Intel386计算机上开发了他的第一个程序,并在互联网上发布了他开发的源代码,将其命名为Linux,从而创造了Linux操作系统,并于同年公开了Linux代码,从而开启了一个伟大的时代。在随后的近30年里,越来越多的工程师投身于Linux,帮助不断完善Linux的功能。当前的Linux系统架构以其出色的分层和模块化设计,集成了大量的设备和不同的物理架构。写这篇文章也是对Linux系统做一个很简单的介绍,主要讲解Linux进程调度、内存管理、设备驱动、文件系统、网络模块等。Linux内核架构图上图是Linux内核的架构图。从硬件层--->操作系统内核--->应用层,该系统架构的设计被应用到各种软硬件结合的系统中,如物联网系统、单片机系统、机器人等领域。进程调度进程在Linux系统中称为进程或任务。操作系统中进程的数据结构包含很多元素,如:地址空间、进程优先级、进程状态、信号量、占用的文件等,它们往往由一个链表链接起来。每次系统节拍(Tick)中断产生时,CPU检查就绪队列中的进程(遍历链表中的进程结构),如果有新的进程满足调度算法需要切换,保存信息当前正在运行的进程(包括栈、地址等),挂起当前进程,然后运行新的进程,这就是进程调度。CPU调度的基本依据是进程的优先级。调度的最终目的是让高优先级的进程及时获得CPU资源,让低优先级的任务公平分配CPU资源。但是由于保存了当前进程的信息,进程的切换本身是有成本的,调度算法也需要考虑效率。在早期的Linux内核中,采用了轮询算法来实现。内核在就绪进程队列中选择优先级高的进程执行,每次运行时间相等。这个算法简单直观,但是还是会导致一些低优先级的进程。进程不能长时间执行。为了提高调度的公平性,在后来的Linux内核(2.6)中引入了CFS调度器算法。CFS引入了虚拟运行时间的概念,用task_struct->se.vruntime表示,用来记录和衡量一个进程应该获得的CPU运行时间。在理想的调度情况下,所有进程在任何时候都应该具有相同的task_struct->se.vruntime值。因为每个进程并发执行,所以没有进程占用比理想情况更多的CPU时间。CFS选择进程运行的逻辑是根据task_struct->se.vruntime值,它总是选择task_struct->se.vruntime值最小的进程运行(为了公平)。CFS使用时间排序的红黑树来为未来进程的执行安排时间线。所有进程都按task_struct->se.vruntime键排序。CFS从树中选择最左边的任务来执行。随着系统的运行,执行过的进程会被放在树的右边,逐渐让每个任务都有机会成为最左边的进程,这样每个进程都可以获得CPU资源。一般来说,CFS算法首先选择一个进程。当进程切换时,进程使用的CPU时间会被添加到进程的task_struct->se.vruntime中。当task_struct->se.vruntime的值逐渐增加到other时,进程成为红黑树最左边的进程,选择最左边的进程执行,当前进程被抢占。内存管理内存,一种硬件设备,操作系统对它进行寻址,找到对应的内存单元,然后对其进行操作。CPU的字节长度决定了最大可寻址空间。32位机器最大可寻址空间为4GBytes,64位机器最大可寻址空间为2^64Bytes。最大寻址空间与物理内存大小无关,称为虚拟地址空间。Linux内核将虚拟地址空间分为内核空间和用户空间。每个用户进程的虚拟地址空间范围为0~TASK_SIZE。从TASK_SIZE~2^32或2^64的区域是为内核保留的,不能被用户进程访问。虚拟地址空间与物理内存的映射大多数情况下,虚拟地址空间比实际物理内存要大,操作系统需要考虑如何将实际可用的物理内存映射到虚拟地址空间。Linux内核使用页表将虚拟地址映射到物理地址。虚拟地址与进程使用的用户&内核地址有关,物理地址用于寻址实际使用的内存。示例图如上图所示,进程A和B的虚拟地址空间被划分为大小相等的相等部分,称为页面。物理内存也分为大小相等的页(页框)。进程A的第一个内存页映射到物理内存(RAM)的第4页;进程B的第一个内存页映射到物理内存的第5页。进程A的第5内存页和进程B的第1内存页都映射到物理内存的第5页(内核可以判断不同进程共享哪些内存空间)。页表将虚拟地址空间映射到物理地址空间。Linux文件系统的核心理念:一切皆文件。Linux系统中有很多文件系统,如EXT2、EXT3、EXT4、rootfs、proc等,每个文件系统都是独立的,有自己的组织和运行方式。为了支持不同的文件系统,内核在用户态和文件系统之间包含了一层虚拟文件系统(VirtualFileSystem)。内核提供的大部分功能都可以通过VFS定义的接口来访问。例如内核的子系统:字符设备、块设备、管道、套接字等。另外,用于操作字符和块设备的文件都是/dev目录下的真实文件。当执行读写操作时,它们将被相应的驱动程序创建。VFS结构图Linux的虚拟文件系统的四大对象:1.superblock(超级块)2.inode(节点)3.dentry(目录)4.block(具体数据块)superblock表示一个具体安装的文件系统,包括文件系统的类型、大小、状态等。inode代表一个特定的文件。在Linux文件管理中,一个文件除了自身的数据外,还有一些附属信息,即文件的元数据。这个元数据用来记录文件的很多信息,比如文件大小、创建者、创建时间等,这个元数据包含在inode中。索引节点是文件从抽象--->具体的关键。inode存储了一些指针,这些指针指向存储设备的一些数据块,文件的内容就存储在这些数据块中。当Linux要打开一个文件时,只需要找到该文件对应的inode,然后沿着指针把所有的数据块累加起来,在内存中就形成了一个文件的数据。Inode结构Inode不是组织文件的唯一方式。最简单的文件组织方式就是将文件按顺序放入存储设备中。难以使用和管理;链表可以使用复杂的方法。每个数据块都有一个指向属于同一文件的下一个数据块的指针。这样做的好处是可以利用分散的自由空间。以线性方式执行,如果随机读取,则必须遍历链表,直到目标位置。由于这种遍历不是在内存中进行的,所以非常慢。inode可以充分利用空间,内存占用的空间与存储设备无关,解决了上述问题。但是inode也有自己的问题。每个inode可以存储的数据块指针总数是固定的。如果一个文件需要的数据块多于这个总数,则inode需要额外的空间来存储额外的指针。Dentry代表一个目录项,是路径的一部分,比如一个路径/home/jackycao/hello.txt,那么目录项有home,jackycao,hello.txt。一个块代表特定的数据。一个文件由多个分散的块组成,组织方式由inode指向。设备驱动程序与外设的交互,说白了就是输入(input)、操作(operate)、输出(ouput)的操作。内核需要做三件事:1.为不同的设备类型实现不同的硬件寻址方法。2.用户空间必须提供操作不同硬件设备的方法,并且需要统一的机制来保证尽可能少的编程工作量。3.让用户空间知道内核中哪些设备可用。DeviceCommunicationDiagram内核访问外设主要有两种方式:I/O端口和I/O内存映射。我就不详细介绍了。内核动态接收来自外设的请求(数据)主要有两种方式:轮询和中断。轮询:周期性访问查询设备是否有数据,有则获取数据。这种方法很浪费CPU资源。中断:核心思想是当有外设请求时,主动通知CPU。中断的优先级最高,会中断CPU的当前进程。每个CPU都提供一条中断线。每个中断都由一个唯一的中断号标识。内核为每个应用程序中断提供了一个中断处理程序方法。当数据准备好供内核使用或间接供应用程序使用时,外围设备会发送中断。使用中断可确保系统仅在外设需要处理器干预时才通知CPU,从而提高效率。PS:块和扇区的概念:块是一个指定大小的字节序列,用于保存内核和设备之间传输的数据,块的大小可以设置,默认为4096字节,扇区存储设备操作的最小单位默认为512Bytes,一个block是一个连续的扇区。网络Linux的网络子系统的模型是基于ISOOSI模型,在Linux内核中会简化相应的层次。下图显示了Linux使用的TCP/IP参考模型。网络模型Host-to-Host层:相当于OSI模型的物理层和数据链路层,负责将数据从一台计算机传送到另一台计算机。从Linux内核的角度来看,这一层是通过网卡的设备驱动来实现的。Internet层:相当于OSI模型的网络层,负责让网络中的计算机交换数据(这些计算机不一定直接相连)。这一层还负责将传输的数据包分成指定的大小,因为在数据包的传输路径上每台计算机支持的最大网络数据包的大小是不同的,数据在传输过程中被分成不同的数据包,然后在接收端重新组装。该层为网络上的计算机分配唯一的网络地址。传输层:相当于OSI模型的传输层,负责两台相连计算机上运行的应用程序之间的数据传输。例如,两台计算机上的客户端和服务器程序使用端口号来识别通信应用程序。App层:相当于OSI模型的会话层、表示层、应用层。网络中不同计算机上的两个应用程序连接起来后,这一层负责实际内容的传输。Linux内核子系统的实现是通过C代码实现的,每一层只能与其上下层通信。Linux网络分层图