linux课程设计报告

《Linux操作系统》课程设计报告

题目:Linux对进程和线程的管理机制研究

所在院系: 软件学院

完成学生: **

计算机科学与技术

指导教师: **

完成日期: 20xx年 6 月 6 日

目 录

1. 课程设计题目概述............................................................................................................................... 1

2. 研究内容与目的 .................................................................................................................................. 4

3. 研究报告 .............................................................................................................................................. 5

4. 总结 .................................................................................................................................................... 17

1. 课程设计题目概述

Linux是一个多用户多任务的操作系统。多用户是指多个用户可以在同一时间使用计算机系统;多任务是指Linux可以同时执行几个任务,它可以在还未执行完一个任务时又执行另一项任务。 操作系统管理多个用户的请求和多个任务。

大多数系统都只有一个CPU和一个主存,但一个系统可能有多个二级存储磁盘和多个输入/输出设备。操作系统管理这些资源并在多个用户间共享资源,当您提出一个请求时,给您造成一种假象,好像系统只被您独自占用。而实际上操作系统监控着一个等待执行的任务队列,这些任务包括用户作业、操作系统任务、邮件和打印作业等。操作系统根据每个任务的优先级为每个任务分配合适的时间片,每个时间片大约都有零点几秒,虽然看起来很短,但实际上已经足够计算机完成成千上万的指令集。每个任务都会被系统运行一段时间,然后挂起,系统转而处理其他任务;过一段时间以后再回来处理这个任务,直到某个任务完成,从任务队列中去除。

Linux系统上所有运行的东西都可以称之为一个进程。每个用户任务、每个系统管理守护进程,都可以称之为进程。Linux用分时管理方法使所有的任务共同分享系统资源。我们讨论进程的时候,不会去关心这些进程究竟是如何分配的,或者是内核如何管理分配时间片的,我们所关心的是如何去控制这些进程,让它们能够很好地为用户服务。

在Linux中,每个进程在创建时都会被分配一个数据结构,称为进程控制块(Process Control Block,简称PCB)。PCB中包含了很多重要的信息,供系统调度和进程本身执行使用,其中最重要的莫过于进程ID(process ID)了,进程ID也被称作进程标识符,是一个非负的整数,在Linux操作系统中唯一地标志一个进程,在我们最常使用的I386架构(即PC使用的架构)上,一个非负的整数的变化范围是0-32767,这也是我们所有可能取到的进程ID。其实从进程ID的名字就可以看出,它就是进程的身份证号码,每个人的身份证号码都不会相同,每个进程的进程ID也不会相同。

一个或多个进程可以合起来构成一个进程组(process group),一个或多个进程组可以合起来构成一个会话(session)。这样我们就有了对进程进行批量操作的能力,比如通过向某个进程组发送信号来实现向该组中的每个进程发送信号。

2. 研究内容与目的

Linux是一个多用户多任务的操作系统。每个用户任务、每个系统管理守护进程,都可以称之为进程。Linux用分时管理方法使所有的任务共同分享系统资源。我们讨论进程的时候,不会去关心这些进程究竟是如何分配的,或者是内核如何管理分配时间片的,我们所关心的是如何去控制这些进程,让它们能够很好地为用户服务。

Linux进程管理还是需要的,虽然在桌面应用上,我们点鼠标就能完成大多的工作,但在服务器管理中,Linux进程管理还是十分重要的。Windows的Linux进程管理真的很方便,按一下CTRL+ALT+DEL就可以调出来,随便你怎么杀和砍。我感觉Windows的Linux进程管理并不怎么样,如果有的程序真的需要CTRL+ALT+DEL的话,呵,那肯定会出现系统假死现象。或者程序错误之类的提示。弄不好就得重启,这是事实。Windows 的Linux进程管理并不优秀,只是一个友好的界面而已,Linux进程管理对于电脑使用的玩家的常用软件,然后我就学习及深入的研究Linux进程管理,参考了多方面的资料,在这里探讨Linux进程管理的使用方法

3. 研究报告

一 、进程的概念和分类

1.进程和线程的概念

Linux是一个多用户多任务的操作系统。多用户是指多个用户可以在同一 时间使用同一个linux系统;多任务是指在Linux下可以同时执行多个任务,更详细的说,linux采用了分时管理的方法,所有的任务都放在一个队列中,操作系统根据每个任务的优先级为每个任务分配合适的时间片,每个时间片很短,用户根本感觉不到是多个任务在运行,从而使所有的任务共同分享系统资源,因此linux可以在一个任务还未执行完时,暂时挂起此任务,又去执行另一个任务,过一段时间以后再回来处理这个任务,直到这个任务完成,才从任务队列中去除。这就是多任务的概念。

上面说的是单CPU多任务操作系统的情形,在这种环境下,虽然系统可以运行多个任务,但是在某一个时间点,CPU只能执行一个进程,而在多CPU多任务的操作系统下,由于有多个CPU,所以在某个时间点上,可以有多个进程同时运行。

进程的的基本定义是:在自身的虚拟地址空间运行的一个独立的程序,从操作系统的角度来看,所有在系统上运行的东西,都可以称为一个进程。 需要注意的是:程序和进程是有区别的,进程虽然有程序产生,但是它并不是程序,程序是一个进程指令的集合,它可以启用一个或多个进程,同时,程序只占用磁盘空间,而不占用系统运行资源,而进程仅仅占用系统内存空间,是动态的、可变的,关闭进程,占用的内存资源随之释放。

例如,用户在linux上打开一个文件、就会产生一个打开文件的进程,关闭文件,进程也随机关闭。如果在系统上启动一个服务,例如启动tomcat服务,就会产生一个对应的java的进程。而如果启动apache服务,就会产生多个httpd进程。

进程是资源管理的最小单位,线程是程序执行的最小单位。在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持SMP以及减小(进程/线程)上下文切换开销。,一个进程至少需要一个线程作为它的指令执行体,进

程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执行。一个进程当然可以拥有多个线程,此时,如果进程运行在SMP机器上,它就可以同时使用多个cpu来执行各个线程,达到最大程度的并行,以提高效率;同时,即使是在单cpu的机器上,采用多线程模型来设计程序,正如当年采用多进程模型代替单进程模型一样,使设计更简洁、功能更完备,程序的执行效率也更高,例如采用多个线程响应多个输入,而此时多线程模型所实现的功能实际上也可以用多进程模型来实现,而与后者相比,线程的上下文切换开销就比进程要小多了,从语义上来说,同时响应多个输入这样的功能,实际上就是共享了除cpu以外的所有资源的。 在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:

1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)臵于一个新的线程,可以避免这种尴尬的情况。

2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。

3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

2.进程的分类

按照进程的功能和运行的程序分类,进程可划分为两大类:

● 系统进程:可以执行内存资源分配和进程切换等管理工作;而且,该进程的运行不受用户的干预,即使是root用户也不能干预系统进程的运行。

● 用户进程:通过执行用户程序、应用程序或内核之外的系统程序而产生的进程,此类进程可以在用户的控制下运行或关闭。

针对用户进程,又可以分为交互进程、批处理进程和守护进程三类。

● 交互进程:由一个shell终端启动的进程,在执行过程中,需要与用户进行交互操作,可以运行于前台,也可以运行在后台。

● 批处理进程:该进程是一个进程集合,负责按顺序启动其他的进程。

● 守护进程:守护进程是一直运行的一种进程,经常在linux系统启动时启动,在系统关闭时终止。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。例如httpd进程,一直处于运行状态,等待用户的访问。还有经常用的crond进程,这个进程类似与windows的计划任务,可以周期性的执行用户设定的某些任务。

3.进程的属性

(1)进程的几种状态

进程在启动后,不一定马上开始运行,因而进程存在很多种状态。

可运行状态:处于这种状态的进程,要么正在运行、要么正准备运行。

●可中断的等待状态:这类进程处于阻塞状态,一旦达到某种条件,就会变为运行态。同时该状态的进程也会由于接收到信号而被提前唤醒进入到运行态。

● 不中断的等待状态:与“可中断的等待状态”含义基本类似,唯一不同的是处

于这个状态的进程对信号不做响应。

● 僵死状态:也就是僵死进程,每个进程在结束后都会处于僵死状态,等待父进程调用进而释放资源,处于该状态的进程已经结束,但是它的父进程还没有释放其系统资源。

● 暂停状态:表明此时的进程暂时停止,来接收某种特殊处理,

(2)进程之间的关系

在linux系统中,进程ID(用PID表示)是区分不同进程的唯一标识,它们的大小是有限制的,最大ID为32768,用UID和GID分别表示启动这个进程的用户和用户组。所有的进程都是PID为1的init进程的后代,内核在系统启动的最后阶段启动init进程,因而,这个进程是linux下所有进程的父进程,用PPID表示父进程。

下面是通过ps命令输出的sendmail进程信息:

[root@localhost ~]# ps -ef|grep sendmail

UID PID PPID C STIME TTY TIME CMD

root 3614 1 0 Oct23 ? 00:00:00 sendmail: accepting connections

相对于父进程,就存在子进程,一般每个进程都必须有一个父进程,父进程与子进程之间是管理与被管理的关系,当父进程停止时,子进程也随之消失,但是子进程关闭,父进程不一定终止。

如果父进程在子进程退出之前就退出,那么所有子进程就变成的一个孤儿进程, 如果没有相应的处理机制的话,这些孤儿进程就会一直处于僵死状态,资源无法释放,此时解决的办法是在启动的进程内找一个进程作为这些孤儿进程的父进程,或者直接让init进程作为它们的父进程,进而释放孤儿进程占用的资源。

二、进程的数据结构与其生命周期

(1)底层数据结构:双向链表

在进程管理中,双向链表是一个基础性的数据结构(后面涉及到的运行队列和等待队列等都使用了这个数据结构)。它的声明如下(虽然名称中含有head,但实际上每个结点都是相同的):

struct list_head {

struct list_head *next, *prev;

};

其中含有指向前一节点和后一节点的指针。而作为双向链表,提供的主要操作就是添加/删除元素、遍历链表(特别是list_for_each()函数很重要,可以对每个元素采取相同的操作)。

(2)进程描述符

进程描述符是一个名为task_struct的C结构(进程也就是任务,所以叫task),其中包含了进程所有的信息。其中有几个成员变量是我们下面将要用到的(图中用小黑框标出):run_list,tasks。(点击这里看大图)

(3)双向链表与宿主的结合

我们回忆一下,如果我们需要实现二叉树数据结构,那么往往需要先实现其树节点的数据结构(含有左右子节点的指针),而且一般是包含在二叉树内部;高级的数据结构需要先实现底层的数据结构。我们将二叉树等这一类高级的数据结构称为“宿主”,其中包含有底层数据结构的节点。Linux内核中也不会直接应用双向链表这种数据结构,但是确实在很多地方都需要链表的接口,于是也采用了这种宿主与节点的模式。这实际上是面向对象设计中的组合,实现了代码复用以及降低了耦合性。

具体代码实现如下:

我们现在知道list成员是可以添加/删除元素、遍历的,但是如何才能遍历所有 的foo对象或者foo对象中的data成员呢?内核采用了一个技巧,即知道宿主对象的首地址以及某成员相对首地址的偏移,就能知道某成员的地址了,然后就能取出相应的值。表示成公式就是:首地址+某成员偏移=某成员地址

struct foo {

int data;

struct list_head list;

};

这样的简单代码用过C语言的也应该都写过:

#include

typedef struct _test

{ char i;

int j;

char k;

}Test;

int main()

{ Test *p = 0; printf("%p\n", &(p->k)); }

这里就可以打印出成员k相对于首地址的偏移。当然,上面的公式移项就可以换一种使用方法,已知某成员地址及其偏移量,即可求出宿主的首地址。

内核中实际上使用的是几个宏来具体计算:

offsetof()/container_of()/typeof()。

其中typeof()宏就是获得其参数的类型,是由GCC编译器提供的。

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

#define container_of(ptr, type, member) ({ \

const typeof( ((type *)0)->member ) *__mptr = (ptr); \

(type *)( (char *)__mptr - offsetof(type,member) );})

这里唯一需要解释的就是offsetof宏中的(size_t) &((TYPE *)0)->MEMBER语句:(TYPE*)0其实就是相当于上面例子代码中的Test *p=0语句,将0看做一个地址,然后通过转型为TYPE型的指针,就创建了一个起始地址为0的TYPE型的对象,然后用&取出其MEMBER成员的地址,转化成以size_t为单位的偏移字节量。

最终我们拥有了已知某成员即可得到宿主及其成员变量的方法,这样也就是说最终宿主也拥有了添加/删除成员、遍历的接口。

(4)双向链表与进程的结合实例:进程链表,运行队列与等待队列

这里所说的进程链表(见《深入》p.93)是指把系统中所有的进程都串起来的链表。使用了进程描述符中的tasks字段,这个字段是list_head型。

运行队列则是将所有状态为TASK_RUNNING的进程(可运行进程,即可被调度马上执行的进程)串接起来的链表。由于2.6版的内核采用了新的调度系统,所有的可

运行进程按照优先级被串在了140个不同的队列中(即共有140个高低不同的优先级)。

使用的是进程描述符中的run_list字段。

等待队列是指状态为TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的进程的组织方式。它的底层数据结构也是双向链表。其头结点和普通节点的声明如下(重要字段用粗体标出):

struct _ _wait_queue_head {

spinlock_t lock;

struct list_head task_list;

};

struct __wait_queue {

unsigned int flags;

struct task_struct * task;

wait_queue_func_t func;

struct list_head task_list;

};

我们可以看到无论是头结点还是普通节点仍然通过包含一个list_head来实现串接,但是明显的与上面谈到的两个队列不同的是:(a)队头节点中含有自旋锁(具体原因请自行查阅);(b)等待队列不是包含在进程描述符中,而是在队列节点中包含了进程描述符task字段。

(5)进程哈希表pidhash及其中的链表

上面我们说到的数据结构都是链表,但是我们在进程管理中也用到一个hash表。为什么要使用hash表呢?因为我们在linux中经常会用到此类操作:给出进程号pid,要求得到进程描述符(例如kill()系统调用,参数为pid,但是函数要去改变进程描述符中的state字段)。如果我们在进程队列中遍历然后看其pid是否为所需的pid,这样做效率是很低的。而hash就是一个以空间换时间的方法。具体的hash函数就不写了(见《深入》p.97)。但是这里仍然使用了一个链表,是因为凡是hash表,就会发生冲突(因为我们一般不会使用一一对应的hash表,这里所用的hash表一般是2048项,但系统中的进程往往可以最大到32767项)。冲突的解决方法采用了分离链

接法:即将所有冲突的项都串联到一个表项上,于是形成了链表。理论上会形成2048个链表,但是基本上冲突的概率比较小,所以链表都不会很长。

(6)进程的生命周期

进程的生命周期本章里面主要包括:创建、切换、撤销(调度将在第七章)。而这些功能主要都是由一些wrapper模式的内核函数实现的。

创建:clone()调用do_fork(),do_fork()再调用copy_process()。

切换:switch_to()宏

撤销:exit_group()调用do_group_exit()终止整个线程组;exit()调用do_exit()终止单个的线程。

三、 进程的监控与管理

Linux下,监控和管理进程的命令有很多,下面我们以ps、top、pstree、lsof四个最常用的指令介绍如果有效的监控和管理linux下的各种进程。

2.1 利用ps命令监控系统进程

ps是linux下最常用的进程监控命令,关于ps命令的语法和使用选项,我们 在第四章已经有了详细的讲解,这里重点讲述如何利用ps指令监控和管理系统进程。 请看下面的示例:

下面是apache进程的输出信息

[root@localhost ~]#ps -ef | grep httpd

UID PID PPID C STIME TTY TIME CMD

nobody 7272 26037 0 Nov06 ? 00:00:00 /apache2/bin/httpd -k start nobody 7274 26037 0 Nov06 ? 00:00:00 /apache2/bin/httpd -k start nobody 7400 26037 0 Nov06 ? 00:00:00 /apache2/bin/httpd -k start nobody 7508 26037 0 00:09 ? 00:00:00 /apache2/bin/httpd -k start nobody 7513 26037 0 00:09 ? 00:00:00 /apache2/bin/httpd -k start nobody 7515 26037 0 00:09 ? 00:00:00 /apache2/bin/httpd -k start nobody 11998 26037 0 11:14 ? 00:00:00 /apache2/bin/httpd -k start nobody 12941 26037 0 16:25 ? 00:00:00 /apache2/bin/httpd -k start nobody 12979 26037 0 16:44 ? 00:00:00 /apache2/bin/httpd -k start

root 26037 1 0 Oct23 ? 00:00:00 /apache2/bin/httpd -k start

其中,UID是用户的ID标识号,PID是进程的标识号,PPID表示父进程,STIME 表示进程的启动时间,TTY表示进程所属的终端控制台,TIME表示进程启动后累计使用的CPU总时间,CMD表示正在执行的命令。

从中可以清楚的看出,父进程和子进程的对应关系, PPID为26037的所有进程均为子进程,而PID为26037的进程是所有子进程的父进程,子进程由nobody用户启动,而父进程由root用户启动,父进程对应的PPID为1,即父进程同时为init进程的子进程。

其实也可以通过下面的指令方式查看子进程与父进程的对应关系,请看如下操作:

[root@localhost ~]# ps auxf | grep httpd

USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND

root 26037 0.0 0.1 6316 2884 ? Ss Oct23 0:00 /apache2/bin/httpd -k start nobody 7272 0.0 0.1 7016 3740 ? S Nov06 0:00 \_ /apache2/bin/httpd -k start nobody 7274 0.0 0.1 7016 3704 ? S Nov06 0:00 \_ /apache2/bin/httpd -k start nobody 7400 0.0 0.1 7012 3676 ? S Nov06 0:00 \_ /apache2/bin/httpd -k start nobody 7508 0.0 0.1 7012 3732 ? S 00:09 0:00 \_ /apache2/bin/httpd -k start nobody 7513 0.0 0.1 7012 3700 ? S 00:09 0:00 \_ /apache2/bin/httpd -k start nobody 12979 0.0 0.1 7016 3684 ? S 16:44 0:00 \_ /apache2/bin/httpd -k start nobody 12980 0.0 0.1 7012 3652 ? S 16:44 0:00 \_ /apache2/bin/httpd -k start nobody 12982 0.0 0.1 7016 3664 ? S 16:44 0:00 \_ /apache2/bin/httpd -k start nobody 22664 0.0 0.1 6880 3540 ? S 22:24 0:00 \_ /apache2/bin/httpd -k start 其中,%CPU表示进程占用的CPU百分比,%MEM表示进程占用内存的百分比,VSZ 表示进程虚拟大小,RSS表示进程的实际内存(驻留集)大小(单位是页)。STAT表示进程的状态,进程的状态有很多种:用“R”表示正在运行中的进程,用“S”表示处于休眠状态的进程,用“Z”表示僵死进程,用“<”表示优先级高的进程,用“N”表示优先级较低的进程,用“s”表示父进程,用“+”表示位于后台的进程。START表示启动进程的时间。

这个例子将进程之间的关系用树形结构形象的表示出来,可以很清楚的看到,第一个进程为父进程,而其它进程均为子进程。同时从这个输出还可以看到每个进程占用CPU、内存的百分比,还有进程所处的状态等等。

zombie 进程 :不是异常情况。一个进程从创建到结束在最后那一段时间遍是僵尸。留在内存中等待父进程取的东西便是僵尸。任何程序都有僵尸状态,它占用一点内存资源,仅仅是表象而已不必害怕。如果程序有问题有机会遇见,解决大批量僵尸简单有效的办法是重起。kill是无任何效果的stop模式:与sleep进程应区别,sleep会主动放弃cpu,而stop是被动放弃cpu ,例单步跟踪,stop(暂停)的进程是无法自己回到运行状态的。

2.2 利用pstree监控系统进程

pstree命令以树形结构显示程序和进程之间的关系,使用格式如下:

pstree [-acnpu] [/]

选项含义如下:

● -a 显示启动每个进程对应的完整指令,包含启动进程的路径、参数等等。 ● -c 不使用精简法显示进程信息,即显示的进程中包含子进程和父进程。 ● -n 根据进程PID号来排序输出,默认是以程序名称排序输出的。

● -p 显示进程的PID。

● -u 显示进程对应的用户名称。

● PID:即进程对应的PID号,或者叫进程识别号。

● user:系统用户名。

pstree清楚的显示了程序和进程之间的关系,如果不指定进程的PID号,或者 不指定用户名称,则将以init进程为根进程,显示系统的所有程序和进程信息,若指定用户或PID,则将以用户或PID为根进程,显示用户或PID对应的所有程序和进程。

举例如下:

如果想知道某个用户下都启动了哪些进程的话,pstree指令可以很容易实现,下面显示mysql用户下对应的进程信息,执行如下命令:

[root@localhost ~]# pstree mysql

mysqld---6*[{mysqld}]

该输出显示了mysql用户下对应的进程为mysqld,并且mysqld进程拥有5个子进程(5个子进程加一个父进程,共6个进程)。

为了更详细的了解每个进程的信息,例如每个子进程和父进程对应的PID,执行如下命令:

[root@localhost ~]# pstree -c -p mysql

mysqld(18785)-+-{mysqld}(18787)

|-{mysqld}(18788)

|-{mysqld}(18789)

|-{mysqld}(18790)

|-{mysqld}(18791)

`-{mysqld}(29625)

通过“-p、-c”参数,清楚的显示了父进程和子进程,以及它们各种的PID。

如果知道进程对应的PID,想得到进程是由哪个用户启动的,可以执行如下命令:

[root@localhost ~]# pstree -u 26037

httpd---10*[httpd(nobody)]

从上面可知,httpd进程是由nobody用户启动的。

如果要查看httpd父进程和每个子进程分别对应的PID,可以执行如下命令组 合:

[root@localhost ~]# pstree -u -p 26037

httpd(26037)-+-httpd(24562,nobody)

|-httpd(24563,nobody)

|-httpd(24566,nobody)

|-httpd(24567,nobody)

|-httpd(24631,nobody)

|-httpd(24648,nobody)

|-httpd(24650,nobody)

|-httpd(24654,nobody)

|-httpd(26156,nobody)

`-httpd(29014,nobody)

如果要得到启动httpd进程的程序路径、参数组合,执行如下命令:

[root@localhost ~]# pstree -a -u -p 26037

httpd,26037 -k start

|-httpd,24563,nobody -k start

|-httpd,24566,nobody -k start

|-httpd,24567,nobody -k start

|-httpd,24631,nobody -k start

|-httpd,24648,nobody -k start

|-httpd,24650,nobody -k start

|-httpd,24654,nobody -k start

|-httpd,26156,nobody -k start

`-httpd,29014,nobody -k start

2.3 利用top监控系统进程

top命令是监控系统进程必不可少的工具,与ps命令相比,top命令动态、实 时的显示进程状态,而ps只能显示进程某一时刻的信息,同时,top命令提供了一个交互界面,用户可以根据需要,人性化的定制自己的输出,更清楚的了解进程的实时状态。

关于top指令的用法,在第四章已经有了详细的介绍,这里通过几个例子,阐述一下top命令在系统进程监控中的作用和优点。

下面这个例子是某系统在某时刻执行top命令后的输出:

[root@webserver ~]# top

Tasks: 126 total, 1 running, 123 sleeping, 1 stopped, 1 zombie

Cpu(s): 0.8% us, 0.1% sy, 0.0% ni, 99.0% id, 0.0% wa, 0.0% hi, 0.0% si Mem: 8306544k total, 8200452k used, 106092k free, 234340k buffers Swap: 8385888k total, 160k used, 8385728k free, 7348560k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND

21115 root 23 0 1236m 360m 2384 S 6 4.4 382:24.14 java

30295 root 16 0 3552 984 760 R 1 0.0 0:00.09 top

30118 nobody 15 0 6904 3132 1676 S 0 0.0 0:00.47 httpd

30250 nobody 15 0 6900 3088 1660 S 0 0.0 0:00.06 httpd

1 root 16 0 1780 552 472 S 0 0.0 0:01.25 init

从top命令的输出可知,此系统有java和httpd两个用户进程在运行。

进程PID为21115的java进程由root用户启动,优先级(PR)为23,占用的 虚拟内存总量(VIRT)为1236M,未被换出的物理内存(RES)为360M,共享内存(SHR)为2384 kb。通过这几个选项可以了解java进程对内存的使用量,有助于系统管理员对系统虚拟内存使用状况的掌控。

此刻java进程处于休眠状态(S),从上次更新到现在java占用cpu时间(%CPU)为6%,占用物理内存(%MEM)为4.4%,从进程启动到现在java占用cpu总时间(TIME+)为“382:24.14”,单位是1/100秒。通过了解这些信息,可以使系统管理员掌握java进程对系统CPU、物理内存的使用状况。

两个httpd进程由nobody用户启动,优先级都为15,同时都处于休眠状态。 除去这两个进程,还有top进程,也就是我们执行top命令产生的进程,从进程状态项可知,此进程处于运行状态,另一个是init进程,即所有系统进程的父进程,对应的PID为1。

当然top的输出还有很多进程信息,这里仅仅拿出前几个进程进行重点讲解,理解其它进程的含义基本与这些相同。

2.4 利用lsof监控系统进程与程序

lsof全名list opened files,也就是列举系统中已经被打开的文件,通过lsof, 我们就可以根据文件找到对应的进程信息,也可以根据进程信息找到进程打开的文件。

lsof指令功能强大,这里介绍“-c,-g,-p,-i”这四个最常用参数的使用。 更详细的介绍请参看man lsof。

lsof filename:显示使用filename文件的进程。

如果想知道某个特定的文件由哪个进程在使用,可以通过“lsof 文件名”方式 得到,例如:

[root@localhost ~]# lsof /var/log/messages

COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME

syslogd 2027 root 1w REG 8,6 43167 31916 /var/log/messages

从这个输出可知,/var/log/messages文件是由syslogd进程在使用。

lsof -c abc :显示abc进程现在打开的文件,例如:

[root@localhost ~]# lsof -c nfs

COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME

nfsd4 2761 root cwd DIR 8,3 4096 2 /

nfsd4 2761 root rtd DIR 8,3 4096 2 /

nfsd4 2761 root txt unknown /proc/2761/exe

nfsd 2762 root cwd DIR 8,3 4096 2 /

nfsd 2762 root rtd DIR 8,3 4096 2 /

nfsd 2762 root txt unknown /proc/2762/exe

nfsd 2763 root cwd DIR 8,3 4096 2 /

nfsd 2763 root rtd DIR 8,3 4096 2 /

nfsd 2763 root txt unknown /proc/2763/exe

上例显示了nfs进程打开的文件信息,FD列表示文件描述符,TYPE列显示文件 的类型,SIZE列显示文件的大小,NODE列显示本地文件的node码,NAME列显示文 件的全路径或挂载点。

lsof -g gid:显示指定的进程组打开的文件情况,例如:

[root@localhost ~]# lsof -g 3626

COMMAND PID PGID USER FD TYPE DEVICE SIZE NODE NAME

sendmail 3626 3626 smmsp cwd DIR 8,8 4853760 32714 /var/spool/clientmqueue

sendmail 3626 3626 smmsp rtd DIR 8,10 4096 2 /

sendmail 3626 3626 smmsp txt REG 8,9 732356 1152124 /usr/sbin/sendmail.sendmail

sendmail 3626 3626 smmsp mem REG 8,10 106397 1158794 /lib/ld-2.3.4.so

sendmail 3626 3626 smmsp mem REG 8,10 95148 1175044 /lib/libnsl-2.3.4.so .............省略...............

sendmail 3626 3626 smmsp 3u unix 0xf41e5bc0 9592 socket

sendmail 3626 3626 smmsp 4wW REG 8,8 50 523293 /var/run/sm-client.pid 其中,PGID列表示进程组的ID编号。

上面输出,显示了sendmail程序当前打开的所有文件、设备、库及套接字等。 lsof -p PID:PID是进程号,通过进程号显示程序打开的所有文件及相关进程, 例如,想知道init进程打开了哪些文件的话,可以执行“lsof -p 1”命令,输出 结果如下:

[root@localhost ~]# lsof -p 1

COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME

init 1 root cwd DIR 8,10 4096 2 /

init 1 root rtd DIR 8,10 4096 2 /

init 1 root txt REG 8,10 32684 897823 /sbin/init

init 1 root mem REG 8,10 56320 2175328 /lib/libselinux.so.1

init 1 root mem REG 8,10 106397 1158794 /lib/ld-2.3.4.so

init 1 root mem REG 8,10 1454462 1161560 /lib/tls/libc-2.3.4.so

init 1 root mem REG 8,10 53736 1158819 /lib/libsepol.so.1

init 1 root 10u FIFO 0,13 966 /dev/initctl

● lsof -i 通过监听指定的协议、端口、主机等信息,显示符合条件的进程信息。 使用语法为:

lsof -i [46] [protocol][@hostname][:service|port]

● 46:4代表IPv4,6代表IPv6。

● protocol:传输协议,可以是TCP或UDP。

● hostname:主机名称或者IP地址。

● service:进程的服务名,例如nfs、ssh、ftp等。

● port:系统中服务对应的端口号。例如http服务默认对应80,ssh服务默认对 应22等等。

例如:

显示系统中tcp协议对应的25端口进程信息:

[root@localhost ~]# lsof -i tcp:25

COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME

sendmail 2252 root 4u IPv4 5874 TCP localhost:smtp (LISTEN)

显示系统中80端口对应的进程信息:

[root@localhost ~]# lsof -i :80

COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME

httpd 16474 nobody 3u IPv6 7316069 TCP *:http (LISTEN)

httpd 16475 nobody 3u IPv6 7316069 TCP *:http (LISTEN)

httpd 16578 nobody 3u IPv6 7316069 TCP *:http (LISTEN)

显示本机udp协议对应的53端口开启的进程信息:

[root@localhost ~]# lsof -i udp@127.0.0.1:53

COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME

named 21322 named 20u IPv4 9130640 UDP localhost:domain

通过lsof命令能够清楚的了解进程和文件以及程序之间的对应关系,熟练掌握 lsof的使用,对linux的进程管理有很大帮助。

大家在平时的编程中,如果用到了exec函数族,一定记得要加错误判断语句。因为与其他系统调用比起来,exec很容易受伤,被执行文件的位臵,权限等很多因素都能导致该调用的失败。最常见的错误是:

1. 找不到文件或路径,此时errno被设臵为ENOENT;

2. 数组argv和envp忘记用NULL结束,此时errno被设臵为EFAULT;

3. 没有对要执行文件的运行权限,此时errno被设臵为EACCES。

4. 总结

进程的一生

下面就让我用一些形象的比喻,来对进程短暂的一生作一个小小的总结: 随着一句fork,一个新进程呱呱落地,但它这时只是老进程的一个克隆。 然后随着exec,新进程脱胎换骨,离家独立,开始了为人民服务的职业生涯。

人有生老病死,进程也一样,它可以是自然死亡,即运行到main函数的最后一个 "}",从容地离我们而去;也可以是自杀,自杀有2种方式,一种是调用exit函数,一种是在main函数内使用return,无论哪一种方式,它都可以留下遗书,放在返回值里保留下来;它还甚至能可被谋杀,被其它进程通过另外一些方式结束他的生命。

进程死掉以后,会留下一具僵尸,wait和waitpid充当了殓尸工,把僵尸推去火化,使其最终归于无形。

这就是进程完整的一生。

相关推荐