1、阅读学习教材「庖丁解牛Linux 分析 」第3章,有问题优先使用chatgpt等AI工具。
2、教材深入学习关注豆列「Linux内核及安全」。
3、学习蓝墨云班课中第三周视频「操作系统是如何工作的?」,并完成实验楼上配套实验二。,注意从下往上看。基于树莓派或其他平台完成ARM相关内容。
一、实验楼上配套实验二
完成一个简单的时间片轮转多道程序内核代码。
1、默认代码执行
这里是使用了一个自定义的Linux内核,即构建和启动一个自定义的Linux内核。首先,它删除任何先前构建的内核相关文件,然后应用一个补丁文件来修改内核源代码。接下来,它配置内核选项,然后编译内核。最后,它使用QEMU虚拟机来启动新编译的内核。这个过程旨在允许用户自定义内核,以满足特定需求或测试新功能。
但是QEMU后会发现,该处进入死循环,不会停止(只有计时器中断处理函数在不断运行),因此需要自行修改代码,实现时间片轮转和多道程序轮转运行。
代码如下(示例):
# 注意路径是区分大小的
$ 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
执行结果如下:
死循环如下,可以看到my_start_kernel这个内核的初始化入口点在执行,同时my_timer_handler时钟中断处理程序周期性执行:
2、完成一个简单的时间片轮转多道程序内核代码
根据上述默认代码可知,这个自定义的Linux内核在时间片轮转方面不够完善,需要根据Github上的代码进行修改。
首先,进入mykernel,完成以下三个内容:
cd mykernel
①创建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文件并进行修改,实现中断控制,增加了对进程切换时的my_schedule函数。
/*
* 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;
}
④完成结果:
3、实验结果
回到~/LinuxKernel/linux-3.9.4目录,重新进行make编译内核,再进行查看。
qemu -kernel arch/x86/boot/bzImage
二、总结
本次实验偏向于操作系统的相关内容,实现了一个自定义的Linux内核,并实现了时间片轮转下的多道程序运行。实验让我深层次的理解了Linux内核的多进程运行的相关原理,能够为以后的学习打下坚实的基础。
该实验的时间片轮转可以避免死锁,提高操作系统资源的利用率等等,具有很多好处。以下便是具体的分析:
时间片轮转(Time Sharing)是一种多道程序运行的调度策略,其主要作用如下:
提高系统资源利用率:时间片轮转允许多个进程并发执行,使得系统资源(如CPU时间、内存等)得到更有效的利用。每个进程在一个小时间片内运行,然后切换到下一个进程,从而避免了资源的浪费。
公平性:时间片轮转算法确保每个就绪进程都有机会运行,而不会因为某个进程一直占用CPU而导致其他进程无法运行。这种公平性有助于确保每个用户或任务都能获得相对平等的CPU时间,提高了系统的响应速度和用户体验。
响应时间短:对于交互式应用程序,时间片轮转可以确保用户的输入和请求能够在较短的时间内得到响应,因为每个进程都有机会在时间片内执行,不会出现长时间的等待。
避免饥饿现象:时间片轮转能够防止某个进程永远无法获得CPU时间的情况,从而避免了饥饿现象(Starvation)。每个进程都有机会执行,尽管可能会等待一段时间。
支持多用户环境:在多用户操作系统中,时间片轮转允许多个用户同时访问系统,每个用户的任务都能够得到适当的处理,而不会因为某个用户的任务而影响其他用户。
三、Chatgpt帮助
了解了时间片轮转:
详细了解了固定代码的含义以及解释。
Github上示例代码的含义:
①mypdb.h
②mymain.c代码的详细分析
③myinterrupt.c代码的详细分析