2020-2021-1 20212824《Linux内核原理与分析》第三周作业

本文详细介绍了Linux内核中的进程控制块(PCB)、进程调度及时间片轮转机制。通过汇编代码展示了如何进行进程切换,并分析了my_start_kernel、my_process、my_timer_handler和my_schedule等关键函数的功能。实验中,每当时间计数器达到一定阈值,就会触发进程调度,实现简单的多道程序运行。
摘要由CSDN通过智能技术生成

前置知识

在汇编代码中
%%两个百分号一起出现的时候,第一个百分号是注释符
%1百分号后台加个数字,一般都是指后面输出的项,就"c","d"这种参数后头跟的,一般从0开始。

make命令时出现的错误

因为我是在自己的虚拟机上整的,出现了gcc版本不匹配的问题。
这个时候需要进入到
/home/roc/roccourses/linux-3.9.4/include/linux
这个目录把文件名称修改一下
compiler-gcc4.h->compiler-gcc7.h
在这里插入图片描述

之前的函数编译器比较的笨,所以必须把所有的变量申明都写在最上面,好预留出内存中的堆栈空间。
现在的编译器会先自动扫描全部代码,找出所有的变量申明。

在这里插入图片描述

mypcb.h

在这里插入图片描述

修改mymain.c

在这里插入图片描述
最主要的还是这一段内嵌式汇编代码,

movl %1,%%ebp\n\t	将进程原堆栈栈顶的地址 存入ESP
pushl %1\n\t		将当前EBP寄存器的值入栈
pushl %0\n\t		将当前进程的EIP入栈
ret\n\t				ret命令正好可以让入栈的进程EIP保存到EIP寄存器

修改myinterrupter.c

在这里插入图片描述

pushl %%ebp\n\t		保存当前进程EBP到堆栈
movl %%esp,%0\n\t	保存当前进程ESP到PCB
movl %2,%%esp\n\t	载入next进程的栈顶地址到ESP寄存器
movl $1f,%1\n\t		保存当前进程的EIP值,下次恢复进程后将在标号1开始执行
pushl %3\n\t		将next进程继续执行的代码位置(标号1)压栈
ret\n\t				出栈标号1到EIP寄存器
1:\t				标号1,即next进程开始执行的位置
popl %%ebp\n\t 		恢复EBP寄存器的值

重新make

在这里插入图片描述
在这里插入图片描述

启动

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

实验分析

进程控制块(PCB)

进程是操作系统调度资源基本单位

mypcb.h

#define MAX_TASK_NUM            4
#define KERNEL_STACK_SIZE       1024*8

/* CPU-specific state of this task */

struct Thread{
        unsigned long ip; //eip
        unsigned long sp; //esp
};

typedef struct PCB{
        int pid; //进程的ID
        volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
        char stack[KERNEL_STACK_SIZE];
        /* CPU-specific state of this task*/
        struct Thread thread;
        unsigned long task_entry;// 入口
        struct PCB *next;
}tPCB;

void my_schedule(void);

mymain.c

/*
 *  linux/mykernel/mymain.c
 *
 *  Kernel internal my_start_kernel
 *  Change IA32 to x86-64 arch, 2021/10/4
 *
 *  20212824 项紫啸 
 *  
 */
#include <linux/types.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/tty.h>
#include <linux/vmalloc.h>

#include "mypcb.h"

tPCB task[MAX_TASK_NUM]; //PCB数组
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*/
    task[pid].pid = pid;
    //0表示可执行
    task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */
    task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; //0号进程的入口
    task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; //初始化0进程的堆栈指针
    task[pid].next = &task[pid];
    /*产生一堆进程 */
    for(i=1;i<MAX_TASK_NUM;i++)
	{
        memcpy(&task[i],&task[0],sizeof(tPCB)); //把0的状态复制给这个进程
        task[i].pid = i;
            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;
	my_current_task = &task[pid];
        asm volatile(
        "movl %1,%%esp\n\t"     /* set task[pid].thread.sp to esp */
        "pushl %1\n\t"          /* push ebp */
        "pushl %0\n\t"          /* push task[pid].thread.ip */
        "ret\n\t"       /* pop task[pid].thread.ip to eip */
        "popl %%ebp\n\t"
        :
        : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)   /* input c or d mean %ecx/%edx*/
        );
}
int i = 0;

void my_process(void)
{
    while(1)
    {
        i++;
        if(i%10000000 == 0)
        {
            printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);
            if(my_need_sched == 1)
            {
                my_need_sched = 0;
                    my_schedule();
                }
                printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
        }
    }
}

然后这些产生的进程都执行函数my_process,每10000000产生一次调度(当标志为1时)

myinterrupte.c

/*
 *  linux/mykernel/myinterrupt.c
 *
 *  Kernel internal my_timer_handler
 *  Change IA32 to x86-64 arch, 2021/10/4
 *
 *  20212824 项紫啸
 *
 */
#include <linux/types.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/tty.h>
#include <linux/vmalloc.h>

#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%1000 == 0 && my_need_sched != 1)
    {
        printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
        my_need_sched = 1;
    }
    time_count ++ ;
    return;
}

时钟中断中,time每增加1000且调度标志不为1时(有程序正在执行时,my_need_sched==0),将my_need_sched置为1,与mymain.c中的if(my_need_sched == 1)相呼应,使得下一次程序进入if条件中,发起主动进程调度,实现简单的时间片轮转多道程序。

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");
    /* schedule */
    next = my_current_task->next;
    prev = my_current_task;
    if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
    {
        my_current_task = next;
        printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
        /* switch to next process */
        asm volatile(
                "pushl %%ebp\n\t"           /* save rbp of prev */
                "movl %%esp,%0\n\t"     /* save rsp of prev */
                "movl %2,%%esp\n\t"     /* restore  rsp of next */
                "movl $1f,%1\n\t"       /* save rip of prev */
                "pushl %3\n\t"
                "ret\n\t"                   /* restore  rip of next */
                "1:\t"                  /* next process start here */
                "popl %%ebp\n\t"
                : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
                : "m" (next->thread.sp),"m" (next->thread.ip)
        );
    }
   return;
}

本次实验,最重要的就是看懂时间片的轮转机制。了解进程的调度

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值