linux内核学习之二 一个精简内核的分析(基于时间片轮转)
一 实验过程及效果
1.准备好相关的代码,分别是mymain.c,mypcb.h,myinterrupt.c ,如下图,make
make成功:
在qemu创建的虚拟环境下的运行效果:(使用的命令如上图所示)
效果分析:可以看到进程在不断切换,分别有进程0,1,2,3,每隔一段时间就进行一次切换。
二 具体代码
mypcb.h
#define MAX_TASK_NUM 4 //定义最大任务(进程数) #define KERNEL_STACK_SIZE 1024*8 //定义内核堆栈的大小 struct Thread{ unsigned long ip; unsigned long sp; }; typedef struct PCB{ //定义进程控制块 int pid; volatile long state; char stack[KERNEL_STACK_SIZE]; struct Thread thread; unsigned long task_entry; struct PCB *next; }tPCB; void my_schedule(void); //声明调度函数
mymain.c
1 #include <linux/types.h> 2 #include <linux/module.h> 3 #include <linux/proc_fs.h> 4 #include <linux/kernel.h> 5 #include <linux/syscalls.h> 6 #include <linux/stackprotector.h> 7 #include <linux/string.h> 8 #include <linux/ctype.h> 9 #include <linux/delay.h> 10 #include <linux/ioport.h> 11 #include <linux/init.h> 12 #include <linux/initrd.h> 13 #include <linux/bootmem.h> 14 #include <linux/acpi.h> 15 #include <linux/tty.h> 16 #include <linux/percpu.h> 17 #include <linux/kmod.h> 18 #include <linux/vmalloc.h> 19 #include <linux/kernel_stat.h> 20 #include <linux/start_kernel.h> 21 #include <linux/security.h> 22 #include <linux/smp.h> 23 #include <linux/profile.h> 24 #include <linux/rcupdate.h> 25 #include <linux/moduleparam.h> 26 #include <linux/kallsyms.h> 27 #include <linux/writeback.h> 28 #include <linux/cpu.h> 29 #include <linux/cpuset.h> 30 #include <linux/cgroup.h> 31 #include <linux/efi.h> 32 #include <linux/tick.h> 33 #include <linux/interrupt.h> 34 #include <linux/taskstats_kern.h> 35 #include <linux/delayacct.h> 36 #include <linux/unistd.h> 37 #include <linux/rmap.h> 38 #include <linux/mempolicy.h> 39 #include <linux/key.h> 40 #include <linux/buffer_head.h> 41 #include <linux/page_cgroup.h> 42 #include <linux/debug_locks.h> 43 #include <linux/debugobjects.h> 44 #include <linux/lockdep.h> 45 #include <linux/kmemleak.h> 46 #include <linux/pid_namespace.h> 47 #include <linux/device.h> 48 #include <linux/kthread.h> 49 #include <linux/sched.h> 50 #include <linux/signal.h> 51 #include <linux/idr.h> 52 #include <linux/kgdb.h> 53 #include <linux/ftrace.h> 54 #include <linux/async.h> 55 #include <linux/kmemcheck.h> 56 #include <linux/sfi.h> 57 #include <linux/shmem_fs.h> 58 #include <linux/slab.h> 59 #include <linux/perf_event.h> 60 #include <linux/file.h> 61 #include <linux/ptrace.h> 62 #include <linux/blkdev.h> 63 #include <linux/elevator.h> 64 65 #include <asm/io.h> 66 #include <asm/bugs.h> 67 #include <asm/setup.h> 68 #include <asm/sections.h> 69 #include <asm/cacheflush.h> 70 71 #ifdef CONFIG_X86_LOCAL_APIC 72 #include <asm/smp.h> 73 #endif 74 75 #include"mypcb.h" 76 77 tPCB task[MAX_TASK_NUM]; 78 tPCB * my_current_task = NULL; 79 volatile int my_need_sched = 0; //定义调度标志 80 81 void my_process(void); 82 83 void __init my_start_kernel(void) //进程初始化,并使进程0开始运行 84 { 85 int pid=0; 86 int i ; 87 88 /*进程0初始化*/ 89 90 task[pid].pid = pid; 91 task[pid].state = 0; 92 task[pid].task_entry = task[pid].thread.ip = (unsigned long) my_process; //指定进程0的入口 93 task[pid].thread.sp=(unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; 94 task[pid].next = &task[pid]; 95 for(i=1;i<MAX_TASK_NUM;i++) //初始化进程1,2,3 96 { 97 memcpy(&task[i],&task[0],sizeof(tPCB)); 98 task[i].pid = i; 99 task[i].state = -1; /* -1 unrunnable, 0 runnable, >0 stopped */ 100 task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1]; 101 task[i].next = task[i-1].next; //构建进程控制块链表 102 task[i-1].next = &task[i]; 103 104 } 105 106 pid = 0; 107 my_current_task = &task[pid]; 108 asm volatile( //嵌入式汇编代码,使进程0运行起来 109 "movl %1,%%esp\n\t" 110 "pushl %1\n\t" 111 "pushl %0\n\t" 112 "ret \n\t" 113 "popl %%ebp\n\t" 114 : 115 :"c" (task[pid].thread.ip),"d" (task[pid].thread.sp) 116 117 ); 118 /* while(1) 119 { 120 i++; 121 if(i%100000 == 0) 122 printk(KERN_NOTICE "my_start_kernel here %d \n",i); 123 124 }*/ 125 } 126 127 void my_process(void) //进程的处理任务,每个进程都一样 128 { 129 int i = 0; 130 while(1) //死循环 131 { 132 i++; 133 if(i%10000000 == 0) //每10000000个指令周期检测一次是否调度 134 { 135 printk(KERN_NOTICE "THIS IS PROCESS %d -\n ",my_current_task->pid); 136 if(my_need_sched == 1) 137 { 138 my_need_sched =0; 139 my_schedule(); //发生调度 140 } 141 printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid); 142 143 } 144 145 } 146 }
myinterrupt.c
#include <linux/kernel_stat.h> #include <linux/export.h> #include <linux/interrupt.h> #include <linux/percpu.h> #include <linux/init.h> #include <linux/mm.h> #include <linux/swap.h> #include <linux/pid_namespace.h> #include <linux/notifier.h> #include <linux/thread_info.h> #include <linux/time.h> #include <linux/jiffies.h> #include <linux/posix-timers.h> #include <linux/cpu.h> #include <linux/syscalls.h> #include <linux/delay.h> #include <linux/tick.h> #include <linux/kallsyms.h> #include <linux/irq_work.h> #include <linux/sched.h> #include <linux/sched/sysctl.h> #include <linux/slab.h> #include <asm/uaccess.h> #include <asm/unistd.h> #include <asm/div64.h> #include <asm/timex.h> #include <asm/io.h> #define CREATE_TRACE_POINTS #include <trace/events/timer.h> #include "mypcb.h" extern tPCB task[MAX_TASK_NUM]; extern tPCB * my_current_task; extern volatile int my_need_sched; volatile int time_count = 0; /******* Called by timer interrupt.******/ void my_timer_handler(void) //定时器中断的调用函数 { #if 1 if(time_count%1000 == 0 && my_need_sched != 1) { printk(KERN_NOTICE ">>>my_timer_handler here<<<\n"); my_need_sched = 1; } time_count ++ ; #endif return; } void my_schedule(void) //调用函数 { tPCB * next; tPCB * prev; if(my_current_task == NULL || my_current_task->next == NULL) { return; //任务无效则退出 } printk(KERN_NOTICE ">>>my_schedule<<<\n"); //开始调度 next = my_current_task->next; prev = my_current_task; if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */ { /* switch to next process */ asm volatile( //进程调度的关键代码,相关的堆栈操作 "pushl %%ebp\n\t" /* save ebp */ "movl %%esp,%0\n\t" /* save esp */ "movl %2,%%esp\n\t" /* restore esp */ "movl $1f,%1\n\t" /* save eip */ "pushl %3\n\t" "ret\n\t" /* restore eip */ "1:\t" "popl %%ebp\n\t" : "=m" (prev->thread.sp),"=m" (prev->thread.ip) : "m" (next->thread.sp), "m" (next->thread.ip) ); my_current_task = next; printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid); } else { //如果下一任务(进程)不为0,从未被调度过 next->state = 0; my_current_task = next; printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid); asm volatile( "pushl %%ebp\n\t" "movl %%esp,%0\n\t" "movl %2,%%esp\n\t" "movl %2,%%ebp\n\t" "movl $1f,%1\n\t" "pushl %3\n\t" "ret \n\t" : "=m" (prev->thread.sp),"=m" (prev->thread.ip) : "m" (next->thread.sp),"m" (next->thread.ip) ); } return; }
三 代码以及原理分析
在代码的已经进行了简要的注释,下面进行分析:首先在mypcb.h文件中定义了进程控制块的结构体 ,其中成员pid代表的是进程号(在内核中唯一标识一个进程),state代表进程的状态,为了简化,并没有列出我们在操作系统中学到的所有的进程状态,0代表运行态,-1非运行态,>0停止态,“struct Thread thread;”定义了进程堆栈操作相关的寄存器;“task_entry;”定义了进程的入口,即具体的处理函数;“struct PCB *next;”定义指向下一进程的指针。首先通过“void __init my_start_kernel(void)”进行初始化0号进程,指定了进程0的入口my_process();并且初始化进程1,2,3,构建了进程链表,接下来用嵌入式汇编使进程运行起来,即执行my_process()函数,可以看到函数内部有个while(1)死循环,每10000000个周期打印进程号信息、检测一次是否发生了调度,如果发生了调度,清除调度的标识my_need_sched,(就是使本进程能够被抢占,相当于清信号量的意思)就执行调度函数my_schedule();调度的标识 my_need_sched在哪里发生改变呢?这涉及到定时器中断了——在my_interrupt.c文件可以看到“void my_timer_handler(void)”,这就是定时器中断的处理函数,当定时中断次数达到1000次时,改变一次调度标志 ,表示能够调度了(主动调度),接下来就是调度函数进行调度了,其中的两段嵌入式汇编就是调度的关键,本质就是修改eip,指向要调度的函数入口,并进行进程上下文的保存(堆栈的方式)。后面就是无限循环了, 每10000000个周期检测一次是否发生了调度,这在期间定时器中断修改调度标识,使能发生调度。。。。。。
总结:个人对“操作系统如何工作的理解”
操作系统是对硬件的抽象,摆脱了用户直接操作硬件(否则想要用电脑之前必须成为编程高手),所以操作系统在电脑大众化的过程中扮演了不可磨灭的角色。同时操作系统是对硬件的最大化利用,在多用户系统中,使每个用户感觉自己在独享电脑;在多任务系统中,使每个进程(任务)感觉自己在霸占cpu。。。闲话不多说,要想要操作系统实现上述的功能(不发生混乱的局面),必须安排好调度机制,一种机制就是时间片轮转机制,就是多用户或者多任务轮流使用cpu,且使用时间有限,时间到了必须让出cpu,但是谁能担当这个安排时间角色呢?这就需要硬件的帮忙——定时器,当设定好规定时间后,当定时的时间到了,就以中断(异步)方式通知cpu作出相应的动作。。。。这是这种机制使计算机有序完成各种任务——任务管理,内存管理,文件管理,I/O设备管理等。
声明:1.实验环境基于网易实验平台——实验楼的64位linux环境。
2. 代码借鉴了孟宁老师的代码。
3.错误之处恳请指导~~~
by:方龙伟
原创作品 转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000