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

 

一、精简的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必须停止它当前正在做的事,并且切换到一个新的活动。

中断的过程

  1. 中断源发出中断请求。
  2. 判断当前处理机是否允许中断以及该中断是否被屏蔽。
  3. 排队。
  4. CPU处理完当前程序指令或无法完成指令,保存上下文,转入中断处理服务。
  5. 执行中断处理程序。
  6. 恢复现场,执行“中断返回”回到被中断的程序或转入其他程序。

三、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系统。

磁盘

最好使用读写速度快,启动延迟低的硬盘产品,比如固态硬盘产品。

软件因素

中断处理

由于系统运行过程中,会频繁进入中断处理,当中断处理的时间过长或者中断处理的恢复过程时间过长时,会造成应用程序运行性能的下降。

解决方法是降低进行中断处理的频次,或者优化中断处理的过程,减少切换进程上下文的开销。

文件系统

操作系统的运行离不开文件系统,文件系统中有许多的文件操作函数,因此各类操作也会影响应用程序的运行性能。

最好选择合适的文件系统,同时设备性能也会对文件系统产生影响,所以也要选择合适的硬件设备。

应用程序自身

优化时间复杂度和空间复杂度,减少不必要的时空支出。

八、致谢

感谢孟老师和李老师的辛勤付出。

posted @ 2021-05-18 22:59  PureDeep  阅读(160)  评论(0)    收藏  举报