从系统的角度分析影响程序执行性能的因素
1.Linux系统概念模型
从全局的角度来看,Linux系统分为内核空间和用户空间,但毫无疑问内核空间是Linux系统的核心,因为内核负责管理整个系统的进程、内存、设备驱动程序、文件,决定着系统的性能和稳定性。于是从这个角度我构建的Linux系统的概念模型如下图所示:
此模型将Linux系统主要划分为四个模块:内存管理、进程管理、设备驱动程序、文件系统。这四个部分也是一个操作系统最基本也是最重要的功能。
2.概念模型解析
2.1 内存管理
Linux系统采用虚拟内存管理技术,使得每个进程都有各自互不干涉的进程地址空间。该空间是块大小为4G的线性虚拟空间,用户所看到和接触到的都是该虚拟地址,无法看到实际的物理内存地址。利用这种虚拟地址不但能起到保护操作系统的效果(用户不能直接访问物理内存),而且更重要的是,用户程序可使用比实际物理内存更大的地址空间。
内存管理主要有分为如下几个功能:地址映射、虚拟地址管理、物理内存管理、内核空间管理、页面换入换出策略和用户空间内存管理,这些模块的架构图如下所示:
2.2 进程管理
进程管理是Linux系统非常重要的一部分,进程管理虽然不像内存管理、文件系统等模块那样复杂,但是它与其他几个模块的联系是非常紧密的。进程管理主要包括进程的创建、切换、撤销和进程调度。
2.2.1 进程的创建、切换、撤销
进程的创建:在Linux编程中,一般采用fork()函数来创建新的进程,当然,那是在用户空间的函数,它会调用内核中的clone()系统调用,由clone()函数继续调用do_fork()完成进程的创建。
整个进程创建过程可能涉及到如下函数:
fork()/vfork()/_clone----------->clone()--------->do_fork()---------->copy_process()
进程的切换:进程切换又称为任务切换、上下文切换。它是这样一种行为,为了控制进程的执行,内核挂起当前在CPU上运行的进程,并恢复以前挂起的某个进程的执行。跟函数的调用类似,进程切换时,一般要在CPU上装载要执行进程的进程上下文。进程的硬件上下文指:可执行程序上下文的一个子集,是进程恢复执行前装入寄存器的一组数据。其中一部分放在TSS段,即任务状态段,剩余部分存放在内核态堆栈中。进程的切换只发生在内核态,在执行进程切换之前,用户态进程使用的所有寄存器内容都已保存在内核态堆栈上。
进程的切换有两种方法,一种是硬件切换,一种是软件切换。软件切换就是利用程序逐步执行切换,它的优点是,可以对切换时装入的数据进行合法性检查,执行时间虽与硬件切换大致相同,但仍有可改进的地方。
进程的撤销:进程终止后,需要通知内核以便内核释放进程所拥有的资源,包括内存、打开文件以及其他资源,如信号量。进程终止的一般方式是调用exit()库函数,该函数释放C函数库所分配的资源,执行编程者所注册的每个函数,并结束从系统回收进程的那个系统调用。
2.2.2 进程调度
Linux操作系统的进程调度基于分时技术:多个进程以“时间多路复用”方式运行,因为CPU的时间被分成“片”,给每个可运行进程分配一片。调度策略也是根据进程的优先级对它们进行分类。在Linux中,进程的优先级是动态的。调度程序跟踪进程正在做什么,并周期性地调整它们的优先级。根据不同的分类标准,可以把进程分成不同的类型。比如可以把一个进程看作是“I/O受限”或“CPU受限”。也可把进程区分为以下三类:交互式进程、批处理进程、实时进程。Linux的进程是抢占式的,无论是处于内核态还是用户态。时间片的长短对系统性能是很关键的:它既不能太长也不能太短。如果平均时间片太短,由进程切换引起的系统额外开销就变得非常高。如果平均时间片太长,进程看起来就不再是并发执行的。对时间片大小的选择始终是一种折中。
2.3 设备驱动程序
和Linux操作系统的其它部分类似,设备驱动程序运行在高特权级的处理器环境中,从而可以直接对硬件进行操作,但正因为如此,任何一个设备驱动程序的错误都可能导致操作系统的崩溃。设备驱动程序实际控制操作系统和硬件设备之间的交互。
设备驱动程序提供一组操作系统可理解的抽象接口完成和操作系统之间的交互,而与硬件相关的具体操作细节由设备驱动程序完成。其中设备分为字符设备、块设备、网络设备。
2.4 文件系统
Linux 操作系统将独立的文件系统组合成了一个层次化的树形结构,并且由一个单独的实体代表这一文件系统。Linux 中最普遍使用的文件系统是 Ext2,它也是 Linux 土生土长的文件系统。但 Linux 也能够支持 FAT、VFAT、FAT32、MINIX 等不同类型的文件系统,从而可以方便地和其它操作系统交换数据。由于 Linux 支持许多不同的文件系统,并且将它们组织成了一个统一的虚拟文件系统(VFS)。
虚拟文件系统(Virtual File System, 简称VFS), 是Linux内核中的一个软件层,用于给用户空间的程序提供文件系统接口;同时,它也提供了内核中的一个 抽象功能,允许不同的文件系统共存。系统中所有的文件系统不但依赖 VFS 共存,而且也依靠VFS协同工作。
为了能够支持各种实际文件系统,VFS 定义了所有文件系统都支持的基本的、概念上的接口和数据结构;同时实际文件系统也提供 VFS 所期望的抽象接口和数据结构,将自身的诸如文件、目录等概念在形式 上与VFS的定义保持一致。换句话说,一个实际的文件系统想要被Linux支持,就必须提供一个符合VFS标准的接口,才能与 VFS 协同工作。实际文件系统在统一的接口和数据结构下隐藏了具体的实现细节,所以在VFS 层和内核的其他部分看来,所有文件系统都是相同的。
下图显示了VFS在Linux内核中与实际的文件系统的协同关系。
3.影响应用程序性能表现的因素
在上述的系统模型中,编写一个程序test.c来查看影响应用程序性能的表现,test.c的代码如下:
#include<stdio.h>
void longa() {
int i,j;
for(i = 0; i < 1000000; i++)
j=i;
}
void foo2() {
int i;
for(i=0 ; i < 10; i++)
longa();
}
void foo1() {
int i;
for(i = 0; i< 100; i++)
longa();
}
int main(void) {
foo1();
foo2();
}
我采用perf工具来查看系统在运行该程序时的性能表现。
在测试中可以看到cpu的利用率、进程切换次数、cpu迁移等数据。
再用perf的另一个功能来查看程序的时间运行情况:
在这里可以很明显的看出占用率最高的是test.c里的longa函数,这是因为在这个函数里执行了100万次的循环。
通过这个程序,可以系统的总结出影响应用程序性能表现的因素有:
- CPU的频率及利用率。CPU的频率决定了处理速度,利用率可以表现程序的多数时间是花费在CPU上还是IO上
- 进程切换次数。这一点记录了程序运行中发生了多少次进程切换,频繁的进程切换应该被避免。
- Cache命中率。缓存的命中率高低也是程序执行性能很重要的一点。
- 磁盘读取速度。该项决定了系统读盘的速度。
- 应用程序的自我优化。如果一个应用程序像test.c一样使用了次数非常大的循环,那么性能表现是会十分糟糕的,因此程序自身的编写优化也是很重要的一个因素。
4.总结
本文首先根据Linux的核心功能构建Linux的系统逻辑模型,再对模型的各个模块进行探究。最后结合系统模型和具体的程序执行情况,总结出影响应用程序性能表现的因素。
最后,通过孟老师和李老师教授的Linux操作系统课程,学到了关于Linux操作系统的进程管理、中断处理、系统调用、设备驱动、可执行程序的工作原理等等知识,对Linux操作系统的底层有了一些更深入的收获与理解,在此感谢两位老师的辛勤付出!