20135202闫佳歆--week 8 实验:理解进程调度时机跟踪分析进程调度与进程切换的过程--实验及总结

week 8 实验:理解进程调度时机跟踪分析进程调度与进程切换的过程

1.环境搭建:

rm menu -rf
git clone https://github.com/megnning/menu.git
cd menu
ls
make rootfs
qemu -kernel ../linux-3.18.6/arch/x86/boot/bzImage -initrd ../rootfs.img -s -S
gdb
file ../linux-3.18.6/vmlinux
target remote:1234

这一堆都是常用配置了,不赘述。

2.使用gdb跟踪分析一个schedule()函数

首先设置几个断点,分别是schedule, pick_next_task,context_switch,switch_to。最后一个switch_to设置失败了,我也无法定位到它,这个另外再说。
设置断点如下:
enter description here

发现一个对schdule的调用:
enter description here

schdule函数定义如下:
enter description here
__visible保证任何地方都能够调用这个函数实现进程的切换。
它首先要建立一个进程描述符,用来表示当前进程,而具体的调度过程交由__schedule()函数执行。

由这张图可以看出来,两个重要的函数context_switch和pick_next_task函数都在__schedule函数中。
enter description here

首先看到的是pick_next_task函数:
enter description here
next指针指向的是由这个函数选择出来的进程,pick_next_task函数里面封装了许多进程调度的算法,通过这个函数就能寻找出适当的下一个执行进程。内部如下:
enter description here

然后看到了context_switch函数,这个函数的功能是进程上下文切换
enter description here

关于switch_to:
switch_to是关键上下文切换的相关代码,切换堆栈和寄存器的状态,利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程。
这段是汇编相关无法跟踪,我只找到了一个__switch_to的函数,这个函数的返回值应该是eax的值。
enter description here

对于switch_to的代码分析如下:

31  #define switch_to(prev, next, last)                    
32  do {                                 
33    /*                              
34     * Context-switching clobbers all registers, so we clobber  
35     * them explicitly, via unused output variables.     
36     * (EAX and EBP is not listed because EBP is saved/restored  
37     * explicitly for wchan access and EAX is the return value of   
38     * __switch_to())                     
39     */                                
40    unsigned long ebx, ecx, edx, esi, edi;                
41                                    
42    asm volatile("pushfl
	"      /* 保存当前进程的标志位 */   
43             "pushl %%ebp
	"        /* 保存当前进程的堆栈基址EBP   */ 
44             "movl %%esp,%[prev_sp]
	"  /* 保存当前栈顶ESP   */ 
45             "movl %[next_sp],%%esp
	"  /* 把下一个进程的栈顶放到esp寄存器中,完成了内核堆栈的切换,从此往下压栈都是在next进程的内核堆栈中。   */ 

46             "movl $1f,%[prev_ip]
	"    /* 保存当前进程的EIP   */ 
47             "pushl %[next_ip]
	"   /* 把下一个进程的起点EIP压入堆栈   */    
48             __switch_canary                   
49             "jmp __switch_to
"  /* 因为是函数所以是jmp,通过寄存器传递参数,寄存器是prev-a,next-d,当函数执行结束ret时因为没有压栈当前eip,所以需要使用之前压栈的eip,就是pop出next_ip。  */ 

以上四行代码实际是使用next进程的进程堆栈,但是还算成prev的进程执行,内核堆栈的切换和进程切换完成并不同时间。

50             "1:	"               /* 认为next进程开始执行。 */         
51             "popl %%ebp
	"     /* restore EBP   */    
52             "popfl
"         /* restore flags */  
53                                    
54             /* output parameters 因为处于中断上下文,在内核中
                prev_sp是内核堆栈栈顶
                prev_ip是当前进程的eip */                
55             : [prev_sp] "=m" (prev->thread.sp),     
56               [prev_ip] "=m" (prev->thread.ip),  //[prev_ip]是标号        
57               "=a" (last),                 
58                                    
59               /* clobbered output registers: */     
60               "=b" (ebx), "=c" (ecx), "=d" (edx),      
61               "=S" (esi), "=D" (edi)             
62                                         
63               __switch_canary_oparam                
64                                    
65               /* input parameters: 
                next_sp下一个进程的内核堆栈的栈顶
                next_ip下一个进程执行的起点,一般是$1f,对于新创建的子进程是ret_from_fork*/                
66             : [next_sp]  "m" (next->thread.sp),        
67               [next_ip]  "m" (next->thread.ip),       
68                                         
69               /* regparm parameters for __switch_to(): */  
70               [prev]     "a" (prev),              
71               [next]     "d" (next)               
72                                    
73               __switch_canary_iparam                
74                                    
75             : /* reloaded segment registers */           
76            "memory");                  
77  } while (0)

简而言之,原理如下:

schedule()函数选择一个新的进程来运行:

使用pick_next_task内部封装的调度算法执行选择
并调用context_switch进行上下文的切换
context_switch又调用switch_to来进行关键上下文切换
switch_to切换堆栈和寄存器的状态,利用了prev和next两个参数:
prev指向当前进程,next指向被调度的进程。

3.学习笔记

请走链接 学习笔记

原文地址:https://www.cnblogs.com/20135202yjx/p/5392618.html