(1)Linux性能调优之Linux进程管理

1,932 阅读10分钟

一、背景

玩过一段时间Linux后,大家是否发现Linux也就是一个操作系统而已,和windows是类似的,只不过是windows拥有图形化界面,而Linux大部分情况下只是个黑黑的窗口。windows多用于个人,而Linux因为出色的性能而多用于服务器。

服务器不是智能的,Linux也不是智能的,所以需要运维人员通过调试,将服务器的硬件和Linux的特性结合起来,从而达到性能最大输出,这就是我理解的Linux性能调优。下面展示的是一张服务器和Linux系统结合的概览图:

服务器和Linux系统结合的概览图

二、基础知识

调优不是说调就调的,我们要先了解Linux是如何处理任务以及与硬件资源进行交互的。性能调优需要在深刻理解硬件资源、操作系统和应用程序的基础上进行。下面说一下在Linux中和性能关系最密切的几个部分。

三、Linux进程管理

  • 3.1 什么是进程?

进程是在处理器中执行的实例,Linux内核调度各类资源来满足进程的需求。我的理解是,应用程序是存储硬盘上的数据,通过程序入口将应用程序装载到内存运行起来后就生成了一个进程(运行中的程序)。进程会使用各种资源,如CPU,键盘鼠标,处理器,硬盘等,而这些工作由Linux内核调度,以满足各个进程间的协同,竞争等。

Linux的进程管理方式类似于Unix的进程管理方式,包含进程调度、中断处理、信号、进程优先级、进程切换、进程状态、进程的内存等等

所有运行在Linux操作系统的进程都被task_struct这个结构体管理,task_struct也被称为进程描述符。进程描述符包含一个进程运行所需的所有信息,比如进程的id、进程的属性以及构建进程的资源。

下图展示了进程信息相关结构的概览:

  • 3.2 进程的生命周期

每个进程都有自己生命周期,比如创建、执行、终止和删除。在系统运行过程中,这些阶段反复执行成千上万次。因此,从性能的角度来看,进程的生命周期十分重要。

下图展示了一般进程的生命周期:

当一个进程创建一个新的进程,创建进程的进程(父进程)使用名为fork()的系统调用。当fork()被调用的时候,它会为新创建的进程(子进程)获得一个进程描述符,并且设置新的进程ID。复制父进程的进程描述符给子进程。这时候,不会复制父进程的地址空间,而是父子进程使用同样的地址空间。

exec()系统调用把新程序复制到子进程的地址空间。由于共享同样的地址空间,写入新进程的数据会引发页错误的异常。此时,内核给子进程分配新的物理页。这个延迟的操作叫做Copy On Write。子进程和父进程执行的程序通常不一样,它执行自己的程序。这个操作避免了不必要的开销,因为,复制整个地址空间是很慢且低效率的,还会消耗很多的处理器时间和资源。

当程序执行完成,子进程使用exit()系统调用终止。exit()会释放进程的大部分数据结构,并且把这个终止的消息通知给父进程。这时候,子进程被称为zombie process(僵尸进程)。直到父进程通过wait()系统调用知悉子进程终止之前,子进程都不会被完全的清除。一旦父进程知道子进程终止,它会清除子进程的所有数据结构和进程描述符。

  • 3.3 线程

说到进程不得不提的就是线程了。线程是单个进程中生成的执行单元。多个线程在同一个进程中并发运行。它们共享内存、地址空间、打开文件等等资源。还能访问同样的应用数据集。线程也被称为轻量级进程(Light Weight Process)。由于线程间共享资源,线程不能同时改变它们共享的资源。互斥、锁、序列化等等都是由用户应用程序来实现。

从性能的角度看,创建线程比创建进程更加低消耗,因为创建线程不需要复制资源。另一方面,从进程和线程在调度上看,他们拥有相似的行为。内核用类似的方法来处理他们。

下图是进程和线程的简单对比:

在当前的Linux实现中,线程由POSIX(Portable Operating System Interface for UNIX,可移 植操作系统接口)的兼容库(pthread)提供。Linux支持多线程。

  • 3.4 进程优先级和nice级别

进程优先级由动态优先级和静态优先级决定,它是决定进程在CPU中执行顺序的数字。优先级越高的进程被处理器执行的机会越大。

根据进程的行为,内核使用启发式算法决定开启或关闭动态优先级。可以通过nice级别直接修改进程的静态优先级,拥有越高静态优先级的进程会获得更长的时间片(时间片是进程在处理器中的执行时间)。

Linux支持的nice级别从19(最低优先级)到-20(最高优先级),默认只是0。只有root身份的用户才能把进程的nice级别调整为负数(让其具备较高优先级)。

  • 3.5 切换上下文

在进程执行过程中,进程的信息存放在处理器的寄存器和缓存中。这部分执行中进程存放在寄存器中的数据就叫做context,上下文。在切换进程中,正在处理的进程上下文被保存起来,把下一个要执行的进程的上下文恢复到寄存器。上下文通常存储在进程描述符和内核态栈中。进程切换就叫做上下文切换(context switching)。因为处理器每次上下文切换都要为新进程刷新寄存器和缓存,可能引发性能上的问题,所以应该尽量避免太多的上下文切换。 下图描述上下文切换是如何工作的:

  • 3.6 中断处理

中断处理是最高优先级别的任务之一。中断通常由I/O设备产生,譬如网络接口、键盘、磁盘控制器。中断处理器把键盘输入、网络帧到达这类事件通知给内核,它告诉内核尽快中断进程执行,因为某些设备需要快速的回应。这对系统稳定性是一个挑战。当中断信号到达内核,内核必须切换当前执行中的进程到新的进程,处理中断。这就意味着会发生上下文切换,同时也意味着大量的的中断会导致系统性能下降。

在Linux中有两类中断,硬中断是由设备产生的需要做出响应的中断,例如磁盘I/O中断,网卡中断,键盘和鼠标中断。软中断用于任务处理,可以推迟,例如TCP/IP操作、SCSI协议操作。可以在/proc/interrupts中看到相关的硬中断信息。

  • 3.7 进程状态

TASK_RUNNING 在这个状态中,进程正在CPU中执行,或者在运行队列(run queue)中等待运行。

TASK_STOPPED 进程由于特定的信号(如SIGINT、SIGSTOP)而挂起就会处于这个状态,等待恢复信号,比如SINCONT。

TASK_INTERRUPTIBLE 在此状态中,进程挂起并且等待一个特定的条件。假如进程处于TASK_INTERRUPTIBLE状态并且收到一个停止信号,进程状态会发生改变,操作会中断。TASK_INTERRUPTIBLE的典型例子是等待键盘中断。

TASK_UNINTERRUPTIBLE 类似于TASK_INTERRUPTIBLE。当进程处于TASK_INTERRUPTIBLE状态可以被中断,发送一个信号给TASK_UNINTERRUPTIBLE却不会有任何反应。TASK_UNINTERRUPTIBLE最典型的例子是进程等待磁盘I/O操作。

TASK_ZOMBIE 进程在使用exit()系统调用退出以后,父进程应该知道进程终结。在TASK_ZOMBIE状态中,进程在等待父进程收到通知并释放所有的数据结构。

它们之间的关系如下图:

说下僵尸进程

当进程已经收到信号而终止,正常情况下,完全结束之前,它有一些时间来完成所有的任务(例如关闭打开的文件)。在这个很短的的正常的时间片里,这个进程是僵尸。

当进程完成了所有的关闭操作, 它向父进程报告它即将终结。有时候,僵尸进程不能够结束它自己,这个状态下,它就显示状态为Z(zombie)。

因为它已经死了,所以不可能使用kill命令杀死这种进程。如果无法摆脱僵尸进程,可以杀死僵尸进程的父进程,这样僵尸进程也会消失。然后,如果僵尸进程的父进程是init,你就别这么做了,init是非常重要的进程,你可能要重启才能摆脱僵尸进程了。

  • 3.8 进程内存段

进程使用自己的内存区域处理任务,任务种类由场景和进程用途决定。进程有不同的工作特性和不同数据大小要求,进程必须处理各种大小的数据。为满足这一要求,Linux内核给各个进程使用动态内存分配机制。进程内存分配结构如下图:

进程内存区域包含如下段:

文本 :存储可执行代码的区域
数据 : 数据段由如下三个区域构成
    Data:存储初始化数据,比如静态变量
    BSS:存储初始化0数据,数据初始化为0
    Heap(堆):根据需要使用malloc()分配动态内存。堆向高地址空间增长。
栈: 该区域存储局部变量、函数参数和函数的返回地址。栈向低地址空间增长。

用户进程的地址空间分配可以使用pmap命令显示出来。你可以使用ps命令显示总的段大小。

  • 3.9 Linux的CPU调度

计算机的最基本功能就是计算,为了实现计算功能,必须要有办法管理计算资源、处理器、计算任务,也就是常说的进程和线程。

下面谈下Linux中CPU调用进程的算法,这个算法使用两个进程优先级数组:

1.active
2.expired

由于调度器根据进程的优先级和先前阻塞率来位分配时间片,进程的优先级被放在一个active数组中。当时间片到期,它们被重新分配新的时间片,并且放置到expired数组上。当所有active数组上的进程都到期,active和expired数组发生对换,重启算法。对于一般的交互进程(对应实时进程),高优先级的进程通常会比低优先级进程分配更多的时间片,但是并不意味着完全不给低优先级进程机会。

如下展示和Linux的CPU调度器是如何工作的:

新调度器的另外一个大有有点是支持非均匀内存架构(Non-Uniform Memory Architecture, NUMA)和对称多线程处理器,例如Intel的超线程技术。支持NUMA保证了正常情况下不会出现负载均衡的情况,除非一个节点负担过重。这个机制保证了在NUMA系统中,比较缓慢的链路负载较小。尽管在一个组中的处理器调度的每一个处理,会被负载均衡,但是调度器的组只会在节点负载过高和要求负载均衡的时候产生。

四、下一节是???

谈完Linux的进程管理,下一节谈一下 Linux的内存体系