首先回顾了下堆栈相关的知识,堆栈机制是高级语言能够运行的一个基础,这一块须要重点掌握。函数发生调用时,如图
html
call指令:将eip的按顺序执行的下一条指令(由于在执行call的时候,eip保存的是call语句下一条指令的地址)的地址保存在当前栈顶,而后设置eip的值为要跳转到的函数的开始的地址
ret指令:将以前使用call指令的保存在栈里面的地址恢复到eip中去。linux
用本身的Ubuntu来搭建实验所须要的环境。可是在使用用apt-get命令安装软件包时,总报错:E:could not get lock /var/lib/dpkg/lock -open等,因而上网搜寻了一下缘由,多是有另一个程序正在运行,致使资源被锁不可用。而致使资源被锁的缘由,多是上次安装时没正常完成,而致使出现此情况。解决办法就是输入命令git
sudo rm /var/cache/apt/archives/lock sudo rm /var/lib/dpkg/lock
而后就能够开始安装编译qemu了(linux下一个相似于虚拟机的软件),步骤的命令以下:(由于要输入的命令挺多的,因此先安装了vmtools,这一步骤参考:Ubuntu16安装VM toolsgithub
sudo apt-get install qemu # install QEMU sudo ln -s /usr/bin/qemu-system-i386 /usr/bin/qemu wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.9.4.tar.xz # download Linux Kernel 3.9.4 source code wget https://raw.github.com/mengning/mykernel/master/mykernel_for_linux3.9.4sc.patch # download mykernel_for_linux3.9.4sc.patch xz -d linux-3.9.4.tar.xz tar -xvf linux-3.9.4.tar cd linux-3.9.4 patch -p1 < ../mykernel_for_linux3.9.4sc.patch make allnoconfig make
在最后的编译环节出错了,没有找到 compiler-gcc5.h 这样一个文件,上网查了查,是个人Ubuntu系统太新的缘由。须要咱们本身手动去网上下一个compiler-gcc5.h放入要编译内核模块的内核代码的include/linux下,找了好久没有找到compiler-gcc5.h这个文件,从新下载gcc5.1.0这个版本又下载不下来,因此就直接用了另一种办法—— **将咱们下载的/linux-3.9.4/include/linux/中的compiler-gcc4.h复制了一份而后更名为compiler-gcc5.h,最后编译成功。
当咱们执行命令编程
qemu -kernel arch/x86/boot/bzImage
以后,就弹出一个新的窗口,不断的输出信息,表示启动内核了,如图
数组
到/mykernel目录下查看mymain.c文件,如图
函数
void __init my_start_kernel(void) { int i = 0; while(1) { i++; if(i%100000 == 0) printk(KERN_NOTICE "my_start_kernel here %d \n",i); } }
有一个my_start_kernel()函数,故名思义,应该就是内核开始的函数。里面写了一个死循环,变量i不停的自加1,每当加到1000000的整数倍的时候就打印出当前的i的值。而后查看另外一个文件myinterrupt.c,如图
学习
定义了一个my_timer_handler()的函数this
/* * Called by timer interrupt. */ void my_timer_handler(void) { printk(KERN_NOTICE "\n>>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<\n\n"); }
只有一条打印语句, 这里有一点要注意,内核编程的打印语句是printk而不是printf。
因为CPU处理速度很快,因此屏幕不停的交替打印出输出信息,而且一闪而过。
上面这是一个极其简单的时间片,下面分析一个稍微复杂的进程切换的程序代码。一共三个文件,分别是mypcb.h,mymain.c,myinterrupt.c。在学习分析代码的时候都用本身学习到的和本身的理解对代码进行了注释,下面给出相关代码和注释。
先是mypcb.h中的代码,这个头文件的目的是定义一下进程控制块指针
#define MAX_TASK_NUM 4 #define KERNEL_STACK_SIZE 1024*8 /* CPU-specific state of this task */ struct Thread { unsigned long ip;//保存eip unsigned long sp;//保存esp }; typedef struct PCB{ int pid; //进程的id 进程的状态 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);
文件的最后声明了一个schedule函数,即调度器。
第二个main.c中的代码以下
#include <linux/types.h> #include <linux/string.h> #include <linux/ctype.h> #include <linux/tty.h> #include <linux/vmalloc.h> #include "mypcb.h" tPCB task[MAX_TASK_NUM]; //PCB的数组task tPCB * my_current_task = NULL; //当前task的指针 volatile int my_need_sched = 0; //是否须要调度 void my_process(void); //声明了一个my_process函数 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;//初始化为my_process,实际上为mystartkernel task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; task[pid].next = &task[pid];//刚一启动只有0号进程 /*fork more process */ for(i=1;i<MAX_TASK_NUM;i++) { memcpy(&task[i],&task[0],sizeof(tPCB)); //将0号进程状态copy过来 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]; //新fork的进程加到进程列表的尾部 } //建立了MAX_TASK_NUM个进程 /* start process 0 by task[0] */ pid = 0; my_current_task = &task[pid]; //当前的进程是0号进程 asm volatile( "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */ //第一号(task[pid].thread.sp)参数放到esp "pushl %1\n\t" /* push ebp */ //当前堆栈空,esp==ebp "pushl %0\n\t" /* push task[pid].thread.ip */ //当前ip压栈 "ret\n\t" /* pop task[pid].thread.ip to eip */ //ret后0号进程正式启动 "popl %%ebp\n\t" : : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/ ); //构建起来了cpu的运行环境(0号进程设定的堆栈和0号进程的入口) };内核初始化完成,把0号进程启动起来了 void my_process(void) { int i = 0; while(1) { i++; if(i%10000000 == 0) { printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);//执行一千万次输出一个,这是几号进程 if(my_need_sched == 1) //是否须要调度 { my_need_sched = 0; my_schedule(); } printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid); } } }//主动调度机制
接下来是myinterrupt.c里面的代码
#include<linux/types.h> #include<linux/string.h> #include<linux/ctype.h> #include<linux/tty.h> #include<linux/vmalloc.h> #include "mypcb.h" extern tPCB task[MAX_TASK_NUM]; extern tPCB * my_current_task; extern volatile int my_need_sched; //把全局的东西extern来 volatile int time_count = 0; //时间计数 void my_timer_handler(void) { #if 1 if(time_count%1000 == 0 && my_need_sched != 1) //时钟中断发生1000次而且my_need_sched不为1(设置时间片的大小,时间片用完时设置一下调度标志,时间片变小调度更频繁) { printk(KERN_NOTICE ">>>my_timer_handler here<<<\n"); my_need_sched = 1; //当进程执行到的时候,发现为1,调度一次,执行一下my_schedule } 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"); /* schedule */ next = my_current_task->next; //当前进程的下一个进程赋给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" /* next process start here */ "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 //切换到新进程的方法 { next->state = 0; //进程设置为运行时状态 my_current_task = next; //进程做为当前正在执行的进程 printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid); /* switch to new process */ asm volatile( "pushl %%ebp\n\t" /* save ebp */ "movl %%esp,%0\n\t" /* save esp */ "movl %2,%%esp\n\t" /* restore esp */ "movl %2,%%ebp\n\t" /* restore ebp */ "movl $1f,%1\n\t" /* save eip */ "pushl %3\n\t" //把当前进程的入口保存起来 "ret\n\t" /* restore eip */ : "=m" (prev->thread.sp),"=m" (prev->thread.ip) : "m" (next->thread.sp),"m" (next->thread.ip) ); } return; }
最后将这些代码复制到对应的文件里面去,保存(本来没有mypcb.h文件,因此要另外建立一个mypcb.h),再回到~/linux-3.9.4目录下,输入命令 make
编译一遍,再执行命令
qemu -kernel arch/x86/boot/bzImage
就可运行刚刚的代码了,结果如图所示
1.__init my_start_kernel(void)这个函数是干什么的,有什么用?第一个程序,代码最精简的那个,我看mymain.c和myinterrupt.c里面也没写什么特别的代码,可是编译运行以后就能实现相互交替的打印出来。因此我在想是__init my_start_kernel(void)这个函数有什么特别的吗?上网查询资料也没有查到想要的结果,加上本身对linux内核这一块的确是很陌生。因此这个问题未解决。