在分时系统中,系统将CPU时间划分成无数个时间片(quantum)分配给不同的进程,一个时间片只执行一个进程,并且不停地切换,以让用户感觉到各个进程是在“同时运行”,这中间所需要的策略和算法便是进程调度。 

一个很好的例子是:假设目前系统只运行了两个进程,一个解压缩程序和一个文本编辑器。由于解压缩程序会大量地占用CPU而不是I/O设备(将解压后的文件写入磁盘),所以对解压缩程序而言CPU是其最大的影响因素,我们将这类程序归类为“CPU-Bound(CPU限制型)”。与之相对应的,文本编辑器对CPU的占用很少,其大多数时间时在等待用户输入,我们将其归类为“I/O-Bound(I/O限制型)”。为了让系统响应对用户更友好,Linux的设计者更倾向于调高I/O-Bound类型进程的优先级。如果现在是解压缩程序正在被CPU执行,而文本编辑器正在等待用户输入而处于休眠状态(TASK_INTERRUPTIBLE),当用户在文本编辑器中敲击键盘时,系统将产生一个中断(interrupt),文本编辑器进程将被唤醒,同时内核会意识到文本编辑器比解压缩进程具有更高的优先级,所以它会将解压缩进程的进程描述符(PCB)中的need_resched字段设成1,表示需要调度器(scheduler)重新调度。当中断处理完成以后,调度器会重新调度并选中文本编辑器进程进行任务切换(task switch, context switch),解压缩进程对CPU的拥有权被抢占(preempted,被抢占并不意味着被挂起,而事实上此时解压缩程序仍然处于TASK_RUNNING状态,表示“可执行”),进而执行文本编辑器程序以显示用户敲入的字符。当文本编辑器将此次击键处理完毕后,其将主动放弃CPU而再次进入休眠状态以等待用户的下次键入,调度程序将再次将解压缩进程选中并交给CPU执行。 

先看看PCB中和进程调度相关的几个字段: 

Ÿ struct list_head run_list; 

系统维护着一个可执行队列(runqueue),所有TASK_RUNNING状态的进程都会被记录在其中。在使用调度程序进行调度时,调度程序会在该队列中选取一个合适的进程来与当前进程进行切换。 

Ÿ long nice; 

nice值作用之一用于用户修正进程的优先级。增加nice值会降低进程的优先级,反之则升高优先级。另外一个作用是影响进程的时间片长度,nice值越小,时间片长度会增加,反之减少。(注:.4以下的内核中priority字段被nice取代) 

nice值的范围是 - ~  

Ÿ long counter; 

一个计数器,用于记录进程在当前时间片内还允许被执行的时间(单位tick,滴答)。counter的初始值就是该进程的时间片长度,当值为0时说明其时间片耗尽。只能等待下一个调度周期从新分配。 

Ÿ unsigned long rt_priority; 

进程可分为实时进程和非实时进程(普通进程),它们的优先级策略不一样,实时进程采用“静态优先级”的形式,让进程拥有一个不可变的优先级值,调度器永远不改变它。普通进程采用“动态优先级”的形式,其优先级值是在调度时通过一系列参数计算出来的。 

rt_priority表示实时进程的静态优先级,范围在1~,如果是普通进程则该字段值为0 

Ÿ unsigned long policy; 

这就是常说的调度策略,实时进程和非实时进程(普通进程)分享CPU的方式是不一样的。实时进程采用CHED_FIFO与SCHED_RR实时调度策略,普通进程只有SCHED_OTHER分时调度策略。  

#define SCHED_OTHER             0 

分时调度策略,其采用优先级等形式,让CPU分配尽量公平。 

#define SCHED_FIFO               1 

First In First Out, 进程根据静态优先级进行排队,如果优先级相同的话,则谁先ready就调度谁。被调度的进程将一直占用CPU直到: 被高优先级进程抢占 或者 等待资源为休眠 或者 主动放弃CPU 

#define SCHED_RR                 2 

Round Robin(轮询),其和SCHED_FIFO很类似,但其会给进程分配时间片,时间片到则切换进程 

另外,在策略中有一个SCHED_YIELD位(#define SCHED_YIELD 0x10),该位被设定时则表示进程主动放弃CPU 

if (policy & SCHED_YIELD) 

//give up CPU 

Ÿ volatile long need_resched; 

该字段值用于指示是否请求激活调度程序重新进行调度。调度器的启动方式有“主动式”和“被动式”两种,need_resched字段用于被动式启动,稍候会提到。 

Ÿ struct mm_struct *mm; 

mm_struct数据结构是描述内存存储信息的数据结构,进程控制块task_struct中用mm指针指想mm_struct数据结构.也就是在进程的属性中通过mm指针来管理起对应的内存区. 

调度工作大部分是在schedule()函数中完成的,其会遍历可执行队列中的进程,并调用一个名为goodness()的函数来计算进程的权值(weight),权值大的优先级就高,优先级高的进程会被调度。 

计算进程p的权值: 

static inline int goodness(struct task_struct * p, int this_cpu, struct mm_struct *this_mm) 

{ 

         int weight; 

         /* 

          * weight为-1则表示没有其他进程就绪. 

          */ 

         weight = -; 

/* 

          *如果p的策略SCHED_YIELD位被设置,则表示p主动放弃CPU 

          */ 

         if (p->policy & SCHED_YIELD) 

                 goto out; 

         /* 

          *普通进程. 

          */ 

         if (p->policy == SCHED_OTHER) { 

                 /* 

                  * 首先将该进程剩余的时间片赋值给weight 

                  */ 

                 weight = p->counter; 

                 if (!weight) 

                         goto out; 

#ifdef CONFIG_SMP 

                 /* 

                  * 如果不是跨CPU调度的话,当前CPU中的一些缓存数据还可以使用 

 * 所以给非跨CPU调度的进程一定的“优惠”: 

* 权值增加一个常量值:PROC_CHANGE_PENALTY 

                  */ 

                 if (p->processor == this_cpu) 

                         weight += PROC_CHANGE_PENALTY; 

#endif 

                 /* 

 * 对于不需要切换内存的进程也给一定的优惠 权值+1 

 * 有两种情况不需要切换内存: 

 * 1. p和当前进程共享内存(比如同一进程内的线程:轻量级进程) 

 * 2. p是内核进程,没有mm属性 

*/ 

                 if (p->mm == this_mm || !p->mm) 

                         weight += ; 

/* 

 * 让进程的nice值参与权值运算,由于nice值越小权值应该越大,并且nice 

 * 值的范围是-20~19所以用20-nice 

 */ 

                 weight +=  - p->nice; 

                 goto out; 

         } 

         /* 

          * 实时进程的计算方式十分简单:直接用rt_priority字段。 

* 1000是为了让实时的权值大于任何普通进程 

          */ 

         weight =  + p->rt_priority; 

out: 

         return weight; 

} 

调度器选定新的进程后,需要进行新进程和旧进程的切换工作(Context Switch),分为两个方面:切换内存(switch_mm)和切换cpu寄存器(switch_to) 

关于切换内存:内核空间的进程的mm属性为空没必要切换,这是因为内核进程是轻量级进程(线程),它们是共享内核空间的,对于两个普通的进程则调用switch_mm()函数进行内存切换(至于如何切换,这里略过,我还不懂linux内存管理,以后更新) 

而switch_to方法,是一段汇编代码(看看就好): 

#define switch_to(prev,next,last) do {          \ 

  asm volatile("pushl %%esi\n\t"  \ 

         "pushl %%edi\n\t"    \ 

         "pushl %%ebp\n\t"  \保存esi、edi、ebp寄存器 

         "movl %%esp,%0\n\t"  \esp保存到prev->thread.esp中 

         "movl %3,%%esp\n\t"  \从next->thread.esp恢复esp 

         "movl $1f,%1\n\t"   

   \在prev->thread.eip中保存"1:"的跳转地址, 

   \当prev被再次切换到的时候将从那里开始执行 

         "pushl %4\n\t"     

      \在栈上保存next->thread.eip,__switch_to()返回时将转到那里执行, 

    \即进入next进程的上下文 

         "jmp __switch_to\n"  \跳转到__switch_to(),进一步处理(见下) 

         "1:\t"        \ 

         "popl %%ebp\n\t"    \ 

         "popl %%edi\n\t"    \ 

         "popl %%esi\n\t"    \先恢复上次被切换走时保存的寄存器值, 

       \再从switch_to()中返回。 

         :"=m" (prev->thread.esp),  \% 

                      "=m" (prev->thread.eip),\% 

          "=b" (last) \ebx, 

\因为进程切换后,恢复的栈上的prev信息不是刚被切换走的进程描述符, 

\因此此处使用ebx寄存器传递该值给prev 

         :"m" (next->thread.esp),  \% 

                       "m" (next->thread.eip),  \% 

            "a" (prev), "d" (next),    \eax,edx 

            "b" (prev));        \ebx 

} while () 

启动调度器有两种方式:一是进程为等待核心事件而主动调用schedule()函数将自己挂起以让出CPU,这称为“主动式”;二是不直接调用schedule()函数,而是设置前面提到的need_resched字段,当进程由内核态返回到用户态时(内核2.6中,包括由用户态到内核态时),系统检查need_resched字段,如果其被设置,那么schedule函数将被调用,这称为“被动式”。 

linux 进程学习笔记-进程调度的更多相关文章

  1. linux进程学习笔记

    学习了linux下的进程,觉得应该整理一下,忘得差不多了,顺便回顾一下. 学而时习之,不亦说乎~~ 进程笔记 ,什么是进程? The Single UNIX Specification, Versio ...

  2. Linux 进程学习笔记

    1.什么是程序?什么是进程?它们有什么区别? 定义: 程序:程序(Program)是一个静态的命令集合,程序一般放在磁盘中,然后通过用户的执行来触发.触发后程序会加载到内存中成为一个个体,就是进程. ...

  3. linux 进程学习笔记-进程ID,PID

    PID,进程号 , 范围在2~(??为什么需要这么多),而一个名为idle (或swapper)的进程占据的编号0,init进程占据了编号1. 进程0和进程1 : 系统启动时会从无到有地创建进程0,它 ...

  4. linux 进程学习笔记-进程跟踪

    进程跟踪 long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data); Linux用ptrace来进行进 ...

  5. linux 进程学习笔记-进程信号sigal

    信号(或软中断)是在软件层次上对中断的一个模拟,其运行在“用户空间”,一个进程对另外一个或几个进程通过发送信号来实现异步通信.当接收进程接收到信号后,其可以注册一下处理函数来说对这些信号进行处理(也可 ...

  6. linux 进程学习笔记-运行新进程

    我们知道,当用fork启动一个新进程以后,新进程会复制父进程的大部份内存空间并接着运行父进程中的代码,如果我们使新进程不运行原父进程的代码,转而运行另外一个程序集中的代码,这就相当于启动了一个新程序. ...

  7. linux 进程学习笔记-进程状态

    task_struct的state字段记录的进程的状态,可分为如下几种: #define TASK_RUNNING 0 可运行状态.这是 “进程正在被CPU运行” 和 “进程正在可运行队列中等待被CP ...

  8. linux 进程学习笔记-进程退出/终止进程

    <!--[if !supportLists]-->Ÿ <!--[endif]-->退出/终止进程 void _exit(int status) 与 void exit(int ...

  9. linux 进程学习笔记-等待子进程结束

    <!--[if !supportLists]-->Ÿ <!--[endif]-->等待子进程结束 pid_t waitpid(pid_t pid, int *stat_loc, ...

随机推荐

  1. swiper-demo1

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  2. 转 FreeBSD通过PORTS安装软件的几个常用命令

    1.怎样找到我想安装的包路径:# cd /usr/ports# make search name=mysql2.仅仅下载源码包,而不安装:# cd /usr/ports/directory# make ...

  3. HDU - 3874 Necklace (线段树 + 离线处理)

    欢迎參加--每周六晚的BestCoder(有米! ) Necklace Time Limit: 15000/5000 MS (Java/Others)    Memory Limit: 65536/3 ...

  4. mac环境下清理系统垃圾clearMyMac 3.9 破解版

    mac环境下清理系统垃圾clearMyMac 3 轻轻松松清理好几十G的垃圾文件 下载地址 链接: https://pan.baidu.com/s/1XZbZwzhgQCnzpvQDvyQrRA 密码 ...

  5. mysql日期格式转化

    select DATE_FORMAT( '20170701', '%Y-%m-%d'); 先挖坑

  6. hadoop修改主机名遇到的坑

    正确的修改方式 CentOS修改主机名(hostname) 需要修改两处:一处是/etc/sysconfig/network,另一处是/etc/hosts,只修改任一处会导致系统启动异常.首先切换到r ...

  7. ubuntu系统安装好后一些基本软件的安装

    preface: 由于某些原因重装了系统,原来在ubuntu下安装的一些软件又要又一次安装.稍微麻烦,整理下须要安装的步骤. #==================================== ...

  8. 为CentOS配置网易163的yum源

    Yum (Yellow dog Updater, Modified)是一个基于 RPM 包管理的字符前端软件包管理器.能够从指定的服务器自动下载 RPM 包并且安装,可以处理依赖性关系,并且一次安装所 ...

  9. 800元组装一台3D打印机全教程流程

    我最近正好要组装一台新的reprap的kossel delta型开源3d打印机,这台机器性价比非常高,具有速度快,静音,三臂并联结构,扩展性强,便宜的特点.图纸啥的都有,只是用到mega2560和ra ...

  10. c# 根据枚举Value 获得名称

    // 定义枚举类型enum sotype : int { book=1, pen=2, other=3 } // 输出名称 switch (Enum.GetName(typeof(sotype), 1 ...