linux内核的一些知识点(下)

2,406 阅读10分钟

内核以物理页作为内存的基本单位,内存管理单元(MMU)正式以页作为单位进行处理,内存管理单元硬件负责将虚拟地址转为物理地址。所以从虚拟内存的角度来看,页就是最小的单位。

多数32位体系结构的页大小为4KB,而64位体系结构则为8KB。也就是说1G大小的内存会被划分为262144个页来进行管理。linux内核使用page结构来描述物理页,位于linux/mm_types.h中。这个结构体大小为40B,对于页大小为8KB内存大小为4GB的系统来说,要描述所有内存则需要524288个物理页,总大小为20MB。

内核会把页划分为不同的区,将相似特性的页分为一组,有些页位于内存特定的物理地址上,所以不能用于特定任务。由于硬件的限制,内核需要对物理页区分对待。比如某些硬件只能通过某些特定的内存地址来执行直接内存访问。还有一些体系结构的内存物理寻址范围比虚拟寻址范围大,于是某些内存就不能永久地映射到内核空间上。

linux通过四种区来区分内存页,

  • ZONE_DMA,表示能用来执行DMA操作。
  • ZONE_DMA32,表示能用来执行DMA操作,但是只能被32位设备访问。
  • ZONE_NORMAL,表示能正常映射的页。
  • ZONE_HIGHEM,表示高端内存,其页不能永久地映射到内核地址空间。

32位x86系统中,小于16MB的物理内存为ZONE_DMA区,能用DMA操作。从16MB到896MB物理内存为ZONE_NORMAL区,正常可寻址页。高于896M的所有内存为ZONE_HIGHEM区,动态映射页。

内核栈

内核的栈不能像用户空间那样奢侈分配,内核栈大小是固定的,每个进程都分配到一个固定大小的内核栈。每个进程的内核栈大小与体系结构和编译选项有关,通常每个进程有2个页大小的内核栈,所以32位和64位的内核栈大小分别为8KB和16KB。此外,中断栈不共享进程的内核栈,中断栈专门为进程提供用于中断处理程序的栈。

虚拟文件系统

虚拟文件系统即VFS,属于内核子系统,为用户空间提供文件和文件系统相关接口。所有文件系统都依赖VFS共存且协同工作,通过VFS可以使用标准UINX系统调用完成文件操作。VFS让用户能够直接使用open、read、write等系统调用来实现不同物理介质的文件操作,它对不同文件系统进行抽象并提供统一的操作。

系统调用通过VFS接口提供给用户空间使用,它实现了具体的文件系统细节。比如用户空间调用write()函数,会间接调用sys_wirte()函数,然后调用具体的文件系统去与物理介质交互。

unix的文件系统提供了四种抽象概念:文件、目录项、索引节点和安装点。文件和文件相关信息进行分离,文件相关的信息保存在索引节点中,即inode。

VFS包括四个主要的对象类型:

  • 超级块对象,表示具体的已安装的文件系统。
  • 索引节点对象,表示文件的属性描述。
  • 目录项对象,表示目录项,是路径的组成部分。
  • 文件对象,表示进程打开的文件。

块设备

能够随机访问固定大小数据的硬件设备称为块设备,块就是这里固定大小的数据。常见块设备有硬盘、软盘、闪存等等。相对于块设备,还有一类设备被称为字符设备。字符设备就是按照字符流的方式有序地访问,比如串口、键盘等。数据的随机访问是区分它们的主要依据。内核没有专门的子系统处理字符设备,但提供了专门的子系统来管理块设备,因为块设备的执行性能要求很高。

块设备最小可寻址单元是扇区,是块设备的基本单元,扇区大小为2的整数倍,常见的是512字节。块是最小逻辑可寻址单元,内核的所有操作都是按照块来执行的,块的大小不能超过页大小。扇区也称硬扇区或设备块,块也称文件块或I/O块。

缓冲区

当一个块被调入内存时会存储在一个缓冲区中,每个缓冲区与块相对应。内核需要知道哪个块设备和对应哪个缓冲区,所以需要一个描述符来描述,它使用buffer_head结构体来表示,即缓冲区头。对于磁盘来说,它的作用就是来描述磁盘块和物理内存缓冲区之间的映射关系。

请求队列

块设备会将挂起的I/O请求放到请求队列中,该队列由reques_queue结构体表示,是一个双向链表。块设备驱动程序会根据请求队列的请求去操作块设备。

磁盘I/O调度

磁盘寻址是最慢的操作之一,每次寻址都会耗费较多时间,所以需要尽量提升寻址性能。为了提升寻址性能,不能简单地按请求顺序去执行操作,而是会做一些合并和排序的预操作,以此来提升整体性能。内核中负责I/O请求的子系统称为I/O调度程序。

IO调度主要通过合并和排序来减少寻址时间,合并就是将两个或两个以上请求合并为一个请求,比如扇区相邻的请求就可以合并,只需一次就能拿到多次请求的数据。而排序就是让整个请求队列按照扇区增长的方向有序地排序,这样就能保证磁盘头以直线方向移动,从而缩短寻址时间。

  • linus电梯调度,当有新请求加入队列时会先检查是否可以合并,包括向前向后合并。
  • deadline调度,为了解决linus电梯带来的饥饿问题而提出deadline调度,一个对磁盘同一位置的操作流可能会导致较远的请求无法得到运行机会。deadline调度引入了超时时间来解决这个问题,保证请求最多等待不超过超时时间。
  • 预测IO调度,它提供了良好的读响应和吞吐量,提交请求后不直接返回处理其他请求,而是有意空闲片刻来处理应用提交的其它读请求,结束后才回到原来位置,继续往下执行。
  • 完全公正排队IO调度,它以时间片轮转来调度请求队列,每个队列中会被选中若干请求去执行。每个进程都有自己的等待队列,从而保证每个进程能有公平的磁盘资源。

进程地址空间

内核除了管理自身内存外还要管理用户空间的进程的内存,这个内存就是进程地址空间,即用户空间看到的内存。linux采用虚拟内存技术,每个用户进程都以虚拟方式来共享内存,对某个进程它就像能访问所有物理内存。

进程地址空间由进程可寻址的虚拟内存组成,内核允许进程使用虚拟内存中的地址。一个进程的地址空间可以与另外一个进程的地址空间相同,它们彼此不相干,即是线程。

进程只能访问有效内存区域的内存地址,每个内存区域具有相关权限,如果一个进程访问不在有效范围内的内存区域,则内核会将该进程终止。

内存区域包括以下对象:

  • 可执行文件代码的内存映射,即代码段。
  • 可执行文件的已初始化全局变量的内存映射,即数据段。
  • 未初始化全局变量的内存映射。
  • 进程用户空间栈的零页内存映射。
  • 动态链接共享库的代码段、数据段、未初始化全局变量。
  • 内存映射文件。
  • 共享内存段。
  • 匿名内存映射,比如由malloc()分配的内存。

内核通过mm_struct结构体来描述进程的地址空间,它包含了所有相关信息。

页表

当程序访问一个虚拟地址时需要将虚拟地址转为物理地址,然后CPU才能解析地址访问请求。地址的转换需要通过查页表来完成,将虚拟地址分段,使每个虚拟地址都作为一个索引指向页表,页表则会指向下一级的页表或最终的物理页面。

linux使用三级页表来完成地址转换,利用多级页表能够节约地址转换需要占用的存放空间。顶级页表是页全局目录,二级页表是中间页目录,最后一级页表指向物理页面。每个进程都有自己的页表,线程则会共享页表。

页高速缓存

页高速缓存是linux内核实现的磁盘缓存,作用是减少磁盘的IO操作。主要是将磁盘数据缓存到物理内存中,从而让对磁盘的访问变为对物理内存的访问。这个主要的思想就是数据被一旦被访问,那么它很可能短期会再次被访问。

写缓存策略

  • 不缓存策略,高速缓存不换成任何写操作。
  • 写操作自动更新内存缓存和磁盘文件策略。
  • 回写策略,写操作直接写到缓存中,后端存储不会立即更新,而是将被写入的页面标记为脏,并加入到脏页链表中。然后由回写进程周期将脏页链表写回到磁盘中。

缓存回收策略

  • 最近最少使用,LRU,回收最老时间戳的页面。
  • 双链策略,活跃链表和非活跃链表,活跃链表的不会被换出,而非活跃链表的会被换出。

设备类型

  • 块设备,提供对数据的随机访问,比如硬盘、光碟、闪存等。
  • 字符设备,提供数据的流式访问,比如鼠标、键盘、打印机等。
  • 网络设备,提供对网络的访问,比如internet。网络设备打破了unix一切皆文件的设计思想。

有些设备是虚拟的,仅仅提供访问内核功能而已,称为伪设备。比如内核随机数发生器、空设备、零设备、满设备、内存设备。

专注于人工智能、读书与感想、聊聊数学、计算机科学、分布式、机器学习、深度学习、自然语言处理、算法与数据结构、Java深度、Tomcat内核等。