正在学习Linux,记录一下学习历程。
定义
进程:一个正在执行或准备执行的程序。
更精确地讲,进程就是一个加载到内存中正在执行或准备执行的程序,再加上程序所需的数据,以及跟踪管理程序状态所需的各种信息。
内核管理进程
所有的进程都由内核管理,内核是操作系统的核心部分。
当进程创建时,内核赋予其一个唯一的标识号,这个标识号称为进程ID或PID。为了跟踪管理系统中的所有进程,内核维护了一个进程表。按照PID索引,每个进程再进程表中有一个条目。进程表中的条目除了PID,还包含有描述及管理进程所需的信息。
系统中有许多进程,有一些进程是由用户运行的程序,但是,大多数进程是自动启动的,再后台执行任务。
这些进程需要共享系统的资源:处理器,内存,I/O设备,网络连接等。
为了管理这样一个复杂的工作负荷,内核提供了一个复杂的调度服务,称为调度器。
调度器一直维护着一个所有正在等待执行的进程的列表。通过使用特定的算法,调度器每次选择一个进程,给予这个进程再一个短暂的时间间隔(称为时间片)中运行的机会。(在多处理系统中,调度器可以一次选择多个进程)
每次进程结束时间片时,内核需要中断进程。内核保存每个被中断进程的数据(如程序中下一条指令的位置,环境的副本等)。当该进程再次启动时,它能够在中断处继续执行。
内核为进程提供服务
内核为进程提供基本的服务:
1. 内存管理(虚拟内存管理,包括分页)
2. 进程管理(进程创建,终止,调度)
3. 进程间通信(本地,网络)
4. 输入/输出(通过设备驱动程序,即与物理设备时间通信的程序)
5. 文件管理
6. 安全和访问控制
7. 网络访问(如TCP/IP)
当进程需要内核提供服务时,它就通过系统调用发送请求。
如:
进程使用系统调用初始化I/O操作。
当编写程序时,系统调用的使用方法取决于所使用的编程语言。如在C程序中,使用的是标准库中的函数。
创建&使用进程
fork | 创建当前进程的一个副本 |
wait | 等待另一个进程结束执行 |
exec | 在当前进程中执行一个新进程 |
exit | 终止当前进程 |
kill | 向另一个进程发送一个信号 |
以shell为例:
shell是一个充当用户界面和脚本解释器的程序。正是shell才允许你输入命令,以及间接访问内核服务。
一旦shell开始运行,它也是一个进程,是众多系统进程中的一个。
和所有的进程一样,shell也拥有自己的PID(可以通过一个特殊的shell变量$显示),并且在进程表中拥有自己的条目。
内部命令直接由shell解释,不需要创建新的进程。
外部命令需要shell运行一个单独的程序,因此每当运行外部命令时,shell必须创建一个新进程。
shell先使用fork系统调用创建一个新进程,原始进程称为父进程,新进程成为子进程。一旦进程分叉(fork)成功,就发生两件事:首先,子进程使用exec系统调用将它自身从运行shell的进程变成运行外部程序的进程。其次,父进程使用wait系统调用暂停,直到子进程结束执行。最终,外部程序结束,此时子进程使用exit系统调用停止自身。
进程死亡&僵尸进程
每当进程永远停止时,不管是由于什么原因停止,我们都称进程死亡或终止。每当进程死亡时,进程所使用的所有资源(内存,文件等)都将被释放,从而可以被其他进程所使用。
此时,称被杀死的进程为僵尸进程。尽管僵尸进程是死的,而且已经不再是一个真正的进程,但是它仍然在进程表中保留者自己的条目。因为该条目包含只最近死亡的子进程的数据,而父进程可能对这些数据感兴趣。
在子进程成为僵尸进程后,父进程(一直在耐心地等待着子进程死亡)立即被内核唤醒。现在父进程有机会查看进程表中成为僵尸进程的子进程的条目,看看发生了什么结果。然后内核将进程表中的僵尸进程条目移除。
孤儿进程
如果父进程分叉之后,出乎意料地死亡,那么子进程还会继续执行,但是它已经成为孤儿。
孤儿进程仍然可以继续完成工作,但是当它死亡时,没有父进程被唤醒。最终,死掉的孤儿进程,现在以僵尸进程的形式存在,被遗忘在一边。
以前,孤儿僵尸进程将永远停留在进程表中,直到系统重新启动。
现在,孤儿进程将自动地被#1进程,即init进程(后面会讨论)收养。通过这种方式,每当孤儿进程死亡时,init进程充当替身父进程,快速地清理孤儿僵尸进程。
幸运的是,这种现象并不常见。如果一个程序偶然以这种方式创建了一个不死的僵尸进程,那么将没有直接的办法清除它。毕竟,如何去杀死那些已经死掉的东西呢?
区分父进程与子进程
当fork系统调用结束它的工作时,它向父进程和子进程各传递一个数值,这个数值称为返回值。
子进程的返回值为0,父进程的返回值为新创建进程的PID.
因此,在分叉(fork)操作完成后,进程可以通过简单地查看返回值来识别它是父进程还是子进程。
如果返回值为0,则为子进程。如果返回值大于0,则为父进程。
以shell为例:
在shell 分叉(fork)之后,有两个相同的shell。为了识别哪个进程是父进程,哪个进程是子进程,每个进程都检查它从fork接收到的返回值。拥有大于0的返回值的shell知道它是父进程,因此它使用wait系统调用使自己暂停。拥有0返回值的shell知道它是子进程,因此它使用exec系统调用运行外部程序。
init进程
进程通过分叉(fork)创建,每个进程都是由另一个进程创建的,都拥有自己的父进程。
那么,在某个地方,必须有第一个进程,是其系统中其他所有进程的父进程。
在引导过程(启动操作系统的一组复杂步骤)的末尾,内核“手动”创建了一个特殊的进程,而不是通过分叉(fork)。
这个进程的PID为0。在执行一些重要的功能(例如初始化内核所需的数据结构)后,#0进程进行分叉(fork),创建#1号进程。然后#0进程执行一个非常简单的程序,该程序实质上是一个无穷的循环,不做任何事情(因此将该进程命名为空闲进程)。每当没有进程等待执行时,调度器就运行空闲进程。当#0号进程变成空闲进程时,它就已经有效地完成了它地目的,然后消失。实际上,如果使用ps(稍后讨论)显示#0进程的状态,那么内核将否认该进程的存在。
#1进程执行设置内核及引导过程所需的剩余步骤。基于这一原因,称它为初始化进程,并将该进程的实际程序命名为init。
具体而言,init进程打开系统控制台,挂载根文件系统,然后运行包含在文件/etc/inittab中的shell脚本。在这一过程中,init多次分叉,创建运行系统所需的基本进程,并允许用户登录。在这一过程中,init成为系统中所有其他进程的祖先。
与空闲进程(#0)不同,初始化进程(#1)永远不会停止运行,它是进程表中的第一个进程,而且它一直存在于进程表中,直到系统关闭。
在系统完成后,有时候仍然调用init来执行重要操作,如收养孤儿。
前台进程&后台进程
当运行程序时,输入和输出通常与终端相连。对于基于文本的程序来说,输入来源于键盘,输出定位到显示器,这样才有意义,因为大多数程序为了完成任务需要与用户进行交互。
但是,有一些程序可以自动完成,不需要终端的合作。
在命令的末尾键入&,这将告诉shell 该程序因该自己执行。在这种情况下,shell 不会等待程序结束。程序一启动,shell就重新获得控制,并显示一个新提示。
前台进程:当shell在提示让用户输入一条新命令之前等待当前程序结束
后台进程:当shell启动一个程序,但是又让该程序自己运行
当前台进程运行时,从键盘读取数据,将结果写入到显示器,不会出现任何问题。
后台进程会稍显复杂:
当后台进程运行时,若希望从键盘读取数据,它会一直等待,因为后台进程的标准输入与/dev/null相连。除非使用fg命令(稍后讨论)将进程移至前台。
将输出显示到显示器上是没有问题的,只是有可能会和正在处理的其他事情混杂在一起,使结果陷入混乱。
另外,后唐进程不响应intr信号(^C键)和quit信号(^\键),只能通过kill命令终止程序。
一般情况下,后台程序适用于从文件中获取数据,并将数据写入另一个文件中。不涉及与终端的交互。如:源程序的编译。
创建延迟
sleep interval [s|m|h|d]
interval是延迟的时长。
默认是以秒作为单位。还可以指定其他单位:s秒(默认),m分钟,h小时,d天。
作业控制
作业:每条输入的命令
作业号:也称作业ID,用来唯一地标识一个作业
允许同时运行多项作业,其中一个作业在前台运行,其他作业在后台运行。作业可以有选择性地挂起(暂停),重新启动,在前台和后台之间切换,以及显示状态。
这样做时,需要使用一系列的命令,变量,终端设置,shell变量和shell选项。
jobs | 显示作业列表 |
ps | 显示进程列表 |
fg | 将作业移至前台 |
bg | 将作业移至后台 |
suspend | 挂起当前shell |
^Z | 挂起当前前台作业 |
kill | 向作业发送信号:默认情况下,终止作业 |
echo $$ | 显示当前shell的PID |
echo $! | 显示上一条移至后台的命令的PID |
stty tostop | 挂起试图向终端写数据的后台作业 |
stty -tostop | 关闭tostop |
set -o monitor | 允许作业控制 |
set +o nomonitor | 关闭monitor |
set -o notify | 当后台作业结束时立即通报 |
set +o nonotify | 关闭notify |
进程和作业之间的区别:
进程是正在执行或准备执行的程序。作业指解释整个命令行所需要的全部进程。
进程由内核控制。作业由shell控制。
内核使用进程表来记录进程,shell使用作业表来记录作业。
如以下命令:
who | cut -c 1-8 | sort | uniq -c
该命令生成4个进程,一个作业。
date; who; uptime
该命令生成3个进程,一个作业。
在后台运行作业
在命令末键入一个&
每次在后台运行作业时,shell会显示作业的作业号和进程ID。
shell从1开始为作业分配作业号。
每当后台作业结束时,shell都会发送一个短的状态消息,通知作业的结束。
为了不打扰此刻正在做的事情,不会立刻通知,直到显示下一个shell提示。
挂起作业
任何时候,每个作业都处于3种状态中的一种:前台运行,后台运行,暂停(等待信号恢复执行)
挂起:暂停,临时中止作业
^Z键可以挂起当前前台作业
如果希望恢复挂起,可以使用fg命令将该作业移回前台
挂起shell
按下^Z键将挂起在前台运行的任何作业,但是有一个进程不会挂起,就是当前shell。
如果希望暂停当前shell,则需要使用suspend命令。
同样可以使用fg命令接挂。
显示作业列表
jobs命令可以显示所有作业的列表。
包括作业号,作业标记,作业状态,作业内容。
-l 选项会额外显示作业所包含的进程的PID。
许多命令(如fg),如果没有指定作业号,就默认作用于当前作业。
作业标记跟在作业号之后,用+,-表示。
+代表是当前作业(如果有挂起的作业,就是最近挂起的作业,如果没有挂起作业,就是最近移至后台的作业)
-代表是前一个作业(队列里的下一个作业)
将作业移至前台
fg 将当前作业移至前台并执行
也可以指定作业号。
%% | 当前作业 |
%+ | 当前作业 |
%- | 前一个作业 |
%n | 作业#n |
%name | 含有指定命令名的作业(name不需要完整,但要足够长到足以唯一识别一个作业) |
%?name | 命令的任何位置含有name的作业 |
也可以省略fg,指用作业号作为命令。
将作业移至后台
bg命令可以作业移至后台执行
默认是当前作业,也可指定作业号。
如果要将当前前台的作业移至后台执行,先用^Z挂起该作业,然后用bg移至后台执行。
因为无法在前台作业正在执行的时候进行使shell执行bg命令。
显示进程信息
ps命令
更多内容参见Linux学习-ps显示进程信息
监控系统进程
ps只显示进程的静态快照,及瞬间的进程状态。
但是进程是动态的,需要查看各种进程如何不断变化。
top程序可以每隔几秒显示整个系统的统计更新,并且实时显示最重要的进程(使用CPU时间最多)的信息。
选项:
-d 指定刷新间隔(单位为秒)
-n 指定刷新总次数
-p 指定进程ID
退出top,按q或^C。
按<Space>可以强制它top立即刷新。
显示进程树
系统中所有的进程都被安排到一个大型的树型层次结构中,初始化进程位于树的根部。
pstree可以显示这棵进程树。
选项:
-n 按PID对进程进行排序(默认是按进程名称)
-p 显示每个进程的PID
-A 强制使用ASCII字符绘制
-G 强制使用行绘制字符
-a 显示每个进程的整个命令行,而不只是程序的名称
-u 显示当子进程以不同于父进程的用户标识运行时所发生的改变
如何收集进程信息
在进程表中可以查看一些基本数据。但是,大多数动态信息必须从内核中获取。
ps和top程序使用一种称为proc文件的伪文件。
zai/proc目录中,每个进程都用自己的proc文件表示。当需要进程的现象时,就读取该进程的proc文件,并最终触发一个向内核请求提供所需数据的请求。
杀死进程
kill 进程ID|作业号
注意,进程ID一般为4位数字,作业号以%开头。
有一种常见情况:
当前台进程失去响应时
可以尝试按^Z挂起该进程,然后使用ps或jobs查找该进程,然后使用kill终止。
也可以打开一个新的终端窗口,使用ps -u列举你用户标识运行的所以有进程,然后确定是失控的进程,用kill终止。
选项:
-9 确定杀死,在杀死进程时不允许进程释放正在使用的资源(最后一种选择,不推荐)
向进程发送信号
kill [-signal] pid...|jobid...
signal 是希望发送的信号类型。
进程间通信是两个进程之间的数据交换。kill程序就是一种进程间通信方式,发送一个非常简单的消息,被称为信号。
信号只不过是发送给进程的一个数字,从而让进程知道发生了哪些类型的事情。进程会识别信号,从而决定执行什么动作。
使用kill -l 可查看系统所支持的全部信号列表。
编号 | 名称 | 缩写 | 描述 |
1 | SIGHUP | HUP | 中止:注销或者终端失去连接时发送给进程 |
2 | SIGINT | INT | 中断:当按下^C时发送 |
9 | SIGKILL | KILL | 杀死:立即终止,进程不能捕获 |
15 | SIGTERM | TERM | 终止:请求终止,进程不能捕获 |
18 | SIGGONT | GONT | 继续:恢复挂起的进程,由fg或bg发送 |
19 | SIGSTOP | STOP | 停止(挂起):当按下^Z时发送 |
kill默认情况下发送信号TERM。
设置进程的优先级
nice [-n
adjustment] command
adjustment是一个数值,command是希望运行的命令
通常是在后台运行且需要使用大量CPU时间的程序(如编译程序)
可以指定0~19的nice值,数值越高,优先级越低。
程序的默认优先级是0。
nice的默认设置是10。
改变现有进程的优先级
renice可以改变现有进程的优先级。
renice niceness -p processid
niceness是nice值,processid是PID
守护进程
不是由用户运行的程序,在后台运行,完全不与任何终端连接,目的是体哦嗯服务。
一般情况下,守护进程静静在后台等待某些事情的发生:事件,请求,中断,给定的时间间隔等。
当触发发生时,守护进程进入动作,完成执行作业所需的事情。
如:
init是一个守护进程,收养孤儿。