Preface
额…Perl脚本,为什么题目中还包括特定语言的指定命题啊= =,好吧,玩玩Perl吧
Content
进程是Linux用来表示正在运行的程序的一种抽象概念.程序的内存使用,处理器时间和I/O资源就是通过这个对象进行管理和监视的.
一个进程由一个地址空间和内核内部的一组数据结构组成.地址空间是由内核标记出来的供进程使用的一组内存页面.它包含进程正在执行的代码和库,进程变量,进程堆栈以及在进程运行的同时,内核所需要的各种其他信息.因为Linux是一种采用虚拟内存的系统,因此内存页面在地址空间中的位置和它在机器的物理内存或交换空间中的位置之间并不一定相关.
内核的内部数据结构记录了有关每个进程的各种信息:
- 进程的地址空间映射
- 进程的当前状态(睡眠状态,停止状态,可运行状态等)
- 进程执行的优先级
- 进程已用资源的信息
- 进程已打开的文件和网络端口的信息
- 进程的信号掩码(一个记录,确定要封锁哪些信号)
- 进程的属主
这些属性有些可由构成"线程组"的几个进程所共享,Linux中的线程组和传统UNIX中的多线程进程类似.虽然它们共享同一个地址空间,但是一个线程组的成员们却有各自的执行权限和执行状态.
-
PID:进程的ID号
内核给每个进程分配一个独一无二的ID号.控制进程的大多数命令和系统调用需要用户指定PID来标识操作的目标.PID按照创建进程的顺序来分配. -
PPID:父PID
Linux没有提供创建新进程去运行某个特定程序的系统调用,现有进程必须克隆自身去常见一个新进程.克隆出的进程能够把它正在运行的那个程序替换成另一个不同程序. -
UID和EUID:真实的和有效的用户ID
进程的UID就是其创建者的用户标识好,或者更确切地说,就是复制了父进程的UID值.通常,只允许创建者(也称为属主)和超级用户对进程进行操作.
EUID是"有效(effective)"的用户ID,这是一个额外的UID,用来确定进程在任何给定的时刻对哪些资源和文件具有访问权限.
Linux还有一种"保存UID(saved UID)",它是进程刚开始执行时刻,进程EUID的副本.除非进程采取的措施删除这个保存下来的UID,否则它就留了下来,作为真实的或者有效的UID来用.因此,编写严谨的setuid程序可以让其大部分操作和它的特殊权限无关,只有在需要额外权限的特定时刻才用上它们.
感觉关键在于euid的不断变换,同时要避免euid的使用泛滥
进程的调度优先级决定了它所收到的CPU时间有杜少.内核使用动态算法来计算优先级:它考虑一个进程近来已经消耗CPU的时间量以及考虑该进程已经等待运行的时间等因素.
为了创建一个新进程,一个集成就会用系统调用fork来复制自身.fork创建原进程的一个副本,这个副本在很大程度上与父进程相同.新进程拥有一个不同的PID和它自己的记账信息.
fork具有能够返回两个不同值的特性.从子进程的角度来看,它返回0.另一方面,对于父进程来说,则返回新创建的子线程的PID.由于这两个进程在其他方面是相同的,所以他们都必须检查返回值来确定它们要扮演什么角色.
在fork之后,子进程经常使用exec族系统调用中的一个成员开始执行新的程序.这些调用能够改变正在执行的程序正文(text),并把数据和堆栈重新设定到预先定义的初始状态.exec各种不同形式的却别仅在于它们为新程序制定命令行参数和环境时采用的方式不同.
Linux定义fork的另一种实现clone.这个调用创建的进程组共享内存和I/O空间.这一特性与大多数版本的UNIX上有的多线程机制类似,但是执行的每个线程都表现为一个完整的进程而不是一个专门的"线程".
除了内核创建的那几个进程之外,其他所有的进程都是init的后代.
init在进程管理中还负担这另外一个重要角色.当一个进程执行完毕时,它调用一个名为_exit的例程来通知内核它已经做好"消亡"的准备了.它提供一个退出码(一个整数)表明它准备 退出的原因.按照惯例,0用来表示正常或者说"成功"的终止.
在允许进程完全消失之前 ,Linux要求该进程的消亡得到其父进程的确认,父进程是通过调用wait来确认的.父进程接收到子进程退出码的一个副本(或者是一个通知,在子进程不是自愿退出的情况下.说明子进程被中止的原因),如果父进程愿意,它还可以得到子进程对自愿的使用情况的一个总结.
如果父进程比子进程的生命期长并能负责地调用wait清理死亡的进程,那么这种方案可以工作得很好.但是如果父进程比子进程先消亡,那么内核则会意识到将来不会有wait调用处理子进程了,于是就调整子进程使他这个"孤儿"成为init的子进程.内核要求init接收这些"孤儿"进程,并在他们消亡的时候执行所需的wait调用清除他们.
信号是进程级的终端请求.系统定义了大约30种不同种类的信号,使用这些信号的方式可以有以下几种:
- 作为一种通信手段在进程之间发送信号
- 当键入特殊的按键时,例如和,可以由终端驱动程序发送信号去中止,中断或挂起进程
- 可以由管理员(使用kill)发送信号来达到各种结果
- 当进程出错,例如出现被0除的错误时,可以由内核发送信号
可以由内核发送信号,通知一个进程有某种"受关注的"条件出现,比如一个子进程死亡或者在I/O通道上有数据
当收到信号时,可能发生两种情况之一.如果接收进程已经为这个特定的信号指派了一个信号处理例程,那么就使用和传递该信号的上下文有关的信息来调用这个信号处理例程.否则,内核代表该进程采取某种默认措施.根据新型号偶的不同采取的默认措施也不同.许多信号会中止进程,有些信号也会产生一次内存转储.
KILL,INT,TERM,HUP和QUIT听起来似乎表示同一回事,但是实际上他们的使用方法非常不同:
- KILL不可以封锁,他在内核的层面上中止进程.进程实际上从来就不能够"接收"到这个信号
- INT是当用户键入时由终端驱动程序发送的信号.这是一个中止当前操作的请求.如果捕获了这个信号,一些简单的程序应该退出,或者只是让自己被杀死,这也是程序没有捕捉到这个信号时的默认处理方法.拥有命令行模式的那些程序应该停止它们正在做的事情,清除状态,并等待用户的再次输入.
- TERM请求彻底中止某项执行操作.它期望接收进程清除自己的状态并退出.
- HUP有两种常见的解释.第一种,它被许多守护进程理解为一个重置的请求.如果一个守护进程不同重新启动就能够重新读取它自己的配置文件并调整自己以适应变化,那么HUP信号通常可以用来触发这种欣慰.第二种,HUP信号有时候由终端驱动程序生成,视图用来"清楚"(也就是"杀死")跟某个特定终端相链接的那些进程.例如,当一个终端会话结束时,或者当一个调制解调器连接被不经意地断开(因而得名为"挂断")时,就可能出现这种情况.各种不同的希望在具体的处理细节上有些区别.
- QUIT与TEAM类似,不同之处,如果没有被捕获的话,它的默认行为是产生一个内存转储.有一些程序把这个信号解释为其他意思
- 信号USR1和USR2没有设定意义.程序可以按照自己需要的方式,任意使用这两个信号,例如Apache web服务器把信息USR1解释为一次要求妥善重启的请求.
进程不会仅仅因为其存在就自动地具有获得CPU时间的资格.进程具有四种基本状态:Runnable,Sleeping,Zombie,Stopped
进程的"谦让度"是以数字形式给内核的按时,通过它来表明一个进程在同其他进程竞争CPU时应该如何对待这个进程.高谦让值具有低优先级.
进程的谦让值可以在创建进程时用nice命令来设置,并可以在执行时使用renice命令进行调整.
在现代世界中,最常见的使用nice的进程是xntpd,它是时钟同步守护进程.由于CPU的及时性对它的任务非常中啊哟,因此它通常运行在比默认值低12左右的谦让值上(也就是说,它的优先级比普通进程更高)
PS是系统管理员监视进程的主要工具.用户可以用它显示进程的PID,UID,优先级和控制终端
您可以用命令ps aux了解正字运行的所有进程的全貌.
另一组有用的选项是lax,它提供了技术性更强的信息.而且它运行的速度也更快,因为它不必把每个UID都转换为用户名–如果系统已经因别的某个进程而停顿了,那么效率就显得很重要了,运行ps命令的开销一般都相当大
由于ps这样的命令只提供系统过去时间的一次性快照,因此,要获得系统上正在发生事情的"全景"往往是非常困难的.top命令对活动进程以及其所使用的资源情况提供定期更新的汇总信息.
默认设置下,这些显示信息每10s更新一次.那些活跃的进程显示在顶部.top还接收键盘的输入病允许用户向进程发送信号和调整进程的谦让值,因此用户可以观察自己的操作是如何影响到机器的整体状态的.
root用户能够以q选项去运行top,一遍把它提升到可能的最高优先级.当试图跟踪一个已经把系统拖垮的进程时,这种提升就非常有用.
Linux版的ps和top命令都从/proc目录读取进程的状态信息,内核把有关系统状态的各种有意义的信息都放在这个伪目录.虽然这么目录叫做/proc(下面的文件系统类型也叫做"proc"),但是它里面的信息却并不局限于进程信息–内核产生的所有状态信息和统计数据都在这里.用户可以通过向/proc下的适当文件写入数据的方法来修改某些参数
进程特有的信息都分别被放到了按PID起名字的子目录里
文件 | 内容 |
---|---|
cmd | 进程正在执行的命令或者程序 |
cmdline | 进程的完整命令行 |
cwd | 链到进程当前目录的符号链接 |
environ进程的环境变量
exe|链到正被执行的文件的符号链接
fd|子目录,其中包含链到每个打开文件的描述符的链接
maps|内存映射信息(共享段,库等)
root|链到进程的根目录(由chroot设置)的符号链接
stat|进出给你的总体状态信息(ps最擅长解析这些信息)
statm|内存使用情况的信息
Linux用户能让用户通过strace命令直接观察一个进程,进程每调用一次系统调用,以及毎接收到一个信号,这个命令都能显示出来.用户甚至可以把strace附在一个正在执行的进程上,监视一会该进程,再从进程上脱离,整个过程都不会影响那个进程
失控进程有两类:一类是过度占用了某种系统资源(例如CPU时间或磁盘空间等)的用户进程,另一类是突然发狂并呈狂暴行为的系统进程.第一种失去控制的类型未必是一种故障,它可能仅仅是资源被过渡占用了,系统进程总是被要求应该具有合理的行为方式.
管理员需要在针对进程做出反应以前,先查明该进程正视试图要做的事情,原因有两个.
第一,该进程可能不但是合法的,而且对用户来说非常重要,这时候仅仅因为进程碰巧占用了大量的CPU时间而杀死他们并不合理.第二,进程可能是恶意的或具有破坏性的,如果是这样的话,管理员已经知道进程正在做的是什么事情(例如破译口令),因此就能够制止破坏行为.
相对于系统的物理储存器而言,过多占用内存的进程会导致严重的性能问题.用户可以使用top来检查进程占用内存的多少.top输出的VIRT列给出了每个进程分配的虚拟内存量,RES列给出了当前映射到特定内存页的那部分内存(驻留集)
这两个数字都包括了像库这样的共享资源,从而让他们可能有误导性.在DATA列里能找到进程专门的内存消耗量更为直接的测定值.为了在top的输出里加上这一列的显示,要在运行top之后键入f,从列表中选择DATA.DATA值表明了每个进程的数据和堆栈段占用的内存量,所以这个值对单个进程的针对性相当强(模块共享的内存段).在观察随时间的增加情况的同事,也要观察绝对的内存量.
作为管理员,可能想把所有看起来有问题的进程都挂起,一直自己找到导致故障的那个进程为止.但当确定问题症结所在以后,要记得把其他没有问题的进程重新启动起来.一旦找到导致故障的那个进程,就要删除该进程创建的所有问题.
4.13 习题
-
E4.1 解释一个文件的UID和一个正在运行的进程的真实UID以及有效UID之间的关系.除了文件的访问控制之外,进程的有效UID有什么用途?
进程的真实UID就是其创建者的用户标识号,或者更确切地说,就是复制了父进程的UID值.通常,只允许创建者(也称为属主)和超级用户对进程进行操作.EUID是"有效(effective)"的用户ID,这是一个为的UID,用来确定进程在任何给定的时刻对哪些资源和文件具有访问权限.
EUID还可以通过setuid的方式来改变进程的有效ID,EUID的当前数值,直接决定了其权限的变化范围.编写严谨的setuid程序可以让大部分执行操作与它的特殊权限无关,只有在需要额外特权的特定时刻才用得上他们.(P44) -
E4.2 假定您所负责的站点中,有一个用户已经启动了一个长期运行的进程,它消耗了机器的很大一部分资源.
a) 您如何意识到某个进程正在消耗资源?
通过查看ps或者top的输出,能够确定是哪些进程占用了过多的CPU时间.如果发现某个用户进程正在消耗的CPU时间比预计的合理值多,那么请调查这个进程.通过strace命令可以追踪特定进程的信息.
b) 假定异常的进程可能是合法的,不应该杀死它.给出您要把它"冻结"起来(在您调查期间,暂时停止它的运行)应该使用的命令
如果不能够确认失控进程存在的原因,那么可以采用STOP信号把它挂起,并发送电子邮件给该进程的属主,解释所发生的事情.以后可以采用CONT信号来重新启动该进程.P55
也就是说,这里应该停止该进程,故而命令为
sudo killall -STOP PID
c) 随后,您发现这个进程属于你的老板,必须继续运行辖区,给出您要继续执行这个任务应该使用的命令
同问题b),需要CONT信号来重新启动该进程,故而命令为
sudo killall -CONT PID
d) 另一种可能是假定需要杀死这个进程.您会发送什么信号?为什么?如果您要保证这个进程确实已经被杀死了,该怎么做?
kill -KILL pid
没有信号编号的kill命令不保证进程会被杀死,因为TERM信号可能被捕获,封锁,或忽略.而带有KILL信号的kill命令将"保证"进程的消亡,因为信号9,即KILL不能被捕获到.我们给"保证"加引号是因为进程的生命力有时候能够变得相当"旺盛",以至于连KILL也不能够影响到他们(通常是由于有些退化的I/O虚假设定,例如等待已经停止旋转的磁盘).重新启动系统通常是解决这些"不听话"的进程的唯一方法.
通过top查看某进程是否还存在或trace这个进程可以确认该进程是否依然存在.
- E4.3 找出一个能导致内存泄露的进程(如果手头没有,就自己写一个).用ps或者top来监视程序运行时的内存使用情况
懒癌末期发作,使用java代码实现,直接创建一个全局静态容器,然后死命往里面塞东西就行了,所谓内存泄露就是这个东西你已经完全用不到了,但是实际上这玩意,这玩意依然没有被java 的GC机制正常回收,从而导致有效内存越变越少,越变越少,最终导致OOM进而程序崩溃的现象.ps或者top来监视的步骤也就不做了,并不是什么意义重大的操作. - E4.4 编写一个处理ps输出的Perl脚本,确定在系统上正在运行的进程总的VSZ和RSS值.这些数字和系统物理内存和交换空间的实际量有什么样的关系?
Daily Task
终有一天,你会老去,而我,将会加冕为王!