Linux内核分析(二)之简单的进程调度

2019-07-14 11:47发布

前言

本文的内容来自于《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 #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 " /* 将0号进程的sp赋给%esp */ "pushl %1 " /* 将0号进程的sp入栈 */ "pushl %0 " /* 将0号进程的ip入栈 */ "ret " /* 将0号进程的ip出栈,并赋给%eip。即当0号进程开始运行时,将从my_process函数开始执行。 */ "popl %%ebp " /* 将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 - ", my_current_task->pid); if(my_need_schedule == TRUE){ my_need_schedule = FALSE; my_schedule(); } printk(KERN_NOTICE "This is process %d + ", my_current_task->pid); } } }

进程调度

/* * linux/mykernel/myinterrupt.c * * Kernel internal my_timer_handler * */ /* * Headers are omitted. */ #define CREATE_TRACE_POINTS #include #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<<<<<<<<<<<<<<<<<< "); 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<<<<<<<<<<<<<<<<<< "); next = my_current_task->next; prev = my_current_task; if(next->state == RUNNABLE){ /* switch to next process */ asm volatile( "pushl %%ebp " /* save prev process’s ebp */ "movl %%esp,%0 " /* save prev process’s esp */ "movl %2,%%esp " /* restore next process's ebp */ "movl $start,%1 " /* save eip, $start就是指标号start:的代码在内存中存储的地址*/ "pushl %3 " "ret " /* restore next process's eip */ "start: " /* next process starts here */ "popl %%ebp " /* 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 <<<<<< ", prev->pid, next->pid); }else{ next->state = RUNNABLE; my_current_task= next; printk(KERN_NOTICE ">>>>>> switch from process %d toprocess %d <<<<<< ", prev->pid, next->pid); /* switch to next process */ asm volatile( "pushl %%ebp " /* save prev process’s ebp */ "movl %%esp,%0 " /* save prev process’s esp */ "movl %2,%%esp " /* restore next process's sp to esp */ "movl %2,%%ebp " /* restore next process's sp to ebp * because the process has never run before * hence, bp equals to sp */ "movl $start,%1 " /* save eip */ "pushl %3 " /* restore next process's eip, here eip->my_process */ "ret " :"=m"(prev->thread.sp),"=m"(prev->thread.ip) :"m"(next->thread.sp),"m"(next->thread.ip) ); } }