2023-2024-1 20232808《Linux内核原理与分析》第三周作业

一、实验准备

实验代码如下:

# 注意路径是区分大小的
$ cd ~/LinuxKernel/linux-3.9.4
$ rm -rf mykernel
$ patch -p1 < ../mykernel_for_linux3.9.4sc.patch
$ make allnoconfig
# 编译内核请耐心等待
$ make
$ qemu -kernel arch/x86/boot/bzImage

这段代码是用来编译和运行一个自定义的 Linux 内核。假设当前工作目录是 ~/LinuxKernel/linux-3.9.4,那么这些命令将会执行以下操作:

  • 删除名为 mykernel 的文件夹(如果存在)
  • 应用一个名为 mykernel_for_linux3.9.4sc.patch 的补丁,该补丁包含了内核定制代码
  • 使用默认配置生成 .config 文件
  • 编译内核并生成镜像文件
  • 运行 QEMU 模拟器,并加载刚刚编译的内核镜像

二、编译自定义内核

$ cd ~/LinuxKernel/linux-3.9.4
$ rm -rf mykernel
$ patch -p1 < ../mykernel_for_linux3.9.4sc.patch
$ make allnoconfig
$ make

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

三、使用QEMU虚拟机工具运行自定义内核

$ qemu -kernel arch/x86/boot/bzImage

在这里插入图片描述
出现死循环,可以看到my_start_kernel这个内核的初始化入口点在执行,同时my_timer_handler时钟中断处理程序周期性执行。

四、编写一个简单的时间片轮转多道程序

根据上述默认代码可知,这个自定义的Linux内核在时间片轮转方面不够完善,需要根据Github上的代码进行修改。

4.1 创建mypcb.h

/*
 *  linux/mykernel/mypcb.h
 *  Kernel internal PCB types
 *  Copyright (C) 2013  Mengning
 */

#define MAX_TASK_NUM        4              /* 定义最大任务数为4 */

#define KERNEL_STACK_SIZE   1024*2         /* 定义内核堆栈大小为1024*2字节 */

/* CPU-specific state of this task */
struct Thread {                             /* 定义Thread结构体,表示任务的CPU相关状态 */
    unsigned long       ip;                /* 存储指令指针 */
    unsigned long       sp;                /* 存储栈指针 */
};

typedef struct PCB {                        /* 定义进程控制块参数 */
    int pid;                                /* 进程ID(Process ID)*/
    volatile long state;                    /* 进程状态:-1 表示不可运行, 0 表示可运行, >0 表示停止 */
    unsigned long stack[KERNEL_STACK_SIZE];  /* 存储内核堆栈 */
    /* CPU-specific state of this task */
    struct Thread thread;                   /* 存储任务的CPU相关状态 */
    unsigned long task_entry;               /* 存储任务的入口地址 */
    struct PCB *next;                       /* 存储下一个进程控制块的指针,用于构建链表 */
} tPCB;

void my_schedule(void);                     /* 声明调度函数my_schedule() */

修改mymain.c

/*
 *  linux/mykernel/mymain.c
 */

#include "mypcb.h"        // 包含自定义的进程控制块头文件
#include <linux/tty.h>

tPCB task[MAX_TASK_NUM];      // 定义一个tPCB类型的数组,存储任务控制块
tPCB *my_current_task = NULL; // 指向当前运行任务的指针
volatile int my_need_sched = 0; // 表示是否需要进行进程调度的标志

void my_process(void); // 声明my_process函数

void __init my_start_kernel(void) // 内核初始化函数
{
    int pid = 0;
    int i;
    /* 初始化进程0 */
    task[pid].pid = pid;
    task[pid].state = 0; /* -1 不可运行, 0 可运行, >0 停止 */
    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]; // 初始化进程0的next指针,形成循环链表

    /* 复制进程0的状态来创建更多进程 */
    for (i = 1; i < MAX_TASK_NUM; i++)
    {
        memcpy(&task[i], &task[0], sizeof(tPCB));
        task[i].pid = i;                // 初始化进程ID
        task[i].state = -1;            // 设置状态为不可运行
        task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE - 1]; // 初始化堆栈指针
        task[i].next = task[i - 1].next; // 更新next指针,构建进程链表
        task[i - 1].next = &task[i];
    }

    /* 启动进程0 */
    pid = 0;
    my_current_task = &task[pid]; // 设置当前任务为进程0
    asm volatile(
        "movl %1,%%esp\n\t"   /* 将栈顶指针指向进程0的栈底 */
        "pushl %1\n\t"        /* 压入栈底地址 */
        "pushl %0\n\t"        /* 压入入口地址 */
        "ret\n\t"             /* 返回到入口地址 */
        "popl %%ebp\n\t"      /* 弹出栈底地址到ebp寄存器 */
        :
        : "c"(task[pid].thread.ip), "d"(task[pid].thread.sp) // 输入操作数
    );
}

void my_process(void)
{
    int i = 0;
    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); // 打印当前进程的信息
        }
    }
}

修改myinterrupt.c

/*
 *  linux/mykernel/myinterrupt.c
 */

#include "mypcb.h"              // 包含自定义的进程控制块头文件
#include <linux/types.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/tty.h>
#include <linux/vmalloc.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;
}

/*
 * 进程调度函数
 */
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"); // 打印调度信息

    /* 进行任务切换 */
    next = my_current_task->next; // 获取下一个任务
    prev = my_current_task;       // 获取当前任务

    if (next->state == 0) /* -1 不可运行, 0 可运行, >0 停止 */
    {
        my_current_task = next; // 切换到下一个任务
        printk(KERN_NOTICE ">>>switch %d to %d<<<\n", prev->pid, next->pid); // 打印任务切换信息

        /* 执行任务切换 */
        asm volatile(
            "pushl %%ebp\n\t"     /* 压栈当前任务的值 */
            "movl %%esp,%0\n\t"   /* 将当前栈指针保存到当前任务的tPCB结构中 */
            "movl %2,%%esp\n\t"   /* 切换到下一个任务的栈指针 */
            "movl $1f,%1\n\t"     /* 将标签1的地址保存到当前任务的thread.ip中 */
            "pushl %3\n\t"        /* 将下一个任务的thread.ip压栈 */
            "ret\n\t"             /* 返回,实现任务切换 */
            "1:\t"                 /* 标签1,用于返回时跳转 */
            "popl %%ebp\n\t"
            : "=m"(prev->thread.sp), "=m"(prev->thread.ip)
            : "m"(next->thread.sp), "m"(next->thread.ip)
        );
    }
    else
    {
        next->state = 0; // 设置下一个任务的状态为可运行
        my_current_task = next; // 切换到下一个任务
        printk(KERN_NOTICE ">>>switch %d to %d<<<\n", prev->pid, next->pid); // 打印任务切换信息

        /* 执行任务切换 */
        asm volatile(
            "pushl %%ebp\n\t"       /* 压栈当前任务的值 */
            "movl %%esp,%0\n\t"     /* 将当前栈指针保存到当前任务的tPCB结构中 */
            "movl %2,%%esp\n\t"     /* 切换到下一个任务的栈指针 */
            "movl %2,%%ebp\n\t"     /* 切换到下一个任务的栈底指针 */
            "movl $1f,%1\n\t"       /* 将标签1的地址保存到当前任务的thread.ip中 */
            "pushl %3\n\t"          /* 将下一个任务的thread.ip压栈 */
            "ret\n\t"               /* 返回,实现任务切换 */
            "1:\t"                   /* 标签1,用于返回时跳转 */
            "popl %%ebp\n\t"
            : "=m"(prev->thread.sp), "=m"(prev->thread.ip)
            : "m"(next->thread.sp), "m"(next->thread.ip)
        );
    }
    return;
}

五、重新make,再运行

$ make
$ qemu -kernel arch/x86/boot/bzImage

在这里插入图片描述

六、总结

本次实验偏向于操作系统的相关内容,实现了一个自定义的Linux内核,并实现了时间片轮转下的多道程序运行。让我深层次的理解了Linux内核的多进程运行的相关原理,为以后的学习打下坚实的基础。

Linux中的时间片轮转(Time Slice Round-Robin)是一种常见的进程调度算法。它是一种抢占式调度算法,用于在多个进程之间共享CPU时间。
时间片轮转调度算法将CPU时间划分为一个个固定大小的时间片(通常为几毫秒),每个进程被分配一个时间片来执行。当一个进程的时间片用完后,调度器会将其暂停,并将CPU分配给下一个就绪队列中的进程。
时间片轮转算法的优点是公平性,每个进程都有机会获得相同长度的CPU时间。它可以防止某个进程长时间占用CPU,导致其他进程无法运行。此外,它可以避免进程饥饿,即使是短时间到达的进程也有机会运行。
实现时间片轮转调度算法需要一个就绪队列来保存等待CPU的进程。当一个进程用完时间片或发生I/O等阻塞情况时,调度器会将其放回就绪队列的末尾,并选择队列中的下一个进程来执行。
需要注意的是,时间片轮转调度算法可能存在一些问题,如上下文切换开销和响应时间较长。因此,在实际应用中,可能会对时间片长度进行调整或结合其他调度算法来提高性能。

七、Chatgpt使用

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值