一 引言
本文针对linux系统开源代码进行分析和理解,具体内容包括:
- 操作系统是怎么组织进程的
- 进程状态如何转换
- 进程是如何调度的
- 谈谈对Linux操作系统进程模型的看法
二 什么是进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
这里以Windows进程为例,
三 操作系统是怎么组织进程的
1.无进程的内核
在一些操作系统当中操作系统的内核在所有的进程之外执行。也就是说操作系统有自己的内存区域和系统栈,当进程发生 中断,陷阱或系统调用时,此进程的上下文环境被保存在系统栈中,控制权转移给内核。操作系统执行完毕后,恢复进程的上 下文,此进程继续执行,或者保存该进程的上下文环境,然后指派另一进程执行。
2.在用户进程中执行
当发生一个中断,陷阱或是系统调用时,处理器置于内核态,控制权交给操作系统处理,并将此进程上下文环境保存起来进行模式切换。当中断完成后,恢复进程继续执行或是将控制权交给进程切换例程进行进程切换,这样进程中断并恢复执行的过程中只需要进行模式切换而不需要进行进程切换,从而减少开销。
3.基于进程的操作系统
把操作系统作为一组普通的系统进程来执行,既当一个进程发生中断,陷阱或是系统调用时,处理器转变为内核态,进程切换例程切换至系统进程,并保存用户进程的上下文执行环境,也就是说,内核函数被组织成独立的进程。当终端结束时,恢复用户进程继续执行,处理器转变为用户态,由进程切换例程将系统进程切换为用户进程,或是将控制权交给分派器执行下一进程。
四 进程状态如何转换
一.进程有哪些状态:
①就绪状态
当进程已分配到除CPU以外的所有必要资源后,只要再获得CPU,便可立即执行,进程这时的状态就称为就绪状态。
在一个系统中处于就绪状态的进程可能有多个,通常将他们排成一个队列,称为就绪队列。
②执行状态
进程已获得CPU,其程序正在执行。在单处理机系统中,只有一个进程处于执行状态;在多处理机系统中,则有多个进程处于执行状态。
③阻塞状态
正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
二.状态转换图:
五 进程是如何调度的
调度器的结构
在Linux内核中,调度器可以分成两个层级,在进程中被直接调用的成为通用调度器或者核心调度器,他们作为一个组件和进程其他部分分开,而通用调度器和进程并没有直接关系,其通过第二层的具体的调度器类来直接管理进程。具体架构如下图:
如上图所示,每个进程必然属于一个特定的调度器类,Linux会根据不同的需求实现不同的调度器类。各个调度器类之间具备一定的层次关系,即在通用调度器选择进程的时候,会从最高优先级的调度器类开始选择,如果通用调度器类没有可运行的进程,就选择下一个调度器类的可用进程,这样逐层递减。
每个CPU会维护一个调度队列称之为就绪队列,每个进程只会出现在一个就绪队列中,因为同一进程不能同时被两个CPU选中执行。就绪队列的数据结构为struct rq,和上面的层次结构一样,通用调度器直接和rq打交道,而具体和进程交互的是特定于调度器类的子就绪队列。
调度器类
在linux内核中实现了一个调度器类的框架,其中定义了调度器应该实现的函数,每一个具体的调度器类都要实现这些函数 。
在当前linux版本中(3.11.1),使用了四个调度器类:stop_sched_class、rt_sched_class、fair_sched_class、idle_sched_class。在最新的内核中又添加了一个调度类dl_sched_class,但是由于笔者能力所限,且大部分进程都是属于实时调度器和完全公平调度器,所以我们主要分析实时调度器和完全公平调度器。
看下调度器类的定义:
1 struct sched_class { 2 const struct sched_class *next; 3 4 void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags); 5 void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags); 6 void (*yield_task) (struct rq *rq); 7 bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt); 8 9 void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags); 10 11 struct task_struct * (*pick_next_task) (struct rq *rq); 12 void (*put_prev_task) (struct rq *rq, struct task_struct *p); 13 14 #ifdef CONFIG_SMP 15 int (*select_task_rq)(struct task_struct *p, int sd_flag, int flags); 16 void (*migrate_task_rq)(struct task_struct *p, int next_cpu); 17 18 void (*pre_schedule) (struct rq *this_rq, struct task_struct *task); 19 void (*post_schedule) (struct rq *this_rq); 20 void (*task_waking) (struct task_struct *task); 21 void (*task_woken) (struct rq *this_rq, struct task_struct *task); 22 23 void (*set_cpus_allowed)(struct task_struct *p, 24 const struct cpumask *newmask); 25 26 void (*rq_online)(struct rq *rq); 27 void (*rq_offline)(struct rq *rq); 28 #endif 29 30 void (*set_curr_task) (struct rq *rq); 31 void (*task_tick) (struct rq *rq, struct task_struct *p, int queued); 32 void (*task_fork) (struct task_struct *p); 33 34 void (*switched_from) (struct rq *this_rq, struct task_struct *task); 35 void (*switched_to) (struct rq *this_rq, struct task_struct *task); 36 void (*prio_changed) (struct rq *this_rq, struct task_struct *task, 37 int oldprio); 38 39 unsigned int (*get_rr_interval) (struct rq *rq, 40 struct task_struct *task); 41 42 #ifdef CONFIG_FAIR_GROUP_SCHED 43 void (*task_move_group) (struct task_struct *p, int on_rq); 44 #endif 45 };
enqueue_task向就绪队列添加一个进程,该操作发生在一个进程变成就绪态(可运行态)的时候。
dequeue_task就是执行enqueue_task的逆操作,在一个进程由运行态转为阻塞的时候就会发生该操作。
yield_task用于进程自愿放弃控制权的时候。
pick_next_task用于挑选下一个可运行的进程,发生在进程调度的时候,又调度器调用。
set_curr_task当进程的调度策略发生变化时,需要执行此函数
task_tick,在每次激活周期调度器时,由周期调度器调用。
task_fork用于建立fork系统调用和调度器之间的关联,每次新进程建立后,就调用该函数通知调度器。
Linux进程状态机:
六 谈谈对Linux操作系统进程模型的看法
第一次接触Linux,与平时使用的Windows有相通之处但更多的是不同。这几天了解了组织、转换和调度,明白了Linux系统如何在不影响使用体验的情况下达到最高使用效率。操作系统相当于一个大型的软件,是用户和计算机的接口资源管理,程序控制和人机交互都需要通过操作系统来控制,可以说是计算机的灵魂。操作系统通过对算法的不断优化用以提升计算效率来提升用户的使用体验。