前言
本文的内容来自于《Linux内核分析》MOOC课程。
文中实现对进程控制块结构(PCB)提到定义以及一个简单的Linux内核调度函数。
实验环境
操作系统Ubuntu 14.04 LTS,gcc版本4.8.4。
代码分析
进程和线程的定义
首先在mykernel/下建立一个与程序控制块相关的头文件mypcb.h,该文件中主要实现了对进程控制块结构体(tPCB)和线程结构体(Thread)的声明和定义,以及my_schedule
函数和其他一些宏的声明。代码及详细分析如下:
/*
* linux/mykernel/mypcb.h
*
* Kernel internal PCB types
*
*/
#define MAX_TASK_NUM 4 /* 支持的最大进程数(4个) */
#define KERNEL_STACK_SIZE 1024*8 /* 进程栈空间的大小(8k word,即64kB) */
#define UNRUNNABLE -1 /* 进程可执行 */
#define RUNNABLE 0 /* 进程不可执行 */
#define FALSE 0
#define TRUE 1
/* CPU-specificstate of a task, only fit for 32-bit system. */
struct Thread {
unsigned long ip; /* instruction pointer */
unsigned long sp; /* 线程的栈顶指针 */
};
typedef struct PCB{
int pid; /* 进程ID,进程在操作系统中的唯一标识 */
volatile long state; /* 进程状态,目前支持“可运行”和“不可运行” */
char stack[KERNEL_STACK_SIZE]; /* 进程的栈空间 */
struct Thread thread; /* 进程的线程信息 */
unsigned long task_entry; /* 进程的入口地址 */
struct PCB *next; /* 进程队列中的下一个进程 */
} tPCB;
void my_schedule(void); /* 进程调度的具体实现 */
进程的初始化
/*
* linux/mykernel/mymain.c
*
* Kernel internal my_start_kernel
*
*/
/*
* Headers are omitted.
* 此处省略若干头文件
*/
#ifdef CONFIG_X86_LOCAL_APIC
#include <asm/smp.h>
#endif
#include "mypcb.h"
#define SLICING (10000000)
tPCB task[MAX_TASK_NUM]; /* 系统进程控制块数组 */
tPCB *my_current_task = NULL; /* 当前正在执行的进程指针 */
volatile int my_need_schedule = FALSE; /* 当前进程是否可以调度的标志位 */
void my_process(void);
/* 初始化进程控制块,并将该kernel从0号进程的入口地址开始执行 */
void __init my_start_kernel(void)
{
int pid =0;
int i;
/* 初始化0号进程 */
task[pid].pid = pid; /* 进程号设为0 */
task[pid].state = RUNNABLE; /* 进程状态设为“可运行” */
task[pid].task_entry = task[pid].thread.ip =(unsigned long)my_process; /* 进程的入口设置为my_process函数 */
task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE -1]; /* 当前线程的栈指针指向进程栈空间的最高地址。这么做的原因是X86处理器支持的 */
task[pid].next = &task[pid]; /* 进程队列的下一个进程指向自己 */
/* 初始化其他进程(1-3号) */
for(i =1; i < MAX_TASK_NUM; i ++){
memcpy(&task[i],&task[0],sizeof(tPCB)); /* 将0号进程的PCB完整复制 */
task[i].pid = i; /* 进程号设为i */
task[i].state = UNRUNNABLE; /* 进程状态设为“不可运行” */
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号进程放在i号进程之后,并将将i号进程放在i-1号进程之后*/
}
pid =0;
my_current_task = &task[pid]; /* 当前的运行的进程设置为0号进程 */
my_need_schedule = TRUE; /* 当前可以进行进程调度 */
asm volatile(
"movl %1,%%esp\n\t" /* 将0号进程的sp赋给%esp */
"pushl %1\n\t" /* 将0号进程的sp入栈 */
"pushl %0\n\t" /* 将0号进程的ip入栈 */
"ret\n\t" /* 将0号进程的ip出栈,并赋给%eip。即当0号进程开始运行时,将从my_process函数开始执行。 */
"popl %%ebp\n\t" /* 将0号进程的sp出栈,并赋给%ebp。即将0号进程栈空间的最高地址作为基址地址。 */
:
:"c"(my_current_task->thread.ip),"d"(my_current_task->thread.sp)/* "c" = ecx, "d" =edx*/
);
}
/*
* my_process 是每个进程执行的唯一一个函数。
* 它是一个无限循环,当i是SLICING的整数倍时,检查当前是否可以进行进程调度。
* 如果系统允许进程调度的话,则将进程调度标志设为false,并调用my_schedule函数进行调度。
* my_schedule函数将在下文中进行分析。
*/
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);
}
}
}
进程调度
/*
* linux/mykernel/myinterrupt.c
*
* Kernel internal my_timer_handler
*
*/
/*
* Headers are omitted.
*/
#define CREATE_TRACE_POINTS
#include <trace/events/timer.h>
#include"mypcb.h"
#define SLICING 1000
extern tPCB task[MAX_TASK_NUM];
extern tPCB *my_current_task;
extern volatile int my_need_schedule;
volatile int time_count =0;
/*
* 每发生一次时间中断,就会调用一次my_timer_handler函数。
* 需要注意的是,对于不同的进程,time_count和my_need_schedule变量是不同的实例。
*/
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 prev process’s ebp */
"movl %%esp,%0\n\t" /* save prev process’s esp */
"movl %2,%%esp\n\t" /* restore next process's ebp */
"movl $start,%1\n\t" /* save eip, $start就是指标号start:的代码在内存中存储的地址*/
"pushl %3\n\t"
"ret\n\t" /* restore next process's eip */
"start:\n\t" /* next process starts 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 prev process’s ebp */
"movl %%esp,%0\n\t" /* save prev process’s esp */
"movl %2,%%esp\n\t" /* restore next process's sp to esp */
"movl %2,%%ebp\n\t"
/* restore next process's sp to ebp
* because the process has never run before
* hence, bp equals to sp
*/
"movl $start,%1\n\t" /* save eip */
"pushl %3\n\t" /* restore next process's eip, here eip->my_process */
"ret\n\t"
:"=m"(prev->thread.sp),"=m"(prev->thread.ip)
:"m"(next->thread.sp),"m"(next->thread.ip)
);
}
}