原文链接:http://bbs.linuxtone.org/thread-626-1-1.html
作者:神仙姐姐
一、进程概述
1、进程的概念
进程(Process)是指一个程序在其自身的虚拟空间中的一次执行的活动。之所以要创建进程,就是为了使多个程序可以并发的执行,从而提高系统的资源利用率和吞吐量。
进程和程序的概念不同,下面是对这两个概念的比较:
※程序只是一个静态指令的集合;而进程是一个程序的动态执行过程,它具有生命期,是动态产生和消亡的。
※进程是资源申请、高度和独立运行的单位,因此,它使用系统中的运行资源;而程序不能申请系统资源,不能被系统调度,也不能作为独立运行的单位,因此,它不占用系统的运行资源。
※程序和进程无一一对应的关系。一方面一个程序可以由多个进程所共用,即一个程序在运行过程中可以产生多个进程;另一方面,一个进程在生命期内可以顺序地执行若干的程序。
2、Linux中的进程
在 Linux系统中,每一个进程都有一个识别号PID(Process ID),用以区分不同的进程,系统启动后的第一个进程是init,它的PID是1。init是唯一一个由系统内核直接运行的进程。新的进程可以用系统调用 fork来产生,就是从一个已存在的旧进程分聘个新进程来,旧进程是新产生进程的父进程,新进程是产生它的进程的子进程,除了init之外,每一个进程都有一个父进程。当系统启动后,init进程会创建login进程等待用户登录系统,用户登录后,login进程会为用户启动shell进程,而此后用户运行的进程都是由shell衍生出来的。
除了进程识别号外,每个进程还有另外4个识别号,即实际用户识别号(real user ID)、实际组识别号(real group ID)、有效用户识别号(effect user ID)和有效组识别号(effect group ID)。实际用户识别号和实际组识别号的作用是识别正在运行此进程的用户和组。一个进程的实际用户识别号和实际组识别号就是运行此进程的用户的识别号(UID)和组识别号(GID)。有效用户识别号和有效组识别号的作用是确定一个进程对其访问的文件的权限和优先权。除了产生进程的程序被设置SUID和 SGID之外,一般有效用户识别号和有效组识别号与实际用户识别号和实际组识别号相同。如果程序设置了SUID和SGID,则此进程相应地有效用户识别号和有效组识别号将和运行此进程的文件的所属用户的UID或所属组的GID相同。
例如:一个可执行文件/usr/bin /passwd,其所属用户是root(UID为0),此文件被设置了SUID位。则当一个UID为500、GID为501的用户执行此命令时,产生的进程的实际用户识别号和实际组识别号分别是500和501,而其有效用户识别号是0,有效组识别号是501。
3、进程的类型
Linux系统中进程分为3种不同的类型:
※交互进程:由一个shell启动的进程。交互进程既可以在前台运行,也可以后台运行。
※批处理进程:不与特定的终端相关联,提交到等待队列中顺序执行的进程。
※守护进程(也就是实时进程? ):在Linux启动时初始化,需要时运行于后台的进程。
4、进程的启动方式
调动一个进程有两个主要途径:手工启动和调度启动。
(1)手工启动
由用户输入命令,直接启动一个进程便是手工启动进程。手工启动进程又可分为前台启动和
后台启动。
前台启动:手工启动一个进程的最常用的方式。一般地,用户输入一个命令“ls -l”,这
就已经启动了一个进程,而且是一个前台的进程。
后台启动:直接从后台手工启动一个进程用的比较少一些,除非是该进程甚为耗时,且用户也不着急输入结果的时候。假设用户要启动一个需要长时间运行的格式化文本的进程,为了不使整个shell在耗时进程的运行过程中都处于“霸占终端”的状态,从后启动这个进程是明智的选择。在后台启动一个进程,可以在命令行后使用&命令,例如:ls -R />list&
(2)调度启动
这种启动方式是事先进行设置,根据用户要求让系统自行启动,以下详解。
二、进程管理
1、查看系统中的进程
在Linux中,使用ps对进程进行查看,格式为:ps [选项]
ps命令有大量的参数,下面介绍几个常用的选项:
a:显示所有进程
e:在命令后显示环境变量
u:显示用户名和启动时间等信息
x:显示没有控制终端的进程
f:显示进程树
w:宽行输出
-e:显示所有进程
-f:显示全部
下面列出ps命令输出的重要信息的含义:
PID:进程号
PPID:父进程的进程号
TTY:进程从哪个终端启动
STAT:进程当前状态
START:进程开始执行的时间
VSZ:进程占用的虚拟内存空间(KB)
RSS:进程所占用的内存的空间
TIME:进程自从启动以来占用CPU的总时间
USER:用户名
%CPU:占用CPU时间与总时间的百分比
%MEM:占用内存与系统内存总量的百分比
SIZE:进程代码大小+数据大小+栈空间大小(KB)
COMMAND/CMD:进程的命令名
其中,在进程状态(STAT)一栏中表示状态的字符的意义如下:
R:进程正在执行中(进程排在执行队列里,随时都会被执行)
S:进程处于睡眠状态(sleeping)
T:追踪或停止
Z:僵尸进程(zombie),进程已经被终止,但其父进程并不知道,没有妥善处理,导致其处于僵尸状态
W:进程没有固定的pages
<:高优先级的进程
N:低优先级的进程
例如:
ps //显示出当前用户在shell下所运行的进程
ps -u osmond //只查看用户osmond的进程
ps -aux //列出系统中正在运行的所有进程的详细信息
ps -auxf //显示系统进程树
提示:①如果想看清所运行的进程的完整命令行,可以使用w参数
②ps命令经常与管道命令连用,如: ps -aux|more ps -aux|grep httpd
2、杀死系统中的进程
(1)kill命令
程序无法正常终止时,需要用kill命令来杀死程序产生的进程。kill命令不但能杀死进程,同时也会杀死该进程的所有子进程。其命令格式为:kill [-signal] PID
其中PID是进程的识别号,signal是向进程发出的进程信号。下面列出常的信号说明:
信号 数值 用途
SIGHUP 1 从终端发出的结束信号
SIGINT 2 从键盘上发出的中断信号(Ctrl+C)
SIGQUT 3 从键盘上发出的退出信号(Ctrl+\)
SIGFPE 8 浮点异常(如:被0除)
SIGKILL 9 结束接收信号的进程(强行杀死进程)
SIGTERM 15 kill命令默认的终止信号
SIGCHLD 17 子进程终止或结束的信号
SIGSTOP 19 从键盘来执行的信号(Ctrl+D)
要终止一个进程首先要知道它的PID,这就需要用到上面介绍过的ps命令。先利用ps查看要杀死进程的PID,然后用kill命令杀死进程。默认情况下,kill命令发送给进程的终止信号是15,有些进程会不理会这个信号,这时可以用信号9来强制杀死进程,信号9是不会被忽略的强制执行信号。如:kill -9 1621
(2)killall命令
用户可以用killall命令来杀死进程。和kill不同的是,在killall命令后面指定的是要杀死的进程的命令名称,而不是PID;和kill命令相同的是,也可以指定发送给进程的终止信号。如:要杀死所有名为cat的进程,可以用如下命令:killall -9 cat
二、守护进程
1、守护进程的概念
(1)什么是守护进程
通常Linux系统上提供服务程序是由运行在后台的守护程序(Daemon)来执行的。一个实际运行中的系统一般会有多个这样的程序在运行。这些后台守护程序在系统开机后就运行了,并且时刻地监听前台客户的服务请求,一旦客户发出了服务请求,守护进程便为他们提供服务。Windows中的一些守护进程被称为 “服务”。由于此类程序运行在后台,除非程序主动退出或者人为终止,否则他们将一直运行下去直至系统关闭。所以我们将此类提供服务功能的程序称为守护进程。
(2)守护进程的工作原理
守护进程在系统中是如何工作的呢?我们知道,网络程序之间的连接是通过端口之间的连接而实现的。在C/S模型中,服务器监听(Listen)在一个特定的端口上等待客户的连接。连接成功之后客户机与服务器通过端口进行数据通信。守护进程的工作就是打开一个端口,并且等待进入的连接,守护进程就创建(Fork)子进程来响应此连接,而父进程继续监听更多的服务请求。正因为如此,每个守护进程都可以处理多个客户服务请求。
(3)网络守护进程(xinetd)[Extended Internet Daemon]
xinetd是新一代的网络守护进程服务程序,提供类似于早期的inetd+tcp_wrapper的功能,与之相比,xinetd更加强大和安全。
Linux 的很多网络服务都是通过守护进程(Daemon)的方式执行的。守护进程是在系统中持续运行的进程,它等待着随时执行自身负责的服务。Linux系统可以同时提供多种不同的网络服务,每一种网络服务由一个守护进程负责,这个守护进程在特定的网络端口进行监听,一旦有客户端发出要求服务的请求,守护进程就启动相应的服务程序来回应客户的请求。例如,SSH服务的守护进程是sshd,sshd从系统启动时就一直监听着22端口,如果有远程用户用ssh命令来连接服务器,sshd在收到客户端的请求后就会为用户启动连接会话,同时自动启动另一个sshd来继续监听此端口,等待处理其他客户的请求。
但是如果所有网络服务都要启动一个单独的守护进程,这些守护进程会从系统启动开始一直持续运行,这样无疑会消耗大量的系统资源。其实如果没有客户端提出请求,服务端的网络功能可以不启动,只要在需要时启动就可以了。xinetd就是一个管理很多网络服务的守护进程,它同时监听着它所管理的服务的所有端口,当有客户提出服务请求时,它会判断这是对哪一个服务的请求,然后再开启此服务的守护进程,由该守护进程处理客户的请求。这样很多不同的服务的守护进程就被 xinetd一个守护进程替代了,因此xinetd也称为超级服务器(Super-Server)。
在Red Hat Linux中xinetd的主配置文件是/etc/xinetd.conf,另外每一个由xinetd启动的服务在目录/etc/xinetd.d/下都有一个以服务名称命名的配置文件,在主配置文件/etc/winetd.conf中将/etc/xinetd.d/目录下的所有文件的内容使用 include dir /etc/xinetd.d语句包含进来。
补充:
一个或多个进程可以合起来构成一个进程组(process group),一个或多个进程组可以合起来构成一个会话(session)。这样我们就有了对进程进行批量操作的能力,比如通过向某个进程组发送信号来实现向该组中的每个进程发送信号。
疑问:
如果fork后子进程和父进程几乎完全一样,而系统中产生新进程唯一的方法就是fork,那岂不是系统中所有的进程都要一模一样吗?那我们要执行新的应用程序时候怎么办呢?和exec函数有关!
帖子2来自:http://topic.csdn.net/t/20061205/16/5208352.html,原作者就不清楚了。
首先,让我们了解一下linux的进程队列结构。
1.1 linux进程队列其实是一个双向链表,每一个结点就是一个进程描述符。进程描述符里面包含了进程所有的信息:进程所打开的文件、进程的地址空间、挂起信号、进程状态和其他更多的信息。以下是进程描述符的部分定义:
struct task_struct {
volatile long state;
void *stack;
unsigned int flags;
int prio, static_prio;
struct list_head tasks;
struct mm_struct *mm, *active_mm;//mm 代表的是进程的内存描述符
pid_t pid;
pid_t tgid;
struct task_struct *real_parent;
char comm[TASK_COMM_LEN];
struct thread_struct thread;
struct files_struct *files;
...
};
linux通过slab分配器分配task-struct结构。它实际上是一个栈,其栈顶(向上增长的栈)或栈底(向下增长的栈)中有一个thread info结构,其中的task指针指向task_struct。 Linux 维护一个称为 current 的符号,代表的是当前运行的进程(类型是 task_struct)。
1.2 那么,如何识别这些进程呢?进程描述符里面有一个pid,它就是进程间区别标志。它实际上是一个短整型数据,也就是说它最大值为32767。一般来说,32767对于很多桌面系统已经足够,但是对于大型服务器,就必须修改这个上限。
那么,又如何寻找这些进程呢?呵呵,进程队列是一个双向链表,那么我们当然可以遍历它寻找需要的进程。当然,这不是什么好主意,因为这很耗费时间!一般的,有些硬件体系有专门的寄存器存放了指向当前task_struct的指针,可以很快的找到当前进程。在X86系列机中,寄存器非常有限,一般使用通过计算thread_info的偏移量的方法来找到当前进程。
1.3 接下来,我们讨论linux进程的状态。
·运行(TASK_RUNNING):一般指就绪状态——指进程随时可以投入运行和运行状态——指进程正在运行。
·可中断(TASK_INTERUPTIBLE):这个时候,进程停止运行,直到它获得满足它继续运行的条件。
·不可中断(TASK_UNINTERUPTIBLE):这个时候,进程也是停止运行,但是,即便它获得满足它继续运行的条件,它也不会马上被激活。
·僵死(TASK_ZOMBIE):进程运行结束,等待父进程销毁它。
·停止(TASK_STOPPED):进程停止运行,当进程收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信号,就会停止。在调试期间,进程收到任何信号,也会停止运行。
那么,如何设置进程这些状态呢?使用set_task_state(task,state)函数,就可以改变进程描述符里的进程状态state。
1.4 好了,进程之间的关系如何?前面说了,进程队列是一个双向链表,那么,他们具体是如何连接的呢?
首先,系统开启的最后一段时间,系统将创建一个init进程,它是系统的第一个进程,它是所有进程的祖先,任何进程,如果不断的追朔其父进程,那么都会回到这个init进程。
这样,进程队列中的进程就有两种连接方式:一种就是刚才说的双向链表;还有一种就是各进程有父子关系,或者兄弟关系。
1.5 关于创建进程。linux创建进程很复杂,首先调用fork(),接着fork()调用clone(),然后clone()调用do_fork(),而do_fork()调用copy_process()。每一步调用都完成不同的任务。
首先是copy_process()的一系列工作:
第一步调用dup_task_struct()复制父进程(没错,这个时候描述符也是一样的!),创建内核栈,检查进程数目有没有超过资源限制。
接着,把进程描述符中的各项设为0或者初始值,并把进程状态设为不可中断(TASK_UNINTERUPTIBLE),保证进程未创建完毕前不要投入运行。
第三步,调用copy_flags(),设置描述符中的flag,表明进程是否有超级权限。
然后调用get_pid()设置进程pid。
第五步,根据clone()传来的参数,拷贝或者打开文件、文件系统信息信号处理函数、进程地址空间和命名空间等等。一般来说,linux其实使用的是写时复用,也就是共享资源。(待会会讨论到)
第六步,让父进程和子进程平分剩余的时间片。
最后,返回一个指向子进程的指针。
如果成功的返回到do_fork(),这个新建的子进程会被唤醒,准备投入运行。当然,一般是马上投入运行,也即调用exec()函数,将程序代码(当然是计算机可以直接识别运行的代码)载入进程地址空间,执行里面的代码!
1.6 vfork()——这也是一个创建进程的函数,唯一和fork()不一样的是,再创建子进程时它会使父进程挂起,直到创建结束。
1.7 线程的话题。其实linux系统没有线程的概念!很吃惊吧?更准确的说,linux内核中,进程和线程都称为任务(task),只有在用户级层面,才有线程这一说法。即便如此,linux仍然视进程和线程为同样的概念,因为它们都有自己的描述符。
一般来说,我们都说线程是“轻进程”,因为线程共享产生它的进程的地址空间、文件资源等等。但是,linux中,进程的产生很多时候使用写时复用,既和父进程共享资源(如果子进程创建后马上调用exec(),那么连共享资源也免了,进程可以直接投入运行!),这一点和线程一样,使得进程和线程没有太大区别。
1.8终结进程。进程终结也需要做很多繁琐的收尾工作。系统必须保证进程所占用的资源回收,并通知父进程。
这里就只大致说下具体进程结束过程:
系统调用exit(),开始析构进程。
然后调用sem__exit()使该进程进程队列。
把进程设置为僵死状态。这个时候,进程无法投入运行了,它的存在只为父进程提供信息——申请死亡。
父进程得到信息后,开始调用wait4(),最终赐死子进程——子进程占用的所有资源被全部释放。
这里很显然有一个问题:如果被杀死的子进程同时是其它进程的父进程,那么该怎么办呢?如果置之不理,那么那些子进程将会变为僵死状态,这不是我们所想看到的。
处理方法是为那些子进程寻找父进程,甚至可以设置init为其父进程。
系统用一个叫做“进程表”的东西来维护中系统中的进程,进程表中的一个条目维护着存储着一个进程的相关信息,比如进程号,进程状态,寄存器值等等...
当分配给进程A的“时间片”使用完时,CPU会进行上下文切换以便运行其他进程,比如进程B,这里所谓的“上下文切换”,主要就是在操作那个“进程表”,其将进程A的相关信息(上下文)保存到其对应的进程表项中, 与之相反,其会从对应于进程B的进程表项中读取相关信息并运行之。
关于fork小例子:
http://www.cnblogs.com/zhouyinhui/archive/2010/09/01/1814954.html
#include <stdio.h>
#include <unistd.h>
int main ()
{
printf("app start...");
fork();
return 0;
}输出为:
好奇怪是吧?情况是这样的:
当你调用printf时,字符串被写入stdout缓冲区(还没刷到屏幕上的哦),然后fork,子进程复制了父进程的缓冲区,所以子进程的stdout缓冲区中也包含了“app start ...”这个字符串,然后父子进程各自运行,当他们遇到return语句时,缓冲器会被强制刷新,然后就分别将“app start...”刷到了屏幕上。如果想避免,在fork前,调用fflush强制刷新下缓冲区就可以了,在字符串后面加上“\n”也可以,因为stdout是按行缓冲的。