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

本文介绍了在Linux内核中如何实现进程控制块(PCB)的定义及简单的内核调度函数。通过对mypcb.h文件的分析,展示了进程和线程结构的定义,并详细解释了进程初始化过程及my_start_kernel函数的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

本文的内容来自于《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)
        );
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值