进程—进程调度(1)

进程—进程调度(1)


上下文切换

进程可以调度,但必须保证每个进程都可以顺序的执行,而一个进程执行所需的全部信息可由进程的PCB(task_struct)维护,所以在进程发生切换的时候可以将当前进程的运行状态信息(快照)保存到它的PCB中(这样就能在下一次调度程序选择到它时接着上一状态继续执行),将马上要执行的进程的运行状态信息(在PCB中)恢复,这样就可以合理的完成调度,这个过程就叫上下文切换。

中断

上下文切换是在内核中完成的,对用户透明,所以在上下文切换的时候必须先陷入内核(一般是通过时钟中断和系统调用)。上下文的切换需要硬件的支持。当前进程正在运行,当中断发生时,中断硬件将程序计数器、程序状态字、有时还有一个或多个寄存器(进程的运行状态信息)压入当前进程的内核堆栈中,PC随即跳转到中断服务程序入口(根据硬件向量法或软件查询法得到中断服务程序入口地址)去执行中断服务程序。注意,这些工作都是硬件完成的,在这些工作完成的同时完成了一次堆栈切换(从进程的用户堆栈切换到进程的内核堆栈,由中断硬件完成)。然后,PC的控制权转移到了软件(中断服务程序),一般地,中断服务程序有一个自己的中断堆栈(就像进程有自己的内核堆栈一样),为了不破坏内核堆栈,在执行中断服务程序的过程中又会发生一次堆栈切换(从内核堆栈切换到中断堆栈,这次的切换是软件完成的),进入到中断上下文之中,随后中断服务程序会调用处理特定中断请求的中断处理例程,让中断处理例程运行在中断上下文之中。中断处理例程完成中断处理之后返回到中断服务程序,返回的过程又会涉及到一次堆栈切换(由中断堆栈切换到内核堆栈,这次的切换也是软件完成的),最后,中断服务程序执行中断返回指令,由中断硬件将内核堆栈中的状态信息恢复到相应的寄存器中,在恢复寄存器的同时清空内核堆栈中保存的状态信息,系统从内核态返回到用户态,这里又发生了一次堆栈切换(从内核堆栈切换到用户堆栈,由中断硬件完成)。

发生上下文切换的中断过程

中断服务程序调用中断处理例程,中断处理例程在执行的过程中调用了调度程序进行调度,这时,调度程序会检查是否需要发生上下文切换(不是每次中断都会发生上下文的切换,例如某个时钟中断就可能不会导致上下文切换),发现需要发生上下文切换,接下来,首先要完成的就是保存当前进程的运行状态到它的PCB中(这个工作是调度程序做的,由软件过程完成)。

保存当前进程的状态的工作会由一段短小的汇编语言例程来完成,它将中断硬件保存到内核堆栈中的状态数据全部保存(pop)到进程的PCB中,与此同时,pop操作会将内核堆栈中进程的状态信息全部清空,为加载下一个被调度器选择的进程的状态信息做好准备。

在当前进程的运行状态被保存之后,进程内核堆栈中被中断的进程的状态信息已被清空,为装载下一个进程的运行状态信息做好了准备。接下来,调度程序会选择一个进程(位于当前最高优先级队列中的一个进程),将它上一次保存的运行状态信息(在PCB中)压入到内核堆栈中,更新内核堆栈中的thread_info结构体,然后返回到中断服务程序。随后,中断服务程序执行从中断返回指令(return-from-trap),剩下的恢复工作就交由中断硬件完成,之后,系统从内核态切换到用户态,整个上下文切换过程完成。

Linux进程类别

1.实时进程:高优先级,响应快,优先级范围[0,99]。

2.普通进程:分为交互式进程(I/O消耗型)和批处理进程(CPU消耗型),优先级低于实时进程,范围为[100,139]。

在Linux中,调度算法可以明确地确认所有实时进程的身份,但却不能区分交互式进程和批处理进程。Linux2.6调度程序实现了基于进程过去行为的启发式算法,以确定进程此刻应该被当作交互式进程还是批处理进程。与批处理进程相比,交互式进程的调度优先级要更高一些。

Linux中与调度相关的系统调用

系统调用 说明
nice() 改变一个普通进程的静态优先级
getpriority() 获得一组普通进程的最大静态优先级
setpriority() 设置一组普通进程的静态优先级
sched_getscheduler() 获得一个进程的调度策略
sched_setscheduler() 设定一个进程的调度策略和实时优先级
sched_getparam() 获得一个进程的实时优先级
sched_setparam() 设置一个进程的实时优先级
sched_yield() 自愿放弃处理器而不阻塞
sched_get_priority_min() 获得一种策略的最小实时优先级
sched_rr_get_interval() 获得时间片轮转策略的时间片值
sched_setaffinity() 设置进程的CPU亲和力掩码
sched_getaffinity() 获得进程的CPU亲和力掩码

几个优先级字段

nice

static_prio

nice和static_prio是普通进程会用到的两个优先级字段,用轮转策略的实时进程也会用到它们,只是用来重新计算轮转时间片长度。

rt_priority

rt_priority是实时进程用到的优先级字段。

prio

这才是决定进程调度优先级的字段,也就是说,进程在哪个就绪队列中由这个字段决定。

Linux进程调度策略

用户可以调用系统调用sched_setscheduler()设置调度策略。

unsigned int policy;

Linux的进程调度是抢占式的,允许高优先级进程抢占低优先级进程,这是下面几个调度策略对应的算法都必须确保的基本机制。另外,调度只会发生在位于就绪队列中的进程之间(ranqueue)。

实时进程的调度优先级只由实时优先级(rt_priority,范围为[0,MAX_RT_PRIO-1])静态的决定,从一开始由用户指定后,就不再动态地改变,不受nice值的影响(nice值只影响调度优先级在[100,139]范围内的普通进程的调度优先级)。实时优先级的调度优先级(prio)通过其实时优先级(rt_priority)计算(prio = MAX_RT_PRIO-1 - rt_priority)得到,范围在0~MAX_RT_PRIO-1间。默认MAX_RT_PRIO为100,所以默认的实时进程的调度优先级(prio)范围是[0,99],实时进程的调度优先级虽然不会自己动态的改变,但是可以由用户使用系统调用sched_setparam()和sched_setscheduler()来重新设置它的实时优先级,进而改变调度优先级。实时进程位于高优先级队列(明确地说,优先权在[0~MAX_RT_PRIO-1]区间内的优先级队列)中,而且总是位于活动运行队列中,所以只要有实时进程存在,普通进程永远也别想运行。

普通进程的调度优先级根据静态优先级static_prio和平均睡眠时间sleep_avg动态的来计算,static_prio是根据nice值得到的,两者可以相互转换。详细的说明请见task_struct代码注释。默认的普通进程的调度优先级(prio)范围是[100,139]。

1.SCHED_FIFO

值为1-实时进程-用基于优先权的先进先出算法。基于SCHED_FIFO调度机制的实时进程在运行时会一直占用CPU,除非就绪队列中有优先级更高的实时进程,或自愿调用阻塞原语(如sleep_on_interruptible()),或停止,或被杀死,或通过调用sched_yield()自动放弃CPU。

2.SCHED_RR

值为2-实时进程-用基于优先权的轮转法。一旦当前进程自愿调用阻塞原语(如sleep_on_interruptible()),或停止,或被杀死,或通过调用sched_yield()自动放弃CPU或一个时间片消耗完毕,或就绪队列中有优先级更高的实时进程,则会在中断返回时发生调度。基于SCHED_RR调度机制的调度程序将该进程置于同优先级队列的末尾,然后运行该优先级队列中的下一个实时进程。这是分时系统实现良好交互性的基础算法,又名时间片轮转调度算法。

3.SCHED_NORMAL

值为0-非实时进程-用基于优先权的多级反馈队列轮转法,根据进程过去的执行情况动态的调整调度优先级,一般还会将执行完轮转时间片的进程放入过期可运行队列中。兼顾了作业的周转时间和交互性,并且防止了饿死。

普通进程的调度策略:SCHED_NORMAL

普通进程的调度优先级(prio)由2个因素决定,一个是进程的静态优先级(static_prio,又叫基本优先级,默认值是120),另一个是进程的平均睡眠时间(sleep_avg)。static_prio的范围为[100,139],新进程总是继承其父进程的静态优先级。

static_prio

static_prio通过nice(范围为[-20,19])计算得到(static_prio = 120 + nice),所以用户可以通过系统调用nice()和setpriority()来设置nice值进而改变自己拥有的进程的静态优先级(也只能通过这两个系统调用改变静态优先级,否则静态优先级不会发生变化,尽管动态优先级会发生变化)。

普通进程每次执行的时候都有一个有限的轮转时间片,这是进程在被抢占前能够连续占用CPU的时间片长度,如果没有高优先级进程抢断,当前进程会可以一直占用CPU直到用完它的轮转时间片。静态优先级用来计算普通进程的轮转时间片,基本规则是静态优先级越高,static_prio越小,轮转时间片越长。所以对于普通进程,静态优先级越高的进程获得的连续执行CPU的时间片越长,也可以说nice值唯一决定了普通进程能获得的轮转时间片长度。具体的计算规则可以参考../kernel/sched.c 中的task_timeslice(),这个函数计算并返回一个进程轮转时间片长度,或者参考《深入理解Linux内核》第七章中普通进程的调度章节下的基本时间片一节。

prio

task_struct中的这个字段是进程的调度优先级,用来决定进程在哪个优先级队列中,进而实现O(1)调度。对于普通进程,这个字段被称为它的动态优先级,通过静态优先级和平均睡眠时间计算得到,范围是[100,139]。

普通进程的调度策略(SCHED_NORMAL)的主要思想是通过进程过去的执行情况来衡量这个进程是属于I/O消耗型还是CPU消耗型,具体的衡量指标是一个进程的平均睡眠时间sleep_avg。如果一个进程的sleep_avg大,则该进程更加趋向于I/O消耗型,优先级就越高,prio就越小。

动态优先级: prio = max (100, min (static_prio - bonus,139) )

bonus = 10*sleep_avg/HZ - 5,范围在[-5,5]之间。(sleep_avg 范围为[0,HZ]),所以sleep_avg = HZ/2时,进程调度优先级保持不变;当HZ > sleep_avg > HZ/2时,进程调度优先级会提高;当0 < sleep_avg < HZ/2时,进程调度优先级会降低。

对于交互性强的进程,它不会一次性的就将轮转时间片给用光(这一般是CPU消耗型进程干的事),而是用一小会儿之后,进行一次I/O操作,放弃掉CPU,这使得它可以长时间的位于活期可执行队列中,不至于用完时间片了就被重新计算时间片,然后放到过期可执行队列中,久久得不到执行机会。而且经常性的执行I/O操作正是交互型进程的一大特点,这也是交互型进程之所以为交换型进程的根本原因,系统给予这种进程以较高的bonus,使其调度优先级更高,有更多的被调度的机会。

实时进程的调度策略

参考task_struct代码注释和Linux进程调度策略的前言部分。

进程调度时机

调度时机来临时,内核或驱动将调用schedule(),在Linux中调度的时机主要有:

一、current的状态从running转换为其它状态时,如:

1)进程终止。exit()在最后调用schedule()。

2)进程因某种原因进入等待状态(随后会从就绪队列中删除,被插入到等待队列中)。

比较常见的就是进程调用nanosleep()或者wait系列的系统调用。此外,在设备驱动程序中,最常见的原因就是驱动程序引发一次I/O操作后,为等待I/O操作的结束而进入等待状态。多数情况下,驱动程序会直接调用schedule()。

二、当前进程的时间片用完时。

时间片是否用完,由时钟中断处理程序进行判断,若用完,就将current进程的need_resched位置1。在中断将要返回用户态时,如果current的need_resched位为1,则调用schedule()。

三、进程从中断、异常、系统调用状态(即内核态)返回时。

每次从内核返回到用户态时都会检查need_resched标记,若在中断、异常、系统调用中,current的need_resched被置1,就会导致进程调度,时钟中断属于此类。

计算调度优先级:effective_prio(p)

计算进程p的调度优先级,在更新进程调度优先级时会被recalc_task_prio()调用。

//../kernel/sched.c

/*
* return the priority that is based on the static
* priority but is modified by bonuses/penalties.
   *
* We scale the actual sleep average [0 .... MAX_SLEEP_AVG]
* into the -5 ... 0 ... +5 bonus/penalty range.
   *
   */
  static int effective_prio(task_t *p)
  {
  int bonus, prio;

  if (rt_task(p))//如果是实时进程
    return p->prio; //返回实时进程的调度优先级

  //如果是普通进程

  bonus = CURRENT_BONUS(p) - MAX_BONUS / 2; // 10*(sleep_avg/HZ) - 5,值在[-5,5]之间。

  prio = p->static_prio - bonus;
  if (prio < MAX_RT_PRIO)
    prio = MAX_RT_PRIO;
  if (prio > MAX_PRIO-1)
    prio = MAX_PRIO-1;
  return prio; //普通进程的调度优先级
   //prio = max (100, min (static_prio - bonus, 139))
  }

//下面的代码段是一些宏定义,用来参考。
//../include/linux/sched.h

#define MAX_USER_RT_PRIO    100
#define MAX_RT_PRIO     MAX_USER_RT_PRIO  //100
#define MAX_PRIO        (MAX_RT_PRIO + 40) //140

#define rt_task(p)      (unlikely((p)->prio < MAX_RT_PRIO)) //prio<100?

//../kernel/sched.c

#define USER_PRIO(p)        ((p)-MAX_RT_PRIO)  //p-100

#define MAX_USER_PRIO       (USER_PRIO(MAX_PRIO)) //40
#define PRIO_BONUS_RATIO     25

#define MAX_BONUS       (MAX_USER_PRIO * PRIO_BONUS_RATIO / 100) //10

#define DEF_TIMESLICE       (100 * HZ / 1000)

#define MAX_SLEEP_AVG       (DEF_TIMESLICE * MAX_BONUS)  //HZ

#define NS_TO_JIFFIES(TIME) ((TIME) / (1000000000 / HZ))
#define MAX_SLEEP_AVG       (DEF_TIMESLICE * MAX_BONUS)   //HZ 

#define CURRENT_BONUS(p) \
    (NS_TO_JIFFIES((p)->sleep_avg) * MAX_BONUS /         MAX_SLEEP_AVG)              // 10*sleep_avg/HZ  值在[0,10]之间

参考

task_struct

//---------------------------------------  Linux 2.6 进程调度相关信息  -----------------------------------------
  long                              nice; //进程的初始优先级,范围[-20,+19],默认0,nice值越大优先级越低,分配的
  //时间片可能更少。能通过系统调用nice()可以修改nice值。
  int                               static_prio;//静态优先级。范围为[MAX_RT_PRIO, MAX_RT_PRIO+39],默认情况
  //[100,139]。Normal进程使用静态优先级static_prio和平均睡眠时间sleep_avg动态的计算进程的调度优先级prio。
   /*
    static_prio= MAX_RT_PRIO + nice + 20

    在../kernel/sched.c中有两个宏实现nice值和static_prio值之间的转换
    #defineNICE_TO_PRIO(nice)  (MAX_RT_PRIO + (nice)+ 20)
    #definePRIO_TO_NICE(prio)  ((prio) - MAX_RT_PRIO- 20)
  */

  unsigned int                      rt_priority; //实时优先级,[0,MAX_RT_PRIO-1],默认情况下范围[0,99],在
  //setscheduler()中设置,且一经设定就不再改变。Real_time进程使用实时优先级rt_priority静态的计算进程的调度优先级prio。
   /*
    0 -> normal
    1-99 -> realtime
   */

  int                               prio; //存放"调度程序"要用到的优先级,对应优先级位图中的相应优先级位。数值越
  //大,表示进程优先级越小。
  /*
    0-99 -> Realtime process
    100-140 -> Normal process

   for Realtime process: prio = MAX_RT_PRIO-1 – rt_priority
   for Normal process: prio = max (100, min (static_prio - bonus, 139))
   其中bonus在[-5,5]之间。bonus越大,prio越小,优先级越高。
  */ 

  unsigned long                     sleep_avg;//这个字段的值用来支持调度程序对进程的类型(I/O消耗型 or CPU消耗型)
  //进行判断,值越大表示睡眠的时候越多,更趋向于I/O消耗型,系统调度时,会给该进程更多奖励以便该进程有更多的机会能够执行,反
  //之,更趋向于CPU消耗型,会给该进程更多惩罚。sleep_avg 的范围是 0~MAX_SLEEP_AVG。

  unsigned long long                timestamp;//进程最近插入运行队列的时间,或涉及本进程的最近一次进程切换发生的时
 //间。
  unsigned long long                last_ran;//最近一次替换本进程的进程切换发生的时间。

  cputime_t                         utime;//该进程在用户态的cpu的使用时间。
  cputime_t                         stime;//该进程在内核态的cpu的使用时间。
  unsigned long                     sleep_time; //进程的睡眠时间

  unsigned int                      time_slice;//进程剩余时间片,当一个普通进程(或者基于时间片轮转策略的实时进程)
  //的时间片用完之后,要根据任务的静态优先级static_prio重新计算时间片。task_timeslice()为给定的任务返回一个新的时间
  //片。对于交互性强的进程,时间片用完之后,它会被再放到活动数组而不是过期数组,该逻辑在scheduler_tick()中实现。

  unsigned int                      first_time_slice;//如果进程肯定不会用完其时间片,就把该标志设置为1

  const struct sched_class          *sched_class;  //与调度相关的函数
  struct sched_entity               se;  //调度实体
  struct sched_rt_entity            rt;  //实时任务调度实体
#ifdef CONFIG_PREEMPT_NOTIFIERS
  /*list of struct preempt_notifier:*/
  struct hlist_head                 preempt_notifiers;  //与抢占有关
#endif
#if defined(CONFIG_SCHEDSTATS)||define(CONFIG_TASK_DELAY_ACCT)
  unsigned int                      policy;  //表示该进程的进程调度策略。调度策略有:
//SCHED_NORMAL 0, 非实时进程, 用基于优先权的轮转法。
//SCHED_FIFO 1, 实时进程, 用先进先出算法。
//SCHED_RR 2, 实时进程, 用基于优先权的轮转法

  struct sched_info                 sched_info;  //调度相关的信息,如进程在cpu上运行的时间/在队列中等待的时间...
#endif

  struct list_head                  tasks;  //任务队列,通过这个寄宿于PCB(task_struct)中的字段构成的双向循环链表
  //将宿主PCB链接起来。

  volatile long                     need_resched; //调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到
  //用户态或从中断返回时,会发生调度。当进程的时间片耗尽时,scheduler_tick()会设置这个标识,当一个优先级高的进程进入可执
  //行状态的时候,try_to_wake_up()会设置这个标识。

  struct list_head                  run_list; //该进程所在的运行队列。这个队列有一个与之对应的优先级k,所有位于这个
  //队列中的进程的优先级都是k,这些k优先级进程之间使用轮转法进行调度。k的取值是0~139。这个位于宿主PCB中的struct
  //list_head类型的run_list字段将构成一个优先级为k的双向循环链表,像一条细细的绳子一样,将所有优先级为k的处于可运行状态
  //的进程的PCB(task_struct)链接起来。

  prio_array_t                      *array; //指向当前进程所在CPU的就绪进程链表。

  cpumask_t                         cpus_allowed//能执行进程的CPU的位掩码

  struct thread_info                *thread_info;
/*
thread_info中与调度相关字段:
__u32                       flags;//存放TIF_NEED_RESCHED标志,如果必须调用调度程序,则设置该标志
__u32                       cpu;//运行进程所在运行队列的CPU逻辑号 

*/
时间: 2024-11-10 14:16:43

进程—进程调度(1)的相关文章

操作系统——进程调度之短进程优先

 1.什么是进程调度 无论是在批处理系统还是分时系统中,用户进程数一般都多于处理机数.这将导致它们互相争夺处理机.另外,系统进程也同样需要使用处理机.这就要求进程调度程序按一定的策略,动态地把处理机分配给处于就绪队列中的某一个进程,以使之执行. 2.处理机调度分类 高级.中级和低级调度作业从提交开始直到完成,往往要经历下述三级调度: 高级调度:(High-Level Scheduling)又称为作业调度,它决定把后备进程调入内存运行: 低级调度:(Low-Level Scheduling)又称为

Linux内核工程导论——进程

进程 进程调度 概要 linux是个多进程的环境,不但用户空间可以有多个进程,而且内核内部也可以有内核进程.linux内核中线程与进程没有区别,因此叫线程和进程都是一样的.调度器调度的是CPU资源,按照特定的规则分配给特定的进程.然后占有CPU资源的资源去申请或使用硬件或资源.因此这里面涉及到的几个问题: 对于调度器来说: l  调度程序在运行时,如何确定哪一个程序将被调度来使用CPU资源? n  如何不让任何一个进程饥饿? n  如何更快的定位和响应交互式进程? l  单个CPU只有一个流水线

【操作系统】进程调度及其算法

进程调度的任务 保存处理机信息 按某种算法选取进程 把处理器分配给进程 进程调度机制 排队器.把就绪进程排成一个或者几个队列 分派器.把进程从就绪队列中取出来,然后把处理机给他 上下文切换器.保存上一个进程的信息,分配下一个进程的信息 进程调度的方式 非抢占式 抢占式 进程调度算法 轮转调度算法 把就绪进程排成一个队列,把CPU分配给队首进程,执行一定的时间,运行完毕就分配给另一个新的队首进程,每隔一定的时间就执行一个进程 优先级调度算法 非抢占式优先级调度算法 一旦把处理机分配给一个就绪队列中

进程调度总结

进程调度 基本属性 1.多态性 从诞生.运行,直至消灭. 2.多个不同的进程可以包括相同的程序 3.三种基本状态 它们之间可进行转换 4.并发性并发执行的进程轮流占用处理器 基本状态 1.等待态:等待某个事件的完成: 2.就绪态:等待系统分配处理器以便运行: 3.运行态:占有处理器正在运行. 运行态→等待态 往往是由于等待外设,等待主存等资源分配或等待人工干预而引起的. 等待态→就绪态 则是等待的条件已满足,只需分配到处理器后就能运行. 运行态→就绪态 不是由于自身原因,而是由外界原因使运行状态

进程管理,及性能监控 ps, pstree, pidof, top, htop, pmap, vmstat, dstat

进程的分类:    CPU-Bound: CPU密集型    I/O-Bound: I/O密集型 进程的分类:    批处理进程    交互式进程    实时进程 进程调度器:    进程优先级:0-139        实时优先级:1-99,数字越大,优先级越高        静态优先级:100-139, nice值调整,数字越大,优先级越低            nice:     -20, 19            priority: 100, 139        动态优先级:由内核维

python之路——进程

阅读目录 理论知识 操作系统背景知识 什么是进程 进程调度 进程的并发与并行 同步\异步\阻塞\非阻塞 进程的创建与结束 在python程序中的进程操作 multiprocess模块 进程的创建和multiprocess.Process 进程同步控制 -- 锁\信号量\事件 (multiprocess.Lock.multiprocess.Semaphore.multiprocess.Event) 进程间通信 -- 队列和管道(multiprocess.Queue.multiprocess.Pip

计算机发展史与进程

目录 1.操作系统发展史 1).穿孔卡片 2).联机批处理系统 3).脱机批处理系统 4).多道技术 2.进程 程序与进程 进程调度: 程序运行时的三个状态: 同步和异步: 阻塞与非阻塞: 创建进程的两种方式 方式一:定义一个任务 方式二:自定义一个类,并继承Process 1.操作系统发展史 1).穿孔卡片 ? 一个机房一次只能使用一个卡片 ? CPU使用率极低 2).联机批处理系统 ? 支持多用户使用一个计算机机房. 3).脱机批处理系统 ? 高速磁带提升了读取的速度,提高了CPU的利用率.

supervisor简单配置

本节内容: supervisor简单介绍 配置内容 启动过程 错误处理 参考文章 一.supervisor简单介绍 折腾了好几天,supervisor终于能行了,折腾死了,所以写这篇博客,把遇到的坑记下来,省的下次再弄不上 环境:Ubuntu 16.04 LTS server python 2.7 apt-get install supervisor就能安装上了 以命令行的方式创建默认配置文件  echo_supervisord_conf > /etc/supervisor/supervisor

并发编程小结

目录 多道技术 并发与并行 进程 程序与进程 进程调度 进程的状态 同步异步阻塞非阻塞 创建进程的两种方式 回收进程资源的两种方式 僵尸进程.孤儿进程.守护进程 进程互斥锁 进程间通信 队列 堆栈 生产者与消费者模型 线程 进程与线程的优缺点 线程间数据是共享的 GIL全局解释器锁 死锁与递归锁 死锁 递归锁 信号量 Event事件 线程队列 进程池与线程池 协程 gevent IO模型 多道技术 单道:一台哦到 多道: 时间上复用, 遇到IO操作就会切换,程序占用CPU时间过长就会切换 空间上