Linux -3 进程概念(二)

1.进程状态

1.1进程排队的了解 ---队列

进程不是一直在运行的,即便进程放在了CPU上,也不是一直会运行的

它可能在等待某种软硬件资源,比如:

当你的代码包含scanf函数,执行到此语句时进程在等键盘输入

时间片:一个进程占用CPU资源(不管有没有跑完)固定时间(例:1ms)后会下来

所谓的进程排队,一定在等待某种"资源"

进程 = task_struct + 可执行程序  注意:排队的是 task_struct

一个task_struct可以被连入多种数据结构中,只不过这种连入和平常的连入不一样

Linux -3 进程概念(二)_第1张图片

这样所有的数据结构就可以用同一套增删查改 

队列底层不就可以用链表实现了吗

1.2关于进程状态的描述 ——运行、阻塞、挂起

所谓的状态,其本质就是一个整形变量,在task_struct中的一个整形变量(进程的状态属于进程的属性,所以它是在task_struct之中)

1.2.1运行

状态决定了什么?你的后续动作

Linux中可能会存在多个进程都要根据它的状态执行后续动作                                                             当多个进程处在相同状态时就需要进行进程排队了

一个CPU一个进程队列

Linux -3 进程概念(二)_第2张图片

一个进程只要放在CPU上对应的运行队列当中,把该进程的状态称之为运行状态(大部分操作系统)

R状态(running):我已经准备好随时被CPU调度了

1.2.2阻塞

操作系统这么管理硬件?先描述再组织

进程有队列,设备也有队列

Linux -3 进程概念(二)_第3张图片

当进程需要某个设备进行操作时进程会被OS移动到该设备的队列中,在设备队列排队的过程也就叫阻塞状态

1.2.3挂起 

前提:计算机的资源已经比较吃紧了

当一个进程需要某种资源而进入阻塞状态(占用内存)

操作系统会将这些暂时用不到的进程的代码和数据转存的磁盘的swap分区中,当压力不大时再将磁盘中的进程换回来(swap分区一般为内存的一半或者与内存大小相同)

当一个进程的代码和数据不在内存当中,那么我们称其状态为挂起状态

换入换出时PCB不会被移动(换入:数据从外设换入到内存;换出:把数据拷贝到外设)

创建进程时要先创建对应的类和数据结构(PCB),再将进程拉到内存来

1.3Linux下具体的进程状态

状态带 + 为前台进程 ctrl c 可退出;无 + 为后台进程 只能 kill

R运行状态(running):

并不意味着进程一定在运行中,它表明进程要么在运行中,要么在(CPU)运行队列里

S睡眠状态(sleeping):

意味着进程在等待事件完成,这里的睡眠可叫做“可中断睡眠”(浅度睡眠也属于操作系统的阻塞状态    ctrl c 可以中止该状态)

D磁盘休眠状态(Disk Sleep):

也称不可中断睡眠状态,操作系统在逼急的时候会杀进程(当换入换出没用时),而D状态就是给进程一个免死金牌(也是阻塞状态)

T停止状态(Stopped):

可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。调试在断点处时,进程也处于停止(T)状态,T状态也在等待某种资源

X死亡状态(dead):

这个状态只是一个返回状态,你不会在任务列表里看到这个状态

Z僵尸状态(Zombie):

在进程退出且父进程没有读取到子进程退出的返回代码时就会产生僵尸进程(死了又没完全死)      僵尸进程会以终止状态保持在进程表中,并且会一直等待父进程读取退出状态代码                          代码和数据释放    进程控制块(PCB)不释放 

所以,只要子进程退出,父进程还在运行,但父进程没有读取到子进程状态,子进程进入Z状态

为什么会有Z状态?创建进程是希望这个进程给用户完成工作的,子进程必须有结果数据(存在PCB中的);维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护

如果我作为父进程不读取呢?那么僵尸状态的进程会一直存在,task_struct也会一直存在,这些都是会占用内存的(内存泄漏)

#include    
#include    
#include    
    
int main()    
{    
  pid_t id = fork();    
  if(id < 0)    
  {    
    return 1;    
  }    
  else if(id > 0)    
  {    
    printf("father[%d] is sleeping...\n",getpid());    
    sleep(30);    
  }    
  else    
  {    
    printf("child[%d] is become Z\n",getpid());    
    sleep(8);    
    exit(EXIT_SUCCESS);                                                                                       
  }    
  return 0;    
}  

 Linux -3 进程概念(二)_第4张图片

孤儿进程

如果父进程比子进程先退(挂),子进程会被1号进程(操作系统)所领养

这种被领养的进程叫孤儿进程,孤儿进程是后台进程

#include    
#include    
#include    
int main()    
{    
  pid_t id = fork();    
  if(id<0)    
  {    
    return 1;    
  }    
  else if(id==0)    
  {    
    //child    
    printf("i am child pid:%d\n",getpid());    
    sleep(10);    
  }    
  else    
  {    
    printf("i am parent pid:%d\n",getpid());    
    sleep(3);    
    exit(0);    
  }    
  return 0;                                                                                                                                                                                                       
} 

2.进程优先级 

2.1进程优先级的概念

什么是优先级?

先想想 权限 是什么?权限的本质是谈论“能”还是“不能”的问题

那什么是 优先级?优先级是进程获取某种资源的先后顺序

这有一个前提:进程要访问某种资源,进程以一定的方式(排队),确认享受资源的先后顺序

优先权高的进程有优先执行权利。

配置进程优先权对多任务环境的linux很有用,可以改善系统性能,还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。

为什么会存在优先级?

因为某种资源过少(相对的概念),你看系统里面永远是进程占大多数,资源是少数。这就导致了进程之间竞争资源是常态。排队和进程资源竞争的本质都是确认 优先级 。

怎么办?

优先级在OS中也就是个整形,每错还是存在与 task_struct 中

Linux -3 进程概念(二)_第5张图片

2.2查看进程的优先级

在linux或者unix系统中,用ps –al或者ps -l命令则会类似输出以下几个内容:

让我们来了解一下其中的几个重要信息:

  •  UID : 代表执行者的身份
  • PID : 代表这个进程的代号
  • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI :代表这个进程可被执行的优先级,其值越小越早被执行
  • NI :代表这个进程的nice值

Linux 的默认优先级是 80 

优先级可以被修改,范围是[60,99]->40个优先级 本质是数字,数字越小,优先级越高

可以用top修改优先级

进入top后按“r”–>输入进程PID–>输入nice值

虽然允许用户调优先级,但不能直接该PRI,而是修改nice值(NI)->不是优先级,而是其修正数据

PRI = PRI(old) + nice  nice其取值范围是-20至19,一共40个级别

Linux为什么调整优先级是要受限制的?

不加限制,可能会被胡乱改,有的非常高or低会导致常规进程很难享受CPU资源——进程饥饿

2.3Linux调度与切换

进程在运行时,必须在CPU上跑完才行吗?不是,现代操作系统都是基于时间片进行轮转执行的

  • 竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
  • 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰
  • 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
  • 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发——> 进程切换

2.3.1 进程切换

CPU中存在大量寄存器   eax/ebx/ecx/edx    eds/ecs/fg/gs  eip,cr0-cr4,程序状态字,浮点数寄存器,ebp,esp......

进程在运行的过程中,要产生大量的临时数据,放在CPU的寄存器中

进程:所有的保存都是为了恢复,所有的恢复都是为了延续在上次的位置运行

CPU内部的所有临时数据,我们叫做进程的硬件上下文

如果当前进程在被调度时,因为时间片到了,而需要进程切换,我们的进程会保存硬件上下文,保护上下文;当进程被二次或者多次调度时,被放到CPU上开始运行,将曾经保存的硬件上下文进行恢复

CPU内的寄存器只有一套,寄存器内保存的数据可以有多套

虽然寄存器数据放在了一个共享的CPU设备里面,但是所有的数据其实都是被进程私有的

寄存器   !=   寄存器的内容

2.3.2活动队列与过期队列

活动队列

时间片还没有结束的所有进程都按照优先级放在该队列
nr_active: 总共有多少个运行状态的进程
queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下标就是优先级!
从该结构中,选择一个最合适的进程,过程是怎么的呢?
1. 从0下表开始遍历queue[140]
2. 找到第一个非空队列,该队列必定为优先级最高的队列。
3. 拿到选中队列的第一个进程,开始运行,调度完成。
4. 遍历queue[140]时间复杂度是常数,但还是太低效了。
bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个比特位表示队列是否为空,这样,便可以大大提高查找效率

过期队列

CPU只执行活跃队列的进程,当进程的时间片结束会换入过期队列

  • 过期队列和活动队列结构一模一样
  • 过期队列上放置的进程,都是时间片耗尽的进程
  • 当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算

活跃队列没执行完时后面排队的进程都入过期队列

活跃队列的进程不断减少,过期队列的进程不断增多

active指针和expired指针

active指针永远指向活动队列
expired指针永远指向过期队列
在合适的时候,交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程

你可能感兴趣的:(linux)