Linux进程的调度

上回书说到Linux进程的由来Linux进程的创建,其实在同一时刻只能支持有限个进程或线程同时运行(这取决于CPU核数量,基本上一个进程对应一个CPU),在一个运行的操作系统上可能运行着很多进程,如果运行的进程占据CPU的时间很长,就有可能导致其他进程饿死。为了解决这种问题,操作系统引入了进程调度器来进行进程的切换,轮流让各个进程使用CPU资源。

进程调度的框架

1. Linux调度架构的核心概念

1)rq:进程的运行队列(runqueue),每个CPU对应一个,包含自旋锁(spinlock)、进程数量、用于公平调度的CFS信息结构、当前运行的进程描述符等。实际的进程队列用红黑树来维护(通过CFS信息结构来访问)。

2)cfs_rq:cfs调度的进程运行队列信息,包含红黑树的根结点、正在运行的进程指针、用于负载均衡的叶子队列等。

3)sched_entity:把需要调度的东西抽象成调度实体,调度实体可以是进程、进程组、用户等。这里包含负载权重值、对应红黑树结点、虚拟运行时vruntime等。

4)sched_class:把调度策略(算法)抽象成调度类,包含一组通用的调度操作接口。接口和实现是分离,可以根据调度接口去实现不同的调度算法,使一个Linux调度程序可以有多个不同的调度策略。

2. Linux进程通过schedule函数来切换的,主要分为以下几个阶段:

1)关闭内核抢占,初始化部分变量。获取当前CPU的ID号,并赋值给局部变量CPU,使rq指向CPU对应的运行队列标识当前CPU发生任务切换,通知RCU更新状态,如果当前CPU处于rcu_read_lock状态,当前进程将会放入rnp-> blkd_tasks阻塞队列,并呈现在rnp-> gp_tasks链表中。关闭本地中断,获取所要保护的运行队列的自旋锁,为查找可运行进程做准备

2)检查prev的状态,更新运行队列。如果不是可运行状态,而且在内核态没被抢占,应该从运行队列中删除prev进程。如果是非阻塞挂起信号,而且状态为TASK_INTER-RUPTIBLE,就把该进程的状态设置为TASK_RUNNING,并将它插入到运行队列

3)task_on_rq_queued(prev) 将pre进程插入到运行队列的队尾。

4)pick_next_task选取将要执行的next进程。

5)context_switch(rq, prev, next)进行进程上下文切换

3. 进程切换的场景有以下3种:

1) 该进程分配的CPU时间片用完。

2) 该进程主动放弃CPU(例如IO操作)。

3) 某一进程抢占CPU获得执行机会。

Linux并没有使用x86 CPU自带的任务切换机制,需要通过手工的方式实现了切换。

4. 调度中的CPU亲和度

进程创建后在内核的数据结构为task_struct , 该结构中有掩码属性cpus_allowed,4个核的CPU可以有4位掩码,如果CPU开启超线程,有一个8位掩码,进程可以运行在掩码位设置为1的CPU上。

Linux内核API提供了两个系统调用,让用户可以修改和查看当前的掩码:

1) sched_setaffinity():用来修改位掩码。

2) sched_getaffinity():用来查看当前的位掩码。

在下次task被唤醒时,select_task_rq_fair根据cpu_allowed里的掩码来确定将其置于哪个CPU的运行队列,一个进程在某一时刻只能存在于一个CPU的运行队列里。

在Nginx中,使用了CPU亲和度来完成某些场景的工作:

worker_processes      4;

worker_cpu_affinity 0001001001001000;

上面这个配置说明了4个工作进程中的每一个和一个CPU核挂钩。如果这个内容写入Nginx的配置文件中,然后Nginx启动或者重新加载配置的时候,若worker_process是4,就会启用4个worker,然后把worker_cpu_affinity后面的4个值当作4个cpu affinity mask,分别调用ngx_setaffinity,然后就把4个worker进程分别绑定到CPU0~3上。

worker_processes      2;

worker_cpu_affinity 01011010;

上面这个配置则说明了两个工作进程中的每一个和2个核挂钩。

参考资料:陈科的《Linux内核分析及应用》1.3 Linux进程的调度
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容