进程和作业控制
进程就是加载到内存中准备执行的程序。当进程创建的时候,内核赋予了唯一的标识号,这个标识号叫做进程ID或PID。
进程同一时间内可达数百个之多,为了管理这么多的进程。系统提供了一个调度器来维护。调度器维护一个所有进程的列表,每次选择一个进程(实际上可以一次选择多个进程),然后执行一个短暂的时间(时间片)。
典型的时间片是10毫秒。为了为了下一个时间片还可以顺利的执行,系统必须要保存下一条指令的位置,环境的副本等。
进程分叉到死亡
当进程需要使用内核的服务时,会使用系统调用发送请求。在编写程序时,系统调用的使用方法取决于语言。例如,c语言使用标准库中的函数进行调用。
下面列举一些常见的系统调用函数
系统调用 | 作用 |
---|---|
进程控制 | |
fork | 创建当前进程的一个副本 |
wait | 等待另一个程序结束后执行(强制暂停当前进程) |
exec | 在当前进程中执行一个新进程 |
exit | 中止当前进程 |
kill | 杀死子进程 |
文件I/O | |
open | 打开一个用于读取或写入的文件 |
read | 从文件中读取数据 |
write | 向文件中写入了数据 |
close | 关闭文件 |
fork打开的一个新进程叫做当前进程的子女,当前进程是新进程的双亲。
例如,shell打开内部命令时首先就要调用fork创建一个新进程,当子进程结束时会调用exit释放资源,被杀死的进程叫做僵进程。但是进程表中却仍然保存子进程的数据,因为父进程可能需要这些数据。。
当子进程创建时,父进程停止运行,等到子进程被杀死之后,父进程先看一眼进程表中子进程的数据,然后开始运行,与此同时,子进程从进程表中被销毁。
孤儿进程,父进程,子进程
孤儿进程指的是父进程意外死亡,这时只有子进程。所以当子进程结束时,因为没有人来接受它,会一直留在进程表中,直到系统结束才会死亡。
当然,现代系统没有这么傻。孤儿进程会自动被init进程收养,通过这种方式,每当产生孤儿进程时,都会迅速的被init进程销毁。
除了父进程意外死亡外子进程也可能一直不死,这种情况一般是程序出现bug无法正常退出时产生的,这个程序会一直消耗系统资源。kill可以解决这种问题。kill会杀死父进程然后让init进程托管,在适当的时候会杀死子进程(我估计是一定时间,猜测)。
关于父进程,fork创建一个和父进程一模一样的副本,那么这时如何知道谁是父进程,谁是子进程呢?其实fork创建子进程完成之后会返回给父进程和子进程一个值,子进程返回值是零,父进程返回值大于零。如果某个进程得到了零,那么他就会开始工作,得到大于零的就会停止工作(通过wait使自己暂停)
init 第一个进程
假设进程是通过分叉创建的,那么除第一个进程外的进程一定会有父进程,也一定会有第一个进程。
实际上也是这样,Linux在启动时会创建一个特殊的进程,PID是0,这个进程叫做空闲进程。
在进行了一些操作之后,0号进程开始分叉创建1号进程。之后0号进程就会死亡,然后由1号进程进行剩余的初始化步骤,例如进行多次分叉创建其他进程。因为 他要执行初始化步骤,所以叫初始化进程,也就是init进程。
前台和后台进程
前台进程就是当前需要我们交互的进程,后台进程就是自己跑的进程。
例如: sort < temp > temp2 &。后面的与符号就是让程序自己去后面跑,这时我们就可以直接使用shell了。
但是后台进程不能从标准输入中读取,可以输出到标准输出中,这就带来一个问题。如果这个程序需要输入但是你却把它划分成后台程序,那么他会一直等待输入。
sleep 创建延迟
语法: sleep interval [s | m | h | d ]
interval是时间间隔,后面是单位,默认是秒。
例如 sleep 5 表示中断5秒。
作业控制
作业就是每一条输入的指令,该作业有一个唯一的作业号来标识。作业控制就是对输入的指令进行调控。常见的作业控制命令如下表。
作业控制命令 | |
---|---|
jobs | 显示作业列表 |
ps | 显示进程列表 |
fg | 将作业移至前台 |
bg | 将作业移至后台 |
suspend | 挂起当前shell |
^Z | 挂起当前前台作业 |
kill | 终止作业 |
suspend | 挂起shell,例如超级用户暂时返回普通用户 |
echo $$ 显示当前shell的PID
echo $! 显示上一条移至后台的PID
stty tostop 挂起试图向终端写数据的后台作业
set -o monitor 允许作业控制
set -o notify 当后台作业结束时立刻通报
在后台运行作业
为了在后台运行作业,需要在命令的最后加一个&符号。
每次在后台运行作业时,系统都会自动分配一个作业号,从1开始,依次向后增加。
每次后台作业完成时,都会向终端发送一个信息表示已经完成。
fg 将作业移至前台
挂起可能令人有些误解,其实就是暂停程序。可以用fg将挂起的作业恢复。
例如当你用vi编辑一个文件时,突然忘了某些东西想通过man查一下,可以先用ctrl+Z把vi挂起,然后查找,查找完了之后又用fg命令将vi唤醒。
如果有挂起的程序那么关机时系统会给你提示,你可以选择关机或者先把挂起的程序移至前台。
语法: fg %[job]
fg %%代表唤起当前进程。因为一般进程挂起后都是运行别的进程,运行完了才又唤醒进程,此时挂起的进程又成了最前面的进程。
后面的job是作业编号,可以通过jobs程序查看。
bg和fg大致相同。
ps程序
ps程序是用来显示进程状态的
语法: ps [-aefFly] [-p pid] [-u userid]
ps 显示与当前用户标识和终端相关的进程 |
下面列举了ps输出时的标题和所代表的含义
标题 | 含义 |
---|---|
ADDR | 进程表中虚拟地址 |
C | 处理器利用率 |
CMD | 命令名称 |
F | 进程相关的标志 |
NI | nice值,用于设置优先级 |
PID | 进程ID |
PPID | 父进程ID |
PRI | 优先级(大数字 = 小优先级 ) |
RSS | 内存预留空间大小 |
S | 状态代码(D,R,S,T,Z) |
STIME | 累计系统时间 |
SZ | 物理页大小 |
TIME | 累计cpu时间 |
TTY | 控制终端完整名称 |
UID | 用户标识 |
WCHAN | 等待通道 |
状态代码D 不可中断睡眠,等待事件结束
I 空闲,超过20s的睡眠
R 正在运行或者可运行(可运行是在运行队列中等待)
S 可中断睡眠,等待时间结束
T 挂起
Z 僵进程
-ly 显示状态代码
监视系统进程
语法: top [-d delay] [-n count] [p pid[,pid]…]
top的作用是动态显示进程信息。
-d 每隔多少秒刷新一次
-n 只在特定的时间进行刷新
-p 对某几个进程进行监视
显示进程树
语法: pstree [-aAcGnpu] [pid | userid]
作用:因为进程几乎都是通过分叉产生的,所以这个作用是显示进程之间的关系(即谁是父进程谁是子进程)。
具体的可以去看联机手册
kill 杀死进程,向进程发送信号
语法: kill [-signal] pid… | jobid…
signal | 含义 |
---|---|
1 | 中止,注销或终端失去连接时发送给进程 |
2 | 中断,按下^C时发送 |
9 | 杀死,立刻杀死,进程不能捕获 |
15 | 终止,请求终止,进程不能捕获 |
18 | 继续,恢复挂起的进程,由fg或bg发送 |
19 | 停止(挂起),按^Z发送 |
设置进程优先级 nice,renice
优先级决定了你能享有的系统资源。nice程序用于设定优先级。
语法: nice [-n adjustment] command
使用nice时要注意,只能对外部程序例如软件和自己写的程序设置优先级,系统内部命令不能设定优先级。其次,一般只对后台程序降低优先级,对前台程序降低优先级是自己找罪受。
不使用nice时优先级是0,使用nice默认的优先级是10,数字越大优先级越低,最大可以设到20,最小可以设到-20.当设负数的时候是提高优先级。
renice重新设置优先级。
语法: renice niceness -p processid
niceness是nice值,processid是进程ID。
niec程序是在程序开始运行时确定的,使用nice程序后command程序便开始运行。而renice是对已运行的程序重新设置优先级。
守护进程
很多程序并不是由用户运行的,由系统运行的程序就是守护程序。相当于windows中的服务程序。这些程序不受终端控制