您的位置:首页 > 运维架构 > Linux

linux内核进程调度分析总结

2016-03-06 17:29 549 查看
作者:王鹤楼

原创作品转载请注明出处

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

在孟老师给出的mykernel的基础上增加mypcb.h

#define MAX_TASK_NUM 4  //定义总有4个任务/进程
#define KERNEL_STACK_SIZE 1024*8    //每个进程的堆栈大小为8KB

/*用于保有存当前进程的esp和eip*/
struct Thread {
unsigned long ip; //eip
unsigned long sp; //esp
};

/*定义进程结构体*/
typedef struct PCB {
int pid;//进程ID
volatile long state; //进程状态
char stack[KERNEL_STACK_SIZE];//进程堆栈
struct Thread thread;//保存esp、eip
unsigned long task_entry;//f进程入口
struct PCB * next;//在进程链表中指向下一个进程
struct PCB * prev;//在进程链表中指向上一个进程
}tPCB;

/*声明调度函数*/
void my_schedule(void);


mymain.c文件:

#include "mypcb.h"

/*定义一个数组,保有存所有进程结构*/
tPCB task[MAX_TASK_NUM];
/*定义一个进程指针,指向当前正在运行的进程*/
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;
task[pid].state = 0;
/*进程入口指向进程处理函数*/
task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
/*指向进程栈进址*/
task[pid].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE -1];
/*进程链表中,next指向指向自己*/
task[pid].next = &task[pid];

/*创建其他进程*/
for(i=1; i< MAX_TASK_NUM; i++)
{
/*把0号进程的状态拷贝给当前进程,不同成员值再单独赋值*/
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];
}

/* 运行0号进程*/
pid = 0;
/*把0号进程的地址赋给当前进程指针*/
my_current_task = &task[pid];
/*嵌入式汇编中%0,%1,%2,%3表示下面的参数,编号从0开始*/
asm volatile(
"movl %1, %%esp\n\t" /*把当前进程的栈指针赋给esp*/
"pushl %1\n\t"/*把当前进程的栈指保存到栈中*/
"pushl %0\n\t"/*保存当前进程的eip到栈中*/
"ret\n\t"/*返回操作*/
"popl %%ebp\n\t"/*弹出栈顶内容赋给ebp*/
:
: "c" (task[pid].thread.ip), "d" (task[pid].thread.sp)/*ecx=当前进程的eip, edx=当前进程的栈指针*/
);
//到此为止,0号进程就启动起来了
//end
}

void  my_process(void)
{
int i=0;
while(1)
{
i++;
if(i%100 == 0) {
printk(KERN_NOTICE "this is process %d- \n", my_current_task->pid);
/*判断是否需要切换进程,1需要切换,0不需要切换*/
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 "mypcb.h"
/*引入外部变量*/
extern tPCB task[MAX_TASK_NUM];
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = 0;
/*
* 定时器中断处理函数
*/
void my_timer_handler(void)
{
/*判断是否需要切换进程*/
if(time_count % 100 == 0 && my_need_sched != 1) {
printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
/*将进程切换标记为需要切换*/
my_need_sched = 1;
}
/* 计数器自加 */
time_count ++;

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;
//state:-1表示不可用,0表示正在执行,>0表示没有运行
if(next->state == 0) {
/*切换进程*/
asm volatile (
"pushl %%ebp\n\t" /*保存当前进程的ebp*/
"movl %%esp, %0\n\t" /*保存当前的栈指针到上一个进程的sp中*/
"movl %2, %%esp\n\t" /*把下一个进程的sp赋给esp*/
"movl $1f, %1\n\t" /*保存当前进程的eip到上进一个进程的ip中*/
"pushl %3\n\t" /*将下一个进程的ip压到栈中*/
"ret\n\t"/*返回*/
"1:\t"
"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);
asm volatile (
"pushl %%ebp\n\t"
"movl %%esp, %0\n\t"
"movl %2, %%esp\n\t"
"movl %2, %%ebp\n\t"
"movl $1f, %1\n\t"
"pushl %3\n\t"
"ret\n\t"
"popl %%ebp\n\t"
:"=m" (prev->thread.sp), "=m" (prev->thread.ip)
:"m" (next->thread.sp), "m" (next->thread.ip)
);
}
}


make 重新编译

运行程序:

qemu -kernel arch/x86/boot/bzImage

运行效果图





总结:

进程切换的关键是保存现场和恢复现场,当切换到下一个进程时要把当前进程用的变量,栈,和相关的寄存器保存到栈中,这一步叫保存现场。当切换回这个进程时再把这些变量,寄存器恢复,这一步叫恢复现场。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: