进程切换与调度总结
进程的优先级
进程的优先级是操作系统用来决定进程访问CPU资源的顺序的指标。优先级高的进程会优先获得CPU时间,而优先级低的进程则需等待。
理解优先级与权限:
权限: 权限指的是用户或进程是否有权执行某项操作。用学校场景理解的话,学校里有教工食堂和普通食堂。教师可以去教工食堂和普通食堂吃饭,而学生只能去普通食堂吃饭。这体现了权限的不同。
优先级: 优先级决定了进程访问CPU资源的顺序。就好比地铁排队进站,特殊身份的人优先级高,他们可以提前进站,而普通人则需要正常排队。这体现了优先级的不同。
优先级和权限的区别: 优先级和权限的区别在于,优先级决定了进程访问CPU资源的顺序(即做的快和慢),而权限决定了进程是否有权执行某项操作(即是否可以做)。
为什么?
因为目标资源稀缺性,导致需要通过优先级确认谁先谁后的问题。
怎么做?
在Linux系统中,进程优先级也是一个值,一种数字(int)。值越低,代表优先级越高,反之。而Linux系统是分时操作系统,为了考虑优先级公平性,优先级可能变化,但变化不大。
在Linux中使用命令 ps -al |head -1 &&ps -al |grep 可执行文件 就可以查看当前进程的一些信息。
UID:
User id是每个用户的唯一标识,所以一个文件是怎么区分当前用户是属于拥有者还是所属组还是其他人。
PRI:
PRI是进程的优先级,其默认值为80。
NI:
是进程优先级的修正值,又称为nice值。我们修改进程时,并不是直接修改进程的PRI值,而是修改进程的NI值,通过修改NI值再加上进程的默认值最后得出的才是进程的真正优先级值。
优先级的极值:
我们可以手动修改一个进程的优先级,使用命令 renice -n 值 -p PID。
从上图可以看到,当我们尝试将优先级修改为-100和100时,查看当前进程的优先级会发现,Linux的进程优先级范围是[60, 99]。为什么不是直接修改为-100和100呢?其实这是因为Linux的优先级范围是有限的:
优先级范围:
普通进程的优先级通过nice值调整,范围是-20到19,数值越小优先级越高。
实时进程的优先级范围是1到99,数值越大优先级越高(通常映射到60到99)。
如果设置的优先级超出有效范围,系统会自动将其调整为范围内的值。例如,设置为-100会被调整为-20,设置为100会被调整为99。
进程饥饿问题:
如果优先级设置不合理(如将某个进程的优先级设置得过高),可能会导致优先级低的进程长时间得不到CPU资源,进而引发进程饥饿问题。
为了避免这种情况,操作系统通常会采用调度策略(如时间片轮转或多级反馈队列)来确保所有进程都能公平地获得CPU资源。
进程的补充特性:
竞争性: 系统进程数⽬众多,⽽CPU资源只有少量,甚⾄1个,所以进程之间是具有竞争属性的。为了⾼效完成任务,更合理竞争相关资源,便具有了优先级
独⽴性: 多进程运⾏,需要独享各种资源,多进程运⾏期间互不⼲扰
并⾏: 多个进程在多个CPU下分别,同时进⾏运⾏,这称之为并⾏
并发: 多个进程在⼀个CPU下采⽤进程切换的⽅式,在⼀段时间之内,让多个进程都得以推进,称之为并发
理解进程切换:
一个进程在运行时,CPU并不需要一直将其执行完毕才切换到其他进程。现代操作系统通过时间片轮转调度机制来管理多个进程的执行。每个进程会被分配一个固定的时间片(即一小段CPU时间),当时间片用完后,操作系统会强制切换到下一个进程,无论当前进程是否执行完毕。
为了理解这一点,假设我们运行了一个包含死循环的代码。虽然这个死循环会持续占用CPU,但我们发现系统并没有完全挂掉,后台运行的QQ、微信等程序仍然可以正常工作。这是因为操作系统通过时间片机制,将CPU资源公平地分配给所有进程。即使某个进程(如死循环进程)试图无限占用CPU,操作系统也会在时间片结束后将其挂起,切换到其他进程。
因此,死循环进程并不会一直独占CPU,操作系统通过时间片调度确保了多任务的并发执行。
在CPU里有各种寄存器,寄存器的本质就是用来存储临时数据的。如上图,当一个进程载入CPU运行时,就会把进程所对应的代码和数据载入CPU的临时数据里,当时间片到了之后,CPU里的寄存器中存储的数据再返回给task_struct,等下一次进来的时候,CPU就会从该task_struct中获取上一次记录的数据。
Linux真实调度算法:
上图是一个CPU的一个调度算法队列,我们称之为runqueue,runqueue里面存储着各种数据。那我们的进程队列其实是存在queue[140]数组里。
queue[140]:
在queue里前100个数组里的进程优先级属于实时优先级,是不考虑优先级。这种操作系统属于实时操作系统。对于实时操作系统的理解,就可以简单理解为,当一个进程来了时候就必须运行完成后才能运行下一个进程的任务。那么可能这里就有疑惑,为什么Linux既有实时操作系统又有分时操作系统?其实也很简单一个操作系统既是分时操作系统又有实时操作系统,那么这种操作系统的运用领域特就更加广泛,也就更多人使用。
但我们说,我们操作系统的权限是60-99而分时操作系统所占数组的位置是100-140。所以queue是一个Hash表,通过简单的哈希函数(x-60)+(140-100)就可以插入到该进程权限所映射的位置。
现在假设我们插入了一个权限为99的进程AA,而操作系统想要运行这个进程,是否是需要遍历依次整个数组,如果这样遍历的话,效率是否会低下?所以在runque里有一个bitmap[5]的位图。
bitmap[5]:
bitmap[5]里存储了5个int,一个int有32位,所以5*32=160,就能将queue的140个数组全部映射起来,也就只需要遍历bitmap[5]就能知道当前那些权限里存在进程。
现在假设在queue中i权限为61的BB进程运行完成,那么此时应该把BB进程重新链入61进程的尾部吗?但真这么做的话不就造成了进程饥饿,那么低权限的进程不就始终得不到资源。
所以在runqueue中有两个queue,,并用数组struct rqueue_elem prio _array[2]管理起来,其中arr[1]我们称之为运行队列,第二个称之为过期队列。*active指向arr[0],*expired指向arr[1]。
当进程从活跃队列完成后就会断开活跃,链入过期队列中,当所有活跃队列的进程运行完毕后,此时只需要交完active和expried,就完成了链表的交换,又能重新运行活跃队列里的进程。
那么这里有个问题,当有新进程链入时候,应该插入在运行队列还是过期队列里呢?
新进程的动态优先级若高于或等于当前运行队列的最低优先级,则插入运行队列;否则,插入过期队列。
重谈为什么修改进程优先级为什么是修正NI值而不是直接修改进程的优先级值:
通过上文理解后,修改进程优先级是通过调整Nice值而不是直接修改Priority值,原因包括:
配合调度算法的动态性:Nice值是调度器的一个输入参数,不会干扰调度器的动态调整机制。
权限控制:防止普通用户滥用高优先级,确保系统资源的公平分配。
用户友好性:Nice值的设计简单直观,用户可以方便地调整进程优先级。
系统稳定性:避免直接修改Priority值可能导致的系统不稳定问题。
----------------------那么本篇文章就到这里,感谢各位观看