进程管理

进程概述

thread_info:保存了特定体系结构的汇编代码段需要访问的那部分进程的数据,放在内核栈顶,其中嵌入指向task_struct的指针,arm64架构通过current宏直接得到task_struct得到thread_info的地址,可以看出是直接存在sp_el0寄存器中的

 

再看下include/asm-generic/current.h内核通用版本的实现

 

 

 

task_struct:通用的方式描述进程,内核使用slab管理task_stuct的分配和释放,包含了mm_struct这个内存描述结构

thread_union:将内核栈(向下增长)和thread_info(所以在栈顶)放在这个联合体

 

再看下系统调用进入内核之前会把系统调用号及参数啊放进寄存器,进入内核后通过save_all放入内核栈;由于是下图是引用别人的,stack指针讲道理指向栈顶指针可能更好理解

 

进程定义:一个处于执行期的程序及其相关资源(打开的文件,地址空间,挂起的信号,进程状态等)的总称

进程状态:TASK_RUNNING,TASK_INTERRUPTABLE,TASK_UNINTERRUPTABLE,__TASK_TRACED,__TASK_STOPPED,TASK_ZOMBIE,TASK_DEAD;前五个状态可以通过set_task_state设置

 

进程创建:fork没有复制所有信息,而是在被使用时才复制(写时拷贝),fork() -> clone() -> do_fork() -> copy_process(创建新的task_struct) -> dup_task_struct(分配一个内核栈,填充thread_info,task_struct)

线程创建:和创建进程类似,只不过在调用 clone 的时候,传入了一些参数标志,而且共享了地址空间、文件系统资源、文件描述符、信号处理程序fork():返回0的是子进程,大于0的是父进程,小于0则出错

内核进程创建:kthread_create;它没有独立地址空间,也不能到用户空间执行,可被调度和抢占

1 : 构造了一个kthread_create_info 结构体,将其挂接到 kthread_create_list链表上

2 : 唤醒内核线程kthreadd去创建线程

进程终结:当通过显式或隐式调用exit()主动退出,或者被信号或异常被动终结时,进程大部分都是靠do_exi来结束,此时处于僵死态,但它还占有内核栈,thread_info结构体,task_struct结构体;以向父进程提供信息,让其检索之后(调用wait()),将最后占用的内存释放掉

进程调度

调度器

分配进程运行时间,并通过调度算法,切换两个进程的上下文

优先级

静态优先级:范围为100-139(MAX_RT_PRIO - MAX_PRIO-1),内核2.6中的静态优先级相当于内核2.4中的nice值-20,19),但转到MAX_RT_PRIO到MAX_PRIO-1取值范围,其公式为:static priority = nice + 20 + MAX_RT_PRIO;内核定义两个宏来实现此转化:nice_to_prio() and prio_to_nice();实时和非实时进程都一样可通过nice()和setpriority()改变,以影响基时间片

动态优先级:范围与静态优先级相同。动态优先级是调度器选择一个进程时实际参考的值。平均睡眠时间越大,bonus值越大,其动态优先级越大;dynamic priority = max( 100, min((static priority - bonus + 5), 139));实时进程的动态优先级不会改变

实时优先级:取值范围0-MAX_RT_PRIO-1。其大小可以通过sched_setscheduler()和sched_setparam()来改变

调度器类

允许不同的可动态添加的调度算法并存,总调度器根据调度器类的优先顺序,依次调度进行调度器类的中的进程,Stop_ask > RealTime > Fair > Idle_Task,通过sched_setscheduler可设置调度算法

CFS:完全公平调度器,通过一个虚拟时钟 vruntime= 实际运行时间 * 1024 / 进程权重(nice值为0,权重为1024),总是选择 vruntime 最小的进程,让其投入执行。他们被维护到一个以 vruntime 为顺序的rbtree 中;分配的可运行时间 = 调度周期 *(进程权重 / 所有进程权重之和)

FIFO:是让一个任务运行完再调度下一个任务,而顺序就是依照创建的先后,只有更高优先级的 SCHED_FIFO 或者 SCHED_RR 才能抢占它的任务

RR:依据时间片来调度的,当时间片用完时,无论这个进程优先级有多高,都不会在执行,而是进入就绪队列,等待下一个时间片到来

进程切换

自愿切换发生的时候,进程不再处于运行状态,比如由于等待IO而阻塞(TASK_UNINTERRUPTIBLE),或者因等待资源和特定事件而休眠(TASK_INTERRUPTIBLE),又或者被debug/trace设置为TASK_STOPPED/TASK_TRACED状态。

强制切换发生的时候,进程仍然处于运行状态(TASK_RUNNING),通常是由于被优先级更高的进程抢占(preempt),或者进程的时间片用完了。

进程自愿切换(Voluntary)和强制切换(Involuntary)的次数被统计在 /proc/<pid>/status,查看此值,判断进程对cpu的需求度。

触发抢占

给正在CPU上运行的当前进程设置一个请求重新调度的标志(TIF_NEED_RESCHED),仅此而已,此时进程并没有切换;触发抢占的时机如下:

周期性的时钟中断:调用scheduler_tick()检查进程的时间片是否耗尽,如果耗尽则触发抢占

唤醒进程的时候:如果优先级高于CPU上的当前进程,就会触发抢占;try_to_wake_up()最终通过check_preempt_curr()检查是否触发抢占

新进程创建的时候:如果新进程的优先级高于CPU上的当前进程,会触发抢占。相应的调度器核心层代码是sched_fork(),它再通过调度类的 task_fork方法触发抢占

进程修改nice值的时候:如果进程修改nice值导致优先级高于CPU上的当前进程,也会触发抢占。内核代码参见 set_user_nice()

进行负载均衡的时候:进程调度器尽量使各个CPU之间的负载保持均衡,而负载均衡操作可能会需要触发抢占

执行抢占

从系统调用(syscall)返回用户态时

从中断返回用户态时

中断返回:中断处理程序返回内核空间之前会检查TIF_NEED_RESCHED标志,如果置位则调用preempt_schedule_irq()执行抢占。preempt_schedule_irq()是对schedule()的包装。

使能抢占:当内核从non-preemptible(禁止抢占)状态变成preemptible(允许抢占)的时候;
preempt_enable()中,会最终调用 preempt_schedule 来执行抢占

 

posted on 2022-03-08 22:03  lzd626  阅读(56)  评论(0编辑  收藏  举报