实验总结分析报告:从系统的角度分析影响程序执行性能的因素
一、精简的Linux系统概念模型
一个精简的Linux系统概念模型应该包括:
- 内核
- 系统调用
- Shell
- 用户程序
二、Linux内核
精简的Linux内核应该包括:
- 内存管理
- 文件管理
- 进程管理
- 中断

2.1内存管理
系统运行过程中,所有进程都必须占用一定数量的内存,它用来存放从磁盘载入的程序代码,或是存放用户输入的数据。
Linux采用虚拟内存管理技术,使得每个进程都有各自互不干涉的进程地址空间。
Linux把进程地址空间分为内核区和用户区。用户进程通常只能访问用户空间的虚拟地址,不能访问内核空间虚拟地址。只有当用户进程进行系统调用等时刻才可以访问到内核空间。
每个进程的用户空间都是完全独立、互不相干的。每当进程切换时,用户空间就会跟着变换。但是内核空间是由内核负责的,它并不会跟着进程改变。
当内核进程需要用到用户区数据的时候,需要从用户去复制数据,而不是直接访问。
对内存进行管理时,Linux将内存按照段、页进行划分。同时通过虚拟内存,是得在进程看来,进程拥有完整的内存空间。
当内存页面不足时,可以根据情况选择合适的页面调度算法,如:FIFO算法,LRU算法等。
2.2文件管理
Linux系统中有一个重要概念:一切都是文件。Linux将每个硬件设备都看作是一个文件,用户可以通过读写文件的方式来与硬件交互。
为了统一不同硬件之间的操作,我们规定一个虚拟的层次,称为VFS(Virtual File System),用来处理文件相关的系统调用。
VFS
在用户空间中,当用户进程需要对文件进行操作时,就进行系统调用,包括read()、write()、open()等等。
而系统调用转而调用对应函数sys_read()、sys_write()、sys_open()等,在VFS层结构中通过file结构中的f_op找到对应文件并进行操作。

2.3进程管理
进程就是程序执行的过程,它拥有运行所需的资源,是资源分配的单位。
Linux内核中使用struct task_struct来描述进程。
进程的创建
父进程调用fork()系统调用,创建新的进程。
fork()系统调用实际上最终调用_do_fork内核函数。而_do_fork函数主要执行copy_process()(复制父进程task_struct并初始化)和wake_up_new_task()(将子进程加入就绪队列)。
Linux内核中的进程状态
进程拥有三种基本状态:就绪态、运行态、阻塞态。但是在Linux内核管理中的进程状态与这3者不同。
-
TASK_RUNNING(就绪态,但是没有在运行)
当使用fork()系统调用来创建一个新进程时,新进程的状态是TASK_RUNNING(就绪态,但是没有在运行)。
当调度器选择这个新创建的进程运行时,新创建的进程就切换到运行态,它也是TASK_RUNNING。
在Linux内核中,当进程是TASK_RUNNING状态时,它是可运行的,也就是就绪态,是否在运行取决于它有没有获得CPU的控制权,也就是说这个进程有没有在CPU中实际执行。
-
tsk→exit_state(终止状态)
对于一个正在运行的进程,调用用户态库函数exit()会陷入内核执行函数do_exit(),也就是终止进程,那么会进入tsk→exit_state状态,即进程的终止状态。
tsk→exit_state状态的进程一半叫做僵尸进程,Linux内核会在适当的时候把僵尸进程给处理掉,处理掉之后进程描述符被释放,该进程才从Linux系统里消失。
-
TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE(阻塞态的两种状态)
进程调度策略
利用进程调度策略满足CPU和进程的运行需求,调高系统运行效率。
常见的调度策略有:先来先服务调度算法、短作业(进程)优先调度算法、高优先权优先调度算法等。
进程调度时机
进程时间片用完、系统调用退出、中断处理退出、进程状态改变、高优先级进程被创建或转为就绪态
2.4中断
中断(广义)会改变处理器执行指令的顺序,通常与CPU芯片内部或外部硬件电路产生的电信号相对应。
当一个中断信号到达时,CPU必须停止它当前正在做的事,并且切换到一个新的活动。
中断的过程
- 中断源发出中断请求。
- 判断当前处理机是否允许中断以及该中断是否被屏蔽。
- 排队。
- CPU处理完当前程序指令或无法完成指令,保存上下文,转入中断处理服务。
- 执行中断处理程序。
- 恢复现场,执行“中断返回”回到被中断的程序或转入其他程序。
三、Shell
Shell是操作系统的最外层,是给用户与操作系统提供交互操作的命令解释器。
四、系统调用
Linux把进程地址空间分为内核区和用户区。用户进程通常只能访问用户空间的虚拟地址,不能访问内核空间虚拟地址。只有当用户进程进行系统调用等时刻才可以访问到内核空间。
与系统调用打交道的方式是通过库函数的方式,库函数用来把系统调用给封装起来。
五、用户程序
用户程序是为了满足用户不同领域、不同问题的应用需求而提供的软件。
六、举例验证
读写文件:
读写文件前,先在系统打开文件表项struct file中查找是否已经打开。
如果没有打开,执行open,调用系统调用sys_open。
sys_open进行查找,找到文件控制块,建立dentry项,新建一个struct file加入系统文件打开表项和进程打开文件表。
进程文件表项中有fd数组,将空闲的fd指针指向已经创建的file结构,返回fd文件描述符。
调用file对象中的f_op函数操作来对文件进行write和read操作。
七、影响Linux系统性能的因素
使用perf 进行性能分析,主要使用下面两个命令:
- perf record:保存perf追踪的内容,文件名为perf.data。
- perf report:解析perf.data的内容。
编写用户程序:
#define NUM 100000000 #include <iostream> using namespace std; int tmp1 = 10, tmp2 = 10; void f1(int *x, int *y) { *x += *y; *x += *y; } int main() { for (int i = 0; i < NUM; i++) { f1(&tmp1, &tmp2); } cout << tmp1 << endl; }
监测结果



可以看出
*x+=*y;
*x+=*y;
此处的6次引用消耗了过多的资源。
优化程序
#define NUM 100000000 #include <iostream> using namespace std; int tmp1 = 10, tmp2 = 10; void f2(int *x, int *y) { *x += 2 * *y; } int main() { for (int i = 0; i < NUM; i++) { f2(&tmp1, &tmp2); } cout << tmp1 << endl; }
查看结果

对比二者结果,可以看到优化后的任务占用CPU的真实时间几乎为优化前的一半。
不合理的内存读取策略会降低系统运行性能。
硬件因素
CPU
利用CPU的超线程技术,提高CPU利用率。
同时CPU的主频越高,CPU的运行速度越快。
还可以增加CPU的核心数量,降低单个CPU在高负载运行情况下出现高延迟的可能性。
内存
当内存过小时,会频繁出现进程的阻塞情况。当内存过大时,会造成浪费。
虚拟内存技术能够解决内存不足的情况,但是当占用的虚拟内存过多时,系统的性能会下降。
为了能够更好的利用大内存,最好使用64位Linux系统。
磁盘
最好使用读写速度快,启动延迟低的硬盘产品,比如固态硬盘产品。
软件因素
中断处理
由于系统运行过程中,会频繁进入中断处理,当中断处理的时间过长或者中断处理的恢复过程时间过长时,会造成应用程序运行性能的下降。
解决方法是降低进行中断处理的频次,或者优化中断处理的过程,减少切换进程上下文的开销。
文件系统
操作系统的运行离不开文件系统,文件系统中有许多的文件操作函数,因此各类操作也会影响应用程序的运行性能。
最好选择合适的文件系统,同时设备性能也会对文件系统产生影响,所以也要选择合适的硬件设备。
应用程序自身
优化时间复杂度和空间复杂度,减少不必要的时空支出。
八、致谢
感谢孟老师和李老师的辛勤付出。
浙公网安备 33010602011771号