一个精简的linux内核分析

2019-07-14 09:56发布

作者:沈鑫 原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 首先是mypcb.h,这里主要定义了两个结构体分别用于表示Thread 和 PCB /* * linux/mykernel/mypcb.h * * Kernel internal PCB types * * Copyright (C) 2013 Mengning * */ #define MAX_TASK_NUM 4 #define KERNEL_STACK_SIZE 1024*8 /* CPU-specific state of this task */ struct Thread { unsigned long ip; unsigned long sp; }; typedef struct PCB{ int pid; volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ char stack[KERNEL_STACK_SIZE]; /* CPU-specific state of this task */ struct Thread thread; unsigned long task_entry; struct PCB *next; }tPCB; void my_schedule(void); 然后是myiinterrupt.c /* * linux/mykernel/myinterrupt.c * * Kernel internal my_timer_handler * * Copyright (C) 2013 Mengning * */ #include #include #include #include #include #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. * it runs in the name of current running process, * so it use kernel stack of current running process */ //这里是时间控制程序,定义多长时间更换一下进程状态 void my_timer_handler(void) { #if 1 if(time_count%1000 == 0 && my_need_sched != 1) //这里这设置的是进程的运行时间 { printk(KERN_NOTICE ">>>my_timer_handler here<<< "); 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<<< "); /* schedule */ 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 " /* save ebp */ "movl %%esp,%0 " /* save esp */ "movl %2,%%esp " /* restore esp */ "movl $1f,%1 " /* save eip */ "pushl %3 " "ret " /* restore eip */ "1: " /* next process start here */ "popl %%ebp " : "=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<<< ",prev->pid,next->pid); } else //当CPU中只有一个进程的时候,程序会执行到这个位置 { next->state = 0; my_current_task = next; printk(KERN_NOTICE ">>>switch %d to %d<<< ",prev->pid,next->pid); /* switch to new process */ //这段嵌入式汇编实现了当前进程状态的保存,以及跳转到下一进程 asm volatile( "pushl %%ebp " /* save ebp */ "movl %%esp,%0 " /* save esp */ "movl %2,%%esp " /* restore esp */ "movl %2,%%ebp " /* restore ebp */ "movl $1f,%1 " /* save eip */ "pushl %3 " "ret " /* restore eip */ : "=m" (prev->thread.sp),"=m" (prev->thread.ip) : "m" (next->thread.sp),"m" (next->thread.ip) ); } return; } 最后是mymain.c /* * linux/mykernel/mymain.c * * Kernel internal my_start_kernel * * Copyright (C) 2013 Mengning * */ #include #include #include #include #include #include "mypcb.h" tPCB task[MAX_TASK_NUM]; //这里定义了最大任务数量,也就是4 tPCB * my_current_task = NULL; //这个指针指向当前任务 volatile int my_need_sched = 0; //控制进程状态 void my_process(void); void __init my_start_kernel(void) { //最开始内核中是没有任务的,这里对第一个任务进行处理 int pid = 0; int i; /* Initialize process 0*/ task[pid].pid = pid; task[pid].state = 0;/* -1 unrunnable, **0 runnable**, >0 stopped */ //第一个任务的入口地址就是my_process方法的入口地址 task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; //设置第一个任务的占空间 task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; task[pid].next = &task[pid]; //此时系统中只有当前一个任务,所以next指向的地址就是自身的地址 /*fork more process */ //生成MAX_TASK-1个新的进程 for(i=1;imemcpy(&task[i],&task[0],sizeof(tPCB)); //新的进程的进程空间与0号进程相同 //接下来几句是对进程进行初始化 task[i].pid = i; task[i].state = -1; //state == -1,表明当前进程不可执行 task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1]; //让当前进程的下一个进程指向第0号进程,因为第0号进程最初指向的是自己 task[i].next = task[i-1].next; //上一个进程指向的是当前进程 task[i-1].next = &task[i]; } /* start process 0 by task[0] */ pid = 0; my_current_task = &task[pid]; //从第0号进程开始 //这里是嵌入式汇编 asm volatile( "movl %1,%%esp " /* set task[pid].thread.sp to esp */ "pushl %1 " /* push ebp */ //这里将task[pid].thread.ip入栈,在ret的时候就能进入这个入栈的进程 "pushl %0 " /* push task[pid].thread.ip */ "ret " /* pop task[pid].thread.ip to eip */ "popl %%ebp " : : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/ ); } void my_process(void) { int i = 0; while(1) { i++; if(i%100000000 == 0) //这里是设置时间片的大小,设置大一些方便看 { printk(KERN_NOTICE "this is process %d - ",my_current_task->pid); if(my_need_sched == 1) { my_need_sched = 0; //当这个状态为0时,才可以进行进程的切换 my_schedule(); } printk(KERN_NOTICE "this is process %d + ",my_current_task->pid); } } } 这里写图片描述 对进程的分析:
1、CPU首先通过这个方法创建了第一个进程,并对进程进行了初始化工作 void __init my_start_kernel(void) { int pid = 0; int i; /* Initialize process 0*/ task[pid].pid = pid; task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */ task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; task[pid].next = &task[pid]; /*fork more process */ for(i=1;itask[i],&task[0],sizeof(tPCB)); task[i].pid = i; task[i].state = -1; task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1]; task[i].next = task[i-1].next; task[i-1].next = &task[i]; } /* start process 0 by task[0] */ pid = 0; my_current_task = &task[pid]; asm volatile( "movl %1,%%esp " /* set task[pid].thread.sp to esp */ "pushl %1 " /* push ebp */ "pushl %0 " /* push task[pid].thread.ip */ "ret " /* pop task[pid].thread.ip to eip */ "popl %%ebp " : : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/ ); } 2、这段程序创建了多个进程,并对进程进行了初始化 /*fork more process */ for(i=1;itask[i],&task[0],sizeof(tPCB)); task[i].pid = i; task[i].state = -1; task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1]; task[i].next = task[i-1].next; task[i-1].next = &task[i]; } 3、由于第一个进程的入口地址被指向了my_process方法,所以从第一个进程开始便开始执行my_process方法 task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; 4、my_process方法中对my_need_sched 的状态进行了修改,使得CPU能够执行到my_timer_handler方法中的printk方法;
并且调用了my_schedule()方法,更新了进程表 my_need_sched = 0; my_schedule(); 5、当内核中只有一个进程时,首先执行的是else这段代码,但当内核中不止一个进程时,就不会在进入else这段代码了。 else { next->state = 0; my_current_task = next; printk(KERN_NOTICE ">>>switch %d to %d<<< ",prev->pid,next->pid); /* switch to new process */ asm volatile( "pushl %%ebp " /* save ebp */ "movl %%esp,%0 " /* save esp */ "movl %2,%%esp " /* restore esp */ "movl %2,%%ebp " /* restore ebp */ "movl $1f,%1 " /* save eip */ "pushl %3 " "ret " /* restore eip */ : "=m" (prev->thread.sp),"=m" (prev->thread.ip) : "m" (next->thread.sp),"m" (next->thread.ip) ); } 6、下面我们来分析一下里面的汇编代码。
“movl $1f,%1 ”实际是将下一个进程的地址赋给下一个进程的eip,
在”pushl %3 ”之后将这个eip压栈,所以执行接下来的”ret ”
语句之后就跳转到了下一个进程的入口地址,也就是标号1的位置。 /* switch to next process */ asm volatile( "pushl %%ebp " /* save ebp */ "movl %%esp,%0 " /* save esp */ "movl %2,%%esp " /* restore esp */ "movl $1f,%1 " /* save eip */ "pushl %3 " "ret " /* restore eip */ "1: " /* next process start here */ "popl %%ebp " : "=m" (prev->thread.sp),"=m" (prev->thread.ip) : "m" (next->thread.sp),"m" (next->thread.ip) ); 7、由于在my_start_kernel中对进程表进行初始化时使用的是如下方法,所以进程之间形成了一循环,
也就是0->1->2->3->0。 //让当前进程的下一个进程指向第0号进程,因为第0号进程最初指向的是自己 task[i].next = task[i-1].next; //上一个进程指向的是当前进程 task[i-1].next = &task[i]; 总结:
在时间片轮转调度中,所有进程在进程表中形成了一个有向的循环,这样就能实现循环调度了。
next->thread.ip指向的既是当前进程的下一个进程的入口地址,也是下一个进程的当前进程
的入口地址。