Linux操作系统总结报告

目录

前言

操作系统是管理计算机硬件的一种大型软件,我们所有运行的日常软件都基于操作系统之上。操作系统本质上也是软件,也处处体现着软件设计的本质思想,比如:抽象,虚拟,中间层等。从其功能等几大部分来看,内存管理,进程管理,I/O设备,文件管理等等,都具有抽象和虚拟特征,并且操作系统本身就是一大中间层,介于应用软件和硬件中间,平滑我们与硬件的联系。

内存管理

对接物理层面

操作系统的底层与主存对接,对主存空间按页划分,每一页放在比如pageinfo的struct里,这样我们有了所有内存物理页的信息。在这一层面,操作系统着重于对内存的使用与分配,目的是使得内存得以充分使用,减少外部碎片(内部碎片问题由分页机制解决,最大内部碎片不会超过一页)。

与现实使用之间的矛盾

如果直接在实模式下编程,直接面对物理空间的话,可能会面临没有足够大的连续内存块的问题,实际上多个小的内存块加起来是够用的,这就意味着我们需要离散地去使用。虽然这样也并不难实现,但是对用户来说,空间的使用丧失逻辑性,我们使用一个连续的数组,顺序遍历时,地址可能要跳着走,会造成使用的不方便。
同时,还会面临安全问题,因为直接使用物理地址,意味着我们可以使用任意一块地址,这可能是别的用户的,或别的进程的,甚至可能是内核的。而操作系统在管理这些内存时,是根据内存空间合理分配的一块空间,并不会根据你是什么进程/用户/等情况来分配的,即第 i 块内存可能是进程P1的,而i + 1块可能是进程P9的。所以操作系统不会在此处标记属于谁的,这管理太麻烦了,而且这样设计不够单一性。

段页机制和虚拟地址

虚拟地址的设计,便是符合抽象,虚拟的。它是对内存空间的地址做了逻辑化的展现,背后与物理页的关联交给其映射机制,硬件上由MMU处理。比如一个4G的地址空间大概如下:

/*
 * Virtual memory map:                                Permissions
 *                                                    kernel/user
 *
 *    4 Gig -------->  +------------------------------+
 *                     |                              | RW/--
 *                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                     :              .               :
 *                     :              .               :
 *                     :              .               :
 *                     |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| RW/--
 *                     |                              | RW/--
 *                     |   Remapped Physical Memory   | RW/--
 *                     |                              | RW/--
 *    KERNBASE, ---->  +------------------------------+ 0xf0000000      --+
 *    KSTACKTOP        |     CPU0's Kernel Stack      | RW/--  KSTKSIZE   |
 *                     | - - - - - - - - - - - - - - -|                   |
 *                     |      Invalid Memory (*)      | --/--  KSTKGAP    |
 *                     +------------------------------+                   |
 *                     |     CPU1's Kernel Stack      | RW/--  KSTKSIZE   |
 *                     | - - - - - - - - - - - - - - -|                 PTSIZE
 *                     |      Invalid Memory (*)      | --/--  KSTKGAP    |
 *                     +------------------------------+                   |
 *                     :              .               :                   |
 *                     :              .               :                   |
 *    MMIOLIM ------>  +------------------------------+ 0xefc00000      --+
 *                     |       Memory-mapped I/O      | RW/--  PTSIZE
 * ULIM, MMIOBASE -->  +------------------------------+ 0xef800000
 *                     |  Cur. Page Table (User R-)   | R-/R-  PTSIZE
 *    UVPT      ---->  +------------------------------+ 0xef400000
 *                     |          RO PAGES            | R-/R-  PTSIZE
 *    UPAGES    ---->  +------------------------------+ 0xef000000
 *                     |           RO ENVS            | R-/R-  PTSIZE
 * UTOP,UENVS ------>  +------------------------------+ 0xeec00000
 * UXSTACKTOP -/       |     User Exception Stack     | RW/RW  PGSIZE
 *                     +------------------------------+ 0xeebff000
 *                     |       Empty Memory (*)       | --/--  PGSIZE
 *    USTACKTOP  --->  +------------------------------+ 0xeebfe000
 *                     |      Normal User Stack       | RW/RW  PGSIZE
 *                     +------------------------------+ 0xeebfd000
 *                     |                              |
 *                     |                              |
 *                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                     .                              .
 *                     .                              .
 *                     .                              .
 *                     |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
 *                     |     Program Data & Heap      |
 *    UTEXT -------->  +------------------------------+ 0x00800000
 *    PFTEMP ------->  |       Empty Memory (*)       |        PTSIZE
 *                     |                              |
 *    UTEMP -------->  +------------------------------+ 0x00400000      --+
 *                     |       Empty Memory (*)       |                   |
 *                     | - - - - - - - - - - - - - - -|                   |
 *                     |  User STAB Data (optional)   |                 PTSIZE
 *    USTABDATA ---->  +------------------------------+ 0x00200000        |
 *                     |       Empty Memory (*)       |                   |
 *    0 ------------>  +------------------------------+                 --+
 *

从上至下大致为:内核区,cpu对应的内核栈,与用户公共访问的区域(mmio,页表,进程等),用户区域(栈,堆,data,bss,text等)
可见其特征是符合逻辑的,是线性的。整个空间是分成一段段的,每个段的起始地址,和段长度需要记录,这就用到了段机制。

从段表到页表,再到虚拟地址,最后到物理地址的过程

linux的可执行文件一般是ELF格式,其逻辑上是按照段的方式存储的,data段,text段等等,每个段前有一个结构存储其长度,和加载的位置,即虚拟地址:

struct Proghdr {
	uint32_t p_type;
	uint32_t p_offset;
	uint32_t p_va;
	uint32_t p_pa;
	uint32_t p_filesz;
	uint32_t p_memsz;
	uint32_t p_flags;
	uint32_t p_align;
};

这些段按照上述信息,能放入恰当的虚拟地址位置,并且会把段信息记录在LDT表中
在进程运行时,会把这些段的选择子放入寄存器中,这些寄存器会在LDT中找出段描述符,其中会有段的起始地址(虚拟地址),之后根据段偏移组合成数据的虚拟地址,再通过页表机制,找到其对应的物理地址。

段页机制的安全性

以上的虚拟地址都只是对一个进程有用的,每个进程的虚拟地址空间是不同的(除了内核的部分),这个虚拟地址只会映射到 程序刚加载进来时分配的内存。此时虚拟地址空间就像一个中间层,使得物理地址空间对用户不透明,用户根本不能知道其他用户/进程的物理地址。
同时,段之间是被隔离开了,访问一个内存时,都要经过一个段选择+段偏移的过程,这个由操作系统来完成,使得用户不会用错段。
虽然好像用户可能会通过修改虚拟地址,轻易地去访问内核区,但是这在保护模式下,内核区需特权级0,才能访问,用户是访问不到的。同时页表项pte的最后12位是flag位,标记了用户的访问权限的。

中断与异常

这里的中断指硬件中断,异常是process exception,由cpu发出的中断。
虽然多核可以实现真正意义上的并行,但是我们同时需要运行的进程太多,可能上几千个,靠硬件是远远不够的,所以需要并发,而并发的关键就是中断。
硬件中断的意义是:响应硬件中断的请求,尽快让其运行,因为硬件是与cpu并行的。
异常分为:故障,陷阱,错误。除了错误,会终止进程外,其余的本质上是,在正常的任务运行过程中,出现了需要暂时停下来去处理的事情。
对于并发:并发就是为了能够让任务A停一下,再让B做一会,这肯定是需要中断了,比如典型地是用时钟中断,当一个进程时间片用完,便去切换下一个进程。

中断的过程

我们的外设,会经常发出中断的请求,目的是为了让其得到cpu的响应,来处理准备事务或读取结果等。

  • 外设的端口上会有存放irq请求,其会通过总线发出,当其没有被屏蔽,且CPU来查询时,中断请求会正式发出:
    Linux操作系统总结报告
  • 随后便会进入排队器,进入排队器的都是有资格争用cpu的,这时候根据中断优先级,来选出“胜者”:
    Linux操作系统总结报告
  • 随后排队器的结果输出到中断向量形成部件,生成向量地址:
    Linux操作系统总结报告
  • 之后会在IDT表中找到对应的表项,其中有段选择符,offset,在GDT表中找到对应的段,然后根据offset找到入口地址;
    这个地址便是handler的地址,在idt init的时候,用SETGATE设置好了idt中的表项,里面便存了handler的selector。
  • 随后的代码流程:
    • handler汇编内容:
#define TRAPHANDLER_NOEC(name, num)					\
	.globl name;							\
	.type name, @function;						\
	.align 2;							\
	name:								\
	pushl $0;							\
	pushl $(num);							\
	jmp _alltraps

这是定义handler的宏汇编内容,因为irq是没有错误号,的所以push了0

  • 之后跳去_alltraps:
_alltraps:
	pushl %ds
	pushl %es
	pushal 

	movl $GD_KD, %eax
	movw %ax, %ds
	movw %ax, %es

	push %esp
	call trap

主要是保存现场,并调用trap函数

  • trap:如果是陷入指令,这就是从用户态陷入内核态的地方,之后会调用trap_dispatch函数
  • trap_dispatch:会根据传入的trapframe里的trapno(中断号),来选择不同的中断处理函数
  • 异常与系统调用
    异常的处理与上述雷同,只不过少了前期硬件处理阶段,但会传入错误号。
    而系统调用,是先进入syscall中断函数后,根据eax寄存器里的系统调用号选择不同的系统调用函数

进程管理

进程的切换,主要是由schedule通过算法得出下一个运行的进程,算法主要由Robin-Round,FIFO,Normal等。之后使用switch-to进行切换,在之前,会切换到内核栈,会由硬件push进cs, ip, ss, sp, eflag等寄存器值,之后由指令SAVE_ALL保存余下诸如通用寄存器等寄存器。随后该进程进入就绪队列,而新的进程进来,先进入其内核栈,将其保存的寄存器值pop到寄存器,即restore_all,然后硬件恢复保存的内容,最后切换至用户栈,进入用户态运行。

文件管理

VFS又是一种虚拟与抽象,并且是介于用户系统调用与文件操作之间的中间层。关系图如下:
Linux操作系统总结报告
VFS是所有文件系统所面对的一种接口,它提供了最一般的组织形式,具体数据结构如下:
Linux操作系统总结报告

  • 系统里会有一个系统文件打开表,里面是用双链表将file对象连接起来,file里有文件的打开次数,读写位置,dentryfile_operations等。其中file_operations对应具体的文件操作实现。
  • dentry是一个目录项,它有文件最基本的信息,以及inode的指针
  • inode存放数据的地址等信息,如果这个文件是个目录,那么这个数据是其子目录/文件的目录项信息
  • 一个进程里会有一个文件打开表,会用fd_array存放dentry的指针,而对应的fd文件描述符即fd_array的下标
  • fs_struct存放文件系统的信息,如根目录的dentry,当前目录的dentry

驱动设备程序

使用设备

这里存在对文件的进一步抽象,即所有的设备都会用一个文件来代表。用操作文件的方法,实现对设备的操作,因为操作设备实际上就是对端口的编程,而这和对磁盘的操作如出一辙。

驱动程序

file的操作函数,需要驱动程序的进一步的解释,来让设备识别

案例

因为redis比较注重性能,下面都以它举例。
在redis里,它会频繁地计算时间用于不同用途,比如10s一次更新LRU时钟,更新日志,计算服务器上线时间等,这需要获取系统的时间,那么必然要需要使用系统调用,如上所述,系统调用要触发中断,中断需要保护现场,完了还要恢复现场,需要耗费一些时间,但如果频繁的使用势必影响高性能服务器的性能。
为了不那么频繁地使用系统调用,对于一些对精度要求不高的功能,redis采用了服务器时间缓存的方法,即100ms获取一下系统时间,存在unixtime里,当其他功能需要使用时,直接读取unixtime,而不是使用系统调用。
redis采用了单线程模型,主要是考虑了线程切换的代价,它采用了epoll/kqueue等多路复用的方法,去监听多个socket,而不是一个socket创建一个线程。考虑到linux里没有线程,它是用进程模拟的,所以可以从进程切换角度来解释。由上面的论述可得知,进程在切换时,需要保护现场,切换三次栈,第一次是pre进程进入内核栈,之后切换至next进程的内核栈,最后进入next的用户栈,等等,开销很大。一个redis服务器在使用时,可能会面临巨量的socket连接,这种用线程的方式无疑会严重影响性能,即使使用线程池,也只是省去创建和撤销线程的开销,切换线程代价并没有省去。所以redis用单线程,会大大提高性能表现。

上一篇:STM32F429减小FATFS文件系统占用RAM大小


下一篇:keil 代码量、RAM使用情况