Linux性能及调优指南(翻译)之Linux进程管理
本文为IBM RedBook的Linux Performanceand Tuning Guidelines的1.1节的翻译
原文地址:http://www.redbooks.ibm.com/redpapers/pdfs/redp4285.pdf
原文作者:Eduardo Ciliendo, Takechika Kunimasa, Byron Braswell
译文例如以下:
1.1 Linux进程管理
有效率的进程管理能保证一个程序平稳而高效地执行。
Linux的进程管理与UNIX的进程管理类似。它包含进程调度、中断处理、信号、进程优先级、上下文切换、进程状态、进度内存等。
在本节中,我们将描写叙述Linux进程管理的基本原理的实现。
它将更好地帮助你理解Linux内核怎样处理进程及其对系统性能的影响。
1.1.1 什么是进程?
一个进程是一个执行在处理器的程序的一个实例。
该进程使用Linux内核能够处理的不论什么资源来完毕它的任务。
全部执行在Linux操作系统中的进程都被task_struct结构管理,该结构同一时候被叫作进程描写叙述。一个进程描写叙述包含一个执行进程全部的必要信息,比如进程标识、进程属性和构建进程的资源。假设你了解该进程构造,你就能理解对于进程的执行和性能来说,什么是重要的。
图1-2展示了进程结构相关的进程信息概述。
图1-2 task_struct结构体
1.1.2 进程的生命周期
每个进程都有其生命周期,比如创建、执行、终止和消除。这些阶段会在系统启动和执行中反复无数次。因此,进程的生命周期对于其性能的分析是很重要的。
图1-3展示了经典的进程生命周期。
图1-3 经典的进程生命周期
它复制该值到父进程进程描写叙述到子进程中。
此时整个的父进程的地址空间是没有被复制的;父子进程共享同样的地址空间。
exec()系统调用复制新的程序到子进程的地址空间。由于父子进程共享地址空间,写入一个新的程序的数据会引起一个分页错误。在这种情况下,内存会分配新的物理内存页给子进程。
这个推迟的操作叫作写时复制。子进程通常执行他们自己的程序而不是与父进程执行同样的程序。
这个操作避免了不必要的开销。由于复制整个地址空间是一个很缓慢和效率低下的操作,它须要使用大量的处理器时间和资源。
当程序已经执行完毕,子进程通过调用exit()系统调用终止。exit()系统调用释放进程大部分的数据并通过发送一个信号通知其父进程。
此时,子进程是一个被叫作僵尸进程的进程(參阅page 7的“Zombie processes”)。
子进程不会被全然移除直到其父进程知道其子进程的调用wait()系统调用而终止。当父进程被通知子进程终止,它移除子进程的全部数据结构并释放它的进程描写叙述。
1.1.3 线程
一个线程是一个单独的进程生成的一个执行单元。它与其它的线程并行地执行在同一个进程中。
各个线程能够共享进程的资源,比如内存、地址空间、打开的文件等等。
它们能訪问同样的程序数据集。线程也被叫作轻量级的进程(Light Weight Process,LWP)。
由于它们共享资源。所以每个线程不应该在同一时间改变它们共享的资源。
相互排斥的实现、锁、序列化等是用户程序的责任。
从性能的角度来说,创建线程的开销比创建进程少,因数创建一个线程时不须要复制资源。还有一方面,进程和线程拥在调度算法上有类似的特性。
内核以类似的方式处理它们。
图1-4 进程和线程
在如今的Linux实现中,线程支持UNIX的可移植操作系统接口(POSIX)标准库。在Linux操作系统中有几种可用的线程实现。
下面是广泛使用的线程库:
LinuxThreads
LinuxThreads自从Linux内核2.0起就已经被作为默认的线程实现。
LinuxThreads的一些实现并不符合POSIX标准。
Native POSIX Thread Library(NPTL)正在代替LinuxThreads。LinuxThreads在将来的Linux企业发行版中将不被支持。
Native POSIX Thread Libary(NPTL)
NPTL最初是由红帽公司开发的。NPTL与POSIX更加兼容。通过Linux内核2.6的高级特性。比如,新的clone()系统调用、信号处理的实现等等。它具有比LinuxThreads更高的性能和伸缩性。
NPTL与LinuxThreads有一些不兼容。
一个依赖于LinuxThreads的应用可能不能在NPTL实现中工作。
Next Generation POSIX Thread(NGPT)
NGPT是一个IBM开发的POSIX线程库。
如今处于维护阶段而且在未来也没有开发计划。
使用LD_ASSUME_KERNEL环境变量,你能够选择在应用中使用哪一个线程库。
1.1.4 进程优先级和nice值
进程优先级是一个数值。它通过动态的优先级和静态的优先级来决定进程被CPU处理的顺序。一个拥有更高进程优先级的进程拥有更大的机率得到处理器的处理。
内核依据进程的行为和特性使用试探算法。动态地调整调高或调低动态优先级。一个用户进程能够通过使用进程的nice值间接改变静态优先级。一个拥有更高静态优先级的进程将会拥有更长的时间片(进程能在处理上执行多长时间)。
Linux支持从19(最低优先级)到-20(最高优先级)的nice值。
默认值为0。
把程序的nice值改动为负数(使进程的优先级更高)。须要以root身份登陆或使用su命令以root身份执行。
1.1.5 上下文切换
在进程执行过程中。进程的执行信息被保存于处理器的寄存器和它的缓存中。正在执行的进程载入到寄存器中的数据集被称为上下文。为了切换进程,执行中进程的上下文将会被保存。接下来的执行进程的上下文将被被恢复到寄存器中。
进程描写叙述和内核模式堆栈的区域将会用来保存上下文。这个切换被称为上下文切换。过多的上下文切换是不受欢迎的,由于处理器每次都必须清空刷新寄存器和缓存,为新的进程制造空间。
它可能会引起性能问题。
图1-5 说明了上下文切换怎样工作。
图1-5 上下文切换
1.1.6 中断处理
中断处理是优先级最高的任务之中的一个。中断通常由I/O设备产生。比如网络接口卡、键盘、磁盘控制器、串行适配器等等。中断处理器通过一个事件通知内核(比如,键盘输入、以太网帧到达等等)。它让内核中断进程的执行,并尽可能快地执行中断处理。由于一些设备须要高速的响应。它是系统稳定的关键。当一个中断信号到达内核,内核必须切换当前的进程到一个新的中断处理进程。这意味着中断引起了上下文切换,因此大量的中断将会引起性能的下降。
在Linux的实现中,有两种类型的中断。硬中断是由请求响应的设备发出的(磁盘I/O中断、网络适配器中断、键盘中断、鼠标中断)。软中断被用于处理能够延迟的任务(TCP/IP操作,SCSI协议操作等等)。你能够在/proc/interrupts文件里查看硬中断的相关信息。
在多处理器的环境中。中断被每个处理器处理。绑定中断到单个的物理处理中能提高系统的性能。很多其它的细节,请參阅4.4.2,“CPU的中断处理亲和力”。
1.1.7 进程状态
每个进程拥有自己的状态。状态表示了进程当前在发生什么。
在进程的执行期间进程的状态会发生改变。一些进程的状态例如以下:
TASK_RUNNING
在此状态下。表示进程正在CPU中执行或在队列中等待执行(执行队列)。
TASK_STOPPED
在此状态下的进程被某些信号(如SIGINT。SIGSTOP)暂停。进程正在等待通过一个信号恢复执行,比如SIGCONT。
TASK_INTERRUPTIBLE
在此状态下。进程被暂停并等待一个某些条件状态的到达。假设一个进程处于TASK_INTERRUPTIBLE状态并接收到一个停止的信号,进程的状态将会被改变并中断操作。一个典型的TASK_INTERRUPTIBLE状态的进程的样例是一个进程等待键盘中断。
TASK_UNINTERRUPTIBLE
与TASK_INTERRUPTIBLE类似。当一个进程处于TASK_UNINTERRUPTIBLE状态能够被中断。向处于TASK_UNINTERRUPTIBLE状态的进程发送一个信号不会发生不论什么操作。
一个TASK_UNINTERRUPTIBLE进程的典型的样例是等待磁盘I/O操作。
TASK_ZOMBIE
当一个进程调用exit()系统调用退出后。它的父进程应该知道该进程的终止。处于TASK_ZOMBIE状态的进程会等待其父进程通知其释放全部的数据结构。
图1-6 进程状态
僵尸进程
当一个进程接收到一个信号而终止。它在结束自己之前,通常须要一些时间来结束全部的任务(比如关闭打开的文件)。
在这个通常很短暂的时间内,该进程就是一个僵尸进程。
进程已经完毕全部的关闭任务后,它会向父进程报告其即将终止。有些时候,一个僵尸进程不能把自己终止,这将会引导它的状态显示为z(zombie)。
使用kill命令来关闭这种一个进程是不可能的。由于该进程已经被觉得已经死掉了。假设你不能清除僵尸进程,你能够结束其父进程,然后僵尸进程也随之消失。
可是,假设父进程为init进程。你不能结束它。
init进程是一个很重要的进程,因此可能须要重新启动系统来清除僵尸进程。
1.1.8 进程内存段
进程使用其自身的内存区域来执行工作。工作的变化依据情况和进程的使用而决定。
进程能够拥有不同的工作量特性和不同的数据大小需求。进程必须处理各种数据大小。为了满足需求,Linux内核为每个进程使用动态申请内存的机制。进程内存分配的数据结构如图1-7所看到的。
图1-7 进程地址空间
进程内存区由下面几部分组成:
Text段
该区域用于存储执行代码。
Data段
数据段包含三个区域。
- Data:该区域存储已被初始化的数据,如静态变量。
- BSS:该区域存储初始化为0的数据。数据被初始化为0。
- Heap:该区域用于依据需求使用malloc()动态申请的内存。
堆向高地址方向增长。
Stack段
该区域用于存储局部变量、函数參数和返回函数的地址。栈向低地址方向增长。
用户进程的地址空间内存分布能够使用pmap命令来查看。你能够使用ps命令来查看内存段的大小。
能够參阅2.3.10的“pmap”。“ps和pstree”。
1.1.9 Linux CPU调度
不论什么的计算机的基本功能都很easy,就是计算。为了能够计算,它意味着必须管理计算资源或处理器和计算任务,也就是我们所知道的线程或进程。感谢Ingo Molnar的巨大贡献。Linux内核使用一个O(1)的算法代替曾经的O(n)的CPU调度算法。
O(1)指的是一种静态的算法,意味着选择一个进程并执行所花费的时间是一个常数,无论进程的数量的大小。
新的调度算法的扩展性很好,无论进程的数量或者处理器的数量是多少,系统的开销都是很少的。
该算法使用两个进程优先级数组:
active(活动的)
expired(过期的)
调度器依据进程的优先级和优先拦截率为进程分配时间片,然后进程以优先级顺序放置到active数组内。当进程时间片耗尽,进程申请一个新的时间片并放置到expired数组内。当active数组中的全部进程的时间片耗尽。这两个数组进行切换。又一次执行该算法。对于一般的交互式进程(相对于实时进程),拥有高优先级的进程通常比低优先级的进程得到更长的时间片和很多其它的计算时间,但这并不表示低优先级的进程会被全然忽略(饿死)。该算法的优势是为拥有大量线程和进程并拥有多处理器的企业级环境提升Linux内核的扩展性。
该O(1)的新CPU调度器是为内存2.6设计的。可是如今已经移植到2.4系列中。图1-8说明了Linux CPU怎样调度工作。
图1-8 Linux内核2.6 O(1)调度器
新调度器的还有一个显著改进是支持非一致性内存架构(NUMA)和对称多线程处理器,比如Intel超线程技术。
改进后的NUMA支持确保仅仅有某个节点过载时。负载平衡才会跨越某个NUMA节点。这个机制确保了在NUMA系统相对照较缓慢的扩展链接流量的最小化。虽然每个调度节拍时负载平衡会遍历调度域群组中的处理器,但仅仅有在节点过载并请求负载平衡时,负载才会跨越调度域转移。
图1-9 O(1)CPU调度器结构