邵帅 原创作品 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
首先是mypcb.h头文件,这个文件定义了进程控制块
#define MAX_TASK_NUM 4
#define KERNEL_STACK_SIZE 1024*8
struct Thread//定义线程结构体
{
unsigned long ip;//eip寄存器,cpu下一次要执行的指令的地址
unsigned long sp;//esp寄存器,保存的是栈顶的地址
};
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文件,在这个文件中有 my_start_kernel(void)函数:在这个函数中初始化MAX_TASK_NUM个进程控制块,并将第一个进程块通过嵌入式汇编,把第一个进程压入栈中。 my_process(void)函数中,主要是当i=100时,调用跳转 my_schedule()函数
#include "mypcb.h"
tPCB task[MAX_TASK_NUM];
tPCB *my_current_task = NULL;
volatile int my_need_sched = 0;
void my_prosses(void);
void __init my_start_kernel(void)//初始化函数
{
int pid = 0;//第一个进程
int i;
task[pid].pid = pid;//第一个进程id为0
task[pid].state = 0;//状态是0,0代表正在运行,-1代表不在运行
//程序入口是my_process方法,并把地址保存到eip寄存器中
task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_prosses;
//栈顶指针保存在堆栈最后一个位置的地址
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));
task[i].pid = 1;
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];//上一个进程指向当前进程
}
pid = 0;
my_current_task = &task[pid];//将当前进程块指向一个进程
asm volatile(
"mov %1 %%esp\n\t"//将task[pid].thread.sp放入栈顶地址的寄存器中
"pushl %1 \n\t"//将task[pid].thread.sp压栈
"pushl %0 \n\t"//将task[pid].thread.ip压栈
"ret \n\t"//栈顶出栈,将值赋给eip,即将%0赋给eip
"popl %%ebp \n\t"//栈顶出栈,将值赋给ebp,即将%1赋给ebp
:
:"c"(task[pid].thread.ip),"d"(task[pid].thread.sp)
);
}
void my_prosses(void){
int i = 0;
while (1) {
i++;
if(i % 100 == 0){//当为100时,输出当前进程是哪个
printk(KERN_NOTICE "this is process %d ---\n", my_current_task);
if(my_need_sched == 1){//进行调转到其他进程
my_need_sched = 0;
my_schedule();
}//输出调转后进程
printk(KERN_NOTICE "this is process %d +++\n", my_current_task);
}
}
}
最后是my_interrupt.c文件,在这个文件中主要定义个 my_schedule(void)函数,在这个函数中判断这个进程是不是新进程,如果不是则执行函数,如果是新进程则将前后两个进程中的esp,ebp放入当前进程中去。
#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;//指向当前进程的pcb指针
extern volatile int my_need_sched;
volatile int time_count = 0;
void my_time_handler(void){
#if 1//进行计时,当time_count为1000是进行调转
if(time_count % 1000 == 0 && my_need_sched != 1){
printk(KERN_NOTICE ">>>my_time_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){//下一个进程是运行状态时
asm volatile(//进程上下文切换
"pushl %%ebp \n\t"//将ebp中内容保存到esp中,压入栈中
"movl %%esp, %0 \n\t"//保存esp中内容到prev->thread.sp,即保存到上一个进程栈顶
"movl %2, %%esp \n\t"//保存next->thread.sp到esp中
"movl $1f, %1 \n\t"//保存eip,$1f是接下来标号1的位置
"pushl %3 \n\t"//将next->thread.ip保存到栈中
"ret \n\t"//return栈顶
"1: \t"//执行函数1
"popl %%ebp \n\t"//弹出栈到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<<<\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);
/*选择一个新进程*/
asm volatile(
"pushl %%ebp \n\t"//保存ebp
"movl %%esp, %0 \n\t"//保存esp中内容到prev->thread.sp,即保存到上一个进程栈顶
"movl %2, %%esp \n\t"//保存next->thread.sp到esp中
"movl %2, %%ebp \n\t"//保存next->thread.sp到ebp中
"movl $1f, %1 \n\t"//保存eip,$1f是接下来标号1的位置
"pushl %3 \n\t"//将next->thread.ip保存到栈中
"ret \n\t"//return栈顶
:"=m"(prev->thread.sp), "=m"(prev->thread.ip)
:"m"(next->thread.sp), "m"(next->thread.ip)
);
}
return;
}
总结:
通过学习,进程间的上下文切换主要还是要明白这个是一个堆栈,当调用的时候就依次把下一个进程的esp放入,函数的地址放入,把下一个进程的ip放入,然后在依次出栈,首先将ip出栈,因为马上要执行函数1,而函数一的地址放入的是eip中的,然后执行函数1,最后将这个进程的ebp弹出,因为当前进程的ebp保存下一个进程的esp。这样就完成的操作。