2019-2020-9 20199317 "Linux kernel principle and Analysis" in the ninth week of work

General implementation of the process of switching systems and processes in Chapter 8

1 process scheduling time

1.1 hard and soft interrupt terminal

        The process of scheduling time is associated with an interrupt, the interrupt there are many, program execution process is mandatory transfer, be transferred to the operating system kernel appropriate handler. Interrupts are essentially software or hardware has a situation and inform the behavior of the processor, the processor instruction stream and then stopping (the current process) is running, make the appropriate response to these notifications, turn to the implementation that is defined interrupt handler (kernel code).

       Hardware interrupt : hardware interrupt pin of the CPU is two (maskable interrupts and non-maskable interrupts). The CPU detects the level of these two pins after execution of each instruction, if it is high, indicating an interrupt request, CPU will interrupt execution of the current program to process the interrupt. Peripherals are generally transmitted signal in this way the CPU, such as a clock, a keyboard, a hard disk.

       Soft interrupt / exception (Exception): includes a divide by zero error, system calls, all kinds of special circumstances such as debug breakpoint occurs in the CPU instruction execution process referred to as abnormal. An exception will cause the program can not continue, and the CPU jumps to the default handler.

       Disorders fall into the following three:

       ● Fault (Fault): failure is not a problem, but can be restored to the current instruction. In addition, for example, 0 errors, missing pages interruption.

       ● Exit (Abort): it simply is a serious unrecoverable failure, resulting program can not continue to run, only to quit. E.g. failure (double fault) occurs continuously.

       ● trap (Trap): abnormal program is actively generated, takes place after the current instruction. For example to set a breakpoint instruction (int 0x80) and a debugger system call (int 3) fall into this category.

1.2 Process Scheduling opportunity

       Linux kernel implements process scheduling by the schedule function, schedule function to find a process in the run queue, CPU allocated to it. So call schedule function is scheduling a time, calling schedule function, it is the process of scheduling time.

       In general, CPU at any time in one of three situations.

       ● run in user space, execution of the user process context.

       ● runs in kernel space, in the process (usually a kernel thread) context.

       ● runs in kernel space, in interrupt (interrupt handler ISR, including call processing system) context.

       Interrupt context represents the current process of implementation, so the interrupt context get_current can get a pointer to the current process pointer is ready process point is interrupted process or about to run, the corresponding hardware contexts will switch information is also stored in the kernel stack of the process in. Due to the different levels of interrupt, non-maskable interrupts, maskable interrupt, trap (system call), and other abnormalities. To the operating efficiency of the whole system, interrupt context call other kernel code has some limitations.

       Kernel threads run in the form of process context in kernel space, in essence, is the process, but it has permission to call the kernel code, such as the initiative to call schedule () function allows the CPU and so on. 

       The process of scheduling the timing of the following :

       ● user process through certain system calls initiative to the CPU.

       ● interrupt handler in the kernel scheduling return to user mode.

       ● kernel thread automatically call schedule function allows the CPU.

       ● interrupt handler automatically call schedule function allows the CPU, cover more than the first and second cases.

       User mode process can only passively scheduling, dispatching the initiative can not be achieved, can only be scheduled by a timing point into kernel mode, that is, scheduling in interrupt processing. Kernel threads can be active, but also passive scheduling.

       Linux kernel concept of operating system principle there is no thread defined. From the kernel perspective, regardless of the process or kernel thread corresponds to a task_struct data structure, it is essentially thread. Linux system user mode thread library pthread implementation is a shared address space by multiple processes implemented in the kernel.

2 scheduling strategy and algorithm

       Scheduling algorithm is selected from a ready queue process, in general, it is to pick the most important and most in need (most anxious), and so on for the longest time (queue), etc., and human resources line up to grab very similar.

2.1 classification process

       Here select two kinds of scheduling and related classification.

       1 classification process:

       ● I / O consuming process. It requires a lot like a typical file-write or read and write operations of network operations, such as file server service process. Feature of this process is the CPU load is not high, a lot of time waiting to read and write data.

       ● processor consuming process. Typical such as video transcoding, encryption and decryption algorithms. Feature of this process is the CPU occupancy rate of 100%, but there is not much hardware to read and write.

       2 classification process:

       ● interactive process. Such a large number of human-computer interaction process, so the process continuously in a sleep state, waiting for user input, typical applications such as editor VIM. Such process of system response time requirements are relatively high, otherwise the user will feel sluggish system.

       ● batch process. Such process does not require human interaction, runs in the background, require a lot of system resources, but can tolerate delay in response, such as the compiler.

       ● real-time process. Real-time process scheduling delays demanding, these processes tend to perform very important operation, requiring immediate response and execution.

       Depending on the classification processes, Linux uses a different scheduling policy. The current system is Linux solutions for real-time processes, Linux uses FIFO (First In First Out) or Round Robin (round-robin) scheduling policy. Other processes, the current use of Linux CFS (Completely Fair Scheduler) scheduler, the core idea is "completely fair."

2.2 scheduling policy

       Linux 系统中的几种调度策略为SCHED_NORMAL、SCHED_FIFO、SCHED_RR。其中SCHED_NORMAL 是用于普通进程的调度类,而SCHED_FIFO 和SCHED_RR 是用于实时进程的调度类,优先级高于SCHED_NORMAL。内核中根据进程的优先级来区分普通进程与实时进程,Linux内核进程优先级为0~139,数值越高,优先级越低,0为最高优先级。实时进程的优先级取值为0~99;而普通进程只具有nice 值,nice 值映射到优先级为100~139。子进程会继承父进程的优先级。

      SCHED_FIFO 和 SCHED_RR

      实时进程的优先级是静态设定的,而且始终大于普通进程的优先级。因此只有当就绪队列中没有实时进程的情况下,普通进程才能够获得调度。实时进程采用两种调度策略:SCHED_FIFO 和 SCHED_RR。SCHED_FIFO采用先进先出的策略,对于所有相同优先级的进程,最先进入就绪队列的进程总能优先获得调度,直到其主动放弃CPU。SCHED_RR 采用更加公平的轮转策略,比FIFO 多一个时间片,使得相同优先级的实时进程能够轮流获得调度,每次运行一个时间片。

      SCHED_NORMAL

       Linux-2.6 之后的内核版本中,SCHED_NORMAL 使用的是Linux-2.6.23 版本内核中引入的CFS 调度管理程序。每个进程能够分配到的CPU 时间占有比例跟系统当前的负载(所有处于运行态的进程数以及各进程的优先级)有关,同一个进程在本身优先级不变的情况下分到CPU 时间占比会根据系统负载变化而发生变化,即与时间片没有一个固定的对应关系。

       CFS 算法对交互式进程的响应较好,由于交互式进程基本处于等待事件的阻塞态中,执行的时间很少,而计算类进程在执行的时间会比较长。如果计算类进程正在执行时,交互式进程等待的事件发生了,CFS就会判断出交互式进程在之前时间段内执行的时间很少,因此系统总是能及时响应交互式进程。

2.3  CFS调度算法

       CFS即为完全公平调度算法,其基本原理是基于权重的动态优先级调度算法。每个进程使用CPU的顺序由进程已使用的CPU虚拟时间(vruntime)决定,已使用的虚拟时间越少,进程排序就越靠前,进程再次被调度执行的概率也就越高。每个进程每次占用CPU 后能够执行的时间(ideal_runtime)由进程的权重决定,并且保证在每个时间周期(_sched_period)内运行队列里的所有进程都能够至少被调度执行一次。

3  进程上下文切换

3.1  进程执行环境的切换

       为了控制进程的执行,内核必须有能力挂起正在CPU 中运行的进程,并恢复执行以前挂起的某个进程。这种行为被称为进程切换,任务切换或进程上下文切换。

       进程上下文包含了进程执行需要的所有信息。

       ● 用户地址空间:包括程序代码、数据、用户堆栈等。

       ● 控制信息:进程描述符、内核堆栈等。

       ● 硬件上下文,相关寄存器的值。

       进程切换就是变更进程上下文,最核心的是几个关键寄存器的保存与变换。

       ● CR3 寄存器代表进程页目录表,即地址空间、数据。

       ● ESP寄存器(内核态时)代表进程内核堆栈(保存函数调用历史),struct thread、进程控制块、内核堆栈存储于连续8KB 区域中,通过ESP获取地址。

       ● EIP寄存器及其他寄存器代表进程硬件上下文,即要执行的下条指令(代码)及环境。

       这些寄存器从一个进程的状态切换到另一个进程的状态,进程切换就算完成了。

       在实际代码中,每个进程切换基本由两个步骤组成:

       ● 切换页全局目录(CR3)以安装一个新的地址空间,这样不同进程的虚拟地址如0x8048400 就会经过不同的页表转换为不同的物理地址。

       ● 切换内核态堆栈和硬件上下文,因为硬件上下文提供了内核执行新进程所需要的所有信息,包含CPU 寄存器状态。

3.2  核心代码分析

       schedule() 函数选择一个新的进程来运行,并调用 context_switch 进行上下文的切换。context_switch 首先调用 switch_mm 切换CR3,然后调用宏 switch_to 来进行硬件上下文切换。context_switch 部分关键代码及分析摘录如下。

       地址空间切换

       地址空间切换的关键代码在load_cr3 ,将下一进程的页表地址装入CR3。从这里开始,所有虚拟地址转换都使用 next 进程的页表项。当然因为所有进程对内核地址空间是相同的,所以在内核态时,使用任意进程的页表转换的内核地址都是相同的。

static inline void context_switch(struct rq *rq,struct task_struct *prev, struct task_struct *next)
{
...
if (unlikely(!mm)) { /* 如果被切换进来的进程的mm为空切换,内核线程mm为空 */ next->active_mm = oldmm; /* 将共享切换出去进程的active_mm */ atomic_inc(&oldmm->mm_count); /* 有一个进程共享,所有引用计数加一 */
/*
将per cpu变量cpu_tlbstate状态设为LAZY */
enter_lazy_tlb(oldmm, next); } else /* 普通mm不为空,则调用switch_mm切换地址空间 */ switch_mm(oldmm, mm, next) ; ... /* 这里切换寄存器状态和栈 */ switch_to(prev, next, prev) ; ... static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, struct task_struct *tsk) {
...
if (!cpumask_test_and_set_cpu(cpu, mm_cpumask(next))) { load_cr3(next->pgd); //地址空间切换 load_LDT_nolock(&next->context); }
}
#endif

          堆栈及硬件上下文:

         该部分是内联汇编代码,如下所示:

pushf1
push %ebp    // s0 准备工作

prev->thread.sp=%esp   //s1
%esp=next->thread.sp   //s2
prev->thread.ip=$1f       //s3

push next->thread.ip      //s4
jmp _switch_to        //s5

1f:
popl %%ebp           //s6,与s0对称
popf1

         进程切换关键环节如下图所示:

          

         从伪代码中可以看出,s0 两句在prev 的堆栈中压入了EFLAG 和 EBP寄存器。

      (1)s1将当前的ESP寄存器保存到prev->thread.sp 中。

      (2)s2将ESP寄存器替换为 next->thread.sp。

      (3)s3保存$1f 这个位置对应的内存地址到 prev->thread.ip。

      (4)s4在堆栈上压了$1f 这个地址,此刻是next 进程的堆栈。

      (5)s5是一条jmp,跳转到一个 c 函数_switch_to。

      (6)s6,当到达此处就说明是一个相当正常的下一进程在运行了,对称地把 s0 压得数据弹出。

        接下来的部分就要靠对函数调用堆栈的理解了,其实堆栈存储了进程所有的函数调用历史,所以剩下的只要顺着堆栈返回上一级函数即可。由于_switch_to 是被schedule() 函数调用的,而schedule() 函数又在其他系统调用函数中被调用,比如sys_exit() 中,所以先返回到next 进程上次切换让出 CPU 时的schedule() 函数中,然后返回到调用 schedule() 的系统调用处理过程中,而系统调用又是在用户空间通过int 0x80触发的,所以通过中断上下文返回到系统调用被触发的地方,接着执行用户空间的代码。这样就回到了next进程的用户空间代码。

       进程上下文切换时需要保存要切换你进程的相关信息(如 thread.sp 与 thread.ip),这时中断上下文的切换是不同的。中断是在一个进程当中从进程的用户态到进程的内核态,或从进程的内核态返回到进程的用户态,而切换进程需要在不同的进程间切换。

4  Linux系统的运行过程

        Linux 系统的一般执行过程

     (1)正在运行的用户态进程X。

      (2)发生中断(包括异常、系统调用等),硬件完成以下动作。

        ●  save cs:eip/ss:esp/eflags:当前CPU上下文压入用户态进程x的内核堆栈。

        ●  load cs:eip(entry of a specific ISR)and ss:eip(point to kernel stack); 加载当前进程内核堆栈相关信息,跳转到中断处理程序,即中断执行路径的起点。

      (3)SAVE_ALL保存现场, 此时完成了中断上下文切换,即从进程X的用户态到进程X的内核态。

      (4)中断处理过程中或中断返回前调用了schedule 函数,其中的switch_to 做了关键的进程上下文切换。将当前用户进程X的内核堆栈切换到选出的next进程(本例假定为进程Y)的内核堆栈,并完成了进程上下文所需的EIP等寄存器状态切换。

      (5)标号1,即上述代码第50行“1:\t”(地址为swich_to中的“$1f"),之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去,因此可以从标号1继续执行)。

      (6) restore_al,恢复现场,与(3)中保存现场相对应。

      (7)iret - pop cs:eip/ss:esp/eflags,从Y进程的内核堆找中弹出(2)中硬件完成的压栈内容。此时完成了中断上下文的切换,即从进程Y的内核态返回到进程Y的用户态。

      (8)继续运行用户态进程Y。

        如上过程在Linux系统中反复执行,其中的关键点如下:

        ●  中断和中断返回有CPU硬件上下文的切换。

        ●  进程调度过程中有进程上下文的切换,而进程上下文的切换包括:从一个进程的地址空间切换到另一个进程的地址空间;从一个进程的内核堆栈切换到另一个进程的内核堆栈;还有诸如EIP寄存器等寄存器状态的切换。

        Linux 系统执行过程中的几种特殊情况:

        ●  通过中断处理过程中的调度时机,内核线程之间互相切换。比如两个内核线程之间切换,CS段寄存器没有发生变化,没有用户态和内核态的切换。

        ●  用户进程向内核线程的切换。内核现场不需要从内核态返回到用户态,也就是说省略了恢复现场和iret恢复CPU上下文。

        ●  内核线程向用户进程的切换。内核线程主动调用schedule函数,只有进程上下文的切换,不需要发生中断和保存现场。它比最一般的情况更简略,但用户进程从内核态返回到用户态时依然需要恢复现场和iret恢复CPU上下文。

        ●  创建子进程的系统调用在子进程中的执行起点及返回用户态的过程较为特殊。如fork一个子进程时,子进程不是从switch_to 中的标号1开始执行的,而是从ret_from_fork 开始执行的,在源代码中可以找到语句“next ip =ret from fork"。

        ●  加载一个新的可执行程序后返回到用户态的情况也较为特殊。比如execve系统调用加载新的可执行程序,在 execve系统调用处理过程中修改了中断上下文,即在execve系统调用内核处理函数内部修改了中断保存现场的内容,也就是返回到用户态的起点为新程序的elf_entry或者ld动态连接器的起点地址。

5  Linux系统构架与执行过程概览     

        任何计算机系统都包含一个基本的程序集合,称为操作系统。操作系统是一个集合,即包含用户态也包含内核态的组件,Linux操作系统的整体构架如下图所示:

       

         图中中间为内核实现,内核向上为用户提供系统调用接口,向下调用硬件服务接口。其自身实现了进程管理等功能,在内核外还提供了如系统命令、编译器、解释器、函数库等基础设施。对底层来说,与硬件交互管理所有的硬件资源。对上层来说,为用户程序(应用程序)提供一个良好的执行环境。

       接下来,将以ls命令的执行过程来分析整个系统的运行。当用户输入ls并按回车键后,在Linux操作系统中发生的如下图所示:

       

6  进程调度相关源代码跟踪和分析

6.1  配置运行MenuOS系统

       

        

6.2  配置 gdb 远程调试和设置断点

        

 6.3  使用 gdb 跟踪分析schedule()函数

        在调试模式下重新运行 MenuOS 系统可以看到 MenuOS 运行到 schedule 函数停下来,如下图所示:

        

        schedule 函数的作用非常重要,是进程调度的主体函数。其中 pick_next_task 函数是schedule 函数中重要的函数,负责根据调度策略和调度算法选择下一个进程,pick_next_task 函数断点截图如下所示:

        

       context_switch 函数是schedule 函数中实现进程切换的函数,context_switch 函数断点截图如下图所示:

        

        由于switch_to 内部是内嵌汇编代码,无法跟踪调试。


 

 

Guess you like

Origin www.cnblogs.com/chengzhenghua/p/11869273.html