一:进程
1.1进程描述符结构:
在Linux内核中??个数据结构struct task_struct来描述进程,struct task_struct的数据结构?常庞?,其中state是进程状态,stack是堆栈等,此外还有进程的状态信息、进程双向链表的管理,以及控制台tty、?件系统fs的描述、进程打开?件的?件描述符?les、 内存管理的描述mm,还有进程间通信的信号signal的描述等。
值得注意的是task_struct数据结构的最后是保存进程上下?中CPU相关的?些状态信息的关键数据结构thread,这在进行进程切换时是非常重要的数据结构。其中最关键的是sp和ip。
1.2进程的状态:
进程有就绪 态、运?态、阻塞态这3种基本状态,其中阻塞态又分为TASK_INTERRUPTIBLE 和TASK_UNINTERRUPTIBLE两种状态。
TASK_INTERRUPTIBLE
和TASK_UNINTERRUPTIBLE
的区别:
TASK_INTERRUPTIBLE是可以被信号和wake_up()唤醒的,当信号到来时,进程会被设置为可运行。
而TASK_UNINTERRUPTIBLE只能被wake_up()唤醒。
1.3进程链表:
?于管理进程数据结构的双向链表struct list_head task把所有的进程?双向链表链起来,?且还会头尾相连把所有的 进程?双向循环链表链起来
进程的??兄弟关系:
可见,进程的子进程和兄弟进程相关信息均用双向链表链接起来(struct list_head)
1.4Linux内核中进程的初始化
在start_kernel中含有对linux内核0号进程的初始化过程
1)init_task为第?个进程(0号进程)的进程描述符结构体变量,它的初始化是通过硬编码?式固定下来的。除此之外,所有其他进程的初始化都是通过do_fork复制?进程的?式初始化的
2)1号和2号进程的创建是start_kernel初始化到最后由rest_ init通过 kernel_thread(通过_do_fork创建)创建了两个内核线程:?个是kernel_init,最终把?户态的进程init给启动起来,它是所有?户进程的祖先;另?个是kthreadd内核线程,kthreadd内核线程是所有内核线程的祖先,负责管理所有内核线程。
1.5fork()系统调用过程:
1) 父进程调用fork()系统调用,执行个int $0x80指令触发系统调用,跳转到相应系统调???的汇编代码。
2) 系统调?陷?内核态,从?户态堆栈转换到内核态堆栈,然后把相应的CPU关 键的现场栈顶寄存器、指令指针寄存器、标志寄存器等保存到内核堆栈,保存现场。
3) 通过系统调?号找到对应的中断处理程序入口地址,并传递到CS:IP,然后执?系统调?内核处理函数
4) 在fork()内核处理函数中调用_do_fork函数,_do_fork函数主要完成了调?copy_process()复制?进程、获得pid、然后再调用wake_up_new_task将?进程加?就绪队列等待调度执?等
5) copy_process()函数调用dup_task_struct复制当前进程(?进程)描述符task_struct、信息检查、初始化、把进程状态 设置为TASK_RUNNING(此时?进程置为就绪 态)、采?写时复制技术逐?复制所有其他进程资源,然后再调用调?copy_thread_tls初始化?进程内核栈、设置?进程pid等,并将内核堆栈中保存的ip指向 ret_from_fork,?进程从此处开始执?
6) 最后系统调用返回,父进程从内核态正常返回到用户态,执行下一条指令,而?进程则在被调度执?时根据设置的内核堆栈和thread等进程关键上下?开始执?(即从ret_from_form开始执行)。
1.6进程分类:
Linux内核中根据进程的优先级来区分普通进程与实时进程,Linux内核中进程优先级为0~139,数值越?,优先级越低,0为最?优先级。实时进程的优先级取值为0~99;?普通进程只具有nice值,nice值映射到优先级为100~139。?进程会继承?进程的优先级。
1.7进程调度策略:
实时进程:
SCHED_FIFO
1) 先进先出策略
2) 所有相同优先级的进程,最先进?就绪队列的进程优先调度,直到其主动放弃CPU。
SCHED_RR(Round Robin)
1)使用时间?来分配cpu资源
2)使得相同优先级的实时进程能够轮流获得调度,每次运??个时间?。
普通进程:
SCHED_NORMAL
1) 按优先级占不同?例的cpu时间
2) 同?个进程在本身优先级不变的情况下分到的
CPU时间占?会根据系统负载变化?发?变化,即与时间?没有?个固定的对应关系。
1.8进程调度算法:
CFS:
1)CFS即为完全公平调度算法,其基本原理是基于权重的动态优先级调度算法。
2)每个进程使?CPU的顺序由进程已使?的CPU虚拟时间(vruntime)决定,已使?的虚拟时间越少,进程排序就越靠前,进程再次被调度执?的概率也就越?。
vruntime = 实际运行时间 * 1024 (nice为0的进程的权重) / 进程权重
3)每个进程每次占?CPU后能够执?的时间(ideal_runtime)由进程的权重决定,并且保证在某个时间周期内运?队列?的所有进程都能够?少被调度执??次
分配给进程的运行时间 = 调度周期 * 进程权重 / 所有进程权重之和
4)Linux采?红?树(rb_tree)来存储就绪进程指针,当进程插?就绪队列时根据 vruntime排序,调度时只需选择最左的叶?节点即可
1.9进程调度的时机
总的来说,进程调度时机就是内核调?schedule函数的时机
1)?户进程上下?中主动调?特定的系统调?进?中断上下?,系统调?返回?户态之前, 中断处理程序主动调?schedule函数进?进程调度(检测need_resched标记)。
2)内核线程或可中断的中断处理程序,执?过程中发?中断进?中断上下?,在中断返回前, 中断处理程序主动调? schedule函数进?进程调度。
3)内核线程主动调?schedule函数进?进程调度。
1.10进程切换
进程切换本质上是切换两个进程的内核堆栈和thread
发生进程切换时,原进程要保存一下内容,同时也是新进程要更新的内容:
1) CR3寄存器代表进程??录表,即地址空间、数据。
2) 内核堆栈栈顶寄存器sp代表进程内核堆栈(保存函数调?历史),进程描述符(最后的成员thread是关键)和内核堆栈存储于连续存取区域中, 其中thread成员保存变量其中一部分CPU上下?,另外一部分保存在内核堆栈中。
3) 指令指针寄存器ip代表进程的CPU上下?
这些寄存器从?个进程的状态切换到另?个进程的状态,进程切换的关键上下?就算完成了。
具体在实现上,schedule()函数选择?个新的进程来运?,并调?context_switch进?上下?的切换。context_switch?先调?switch_mm切换CR3,然后调?宏switch_to来进?CPU上下?切换。
切换到next进程后,先返回到next进程上次切换让出CPU时的schedule()函数中,然后返回到调?schedule()的中断(系统调?)处理过程中。 系统调?是在?户空间通过int 0x80触发的,所以系统调用完成后通过iret将中断上下?返回到系统调?被触发的地?,接着继续执??户空间的代码。这样就回到了next进程的?户空间代码了。
值得注意的是,进程上下文切换所需要保存的信息与中断上下文切换所需要保存的信息是不同的,中断上下文使用的是被中断进程的内核堆栈,本质上还是一个进程,但是进程上下文切换涉及到两个进程山下的,?般进程上下?切换是嵌 套到中断上下?切换中的。中断上下?中的get_current可获取?个指向当前进程描述符的指针,即指向被中断进程,相应的中断上下?切换的信息存储于该被中断进程的内核堆栈中。
中断上下?和进程上下?的?个关键区别是堆栈切换的?法。中断是由CPU实现的,所以中断上下?切换过程中最关键的栈顶寄存器sp和指令指针寄存器ip 是由CPU协助完成的;进程切换是由内核实现的,所以进程上下?切换过程中最关键的栈顶寄存器sp切换是通过进程描述符的thread.sp实现的,指令指针寄存器ip的切换是在内核堆栈切换的基础上巧妙利?call/ret指令实现的。
另外同样值得注意的是:在linux-3.18.6版本中ip信息是保存在struct thread_struct数据结构中而在linux-5.4.34版本中struct thread_struct数据结构中没有了ip,?是将ip通过内核堆栈来保存,?如fork创建的?进程内核堆栈 中会有?个ret_addr。
1.11Linux系统的?般执?过程
1) 正在运?的?户态进程X
2) 发?中断(包括异常、系统调?等),CPU完成load cs:rip(entry of a speci?c ISR),跳转到中断处理程序??。
3) 中断上下?切换,首先保存现场,然后加载当前进程内核堆栈栈顶地址到RSP寄存器,最后将当前CPU关键上下?(包括之前保存的现场信息)压?进程X的内核堆栈(进程X的?户态—>进程X的内核态)
4) 中断处理过程中或中断返回前调?了schedule函数,其中完成了进程调度算法选择next进程、进程地址空间切换、以及switch_to关键的进程上下?切换等
5) switch_to调?了__switch_to_asm汇编代码做了关键的进程上下?切换。将当前进程X的内核堆栈切换到进程调度算法选出来的Y进程的内核堆栈,并完成了进程上下?所需的指令指针寄存器状态切换,之后开始运?进程Y.
6) 中断上下?恢复, 先返回到调?schedule()的中断(系统调?)处理过程,系统调用完成后通过iret将中断上下?返回到系统调?被触发的地?,接着继续执??户空间的代码。
7) 继续运??户态进程Y。
1.12地址
逻辑地址:逻辑地址?般时?段地址:段内偏移量来表示,
线性地址就是进程的地址空间?从 0 开始线性递增的地址空间范围?的地 址,是逻辑地址经过段地址转换之后的地址;
物理地址就是实际物理内存的地址,线性地址经过内存?转换之后就是物理地址。
EAX?于传递系统调?号 外,参数按顺序赋值给EBX、ECX、EDX、ESI、EDI、EBP,参数的个数不能超过6个
1.13系统调用过程(以time系统调用为例)
1) 应用程序调用time系统调用,触发?int $0x80指令执行
2) 通过EAX 寄存器传递系统调?号,通过EBX传递第一个参数
3) int $0x80指令保存中断发?时当前执?程序(用户态)的栈顶地址(ESP)、当时的状态字(EFlags)、当时的 CS:EIP 的值。同时会将当前进程内核态的栈顶地址、内核态的状态字放? CPU 对应的寄存器,并且 CS:EIP/RIP 寄存器的值会指向中断处理程序的??.
4) 执行中断处理程序(sys_time处理函数)
5) 中断处理程序结束后,检查need_resched标记判断是否需要调度,如果不需要,则执行iret指令从内核栈中恢复之前保存的现场
6) 返回到用户态,继续执行程序
原文:https://www.cnblogs.com/zyc1234/p/13275722.html