进程
1.进程是什么
1.1概念
计算机上所有可运行的软件,通常也包括操作系统,被组织成若干顺序进程(sequential process),简称进程(process)。
一个进程就是一个正在执行程序的实例,包括程序设计器、寄存器和变量的当前值。一个进程是某种类型的一种活动,它有程序、输入、输出以及状态。单个处理器可以被若干进程共享,它使用某种调度算法决定何时停止一个进程的工作,并转而为了另一个进程服务功能。
2.进程如何组织
2.1进程控制块(PCB)
Linux系统中主要的活动实体就是进程。
每个进程执行一段独立的程序并且在进程初始化的时候拥有一个独立的控制线程。换句话说,每一个进程都拥有一个独立的程序计数器,用这个这个程序计数器可以追踪下一条将要被执行的指令。
所有的进程都被放在一个叫做进程控制块(PCB),的数据结构中,可以理解为进程属性的集合,该控制块由操作系统创建和管理。每个进程在内核中都有一个进程控制块来维护进程相关的信息,Linux内核的进程控制块是(task_struct)结构体。
2.2进程标识符(PID)
进程标识符在task_struct下定义
pid_t pid; //内核中用以标识进程的id pid_t tgid; //用来实现线程机制
struct pid { atomic_t count; unsigned int level; /* lists of tasks that use this pid */ struct hlist_head tasks[PIDTYPE_MAX]; struct rcu_head rcu; struct upid numbers[1]; };
每个进程都有一个唯一的标识符(PID),内核通过这个标识符来识别不同的进程,同时,进程标识符(PID)也是内核提供给用户程序的接口,用户程序通过PID对进程发号施令。
PID是32位的无符号整数,它被顺序编号:新创建进程的PID通常是前一个进程的PID加1。然而,为了与16位硬件平台的传统Linux系统保持兼容,在Linux上允许的最大PID号是32767,当内核在系统中创建第32768个进程时,就必须重新开始使用已闲置的PID号。在64位系统中,PID可扩展到4194303。
3.进程状态
3.1六种状态
#define TASK_RUNNING
1.表示进程要么正在执行,要么正在准备执行。
#define TASK_INTERRUPTIBLE
2.表示进程被阻塞(睡眠),只有当某个条件是TRUE时,其状态相应的设置为 TASK_RUNNING。它可以被信号和wake_up唤醒。
#define TASK_UNINTERRUPTIBLE
3.表示进程被阻塞(睡眠),只有当某个条件是TRUE时,其状态相应的设置为 TASK_RUNNING。它只能被wake_up唤醒。
#define TASK_STOPPED
4.表示进程被停止执行。
#define TASK_TRACED
5.表示进程被 debugger 等进程监视着。
#define EXIT_ZOMBIE
6.表示进程的执行被终止,但是其父进程还没有使用 wait() 等系统调用来获知它的终止信息。
#define EXIT_DEAD
7.表示进程的最终状态。
3.2状态转换图
4.Linux下的O(1)调度算法
Linux O(1)调度器(O(1) scheduler) 是历史上一个流行的Linux系统调度程序。命名为这个名字是因为它能够在常数时间内执行任务调度,例如从执行队列中选择一个任务或将一个任务加入执行队列,这与系统中的任务总数有关。
4.1 O(1)调度器
在O(1)调度中,要问最重要的数据结构是运行队列。运行队列描绘了进程队列的结构,在内核源码中用runqueue结构体表示。
struct runqueue { unsigned long nr_running; task_t *curr; prio_array_t *active,*expired,arrays[2]; };
4.2优先级数组
O(1)算法的另一个核心数据结构即为prio_array结构体。该结构体中有一个用来表示进程动态优先级的数组queue,它包含了每一种优先级进程所形成的链表。
#define MAX_USER_RT_PRIO 100 #define MAX_RT_PRIO MAX_USER_RT_PRIO #define MAX_PRIO (MAX_RT_PRIO + 40) typedef struct prio_array prio_array_t; struct prio_array { unsigned int nr_active; unsigned long bitmap[BITMAP_SIZE]; struct list_head queue[MAX_PRIO]; };
4.3静态优先级和动态优先级
进程有两个优先级,一个是静态优先级,一个是动态优先级.静态优先级是用来计算进程运行的时间片长度的,动态优先级是在调度器进行调度时用到的,调度器每次都选取动态优先级最高的进程运行.
静态优先级的计算: nice值和静态优先级之间的关系是:静态优先级=100+nice+20 而nice值的范围是-20~19,所以普通进程的静态优先级的范围是100~139
动态优先级的计算: 动态优先级=max(100 , min(静态优先级 – bonus + 5 , 139))
4.4时间片
O(1)算法采用过期进程数组和活跃进程数组解决以往调度算法所带来的O(n)复杂度问题。过期数组中的进程都已经用完了时间片,而活跃数组的进程还拥有时间片。当一个进程用完自己的时间片后,它就被移动到过期进程数组中,同时这个过期进程在被移动之前就已经计算好了新的时间片。可以看到O(1)调度算法是采用分散计算时间片的方法,并不像以往算法中集中为所有可运行进程重新计算时间片。当活跃进程数组中没有任何进程时,说明此时所有可运行的进程都用完了自己的时间片。那么此时只需要交换一下两个数组即可将过期进程切换为活跃进程,进而继续被调度程序所调度。两个数组之间的切换其实就是指针之间的交换,因此花费的时间是恒定的。
struct prop_array *array = rq->active; if (array->nr_active != 0) { rq->active = rq->expired; rq->expired = array; }
上面的代码说明了两个数组之间的交换,通过分散计算时间片、交换过期和活跃两个进程集合的方法可以使得O(1)算法在恒定的时间内为每个进程重新计算好时间片。
进程运行的时间片长度的计算 静态优先级<120,基本时间片=max((140-静态优先级)*20, MIN_TIMESLICE) 静态优先级>=120,基本时间片=max((140-静态优先级)*5, MIN_TIMESLICE)
4.5调度算法
在每次进程切换时,内核依次扫描就绪队列上的每一个进程,计算每个进程的优先级,再选择出优先级最高的进程来运行;尽管这个算法理解简单,但是它花费在选择优先级最高进程上的时间却不容忽视。系统中可运行的进程越多,花费的时间就越大,时间复杂度为O(n)。
//伪代码 for (系统中的每个进程) { 重新计算时间片; 重新计算优先级; }
5.对操作系统进程模型的看法
进程是操作系统最核心的概念,这是对正在运行的程序的一个抽象。所有操作系统其他的所有内容都是围绕着进程的概念展开的。
从概念上说,每个进程都拥有它自己的虚拟CPU。当然,实际上的真正的CPU在各自进程之间来回切换。
进程管理即是操作系统对CPU的管理,为了提升CPU利用率,使用多道编程,便有了多进程维护管理。操作系统已实现了各管理功能,硬件CPU及一系列进程资源抽象成为了进程的概念,可以说进程算是操作系统的“无中生有”,应用程序的编程人员直接利用进程的机制,达到让应用程序高效利用硬件资源的目的。
6.参考
https://blog.csdn.net/a2796749/article/details/47101533
Modern Operating Systems (Fourth Edition) Andrew S. Tanenbaum and Herbert Bos 著