陈立立
原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
本文实现了一个简单的程序调度内核。
实验环境:操作系统Ubuntu 14.04 LTS,gcc版本4.8.4
搭建环境的方法:https://github.com/mengning/mykernel
首先在mykernel/下建立一个与程序控制块相关的头文件mypcb.h,代码如下:
/*
* linux/mykernel/mypcb.h
*
* Kernel internal PCB types
*
* Copyright (C) 2016 Chen Lili
*
*/
#defineMAX_TASK_NUM 10
#defineKERNEL_STACK_SIZE 1024*8 /* 8k bytes */
#defineUNRUNNABLE -1
#defineRUNNABLE 0
#define FALSE 0
#define TRUE 1
/* CPU-specificstate of a task, only fit for 32-bit system. */
struct Thread {
unsignedlong ip;/* instruction pointer */
unsignedlong sp;/* stack pointer */
};
typedefstruct PCB{
int pid; /* process id */
volatilelong state; /* -1 unrunnable, 0 runnable */
char stack[KERNEL_STACK_SIZE];
/* CPU-specific state of this task */
struct Thread thread;
unsignedlong task_entry;
struct PCB *next;
} tPCB;
void my_schedule(void);
该头文件定义了程序控制块的结构体。该结构体中包括了:
1. 进程ID,类型int。进程在操作系统中的唯一标识。
2. 进程状态,类型volatile long。目前支持“可运行”和“不可运行”。
3. 进程栈空间,类型char[]。本次代码中将栈大小设置为8k字节。
4. 当前线程。用于标识进程中正在执行的线程。
5. 进程的入口地址。
6. 下一个进程指针。用于指向下一个需要执行的进程的PCB。
同时,头文件中还定义了线程的结构体。该结构体中包括了:
1. 指令指针。线程执行的下一条指令的地址。
2. 栈顶指针。线程的栈顶指针。
此外,头文件中还声明了调度函数my_schedule()。
接下来介绍进程调度的主程序mymain.c
/*
* linux/mykernel/mymain.c
*
* Kernel internal my_start_kernel
*
* Copyright (C) 2016 Chenlili
*
*/
/*
* Headers are omitted.
*/
#ifdefCONFIG_X86_LOCAL_APIC
#include
#endif
#include"mypcb.h"
#define SLICING(1000000000)
tPCB task[MAX_TASK_NUM];
tPCB *my_current_task =NULL;
volatileint my_need_schedule = FALSE;
void my_process(void);
void __init my_start_kernel(void)
{
int pid =0;
int i;
/* Initialize process 0 */
task[pid].pid = pid;
task[pid].state = RUNNABLE;
task[pid].task_entry = task[pid].thread.ip =(unsignedlong)my_process;
task[pid].thread.sp =(unsignedlong)&task[pid].stack[KERNEL_STACK_SIZE -1];
task[pid].next =&task[pid];
/* fork more process */
for(i =1; i < MAX_TASK_NUM; i ++){
memcpy(&task[i],&task[0],sizeof(tPCB));
task[i].pid = i;
task[i].state = UNRUNNABLE;
task[i].thread.sp =(unsignedlong)&task[i].stack[KERNEL_STACK_SIZE -1];
task[i].next = task[i-1].next;
task[i-1].next =&task[i];
}
/* start process by task[0] */
pid =0;
my_current_task =&task[pid];
my_need_schedule = TRUE;
asm volatile(
"movl %1,%%esp\n\t" /* settask[pid].thread.sp to esp */
"pushl %1\n\t" /* push ebp */
"pushl %0\n\t" /* push task[pid].thread.ip*/
"ret\n\t" /* poptask[pid].thread.ip to eip */
"popl %%ebp\n\t"
:
:"c"(my_current_task->thread.ip),"d"(my_current_task->thread.sp)/* "c" = ecx, "d" =edx*/
);
}
void my_process(void){
int i =0;
while(1){
i ++;
if(i % SLICING ==0){
printk(KERN_NOTICE "This is process %d -\n", my_current_task->pid);
if(my_need_schedule == TRUE){
my_need_schedule = FALSE;
my_schedule();
}
printk(KERN_NOTICE "This is process %d +\n", my_current_task->pid);
}
}
}
初始化进程队列,将所有进程添加到队列中。队列链表为单向循环链表。
最后介绍简单调度的核心代码,myinterupt.c。
/*
* linux/mykernel/myinterrupt.c
*
* Kernel internal my_timer_handler
*
* Copyright (C) 2013 Mengning
*
*/
/*
* Headers are omitted.
*/
#defineCREATE_TRACE_POINTS
#include
#include"mypcb.h"
#define SLICING1000
extern tPCB task[MAX_TASK_NUM];
extern tPCB *my_current_task;
externvolatileint my_need_schedule;
volatileint time_count =0;
/*
* Called by timer interrupt.
* It runs in the name of the current process.
*/
void my_timer_handler(void)
{
if(time_count % SLICING ==0&& my_need_schedule != TRUE){
printk(KERN_NOTICE ">>>>>>>>>>>>>>>>>my_timer_handlerhere<<<<<<<<<<<<<<<<<<\n");
my_need_schedule = TRUE;
}
time_count ++;
return;
}
void my_schedule(void){
tPCB *next;
tPCB *prev;
if(my_current_task ==NULL
|| my_current_task->next ==NULL){
printk(KERN_ERR "Error occurs in my_schedule!");
return;
}
printk(KERN_NOTICE ">>>>>>>>>>>>>>>>>my_schedule<<<<<<<<<<<<<<<<<<\n");
next = my_current_task->next;
prev = my_current_task;
if(next->state == RUNNABLE){
/* switch to next process */
asm volatile(
"pushl %%ebp\n\t" /* save prevprocess’s ebp */
"movl %%esp,%0\n\t" /* save prev process’s esp */
"movl %2,%%esp\n\t" /* restore nextprocess's ebp */
"movl $start,%1\n\t" /* save eip, $start就是指标号start:的代码在内存中存储的地址*/
"pushl %3\n\t"
"ret\n\t" /* restore nextprocess's eip */
"start:\n\t" /* next process start here */
"popl %%ebp\n\t" /* restore next process's 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 from process %d toprocess %d <<<<<<\n",
prev->pid, next->pid);
}else{
next->state = RUNNABLE;
my_current_task= next;
printk(KERN_NOTICE ">>>>>> switch from process %d toprocess %d <<<<<<\n",
prev->pid, next->pid);
/* switch to next process */
asm volatile(
"pushl %%ebp\n\t" /* save prevprocess’s ebp */
"movl %%esp,%0\n\t" /* save prevprocess’s esp */
"movl %2,%%esp\n\t" /* restore nextprocess's sp to esp */
"movl %2,%%ebp\n\t"
/* restore next process's sp to ebp
* because the process has neverrun
* bp equals to sp
*/
"movl $start,%1\n\t" /* save eip */
"pushl %3\n\t" /* restore next process's eip, hereeip->my_process */
"ret\n\t"
:"=m"(prev->thread.sp),"=m"(prev->thread.ip)
:"m"(next->thread.sp),"m"(next->thread.ip)
);
}
my_need_schedule = TRUE;
}