实验总结分析报告:从系统的角度分析影响程序执行性能的因素

1.Linux系统概念模型

Linux模型的主要模块分以下几个部分:存储管理、进程管理、文件系统、中断和系统调用等。 linux的各个模块之间互相依赖,共同完成操作系统的各项基本的功能和对系统的管理工作,对底层来说,与硬件交互管理所有的硬件资源,对上层来说,为用户程序(应用程序)提供一个良好的执行环境。linux的整体架构如图。内核向上为用户提供系统调用接口,向下调用硬件服务接口。 其自身实现了如上文提到的进程管理等功能,在内核外还提供如系统命令、编译器、解释 器、函数库等基础设施。 

  

1.1进程管理

进程管理用来负责控制进程对 CPU 资源的使用。所采取的调度策略是各进程能够公平合理地访问 CPU,同时保证内核能及时地执行硬件操作。数据结构struct task_struct是描述进程的,保存了程序执行过程中的一些必要信息,如进程的id,进程的状态,堆栈等。进程主要分为进程的创建、进程的切换、进程的调度算法和进程的状态。

 (1)进程创建的主要过程为:

  创建一个进程是复制当前进程的信息,就是通过_do_fork函数来创建了一个新进程。因为父进程和子进程的绝大部分信息是完全一样的,但是有些信息是不能一样的,比如 pid 的值和内核堆栈。还有将新进程链接到各种链表中,要保存进程执行到哪个位置,有一个thread数据结构记录进程执行上下文的关键信息。

(2)进程切换的主要过程为:

  在实际代码中,每个进程切换基本由两个步骤组成。切换页全局目录(CR3)以安装一个新的地址空间,这样不同进程的虚拟地址如0x8048400(32位x86)就会经过不同的页表转换为不同的物理地址。切换内核态堆栈和进程的CPU上下文,因为进程的CPU上下文提供了内核执行新进程所需要的所有信息,包含所有CPU寄存器状态。

(3)进程调度的策略为:

  Linux系统中常用的几种调度策略为SCHED_NORMAL、SCHED_FIFO、SCHED_RR、SCHED_BATCH。其中SCHED_NORMAL是用于普通进程的调度类,而SCHED_FIFO和SCHED_RR是用于实时进程的调度类,优先级高于SCHED_NORMAL。

(4)进程的状态切换:

 

 

 

1.2内存管理

内存管理用于确保所有进程能够安全地共享机器主内存区,同时,内存管理模块还支持虚拟内存管理方式,使得 Linux 支持进程使用比实际内存空间更多的内存容量。并可以利用文件系统把暂时不用的内存数据块会被交换到外部存储设备上去,当需要时再交换回来。Linux采用虚拟地址,在 32 位 Linux 系统上每个进程有4GB 的进程地址空间,在用户态下,只能访问 0x00000000~0xbfffffff 的地址空间,而内核态下可以访问全部空间。linux的地址分为逻辑地址、线性地址和物理地址。逻辑地址和线性地址在 32 位和 64 位上目前都是虚拟地址,需要依次经过分段映射和分页映射最后才转换成物理地址。这个映射计算地址的过程一般由 CPU 内部的 MMU(内存管理单元)负责把虚拟地址转换为物理地址。

 

1.3文件系统

文件系统模块 用于支持对外部设备的驱动和存储。虚拟文件系统模块通过向所有的外部存储设备提供一个通用的文件接口,隐藏了各种硬件设备的不同细节。从而提供并支持与其它操作系统兼容的多种文件系统格式。VFS就把这些不同的文件系统做一个抽象,提供统一的API访问接口,这样,用户空间就不用关心不同文件系统中不一样的API了。VFS所提供的这些统一的API,再经过System Call包装一下,用户空间就可以经过SCI的系统调用来操作不同的文件系统。文件关系示意图如下图所示:

 

 

1.4中断

中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的 CPU 暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。Linux中通常分为外部中断(又叫硬件中断)和内部中断(又叫异常)。中断上下文与进程上下文不一样,内核执行中断服务程序的时候,处于中断上下文。中断处理程序并没有自己的独立的栈,而是使用了内核栈,其大小一般是有限制的(32bit 机器 8KB)。所以其必须短小精悍。同时中断服务程序是打断了正常的程序流程,这一点上也必须保证快速的执行。同时中断上下文中是不允许睡眠,阻塞的。

Linux中断机制由三部分组成:

  (1)中断子系统初始化:内核自身初始化过程中对中断处理机制初始化,例如中断的数据结构以及中断请求等。

  (2)中断或异常处理:中断整体处理过程。

  (3)中断API:为设备驱动提供API,例如注册,释放和激活等。

 

1.5系统调用

Linux系统总体上可以划分为内核态和用户态,也可以说是内核和应用程序,系统调用就是提供给用户程序的一组可以访问内核的接口。普通的函数调用是通过将参数压栈的方式传递的。系统调用从用户态切换到内核态,在用户态和内核态这两种执行模式下使用的是不同的堆栈,即进程的用户态堆栈和进程的内核态堆栈。系统调用的主要过程为:

  (1)用户态执行 int $0X80 或者 syscall 触发系统调用。

  (2)利用寄存器保存现场,其中EAX中存放系统调用号。

  (3)CPU切换到内核态执行system_call(32or64)汇编代码,此时需要从EAX寄存器中获取系统调用号,内核才能知道执行哪个系统调用。

  (4)执行完后恢复现场。

  (5)系统调用返回。

 

举例分析: 

以读文件为例子。首先,read函数所在的程序属于当前进程,并处于运行态。当执行到read函数时,会发生系统调用,从用户态陷入到内核态,根据系统调用号,在系统调用表中找到与之对应的sys_read函数,开始执行。由于之前打开文件时已经创建并初始化好了file系统打开文件表,所以sys_read通过fd就直接得到了对应的file。以普通文件为例,会具体执行file结构中的file_operation结构体中的read(映射到一个实际的和磁盘交互的read函数),从而在磁盘中真正的读取到文件信息了。

 

2.影响应用程序性能表现的因素及原因

(1)CPU:CPU 的速度与性能很大一部分决定了系统整体的性能。

(2)内存:Linux虚拟内存虽然可以缓解物理内存的不足,但是占用过多的虚拟内存,频繁换页,应用程序的性能将明显下降。

(3)磁盘读写(I/O)能力:磁盘的 I/O 能力会直接影响应用程序的性能。

(4)网络带宽:稳定、高速的带宽,可以保证应用程序在网络上畅通无阻地运行。

(5)缓存和分支预测的命中率。

(6)代码中采用消除循环、减少不必要的过程引用和内存引用、循环展开等优化。

举例:分支预测技术和预测错误惩罚

  • 当遇到条件分支时,无法确定继续取指的位置
    • 选择分支:将控制转移到分支目标
    • 不选择分支:继续下一个指令
  •  直到分支/整数单元的结果确定后才能解决

 

分支预测

  • 猜测会走哪个分支
  • 在预测的位置开始执行指令
    • 但不要真修改寄存器或内存数据

 

我们已经看到正确的分支预测会提高程序的效率,但错误的分支预测的影响也可能非常大,会导致程序性能不增反降,但是这并不意味着所有的程序分支都会减缓程序的执行。实际上,现代处理器中的分支预测逻辑非常善于辨别不同的分支指令的有规律的模式和长期的趋势。例如,在合并函数中结束循环的分支通常会被预测为选择分支,因此只在最后一次会导致预测错误处罚。在我们的应用程序中,尽可能优化分支预测技术,减少预测错误惩罚。

 

 

 

 

 

 

 

 

posted @ 2021-05-18 13:33  梁明栋  阅读(118)  评论(0)    收藏  举报