原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
QEMU是一套由Fabrice Bellard所编写的以GPL许可证分发源码的模拟处理器,在GNU/Linux平台上使用广泛。Bochs,PearPC等与其类似,但不具备其许多特性,比如高速度及跨平台的特性,通过KQEMU这个闭源的加速器,QEMU能模拟至接近真实电脑的速度。
程序清单如下:
程序文件 | 说明 |
mypcb.h | 自定义PCB结构定义 |
mymain.c | 内核启动入口 |
myinterrupt.c | 定时器中断(任务切换)处理 |
本试验用于完成一个简单的时间片轮转多道程序内核代码,根据实验的demo代码修改,并加上详细注释。
mypcb.h
#ifndef _MY_PCB_H_
#define _MY_PCB_H_
#define MAX_TASK_NUM 4 /*最大任务数*/
#define KERNEL_STACK_SIZE 1024 /*默认进程堆栈字节*/
#define S_UNRUNNABLE -1 /*进程未运行状态(unrunnable)*/
#define S_RUNNABLE 0 /*进程运行状态(runnable)*/
#define S_STOPPED 1 /*进程停止状态(stopped)*/
typedef struct _Thread
{
uint32_t ip;/*保存eip*/
uint32_t sp;/*保存esp*/
}Thread_t;
typedef struct PCB
{
/*进程控制符Process Identifier*/
int pid;
/*进程状态*/
volatile long state;
/*进程堆栈*/
char stack[KERNEL_STACK_SIZE];
/*上下文切换状态*/
Thread_t thread;
/*进程任务入口*/
uint32_t task_entry;
/*PCB队列节点*/
struct PCB *next;
}tPCB;
/*任务调度处理*/
void my_schedule(void);
#endif //_MY_PCB_H_
mymain.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队列*/
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 = 0;
/*初始化0号进程*/
task[pid].pid = pid; /*0号进程*/
task[pid].state = S_RUNNABLE; /*默认0号进程为运行状态*/
/*设置进程任务入口地址并初始化进程的eip*/
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];
/*创建更多进程*/
for(i=1;i<MAX_TASK_NUM;i++)
{
memcpy(&task[i],&task[0],sizeof(tPCB));
/*设置PID*/
task[i].pid = i;
/*默认进程未运行状态*/
task[i].state = S_UNRUNNABLE;
/*初始化进程堆栈为空栈*/
task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];
/*循环队列,设置下一个进程控制块(PDB)为第一个节点*/
task[i].next = task[i-1].next;
/*追加加到前驱节点的后继节点*/
task[i-1].next = &task[i];
}
/*运行0进程*/
pid = 0;
my_current_task = &task[pid];
/*
内嵌汇编转换为32位AT&T汇编
ecx = task[0].thread.ip(%0) 0进程的入口地址保持到ecx寄存器中
edx = task[0].thread.sp(%1) 0进程的堆栈指针保存到edx寄存器中
movl %edx,%esp 将esp寄存器设置为进程堆栈指针
pushl %edx 等价于ebp入栈保护(此时esp==ebp)
pushl %ecx 将ecp(即进程入口地址)压栈
ret ret操作修改eip为进程入口地址即执行(my_process)
popl %ebp
*/
asm volatile
(
"movl %1,%%esp\n\t"
"pushl %1\n\t"
"pushl %0\n\t"
"ret\n\t"
"popl %%ebp\n\t"
:
: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)
);
}
void my_process(void)
{
int i = 0;
while(1)
{
i++;
/*10000000(执行100万次调度一次)*/
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]; /*PCB队列*/
extern tPCB * my_current_task; /*当前任务节点*/
extern volatile int my_need_sched; /*调度标志*/
volatile int time_count = 0; /*时间计数*/
/*
定时器中断处理函数
*/
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 = NULL;
tPCB *prev = NULL;
if( !my_current_task || !my_current_task->next )
{
return;
}
printk(KERN_NOTICE ">>>my_schedule<<<\n");
/* 开始调度 */
next = my_current_task->next;
prev = my_current_task;
if(S_RUNNABLE == next->state)
{
/*切换进程上下文*/
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 = S_RUNNABLE;
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;
}
更新代码内核代码后编译新内核并运行模拟器:
可见进程在进行上下文切换
关于时间片轮转多道程序内核代码分析
【当前进程与处于运行状态的目标进程切换】 asm volatile(
1 "pushl %%ebp\n\t"
/* save ebp */
2 "movl %%esp,%0\n\t"
/* save esp */
3 "movl %2,%%esp\n\t"
/* restore
esp */
4 "movl $1f,%1\n\t"
/* save eip */
5 "pushl %3\n\t"
6 "ret\n\t"
/* restore
eip */
"1:\t"
/* next process start here */
7 "popl %%ebp\n\t"
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
| -------------------------------------------------------------------------------- %0: prev->thread.sp(原进程的栈顶指针) %1:prev->thread.ip(原进程的指令指针) %2:next->thread.sp(目标进程的栈顶指针) %3:next->thread.ip(目标进程的指令指针) 1、ebp入栈保护 2、当前esp保存到调度前的原进程控制块中(thread.sp) 3、将调度后目标进程的原esp恢复到esp寄存器中 4、保存eip的值 其中立即数"1f"是标号"1:"相对于当前指令的偏移 以便下次恢复eip后从“1:”处继续执行 5、调度后的目标进程eip入栈 6、ret操作将恢复eip,开始目标进程的任务 7、待执行完成后ebp出栈恢复 |
目标进程没有运行的情况
【当前进程与处于非运行状态的目标进程切换】 asm volatile(
1 "pushl %%ebp\n\t"
/* save ebp */
2 "movl %%esp,%0\n\t"
/* save esp */
3 "movl %2,%%esp\n\t"
/* restore
esp */
4 "movl %2,%%ebp\n\t"
/* restore
ebp */
5 "movl $1f,%1\n\t"
/* save eip */
6 "pushl %3\n\t"
7 "ret\n\t"
/* restore
eip */
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
| %0: prev->thread.sp(原进程的栈顶指针) %1:prev->thread.ip(原进程的指令指针) %2:next->thread.sp(目标进程的栈顶指针) %3:next->thread.ip(目标进程的指令指针) 1、ebp入栈保护 2、当前esp保存到调度前的原进程控制块中(thread.sp) 3、将调度后目标进程的原esp恢复到esp寄存器中 4、将调度后目标进程的原esp恢复到ebp寄存器中 (新进程的ebp=esp) 5、保存eip的值 其中立即数"1f"是标号"1:"相对于当前指令的偏移 以便下次恢复eip后从“1:”处继续执行 (因为下次切换时目标进程为运行状态对于上面的处理逻辑) 6、调度后的目标进程eip入栈 7、ret操作将恢复eip,开始目标进程的任务 |
关于任务调度的机制其实是操作系统内核在一个时间片周期结束的时候保存任务现场,主要是当前的eip,esp等寄存器,然后恢复下一个任务的相关寄存器状态,由于计算机硬件在执行指令主要就是靠(CS:IP)来顺序执行指令,在切换过程中通过这些相关寄存器的保存与恢复从而实现任务的切换。
在某些单片机中,有些不可能实现操作系统,就是通过定时器中断与指令指针的存取实现一个模拟的伪多线程操作,其核心思想与PC上的操作系统类似。