《Linux内核分析》 第二节 操作系统是如何工作的
Linux内核分析 第二周 操作系统是如何工作的
张嘉琪 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
一、函数调用堆栈
- 计算机工作的三个法宝
- 存储程序计算机
- 中断机制
- 堆栈
二、借助Linux内核部分源代码模拟存储程序计算机工作模型及时钟中断
- mykernel实验指导(操作系统是如何工作的)
运行并分析一个精简的操作系统内核,理解操作系统是如何工作的
使用实验楼的虚拟机打开shell
cd LinuxKernel/linux-3.9.4 qemu -kernel arch/x86/boot/bzImage
然后cd mykernel ,可以看到qemu窗口输出的内容的代码mymain.c和myinterrupt.c
- 实验源代码
进程的启动和进程的切换机制分析见注释
mypcb.h
1 /* 2 * linux/mykernel/mypcb.h 3 * 4 * Kernel internal PCB types 5 * 6 * Copyright (C) 2013 Mengning 7 * 8 */ 9 10 #define MAX_TASK_NUM 4 11 #define KERNEL_STACK_SIZE 1024*8 12 13 /* CPU-specific state of this task */ 14 struct Thread { 15 unsigned long ip; 16 unsigned long sp; 17 }; 18 19 typedef struct PCB{ 20 int pid; //定义进程的ID 21 volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ //定义进程的状态 22 char stack[KERNEL_STACK_SIZE]; //建立搭建进程的堆栈 23 /* CPU-specific state of this task */ 24 struct Thread thread; 25 unsigned long task_entry; //task_entry指定入口 26 struct PCB *next; //将进程用链表链接起来 27 }tPCB; 28 29 void my_schedule(void);//调度器
mymain.c
1 /* 2 * linux/mykernel/mymain.c 3 * 4 * Kernel internal my_start_kernel 5 * 6 * Copyright (C) 2013 Mengning 7 * 8 */ 9 #include <linux/types.h> 10 #include <linux/string.h> 11 #include <linux/ctype.h> 12 #include <linux/tty.h> 13 #include <linux/vmalloc.h> 14 15 16 #include "mypcb.h" 17 18 tPCB task[MAX_TASK_NUM]; //声明一个PCB的数组 19 tPCB * my_current_task = NULL; 20 volatile int my_need_sched = 0; 是否需要调度 21 22 void my_process(void); 23 24 25 void __init my_start_kernel(void) 26 { 27 int pid = 0; 28 int i; 29 /* Initialize process 0*/ //初始化0号进程的数据结构 30 task[pid].pid = pid; // 进程id 31 task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */ //进程状态 32 task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; //进程入口 33 task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];//堆栈的栈顶 34 task[pid].next = &task[pid]; //next指向自己 35 /*fork more process */ //将新fork的进程放到进程列表的尾部 36 for(i=1;i<MAX_TASK_NUM;i++) 37 { 38 memcpy(&task[i],&task[0],sizeof(tPCB)); 39 task[i].pid = i; 40 task[i].state = -1; 41 task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1]; 42 task[i].next = task[i-1].next; 43 task[i-1].next = &task[i]; 44 } 45 /* start process 0 by task[0] */ //启动0号进程 46 pid = 0; 47 my_current_task = &task[pid]; 48 asm volatile( 49 "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */ //%1 ->thread.sp 50 "pushl %1\n\t" /* push ebp */ 51 "pushl %0\n\t" /* push task[pid].thread.ip */ 52 "ret\n\t" /* pop task[pid].thread.ip to eip */ 53 "popl %%ebp\n\t" 54 : 55 : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/ 56 ); 57 } 58 void my_process(void) 59 { 60 int i = 0; 61 while(1) 62 { 63 i++; 64 if(i%10000000 == 0) 65 { 66 printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid); 67 if(my_need_sched == 1) 68 { 69 my_need_sched = 0; 70 my_schedule(); 71 } 72 printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid); 73 } 74 } 75 }
/*0号进程的堆栈和0号进程的入口构建完成,my_start_kernel工作完成。循环1000万次才有一次机会判断一下是否需要调度*/
myinterrupt.c
1 /* 2 * linux/mykernel/myinterrupt.c 3 * 4 * Kernel internal my_timer_handler 5 * 6 * Copyright (C) 2013 Mengning 7 * 8 */ 9 #include <linux/types.h> 10 #include <linux/string.h> 11 #include <linux/ctype.h> 12 #include <linux/tty.h> 13 #include <linux/vmalloc.h> 14 15 #include "mypcb.h" 16 17 extern tPCB task[MAX_TASK_NUM]; 18 extern tPCB * my_current_task; 19 extern volatile int my_need_sched; 20 volatile int time_count = 0; 21 22 /* 23 * Called by timer interrupt. 24 * it runs in the name of current running process, 25 * so it use kernel stack of current running process 26 */ 27 void my_timer_handler(void) 28 { 29 #if 1 30 if(time_count%1000 == 0 && my_need_sched != 1) //设置时间片大小,时间片用完是设置下一个调度标志 31 { 32 printk(KERN_NOTICE ">>>my_timer_handler here<<<\n"); 33 my_need_sched = 1; 34 } 35 time_count ++ ; 36 #endif 37 return; 38 } 39 40 void my_schedule(void) 41 { 42 tPCB * next; 43 tPCB * prev; 44 45 if(my_current_task == NULL 46 || my_current_task->next == NULL) 47 { 48 return; 49 } 50 printk(KERN_NOTICE ">>>my_schedule<<<\n"); //将当前进程的下一个进城赋给next 51 /* schedule */ 52 next = my_current_task->next; 53 prev = my_current_task; 54 if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
//下一个进程的状态是0(正在执行)时用switch to next process进行上下文切换 55 { 56 /* switch to next process */ /*进程切换的关键代码*/
/*将当前ebp保存,esp赋到0,把下一个进程的sp放入esp*/
57 asm volatile(
58 "pushl %%ebp\n\t" /* save ebp */
59 "movl %%esp,%0\n\t" /* save esp */
60 "movl %2,%%esp\n\t" /* restore esp */
61 "movl $1f,%1\n\t" /* save eip */ //1f是指接下的标号1:的位置
62 "pushl %3\n\t"
63 "ret\n\t" /* restore eip */
64 "1:\t" /* next process start here */
65 "popl %%ebp\n\t"
66 : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
67 : "m" (next->thread.sp),"m" (next->thread.ip)
68 );
69 my_current_task = next;
70 printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
71 }
72 else
73 {
74 next->state = 0;
75 my_current_task = next;
76 printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
77 /* switch to new process */
78 asm volatile(
79 "pushl %%ebp\n\t" /* save ebp */ 保存当前进程ebp
80 "movl %%esp,%0\n\t" /* save esp */ 保存eso
81 "movl %2,%%esp\n\t" /* restore esp */ 下一进程的esp放入esp中
82 "movl %2,%%ebp\n\t" /* restore ebp */
83 "movl $1f,%1\n\t" /* save eip */ 保存eip
84 "pushl %3\n\t" 将下一个进程的eip保存在堆栈中
85 "ret\n\t" /* restore eip */
86 : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
87 : "m" (next->thread.sp),"m" (next->thread.ip)
88 );
89 }
90 return;
91 }
三、学习总结
- 进程的启动和进程的切换机制
进程是接到调度指令才能启动的,进程切换就是从正在运行的进程中收回处理器,然后再使待运行进程来占用处理器。这里所说的从某个进程收回处理器,实质上就是把进程存放在处理器的寄存器中的中间数据找个地方存起来,从而把处理器的寄存器腾出来让其他进程使用。在切换时,一个进程存储在处理器各寄存器中的中间数据叫做进程的上下文,所以进程的切换实质上就是被中止运行进程与待运行进程上下文的切换。在进程未占用处理器时,进程 的上下文是存储在进程的私有堆栈中的。
- 操作系统是如何工作的
操作系统也有“两把剑”,分别是中断上下文和进程上下文的切换。操作系统身负诸如管理与配置内存、决定系统资源供需的优先次序、控制输入与输出设备、操作网络与管理文件系统等基本事务。操作系统通过对进程的控制完成每项事物,进程通过控制数据的压入、弹出堆栈,设置时间片等方式进行进程上下文的切换和中断,从而让操作系统可以正常工作