学号<202>
原创作品转载请注明出处https://github.com/mengning/linuxkernel/
sudo apt-getinstall qemu # install QEMU
sudo ln-s /usr/bin/qemu-system-i386 /usr/bin/qemu
wget--no-check-certificate https://www.kernel.org/pub/linux/kernel/v3.x/linux- 3.9.4.tar.xz # download Linux Kernel 3.9.4 source code
wget --no-check-certificate 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.4patch-p1 < ../mykernel_for_linux3.9.4sc.patch
make allnoconfig
make
其中wget指令后要加--no-check-contificate 否则认证无法通过
指令qemu -kernel arch/x86/boot/bzImage可以看到如图成果
打开mymain.c文件如图
打开myinterrupt.c文件,如图
会在start_kernel中运行,定长时间跳到Interrupt中去执行一次
二、一个简单的时间片轮转多道程序
obj-y = mymain.o myinterrupt.o
mymain.o:
cc -c mymain.c mypcb.h
myinterrupt.o:
cc -c myinterrupt.c mypcb.h
修改Makefile ,通过git-clone获取mymain.c 和 interrupt.c
程序运行如图
下面开始分析程序
structThread {
unsignedlongip;
unsignedlongsp;
};
typedefstructPCB{intpid;volatile long state; /*-1 unrunnable, 0 runnable, >0 stopped*/
charstack[KERNEL_STACK_SIZE];/*CPU-specific state of this task*/
structThread thread;
unsignedlongtask_entry;struct PCB *next;
}tPCB;
定义了进程和线程的结构体。
pid表示进程编号
state表示进程的状态
stack表示进程的栈
thread表示进程的ip sp的值
task_entry表示进程的入口,第一次执行时候指令所处的位置
next指向下一个进程的地址
mymain.c
void __init my_start_kernel(void) {int pid = 0;inti;/*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; i
memcpy(&task[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];
asmvolatile("movl %1,%%esp\n\t" /*set task[pid].thread.sp to esp*/
"pushl %1\n\t" /*push ebp*/
"pushl %0\n\t" /*push task[pid].thread.ip*/
"ret\n\t" /*pop task[pid].thread.ip to eip*/
"popl %%ebp\n\t":
:"c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /*input c or d mean %ecx/%edx*/);
}
这个整个程序的入口,首先初始化所有进程,只有进程0的运行态置为runnable,将线程定义成一个循环链表,方便之后通过next直接点用下一条进程。
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);
}
}
}
这段代码是进程要循行的,init将进程入口设置成了my_process,这个程序通过循环打印输出来表明是哪个进程到了这里面
在jinterrupt.c里面
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;
}
定时器中断时,此函数调用一次,自增一次time_count,自增到达2000次输出一次
void my_schedule(void)
{
tPCB*next;
tPCB*prev;if(my_current_task ==NULL|| my_current_task->next ==NULL)
{return;
}
printk(KERN_NOTICE">>>my_schedule<<next;
prev=my_current_task;if(next->state == 0)/*-1 unrunnable, 0 runnable, >0 stopped*/{
my_current_task=next;
printk(KERN_NOTICE">>>switch %d to %d<<pid,next->pid);/*switch to next process*/asmvolatile("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)
);
}else{
next->state = 0;
my_current_task=next;
printk(KERN_NOTICE">>>switch %d to %d<<pid,next->pid);/*switch to new process*/asmvolatile("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;
}
切换进程的函数。进程切换就是更改sp,bp,ip三个寄存器的值和pcb的结构,就可以运行在不用的线程下
汇编指令解析
1.ebp入栈,保存栈底
2.esp给prev->thread.sp 将当前esp保存到进程的sp
3.next给thread.sp->esp,切换的进程sp给esp,因为这个进程曾经就是这么获取的esp的值
4.$1f表示段1地址,即将段1地址->prev->thread.ip 当前进程如果再去执行的话要到段1去执行
56.将next->thread.ip给eip
7.栈顶元素pop作为ebp
两次操作ebp,先将进程的ebp入栈,然后执行下一个进程,执行完下一个进程后,esp不变,ebp就能够重新得到ebp,能起到保护ebp的作用,也不需要保存ebp的值在数据结构中