Joehanm  

从系统的角度分析影响程序执行性能的因素

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

1、请您根据本课程所学内容总结梳理出一个精简的Linux系统概念模型,最大程度统摄整顿本课程及相关的知识信息,模型应该是逻辑上可以运转的、自洽的,并举例某一两个具体例子(比如读写文件、分配内存、使用I/O驱动某个硬件等)纳入模型中验证模型。

2、然后将一个应用程序放入该系统模型中系统性的梳理影响应用程序性能表现的因素,并说明原因。

3、产出要求是发表一篇博客文章,长度不限,1要简略,2是重点,只谈自己的思考和梳理,严禁引用任何资料(包括本课程的资料)造成文章虚长。

一、精简的Linux系统概念模型

通俗的操作系统主要是包含软件和硬件两部分统一运行的,并由操作系统来统一管理硬件的软件系统,一个精简的操作系统应该包括进程管理,文件管理,内存管理和中断机制四个部分。

二、文件管理

1.1文件表征与文件目录

文件系统(File System)是文件存放在磁盘等存储设备上的组织方法,其主要体现在对文件和目录的组织上。虽然文件系统的规格多种多样,但就原理来说,其思路是及其相似的。

文件的表征:定义FCB(File Control Block)来表征一个文件,通常由文件数据的指针,文件的大小,文件的保护信息,文件的使用计数等其他描述性的信息组成。在Linux系统中,使用inode来表征一个文件。注意,inode中不包括文件名,在Linux中文件名和文件索引被记录在另一个表上用来实现快速“按名查找”

文件目录:可以把文件目录看成是一种特殊的文件,它可以看作书的目录部分,用它来定义文件之间的层次结构。

Bootblock(引导块):用来在系统启动时加载必要的信息

SuperBlock(超级块):inode用来描述一个文件,同理,SuperBlock就用来表征一个文件系统

1.2 VFS

VFS(Virtual File System):正如前文所说,不同规格的文件系统的思路其实是大同小异的,虽然具体操作是实现差距巨大,我们可以定义一个虚拟的层次,用来调用文件系统内部的方法,而不管其具体实现,就是VFS.

使用VFS接口适配举例

用户写入一个文件,使用POSIX标准的write接口,会被操作系统接管,转调sys_write这个系统调用(属于SCI层)。然后VFS层接受到这个调用,通过自身抽象的模型,转换为对给定文件系统、给定设备的操作,这一关键性的步骤是VFS的核心,需要有统一的模型,使得对任意支持的文件系统都能实现系统的功能。这就是VFS提供的统一的文件模型(common file model),底层具体的文件系统负责具体实现这种文件模型,负责完成POSIX API的功能,并最终实现对物理存储设备的操作。

三、内存管理

3.1虚拟内存管理技术

Linux操作系统采用虚拟内存管理技术,使得每个进程都有各自互不干涉的进程地址空间。该空间是块大小为4G的线性虚拟空间,用户所看到和接触到的都是该虚拟地址,无法看到实际的物理内存地址。利用这种虚拟地址不但能起到保护操作系统的效果(用户不能直接访问物理内存),而且更重要的是,用户程序可使用比实际物理内存更大的地址空间(具体的原因请看硬件基础部分)。

在讨论进程空间细节前,这里先要澄清下面几个问题:

第一、4G的进程地址空间被人为的分为两个部分——用户空间与内核空间。用户空间从0到3G(0xC0000000),内核空间占据3G到4G。用户进程通常情况下只能访问用户空间的虚拟地址,不能访问内核空间虚拟地址。只有用户进程进行系统调用(代表用户进程在内核态执行)等时刻可以访问到内核空间。

第二、用户空间对应进程,所以每当进程切换,用户空间就会跟着变化;而内核空间是由内核负责映射,它并不会跟着进程改变,是固定的。内核空间地址有自己对应的页表(init_mm.pgd),用户进程各自有不同的页表。

3.2 分页机制和分段机制

分页机制

分页机制就是把内存地址空间分为若干个很小的固定大小的页,每一页的大小由内存决定,就像Linux中ext文件系统将磁盘分成若干个Block一样,这样做是分别是为了提高内存和磁盘的利用率。试想一下,如果将磁盘空间分成N等份,每一份的大小(一个Block)是1M,如果我想存储在磁盘上的文件是1K字节,那么其余的999字节是不是浪费了。所以需要更加细粒度的磁盘分割方式,我们可以将Block设置得小一点,这当然是根据所存放文件的大小来综合考虑的,好像有点跑题了,我只是想说,内存中的分页机制跟ext文件系统中的磁盘分割机制非常相似。

分段机制
分段(Segmentation):这种方法是人们最开始使用的一种方法,基本思路是将程序所需要的内存地址空间大小的虚拟空间映射到某个物理地址空间。

每个程序都有其独立的虚拟的独立的进程地址空间,可以看到程序A和B的虚拟地址空间都是从0x00000000开始的。我们将两块大小相同的虚拟地址空间和实际物理地址空间一一映射,即虚拟地址空间中的每个字节对应于实际地址空间中的每个字节,这个映射过程由软件来设置映射的机制,实际的转换由硬件来完成。

Linux主要采用段页式内存管理方式图示

四、进程管理

4.1Linux系统中进程的组成

一个进程包含内核中的一部分地址空间和一系列数据结构。其中地址空间是内核标记的一部分内存以供进程使用,而数据结构task_struct则用来纪录每个进程的具体信息。

最主要的进程信息包括:

  • 进程的地址空间图
  • 进程的Id-pid
  • 真是用户ID和有效用户ID(EUID)
  • 进程当前的状态( sleeping、stopped、runnable 等)
  • 进程的执行优先级
  • 进程调用的资源信息
  • 进程打开的文件和网络端口信息
  • 进程的信号掩码(指明哪种信号被屏蔽)
  • 进程的属主

4.2 进程的状态

Linux中进程主要有三种状态:

1.TASK_RUNNING:新进程的状态是TASK_RUNNING(就绪态,但是没有在运行)。当调度器选择这个新创建的进程运行时,新创建的进程就切换到运行态,它也是TASK_RUNNING。也就是说TASK_RUNNING其实包括了就绪和运行态两种状态,而究竟式哪种状态,取决于CPU的的控制权

2.TASK_INTERRUPTIBLE:处于这个状态的进程因为等待某某事件的发生(比如等待socket连接、等待信号量),而被挂起。这些进程的task_struct结构被放入对应事件的等待队列中。当这些事件发生时(由外部中断触发、或由其他进程触发),对应的等待队列中的一个或多个进程将被唤醒。

通过ps命令我们会看到,一般情况下,进程列表中的绝大多数进程都处于task_interruptible状态(除非机器的负载很高)。毕竟CPU就这么一两个,进程动辄几十上百个,如果不是绝大多数进程都在睡眠,CPU又怎么响应得过来。

3.TASK_UNINTERRUPTIBLE:与task_interruptible状态类似,进程处于睡眠状态,但是此刻进程是不可中断的。不可中断,指的并不是CPU不响应外部硬件的中断,而是指进程不响应异步信号。

而task_uninterruptible状态存在的意义就在于,内核的某些处理流程是不能被打断的。如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程(这个插入的流程可能只存在于内核态,也可能延伸到用户态),于是原有的流程就被中断了。在进程对某些硬件进行操作时(比如进程调用read系统调用对某个设备文件进行读操作,而read系统调用最终执行到对应设备驱动的代码,并与对应的物理设备进行交互),可能需要使用task_uninterruptible状态对进程进行保护,以避免进程与设备交互的过程被打断,造成设备陷入不可控的状态。这种情况下的task_uninterruptible状态总是非常短暂的,通过ps命令基本上不可能捕捉到。

4.3进程间关系的表示

所有的进程都被链表连接在一起。除此自外,还有父子兄弟关系的指针,用来快速地找出父子兄弟关系地进程。

成员名称 描述
real_parent 指向创建P,如果不存在指向进程1。(比如,在shell中启动了一个后台进程,然后退出shell,则后台进程的父进程就是init)。
parent 指向P的当前父进程。(当子进程结束时,必须发送信号通知的那个进程);通常等于real_parent。偶尔会有不同的时候,比如当另一个进程发送ptrace()系统调用去监控进程P时。
children 包含P创建的所有子进程的列表的表头。
sibling 包含指向兄弟关系的进程链表中的下一个元素和前一个元素的指针,这些进程的父进程都是P。

4.4 进程的创建

进程的创建主要使用了fork()系统调用。

init_task为第一个进程(0号进程)的进程描述符结构体变量,它的初始化是通过硬编码方式固定下来的。除此之外,所有其他进程的初始化都是通过do_fork复制父进程的方式初始化的

_do_fork具体进程的创建大概就是把当前进程的描述符等相关进程资源复制一份,从而产生一个子进程,并根据子进程的需要对复制的进程描述符做一些修改,然后把创建好的子进程放入运行队列(操作系统原理中的就绪队列)。

五、中断管理和系统调用

5.1 中断

中断分外部中断(硬件中断)和内部中断(软件中断),内部中断又称为异常(Exception),异常又分为故障(fault)和陷阱(trap)。系统调用就是利用陷阱(trap)这种软件中断方式主动从用户态进入内核态的。

5.2 系统调用

为保护内核不因用户误操作而频繁崩溃,将Linux体系架构分为内核态和用户态。在内核态,所有指令包括特权指令都可以执行。相应的,在用户态,代码能够访问的范围就会受到限制。比如:在 32 位 Linux 系统上有 4GB 的进程地址空间,内核态下的这 4GB 的地址空间全都可以访问。但是在用户态时,只能访问 0x00000000~0xbfffffff 的地址空间,0xc0000000 以上的地址空间只能在内核态下访问

5.3 中断与系统调用的关系

从用户态进入内核态是由中断触发的,可能是硬件中断,在用户态进程执行时,硬件中断信号到来,进入内核态,就会执行这个中断对应的中断服务例程。也可能是用户态程序执行过程中,调用了一个系统调用,陷入了内核态,叫作陷阱(trap)(系统调用是特殊的中断)

5.4 中断处理的过程

1.确定与中断或者异常相关联的向量i

2.通过idtr寄存器获得idt地址,读取第i项

3.通过gdtr寄存器获得gdt地址,选出相对应的段描述符

4.确定中断是授权源发出的

5.检测用户特权变化来确定用户是否进入内核态,若进入了内核态,则开始使用新的特权级对应的堆栈

6.若发生故障,则修改CS:EIP的值,使发生异常的指令能再次执行

7.在栈中保存eflags和ES:EIP的内容

8.如果异常产生了一个硬件错误码,将其保存在栈中

9.装载新的CS:ESP的值

六、举例验证

这里描述读写文件的过程。在读写文件之前,必须使用open打开一个文件,打开文件首先open会执行到C库,C库里有INT $0x80指令,然后在中断向量表中找到128项,中断向量表里有中断描述符,可以找到中断处理程序入口,第128项是系统调用处理函数,进入系统调用处理函数,保存现场,系统调用号存储eax中,根据系统调用号执行系统调用表中对那一项的函数。系统调用表是相应的函数指针,这里会执行sys_open。sys_open进行命令查找,找到文件控制块,根据不同文件类型,调用文件打开函数,文件打开函数会创建一个系统文件打开表file,file的很多内容来自文件控制块,填完之后,进程也有一个进程文件打开表,这个结构里面有fd数组,fd数组是指针,找个空闲的,把它指向之前已经创建的file结构,最后返回那一项的索引号,即fd。

当进程使用read系统调用读这个文件,就会根据fd数组的下标 找到fd数组的对应项,然后找到指向之前创建的file结构的指针,再找到这个file结构,最后找到file结构里的file_operations里的具体的read函数来读取文件。write系统调用原理类似。

五、影响Linux系统性能的因素

使用vmstat来检测系统的运行状态,进而考虑影响系统性能的因素。
测试一:

vmstat 1 3 #表示每个一秒采集3次
[root@lgh3 ~]# vmstat 1 3
procs -----------memory---------------- ---swap-- -----io---- --system-- -----cpu-----
 r  b    swpd   free   buff  cache       si   so    bi    bo   in   cs us sy id  wa st
 0  0      0 233483840 758304 20795596    0    0     0     1    0    0  0  0 100  0  0
 0  0      0 233483936 758304 20795596    0    0     0     0 1052 1569  0  0 100  0  0
 0  0      0 233483920 758304 20795596    0    0     0     0  966 1558  0  0 100  0  0

测试二:

[root@mwpl003 ~]# vmstat 2
procs --------------memory------------ ---swap-- ------io---- --system-- -----cpu-----
 r  b    swpd   free    buff  cache      si   so    bi    bo   in   cs us  sy id  wa st
 0  0      0 233481872 758304 20795716    0    0     0     1    0    0  0  0 100  0  0
 0  0      0 233481536 758304 20795720    0    0     0    16 1091 1625  0  0 100  0  0
 0  0      0 233481840 758304 20795720    0    0     0     0  976 1582  0  0 100  0  0

注意:

procs r: 运行的进程比较多,系统很繁忙
bi/bo: 磁盘写的数据量稍大,如果是大文件的写,10M以内基本不用担心,如果是小文件写2M以内基本正常
cpu us: 持续大于50%,服务高峰期可以接受, 如果长期大于50 ,可以考虑优化
cpu sy: 现实内核进程所占的百分比,这里us + sy的参考值为80%,如果us+sy 大于 80%说明可能存在CPU不足。
cpu wa: 列显示了IO等待所占用的CPU时间的百分比。这里wa的参考值为30%,如果wa超过30%,说明IO等待严重,这可能是磁盘大量随机访问造成的, 也可能磁盘或者磁盘访问控制器的带宽瓶颈造成的(主要是块操作)

posted on 2021-05-18 16:46  Joehanm  阅读(59)  评论(0编辑  收藏  举报