一、什么是进程?
根据我的理解,进程可以理解为程序的具象。程序是指令、数据及其组织形式的描述,而进程则是程序的实体。进程它是作为系统进行资源分配和调度运行的基本单位,操作系统操作目标的基本单位即是进程。
进程是一个实体。每一个进程都有它自己的地址空间。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有进程器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。
(WIN10系统中的进程,可以看出不同的进程需要的系统资源(如CPU,内存等)各不相同)
二、进程的状态
进程状态的宏定义
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
/* in tsk->exit_state */
#define EXIT_ZOMBIE 16
#define EXIT_DEAD 32
/* in tsk->state again */
#define TASK_DEAD 64
#define TASK_WAKEKILL 128
#define TASK_WAKING 256
#define TASK_STATE_MAX 512
这段宏定义代码中列举了Linux系统中的进程状态,分别为:
1、TASK_RUNNING:就绪态,或者称为运行态。处于这个状态的进程如字面意思,要么已经在CPU上执行,要么处于准备执行的状态
2、TASK_INTERRUPTIBLE:睡眠态(或者说阻塞态)。在Linux中睡眠态分为浅度睡眠态和深度睡眠态,此状态为浅度睡眠态。此状态按我的理解,和课本上关于阻塞态的介绍和理解相似。操作系统发现进程不能进行下去时就会进入此状态。当有特定的外部事件发生(如传递一个信号进行唤醒)才能使该进程从该状态转入就绪态。
3、TASK_UNINTERRUPTIBLE:睡眠态。该状态为深度睡眠态,与浅度睡眠态不同的是,该状态无法由其他进程通过信号来唤醒,也无法由时钟中断唤醒。
4、EXIT_ZOMBIE:僵尸状态。Linux系统中将已经终止但仍然保留一些信息,等待父进程来回收的进程称为僵尸进程(就好比电影和游戏中的僵尸,虽然已经是死亡状态,但尸体却还在四处走动)。父进程可以通过wait()系统来返回僵尸子进程的信息。
5、EXIT_DEAD:父进程通过wait()系统返回僵尸子进程的信息后,进程将被完全终止的状态。
6、 TASK_STOPPED:暂停状态。进程进入暂停状态,当进程收到SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU信号后进入。
7、 TASK_TRACED:跟踪状态。当一个进程被另一个进程监控时,任何信号都可以把这个进程置于跟踪状态。
关于进程状态转换的理解:
在最开始,一个进程会处在TASK_RUNNING状态。CPU分配时间片供给进程,使得进程进入TASK_RUNNING的运行态;当时间片耗尽后,进程将进入TASK_RUNNING的就绪态,等待CPU分配时间片(此时暂时不在CPU上运行)。
当进程在将数据写入磁盘、等待资源到位等情况时,进程将进入TASK_(UN)INTERRUPTIBLE睡眠态,等到资源到位或者数据写入完成时才返回就绪态。
最后,当进程需要退出时,进程会被设置为EXIT_ZOMBIE僵尸状态,此时进程占用的的内存释放等待父进程通过调用wait()类系统来将进程完全停止,此时进程进入EXIT_DEAD僵尸撤销状态。
三、进程的调度方式
关于进程调度的理解:操作系统通过调度程序来进行进程调度。系统的处理器资源和时间片资源都是有限的,当系统当前运行的所有进程需要的资源大于系统的处理器资源和时间片资源时,就需要确定哪些进程是需要优先运行的,这项工作就需要交给调度器来完成。调度器根据进程的优先级等参数来决定分配给进程多少系统资源和时间片。换句话说,linux系统它基于时间片和优先级来进行对进程的调度。对于优先级越高的进程,系统分配给该进程的时间片就会越长。
进程对优先级定义的源代码
#defineMAX_USER_RT_PRIO 100
#defineMAX_RT_PRIO MAX_USER_RT_PRIO
#defineMAX_PRIO (MAX_RT_PRIO+ 40)
#defineDEFAULT_PRIO (MAX_RT_PRIO + 20)
Linux的进程分普通进程和实时进程,而实时进程又分SCHED_FIFO与SCHED_RR,它们只有静态优先级,范围从0-99,而普通进程的优先级是从100-139,所以实时进程比普通进程的优先级高。
Linux对于进程的分类源代码
#define SCHED_NORMAL 0 //普通进程
#define SCHED_FIFO 1 //实时进程,先进先出
#define SCHED_RR 2 //实时进程,基于优先级的轮回法(Round Robin)
对相同优先级的任务,SCHED_RR是分配给每个任务一个特定的时间片,然后轮转依次执行;而SCHED_FIFO则是让一个任务执行完再调度下一个任务,
SCHED_NORMAL就是普通进程,它不仅有静态优先级,还有动态优先级。
静态优先级是定义进程时就已经定义过的。通俗一点来理解的话,静态优先级是进程与生俱来的优先级。它是用nice值换算出来的。nice值从-20到19,它决定了优先级和时间片,19是最低的而-20最高。普通进程的静态优先级范围从100(最高优先级)到139(最低优先级)。
而动态优先级的计算则是以静态优先级为基础,加上一些变化数值。这个变化数值的决定因素就是进程的平均睡眠时间和进程的交互性。总之,动态优先级可以理解为系统根据进程的实际运行状况,生成一个补正值对静态优先级进行补正。
计算动态优先级的函数是
static int effective_prio(task_t *p)
{
Int bonus, prio;
if(rt_task(p))
return p->prio;
bonus= CURRENT_BONUS(p) - MAX_BONUS / 2;
prio= p->static_prio - bonus;
if(prio < MAX_RT_PRIO)
prio= MAX_RT_PRIO;
if(prio > MAX_PRIO-1)
prio= MAX_PRIO-1;
returnprio;
}
其中bonus就是上文所提到的“变化数值”,它会与静态优先级进行一轮运算来生成动态优先级数值。
计算时间片的函数是
#define SCALE_PRIO(x, prio) \
max(x* (MAX_PRIO - prio) / (MAX_USER_PRIO/2), MIN_TIMESLICE)
static unsigned int task_timeslice(task_t*p)
{
if(p->static_prio < NICE_TO_PRIO(0))
returnSCALE_PRIO(DEF_TIMESLICE*4, p->static_prio);
else
returnSCALE_PRIO(DEF_TIMESLICE, p->static_prio);
}
linux系统使用runqueue数据结构来表示一个可运行队列,这个数据结构用于存放每个CPU的就绪队列信息。可运行状态进程所组成的集合由runqueue中的arrays数组来实现。该数组元素类型为prio_array_t.
struct prio_array {
int nr_active; //进程个数
struct list_head queue[MAXPRIO];
unsigned long bitmap[BITMAP_SIZE];
}
当一个普通进程的时间片耗尽后,将重新计算它的优先级和时间片,先将它从active array(活动进程集合)中删除,添加到expired array(过期进程集合)中相应的优先级队列
四、我对操作系统进程模型的看法
对于一个操作系统来说,最基本的操作单位就是进程。进程它会具有多种状态,多种属性。一个操作系统需要根据进程的状态来处理进程,安排进程的状态切换。操作系统必须要合理为进程安排系统资源和时间,因为所有的进程一般是无法同时不间断地在操作系统上运行的,操作系统必须为进程分配好CPU和时间片,合理调度进程。