为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态,一个进程可以有几个状态。下面的状态在kernel源代码里定义:
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
定义:并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
我们用以下代码来测试一个无限循环向显示器输出的程序:
#include
#include
using namespace std;
int main()
{
while(1)
{
cout<<"running!"<<endl;
}
return 0;
}
这里我们看见R的后面有一个+号,这个+号代表它是一个前台进程。
定义:意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))
我们用以下这段代码来测试:
#include
#include
using namespace std;
int main()
{
int a=0;
while(1)
{
a++;
cout<<"running!"<<endl;
}
return 0;
}
检测程序状态如下:
运行并查看进程,当前进程状态处于 S,叫做睡眠状态,睡眠状态也是阻塞状态的一种。因为 cout 要访问显示器,显示器是外设的一种,外设有个特点就是相较于 CPU 比较慢,慢就要等待显示器就绪,等待就要花较长的时间。我们这个程序可能只有万分之一的时间在运行,其它时间都在休眠,站在用户的角度它是 R,但是对于操作系统来说它不一定是 R,它有可能在队列中等待调度。
所以并不是每次测试的时候打印状态都能打出 S,只不过这个概率很高,因为这个状态是瞬间的,有可能那个瞬间是在执行代码而不是在等 IO就绪 ,所以有时候测试得到的状态可能是 R 。
定义:也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
上面的 S 状态也叫 浅度睡眠,进程可以被杀掉,这里的 D 状态叫做深度睡眠,表示该进程不会被杀掉,即便是操作系统也不行,只有该进程自动醒来才可以恢复或者给机器断电。
假设场景:进程A需要向磁盘写入10万条用户数据,这些数据对用户很重要。进程A 访问磁盘,等待磁盘写入数据,进程A 等待磁盘返回一个结果,数据是否写入成功,此时进程A 处于休眠状态S;此时突然内存空间不足了,挂起也无法解决内存空间不足的问题,操作系统会自主杀掉一些进程(特别是内存资源不用的,比如进程A),操作系统就把进程A 给杀掉了,造成了磁盘写入数据失败,磁盘给进程A 返回结果,发现进程A 没有应答,磁盘只能把这些数据丢弃,然后磁盘继续为其他进程提供服务。结果,这重要的 10万条数据皆丢失了。
为了防止这种情况的发生,Linux 给进程设置了深度睡眠 (D) 状态,处于深度睡眠状态的进程既不能被用户杀掉,也不能被操作系统杀掉,只能通过断电,或者等待进程自己醒来
深度睡眠状态一般很难见到,一般在企业中做高并发或高IO的时候会遇到。
定义:可以通过发送 SIGSTOP (signal stop)信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT (signal continue)信号让进程继续运行。
这里提到了信号,那么我们来介绍两个相关的信号(18号信号是继续,19号信号是暂停):
我们发送19号信号,发现进程状态变为了T,可以看出 T 状态也是阻塞状态的一种,但是有没有挂起完全不知道,这是由操作系统自己决定的!
再发送18号信号,发现状态变为了S,而非S+,所以它变成了后台进程:
前台进程:可以被 [Ctrl]+c 杀掉的进程,命令行在这个终端可以起作用,S+ 的 + 号就是前台进程的意思
后台进程:无法被 [Ctrl]+c 杀掉的进程,命令行在这个终端也可以起作用,这个时候没有 + 号
定义:追踪暂停状态t 是也是一种暂停状态,不过它是一种特殊暂停状态,表示该进程正在被追踪。比如在使用 gdb 调试打断点的时候,程序暂停下来的状态就是 t状态,这里就不细谈了!
定义:这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
死亡状态只是一个返回状态,当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也一瞬间就不存在了,所以你几乎不会在任务列表当中看到死亡状态。
我们创建一个进程的目的是为了让其帮我们完成某种任务,而既然是完成任务,进程在结束前就应该返回任务执行的结果,供父进程或者操作系统读取;所以进程退出的时候,不能立即释放该进程的资源,该进程要保存一段时间,让父进程或操作系统读取该进程的执行结果(保存一段时间是对于CPU 而言)
定义:僵尸状态 Z 就是进程退出时,该进程的资源不能立即被释放,该进程要保留一段时间,等待父进程或操作系统读取该进程结果的过程就叫僵尸状态。
- 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
- 只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
用以下代码来测试僵尸进程:
#include
#include
#include
using namespace std;
int main()
{
pid_t pid = fork();
if(pid==0)
{
cout<<"child process->"<<getpid()<<" ppid->"<<getppid()<<endl;
sleep(2);
exit(-1);
}
else
{
cout<<"father process->"<<getpid()<<" ppid->"<<getppid()<<endl;
sleep(20);
}
return 0;
}
状态变化如下:
僵尸进程的危害:
- 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,子进程就一直处于Z状态
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在 task_struct(PCB)中,换句话说, Z状态一直不退出, PCB一直都要维护
- 那一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费。因为数据结对象本身就要占用内存,比如C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
- 会造成内存泄漏
定义: 父进程先退出,子进程就称之为“孤儿进程”,孤儿进程会被1号 init进程(有些版本高的叫做systemd)领养,由 init进程回收,1号进程就是操作系统。
我们用以下代码来测试孤儿进程:
//孤儿进程
int main()
{
pid_t pid = fork();
if(pid==0)
{
while(1)
{
cout<<"child process->"<<getpid()<<" ppid->"<<getppid()<<endl;
sleep(1);
}
}
else
{
cout<<"father process exit!"<<endl;
exit(-1);
}
return 0;
}
我们可以看到,子进程被1号进程领养,且进程状态由前台转为了后台。
对于优先级的理解:
- cpu资源分配的先后顺序,就是指进程的优先权(priority)。
- 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
- 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能
其中这几个参数的含义如下:
UID : 代表执行者的身份
PID : 代表这个进程的代号
PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
PRI :代表这个进程可被执行的优先级,其值越小越早被执行
NI :代表这个进程的nice值
如何判断一个进程的优先级呢?
- PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
- 那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice。这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
所以,调整进程优先级,在Linux下,就是调整进程nice值nice其取值范围是-20至19,一共40个级别。
我们可以用top 命令来修改优先级:
- top
- 进入top后按“r”–>输入进程PID–>输入nice值