共计 4004 个字符,预计需要花费 11 分钟才能阅读完成。
历史
1991 年,还在芬兰赫尔辛基大学上学的 Linus Torvalds 在自己的 Intel 386 计算机上开发了属于他自己的第一个程序,并利用 Internet 发布了他开发的源代码,将其命名为 Linux,从而创建了 Linux 操作系统,并在同年公开了 Linux 的代码,从而开启了一个伟大的时代。在之后的将近 30 年的时间里,越来越多的工程师投入到 Linux,帮助不断完善 Linux 的功能。现在的 Linux 系统架构凭借优秀的分层和模块化的设计,融合了大量的设备和不同的物理架构。
写这篇文章,也是对 Linux 系统的一个非常简单的介绍,主要讲解 Linux 的进程调度、内存管理、设备驱动、文件系统、网络模块。
上图就是 Linux 内核的架构图,从硬件层 —> 操作系统内核 —> 应用层,这套系统架构的设计应用于各类软硬件结合的系统上,比如物联网系统,单片机系统、机器人等领域。
进程调度
进程在 Linux 系统中称为 process 或 task。操作系统中进程的数据结构包含很多元素,诸如:地址空间、进程优先级、进程状态、信号量、占用的文件等,往往用链表链接。
CPU 在每个系统滴答(Tick)中断产生的时候检查就绪队列里边的进程(遍历链表中的进程结构体),如有符合调度算法的新进程需要切换,保存当前运行的进程的信息(包括栈、地址等)后挂起当前进程,然后运行新的进程,这就是进程调度。
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 的值逐渐增大到别的进程变成了红黑树最左边的进程时,最左边的进程被选中执行,当前的进程被抢占。
内存管理
内存,一种硬件设备,操作系统对其寻址,找到对应的内存单元,然后对其操作。CPU 的字节长度决定了最大的可寻址空间,32 位机器最大寻址空间是 4G Bytes,64 位机器最大寻址空间是 2^64 Bytes。
最大寻址空间和物理内存大小无关,称之为虚拟地址空间。Linux 内核把虚拟地址空间分为内核空间和用户空间。每个用户进程的虚拟地址空间范围是 0~TASK_SIZE。从 TASK_SIZE~2^32 或 2^64 的区域保留给内核,不能被用户进程访问。
虚拟地址空间与物理内存的映射 绝大多数情况下,虚拟地址空间比实际物理内存大,操作系统需要考虑如何将实际可用的物理内存映射到虚拟地址空间。
Linux 内核采用页表(page table)将虚拟地址映射到物理地址。虚拟地址和进程使用的用户 & 内核地址有关,物理地址用来寻址实际使用的内存。
上图所示,A 和 B 进程的虚拟地址空间被分为大小相等的等份,称为页(page)。物理内存同样被分割为大小相等的页(page frame)。
进程 A 第 1 个内存页映射到物理内存 (RAM) 的第 4 页;进程 B 第 1 个内存页映射到物理内存第 5 页。进程 A 第 5 个内存页和进程 B 第 1 个内存页都映射到物理内存的第 5 页(内核可决定哪些内存空间被不同进程共享)。页表将虚拟地址空间映射到物理地址空间。
文件系统 Linux 的核心理念:everything is file。Linux 系统存在很多文件系统,比如 EXT2,EXT3,EXT4,rootfs,proc 等等,每一种文件系统都是独立的,有自己的组织方式、操作方法。
为了支持不同的文件系统,内核在用户态和文件系统之间包含了一层虚拟文件系统(Virtual File System)。大多数内核提供的函数都能通过 VFS 定义的接口来访问。例如内核的子系统:字符设备、块设备,管道,socket 等。另外,用于操作字符和块设备的文件是在 /dev 目录下真实文件,当读写操作执行的时候,其会被对应的驱动程序创建。
Linux 的虚拟文件系统四大对象:
- super block(超级块)
- inode(节点)
- dentry(目录)
- block(具体的数据块)
super block 代表一个具体的已经安装的文件系统,包含文件系统的类型、大小、状态等等。
inode 代表一个具体的文件,在 Linux 文件管理中,一个文件除了自身的数据外,还有一个附属信息,即文件的元数据(metadata),这个元数据用于记录文件的许多信息比如文件大小、创建人、创建时间等,这个元数据就包含在 inode 中。
inode 是文件从抽象 —> 具体的关键。inode 存储了一些指针,这些指针指向存储设备的一些数据块,文件的内容就存储在这些数据块中。Linux 想打开一个文件时,只需要找到文件对应的 inode,然后沿着指针,将所有的数据块攒起来,就可以在内存中组成一个文件的数据了。
inode 并不是组织文件的唯一方式,最简单的组织文件的方式,是把文件依次顺序的放入存储设备,但如果有删除操作的话,删除造成的空余空间夹杂在正常文件之间,很难利用和管理;复杂方式可以用来链表来做,每个数据块有个指针,指向属于同一文件的下一个数据块,这样的好处是可以利用零散的空余空间,坏处是对文件的操作必须按照线性方式进行,如果随机读取就必须要遍历链表,直到目标位置。由于这一遍历不是在内存进行,所以速度很慢。
inode 既可以充分利用空间,在内存占据空间不与存储设备相关,解决了上面的问题。但 inode 也有自己的问题。每个 inode 能够存储的数据块指针总数是固定的。如果一个文件需要的数据块超过这一总数,inode 需要额外的空间来存储多出来的指针。
dentry 代表一个目录项,是路径的一部分,比如一个路径 /home/jackycao/hello.txt,那么目录项就有 home、jackycao、hello.txt。
block 代表具体的数据,一个文件由分散的多个 block 组成,组织的方式由 inode 来指向。
设备驱动 与外设的交互,说白了就是输入(input)、操作(operate)、输出(ouput)的操作。
内核需要完成三件事情:
- 针对不同的设备类型实现不同的方法来寻址硬件。
- 必须为用户空间提供操作不同硬件设备的方法,且需要一个统一的机制来确保尽量有限的编程工作。
- 让用户空间知道在内核中有哪些设备。
内核访问外设主要有两种方式:I/ O 端口和 I / O 内存映射。具体不展开介绍了。
内核动态接收外设发来的请求(数据)主要通过两种方式:轮询和中断。
轮询:周期性的访问查询设备是否有数据,如果有,便获取数据。这种方法比较浪费 CPU 资源。
中断:核心思想是外设有请求时主动通知 CPU,中断的优先级最高,会中断 CPU 的当前进程运行,每个 CPU 都提供了中断线,每个中断由唯一的中断号识别,内核为每个应用的中断提供一个中断处理方法。当有数据已准备好可以给内核或者间接被一个应用程序使用的时候,外设出发一个中断。使用中断确保系统只有在外设需要处理器介入的时候才会通知 CPU,提高了效率。
PS:块和扇区的概念:块是一个指定大小的字节序列,用于保存在内核和设备间传输的数据,块的大小可以被设置,默认是 4096 bytes,扇区是存储设备操作的最小单元,默认是 512 Bytes,块是一段连续的扇区。
网络 Linux 的网络子系统的模型基于 ISO 的 OSI 模型,Linux 内核中会简化相应层级。下图为 Linux 使用的 TCP/IP 参考模型。
Host-to-Host 层:相当于 OSI 模型的物理层和数据链路层,负责将数据从一个计算机传输到另一个计算机。在 Linux 内核的角度来看,这一层是通过网卡的设备驱动程序实现的。
Internet 层:相当于 OSI 模型的网络层,负责让网络中的计算机可以交换数据(这些计算机并不一定是直连的)。该层同时负责传输的包分成指定的大小,因为包在传输路径上每个计算机支持的最大网络包的大小不一样,在传输时数据被分割成不同的包,在接收端再组合。该层为网络中的计算机分配唯一的网络地址。
Transport 层:相当于 OSI 模型的传输层,负责让两个连接的计算机上运行的应用程序之间的数据传输。比如,两台计算机上的客户端和服务端程序,通过端口号来识别通信的应用程序。
App 层:相当于 OSI 模型的会话层、表示层、应用层,网络中不同计算机的两个应用程序建立连接后,这一层负责实际内容的传输。
Linux 内核子系统的实现通过 C 代码实现,每个层只能和它上下层通信。
转载 https://mp.weixin.qq.com/s/Kk…