基于Linux2.6.32 系统下分析进程模型

1、简介

本文主要是基于Linux Kernel 2.6.32 的源码,进行深入分析进程模型,具体包含如下内容:

  • 操作系统是怎么组织进程的
  • 进程状态如何转换(给出进程状态转换图)
  • 进程是如何调度的
  • 谈谈自己对该操作系统进程模型的看法

(Linux Kernel 2.6.32源代码的连接地址:https://elixir.bootlin.com/linux/v2.6.32/source/fs)

2、进程

2.1什么是进程

  进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

  简单的说,计算机上所有可运行的软件,通常也包括操作系统,被组织成若干顺序进程,简称进程。

2.2怎么查看进程

  一个进程就是一个正在执行程序的实例,在Windowns系统上,通过打开任务管理器,我们就可以看到目前正在运行的程序:

 

   在Linux操作系统中,我们可以用 ps 指令(ps查看正处于Running的进程, ps aux 查看所有的进程),来查看当前正在运行的进程的一些基本信息。

 

3、操作系统是怎么组织进程的

 

3.1进程的组成部分

  进程一般是由三个部分组成的:进程控制块、程序段、数据段。

3.1.1进程控制块:

  Linux是通过 task_struct (PCB)结构体来描述一个进程的所有信息,结构体被定义在  include/linux/sched.h 中。进程创建时,操作系统就新建一个PCB结构,它之后就常驻内存,任一时刻可以存取, 在进程结束时删除。PCB是进程实体的一部分,操作系统通过PCB表来管理和控制进程,是进程存在的唯一标志。

PCB通常包含的内容:

进程描述信息 进程控制和管理信息 资源分配清单 处理相关信息
进程标识符(PID) 进程当前状态 代码段指针 通用寄存器值
用户标志符(UID) 进程优先级 数据段指针 地址寄存器值
  代码运行入口地址 堆栈段指针 控制寄存器值
  程序的外存地址 文件描述符 标志寄存器值
  进入内存时间 键盘 状态字
  处理机占用时间 鼠标  
  信号量使用    

 

部分代码:

  1  struct task_struct {  
  2     //进程状态
  3     volatile long state;    
  4    //内存指针
  5     void *stack;    
  6     atomic_t usage; 
  7     //有几个进程正在使用该结构  
  8     unsigned int flags; 
  9     //反应进程状态的信息,但不是运行状态  
 10     unsigned int ptrace;  
 11 
 12    #ifdef CONFIG_SMP  
 13     struct task_struct *wake_entry;  
 14     int on_cpu;   //在哪个CPU上运行  
 15     #endif  
 16     int on_rq;  //on _ rq表示实体当前是否计划在运行队列中。 
 17     int prio, static_prio, normal_prio;  //静态优先级,动态优先级  
 18 /* 
 19 任务结构使用三个元素来表示进程的优先级: prio
 20 而normal _ prio表示进程的动态优先级,static _ prio表示进程的静态优先级。
 21 静态优先级是启动进程时分配给进程的优先级,它可以被修改。
 22 使用nice和sched _ setscheduler系统调用,但要在进程的运行时间。
 23 normal _ priority表示基于静态优先级和进程的调度策略。因此,相同的静态优先级将导致不同的结果
 24 */  
 25     unsigned int rt_priority;  //实时任务的优先级  
 26     const struct sched_class *sched_class;  //与调度相关的函数  
 27     struct sched_entity se; //调度实体  
 28     struct sched_rt_entity rt; //实时任务调度实体  
 29 
 30     #ifdef CONFIG_PREEMPT_NOTIFIERS  //配置抢占树,抢占的结构体的读写机制,即RCU机制。
 31     struct hlist_head preempt_notifiers; //与抢占有关的  
 32     #endif  
 33 
 34    
 35     unsigned char fpu_counter;  //包含连续上下文开关的数量
 36     #ifdef CONFIG_BLK_DEV_IO_TRACE  
 37     unsigned int btrace_seq;  
 38     #endif  
 39 
 40     unsigned int policy;  //调度策略  
 41     cpumask_t cpus_allowed;//多核体系结构中管理CPU的位图:Cpumasks provide a bitmap suitable   
 42                                //for representing the set of CPU's in a system, one bit position per CPU number.   
 43                                // In general, only nr_cpu_ids (<= NR_CPUS) bits are valid.  
 44 
 45     #ifdef CONFIG_PREEMPT_RCU  
 46     int rcu_read_lock_nesting; //RCU是一种新型的锁机制可以参考博文:http://blog.csdn.net/sunnybeike/article/details/6866473
 47     char rcu_read_unlock_special;  
 48 #if defined(CONFIG_RCU_BOOST) && defined(CONFIG_TREE_PREEMPT_RCU)  
 49     int rcu_boosted;  
 50 #endif  
 51     struct list_head rcu_node_entry;  
 52 #endif 
 53 #ifdef CONFIG_TREE_PREEMPT_RCU  
 54     struct rcu_node *rcu_blocked_node;  
 55 #endif 
 56 #ifdef CONFIG_RCU_BOOST  
 57     struct rt_mutex *rcu_boost_mutex;  
 58 #endif 
 59 #if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)  
 60     struct sched_info sched_info;   //调度相关的信息,如在CPU上运行的时间/在队列中等待的时间等。  
 61 #endif  
 62 
 63     struct list_head tasks;   //任务队列  
 64 #ifdef CONFIG_SMP  
 65     struct plist_node pushable_tasks;  
 66 #endif  
 67 
 68     struct mm_struct *mm, *active_mm;   //mm是进程的内存管理信息  
 69 /*关于mm和active_mm 
 70 lazy TLB应该是指在切换进程过程中如果下一个执行进程不会访问用户空间,就没有必要flush TLB; 
 71 kernel thread运行在内核空间,它的mm_struct指针mm是0,它不会访问用户空间。 if (unlikely(!mm))是判断切换到的新进程是否是kernel thread, 
 72 如果是,那么由于内核要求所有进程都需要一个mm_struct结构,所以需要把被切换出去的进程(oldmm)的mm_struct借过来存储在 
 73 active_mm( next->active_mm = oldmm;),这样就产生了一个anomymous user, atomic_inc(&oldmm->mm_count)就用于增加被切换进程的mm_count, 
 74 然后就利用 enter_lazy_tlb标志进入lazeTLB模式(MP),对于UP来说就这个函数不需要任何动作; 
 75 if (unlikely(!prev->mm))这句话是判断被切换出去的进程是不是kernel thread,如果是的话就要释放它前面借来的mm_struct。 
 76 而且如果切换到的进程与被切换的kernel thread的page table相同,那么就要flush与这些page table 相关的entry了。 
 77 注意这里的连个if都是针对mm_struct结构的mm指针进行判断,而设置要切换到的mm_struct用的是active_mm; 
 78 对于MP来说,假如某个CPU#1发出需要flushTLB的要求,对于其它的CPU来说如果该CPU执行kernel thread,那么由CPU设置其进入lazyTLB模式, 
 79 不需要flush TLB,当从lazyTLB模式退出的时候,如果切换到的下个进程需要不同的PageTable,那此时再flush TLB;如果该CPU运行的是普通的进程和#1相同, 
 80 它就要立即flush TLB了 
 81 
 82 大多数情况下mm和active_mm中的内容是一样的;但是在这种情况下是不一致的,就是创建的进程是内核线程的时候,active_mm = oldmm(之前进程的mm), mm = NULL, 
 83 */  
 84 #ifdef CONFIG_COMPAT_BRK  
 85     unsigned brk_randomized:1;  
 86 #endif  
 87 #if defined(SPLIT_RSS_COUNTING)  
 88     struct task_rss_stat    rss_stat;  //RSS is the total memory actually held in RAM for a process.  
 89 #endif  
 90     int exit_state;  //进程退出时的状态  
 91     int exit_code, exit_signal; //进程退出时发出的信号  
 92     int pdeath_signal;  
 93     unsigned int group_stop;   
 94     unsigned int personality;  
 95     unsigned did_exec:1;    //表示当前进程是在执行原来的代码还是在执行由execve调度的新的代码。  
 96     unsigned in_execve:1;  
 97     unsigned in_iowait:1;   
 98     unsigned sched_reset_on_fork:1;  
 99     unsigned sched_contributes_to_load:1;  
100 
101     pid_t pid;  //进程ID  
102     pid_t tgid; //线程组ID  
103 
104 #ifdef CONFIG_CC_STACKPROTECTOR  //配置堆栈保护措施
105     unsigned long stack_canary;  //canary值 保护编译器 防止堆栈溢出 导致的返回地址被填充
106 #endif  
107     struct task_struct *real_parent;   
108     struct task_struct *parent; 
109     struct list_head children;  
110     struct list_head sibling;    
111     struct task_struct *group_leader;  //线程组的头结点 
112 
113     struct list_head ptraced;   //跟踪器的头结点,跟踪器 跟踪 进程的逻辑流,即PC指令流  
114     struct list_head ptrace_entry;  
115  
116     struct pid_link pids[PIDTYPE_MAX];  
117     struct list_head thread_group;  //用来保存线程组的PID
118 
119     struct completion *vfork_done;    
120 
121         int __user *set_child_tid;  //指向用户创造创立的线程的TID号 
122         int __user *clear_child_tid;  //指向被清除的线程的TID号 
123         putime_t utime, stime, utimescaled, stimescaled;  // utime是进程用户态耗费的时间,stime是用户内核态耗费的时间。                                                         
124          //而后边的两个值应该是不同单位的时间cputime_t gtime;    
125         #ifndef CONFIG_VIRT_CPU_ACCOUNTING   
126         cputime_t prev_utime, prev_stime;  
127         #endif  
128         unsigned long nvcsw, nivcsw;  //上下文切换计数
129         struct timespec start_time;  //单调时间
130         struct timespec real_start_time;  //开机时间
131         unsigned long min_flt, maj_flt;   
132         struct task_cputime cputime_expires;  //进程到期的时间?  
133         struct list_head cpu_timers[3];  
134 
135 const struct cred __rcu *real_cred; 
136         const struct cred __rcu *cred; 
137         struct cred *replacement_session_keyring; 
138         char comm[TASK_COMM_LEN]; 
139 
140         int link_count, total_link_count;  //硬连接的数量?  
141         #ifdef CONFIG_SYSVIPC  //进程间通信相关的东西  
142         struct sysv_sem sysvsem;  
143         #endif  
144         #ifdef CONFIG_DETECT_HUNG_TASK  //挂起任务检测
145         unsigned long last_switch_count;  
146         #endif  
147 struct thread_struct thread; /*因为task_stcut是与硬件体系结构无关的,因此用thread_struct这个结构来包容不同的体系结构*/    
148         struct fs_struct *fs;  
149         struct files_struct *files;   
150         struct signal_struct *signal;  
151         struct sighand_struct *sighand;  
152         sigset_t blocked, real_blocked;  
153         sigset_t saved_sigmask; 
154         struct sigpending pending;  //表示进程收到了信号但是尚未处理。  
155         unsigned long sas_ss_sp;size_t sas_ss_size;  
156        
157         int (*notifier)(void *priv);  
158         void *notifier_data;  
159         sigset_t *notifier_mask;  
160         struct audit_context *audit_context; 
161         #ifdef CONFIG_AUDITSYSCALL  
162         uid_t loginuid;  
163         unsigned int sessionid;  
164         #endif  
165         seccomp_t seccomp;    
166         u32 parent_exec_id;   
167         u32 self_exec_id; 
168         spinlock_t alloc_lock;  
169         #ifdef CONFIG_GENERIC_HARDIRQS  //处理程序线程
170         struct irqaction *irqaction;
171         #endif 
172         struct plist_head pi_waiters;
173         struct rt_mutex_waiter *pi_blocked_on;  
174         #endif  
175         #ifdef CONFIG_DEBUG_MUTEXES //互斥死锁检测  
176         struct mutex_waiter *blocked_on;  
177         #endif  
178         #ifdef CONFIG_TRACE_IRQFLAGS  
179         unsigned int irq_events;  
180         unsigned long hardirq_enable_ip;  
181         unsigned long hardirq_disable_ip;  
182         unsigned int hardirq_enable_event;  
183         unsigned int hardirq_disable_event;  
184         int hardirqs_enabled;  
185         int hardirq_context;  
186         unsigned long softirq_disable_ip;  
187         unsigned long softirq_enable_ip;  
188         unsigned int softirq_disable_event;  
189         unsigned int softirq_enable_event;  
190         int softirqs_enabled;   
191         int softirq_context;  
192         #endif  
193         #ifdef CONFIG_LOCKDEP  
194         # define MAX_LOCK_DEPTH 48UL  
195         u64 curr_chain_key;  
196         int lockdep_depth; //锁的深度  
197         unsigned int lockdep_recursion;  
198         struct held_lock held_locks[MAX_LOCK_DEPTH];  
199         gfp_t lockdep_reclaim_gfp;  
200         #endif  
201         void *journal_info; //文件系统日志信息   
202         struct bio_list *bio_list; //块IO设备表  
203         #ifdef CONFIG_BLOCK   
204         struct blk_plug *plug;  
205         #endif  
206 
207         struct reclaim_state *reclaim_state;  
208         struct backing_dev_info *backing_dev_info;  
209         struct io_context *io_context;  
210         unsigned long ptrace_message;  
211         siginfo_t *last_siginfo;  
212         struct task_io_accounting ioac; //用于记录单个任务的IO统计信息的结构
213         #if defined(CONFIG_TASK_XACCT)  
214         u64 acct_rss_mem1;   
215         u64 acct_vm_mem1;    
216         cputime_t acct_timexpd;  
217         #endif  
218         #ifdef CONFIG_CPUSETS  
219         nodemask_t mems_allowed;    
220         int mems_allowed_change_disable;  
221         int cpuset_mem_spread_rotor;  
222         int cpuset_slab_spread_rotor;  
223         #endif  
224         #ifdef CONFIG_CGROUPS  
225         struct css_set __rcu *cgroups;  
226         struct list_head cg_list;  
227         #endif  
228         #ifdef CONFIG_FUTEX  
229         struct robust_list_head __user *robust_list;  
230         #ifdef CONFIG_COMPAT  
231         struct compat_robust_list_head __user *compat_robust_list;  
232         #endifstruct list_head pi_state_list;  
233         struct futex_pi_state *pi_state_cache;  
234         #endif  
235         #ifdef CONFIG_PERF_EVENTS  
236         struct perf_event_context *perf_event_ctxp[perf_nr_task_contexts];  
237         struct mutex perf_event_mutex;  
238         struct list_head perf_event_list;  
239         #endif  
240         #ifdef CONFIG_NUMA  
241         struct mempolicy *mempolicy;  
242         short il_next;  
243         short pref_node_fork;  
244         #endifatomic_t fs_excl; //是否允许进程独占文件系统。为0表示否。  
245         struct rcu_head rcu; //缓存上次用于拼接的管道
246         struct pipe_inode_info *splice_pipe;  
247         #ifdef CONFIG_TASK_DELAY_ACCT  
248         struct task_delay_info *delays;  
249         #endif  
250         #ifdef CONFIG_FAULT_INJECTION  
251         int make_it_fail;  
252         #endif  
253         struct prop_local_single dirties;  
254         #ifdef CONFIG_LATENCYTOP  
255         int latency_record_count;  
256         struct latency_record latency_record[LT_SAVECOUNT];  
257         #endif  
258         unsigned long timer_slack_ns;  
259         unsigned long default_timer_slack_ns;  
260         struct list_head *scm_work_list;  
261         #ifdef CONFIG_FUNCTION_GRAPH_TRACER  
262         int curr_ret_stack; //返回函数跟踪的返回地址堆栈
263         struct ftrace_ret_stack *ret_stack; //上次排程的时间戳记 
264         unsigned long long ftrace_timestamp;  
265         atomic_t trace_overrun;  
266         atomic_t tracing_graph_pause;  
267         #endif  
268         #ifdef CONFIG_TRACING  
269         unsigned long trace; //位掩码与跟踪递归计数器
270         unsigned long trace_recursion;  
271         #endif 
272         #ifdef CONFIG_CGROUP_MEM_RES_CTLR   
273         struct memcg_batch_info {
274         int do_batch;  //启动批未知时递增 
275         struct mem_cgroup *memcg; 
276         unsigned long nr_pages;  
277         unsigned long memsw_nr_pages;  
278         } memcg_batch;  
279         #endif  
280         #ifdef CONFIG_HAVE_HW_BREAKPOINT  
281         atomic_t ptrace_bp_refcnt;  
282         #endif  
283      }        

3.1.2程序段

  程序段是能被进程调度程序调度到CPU执行的程序代码段。

3.1.3数据段

  一个进程的数据段,可以是进程对应的程序加工处理的原始数据,也可以是程序执行是产的中心或最终结果。

3.2进程的状态

   一般来说,进程有三个状态:

运行态:该时刻进程实际占用CPU;

就绪态:可运行,但因为其他进程正在运行而暂时停止;

阻塞态:除非某种外部事件发生,否则进程不能进行。

3.3进程状态的转换

  对于进程状态的各类转换,这里引用网上(http://book.51cto.com/art/201106/270771.htm)查找到《Linux内核设计与实现(原书第3版)》的进程状态转换图来说明:

4、进程是如何调度的

  对于Linux中的进程调度器,其演变过程是O(n),O(1),CFS。因为在Linux Kernel 2.6.23 之后就开始采用的CFS进程调度器,所以下面主要讲一下CFS调度器。

CFS调度器:

  CFS(完全公平调度器)是Linux内核2.6.23版本开始采用的进程调度器,它的基本原理是这样的:设定一个调度周期(sched_latency_ns),目标是让每个进程在这个周期内至少有机会运行一次,换一种说法就是每个进程等待CPU的时间最长不超过这个调度周期;然后根据进程的数量,大家平分这个调度周期内的CPU使用权,由于进程的优先级即nice值不同,分割调度周期的时候要加权;每个进程的累计运行时间保存在自己的vruntime(进程的虚拟运行时间)字段里,哪个进程的vruntime最小就获得本轮运行的权利。

  因此下面主要就说一下进程的vruntime值在下面三种情况下的变化。

4.1 新进程的vruntime(进程的虚拟运行时间)的初值

  在CFS调度器中,每个CPU的就绪队列cfs_rq都维护一个min_vruntime字段,记录该就绪队列中所有进程的vruntime最小值,新进程的初始vruntime值就是以它所在运行队列的min_vruntime为基础来设置的,与老进程保持在一个合理的差距范围内。

进程的就绪队列中就存储了CFS相关的虚拟运行时钟的信息, struct cfs_rq定义如下:

 1 struct cfs_rq
 2 {
 3     struct load_weight load;   /*所有进程的累计负荷值*/
 4     unsigned long nr_running;  /*当前就绪队列的进程数*/
 5 
 6     // ========================
 7     u64 min_vruntime;  //  队列的虚拟时钟, 
 8     // =======================
 9     struct rb_root tasks_timeline;  /*红黑树的头结点*/
10     struct rb_node *rb_leftmost;    /*红黑树的最左面节点*/
11 
12     struct sched_entity *curr;      /*当前执行进程的可调度实体*/
13         ...
14 };

 

内核设置min_vruntime是通过update_min_vruntime函数来设置的:

 1 static void update_min_vruntime(struct cfs_rq *cfs_rq)
 2 {
 3     /*  初始化vruntime的值, 相当于如下的代码
 4     if (cfs_rq->curr != NULL)
 5         vruntime = cfs_rq->curr->vruntime;
 6     else
 7         vruntime = cfs_rq->min_vruntime;
 8     */
 9     u64 vruntime = cfs_rq->min_vruntime;
10 
11     if (cfs_rq->curr)
12         vruntime = cfs_rq->curr->vruntime;
13 
14 
15     /*  检测红黑树是都有最左的节点, 即是否有进程在树上等待调度
16      *  cfs_rq->rb_leftmost(struct rb_node *)存储了进程红黑树的最左节点
17      *  这个节点存储了即将要被调度的结点  
18      *  */
19     if (cfs_rq->rb_leftmost)
20     {
21         /*  获取最左结点的调度实体信息se, se中存储了其vruntime
22          *  rb_leftmost的vruntime即树中所有节点的vruntiem中最小的那个  */
23         struct sched_entity *se = rb_entry(cfs_rq->rb_leftmost,
24                            struct sched_entity,
25                            run_node);
26         /*  如果就绪队列上没有curr进程
27          *  则vruntime设置为树种最左结点的vruntime
28          *  否则设置vruntiem值为cfs_rq->curr->vruntime和se->vruntime的最小值
29          */
30         if (!cfs_rq->curr)  /*  此时vruntime的原值为cfs_rq->min_vruntime*/
31             vruntime = se->vruntime;
32         else                /* 此时vruntime的原值为cfs_rq->curr->vruntime*/
33             vruntime = min_vruntime(vruntime, se->vruntime);
34     }
35 
36     /* ensure we never gain time by being placed backwards. 
37      * 为了保证min_vruntime单调不减
38      * 只有在vruntime超出的cfs_rq->min_vruntime的时候才更新
39      */
40     cfs_rq->min_vruntime = max_vruntime(cfs_rq->min_vruntime, vruntime);
41 #ifndef CONFIG_64BIT
42     smp_wmb();
43     cfs_rq->min_vruntime_copy = cfs_rq->min_vruntime;
44 #endif
45 }

 在这里值得一提的是红黑树

  R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。

红黑树的特性:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

注意:
(01) 特性(3)中的叶子节点,是只为空(NIL或null)的节点。
(02) 特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。

红黑树示意图如下:

 

4.2休眠时进程的vruntime值

  如果休眠时期进程的vruntime保持不变,由于其他进程的vruntime在运行中不断的推进,那么等待休眠结束的时候,它的vruntime就会比别人小很多,这样会让它获得较长时间抢占CPU,这对其他进程就显得很不公平。因此在CFS中,进程在休眠时期被唤醒后,需要重新设置该进程的vruntime值,以min_vruntime值为基础,给予一定的补偿。

下面我们看下place_entity是怎么计算新进程的vruntime已经对被唤醒进程的vruntime给予的补偿:

 1 static void
 2 place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
 3 {
 4         u64 vruntime = cfs_rq->min_vruntime;
 5  
 6         /*
 7          * The 'current' period is already promised to the current tasks,
 8          * however the extra weight of the new task will slow them down a
 9          * little, place the new task so that it fits in the slot that
10          * stays open at the end.
11          */
12         if (initial && sched_feat(START_DEBIT)) /* initial表示新进程 */
13                 vruntime += sched_vslice(cfs_rq, se);
14  
15         /* sleeps up to a single latency don't count. */
16         if (!initial) { /* 休眠进程 */
17                 unsigned long thresh = sysctl_sched_latency; /* 一个调度周期 */
18  
19                 /*
20                  * Halve their sleep time's effect, to allow
21                  * for a gentler effect of sleepers:
22                  */
23                 if (sched_feat(GENTLE_FAIR_SLEEPERS)) /* 若设了GENTLE_FAIR_SLEEPERS */
24                         thresh >>= 1; /* 补偿减为调度周期的一半 */
25  
26                 vruntime -= thresh;
27         }
28  
29         /* ensure we never gain time by being placed backwards. */
30         vruntime = max_vruntime(se->vruntime, vruntime);
31  
32         se->vruntime = vruntime;
33 }

   其中sched_vslice = (调度周期 * 进程权重 / 所有进程总权重) * NICE_0_LOAD / 进程权重;也就是算出进程应分配的实际cpu时间,再把它转化为vruntime。

4.3进程从一个CPU迁移到另一个CPU上的时候vruntime的值

  在多CPU系统上,不同的CPU的负载是不一样的,有的CPU更忙一些,而每个CPU都有自己的运行队列,每个队列中的进程的vruntime也走得有快有慢。这样在一个进程从CPU迁移到另一个CPU的时候如果它的vruntime还是保持原值的话,可能会吃亏也可能会占便宜。

  为了公平起见,在CFS中,当进程从一个CPU的运行队列中出来 (dequeue_entity) 的时候,它的vruntime要减去队列的min_vruntime值;而当进程加入另一个CPU的运行队列 ( enqueue_entiry) 时,它的vruntime要加上该队列的min_vruntime值。这样,进程从一个CPU迁移到另一个CPU之后,vruntime保持相对公平。

主要代码如下:

 

 1 static void
 2 dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
 3 {
 4 ...
 5         /*
 6          * Normalize the entity after updating the min_vruntime because the
 7          * update can refer to the ->curr item and we need to reflect this
 8          * movement in our normalized position.
 9          */
10         if (!(flags & DEQUEUE_SLEEP))
11                 se->vruntime -= cfs_rq->min_vruntime;
12 ...
13 }
14  
15 static void
16 enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
17 {
18         /*
19          * Update the normalized vruntime before updating min_vruntime
20          * through callig update_curr().
21          */
22         if (!(flags & ENQUEUE_WAKEUP) || (flags & ENQUEUE_WAKING))
23                 se->vruntime += cfs_rq->min_vruntime;
24 ...
25 }

 

5、对该操作系统进程模型的看法

  在操作系统中最核心的概念就是进程,这是对正在运行程序的一个抽象。可以说我们想要运行的每个程序都是由进程来决定的。而进程模型在我看来就是为了更好的利用资源,更合理的运行我们的程序,从而对进程进行的优化和管理。在深入操作系统观看那个源码后,我们可以知道,为了让个个进程运行的相对公平些,其对进程会出现的一些情况都要进行相对应的调整,让其在相对范围内的进程能够公平的利用CPU。

 

6、参考资料

http://book.51cto.com/art/201106/270771.htm

https://blog.csdn.net/melong100/article/details/6329201

https://blog.csdn.net/gatieme/article/details/52067748

 

posted @ 2018-05-01 15:16  私语沫言  阅读(483)  评论(0编辑  收藏  举报