基于mykernel 2.0编写一个操作系统内核
1.配置mykernel 2.0,并熟悉Linux内核的编译
使用VMware创建Ubuntu 16.04.2 虚拟机,将下载好的 mykernel-2.0_for_linux-5.4.34.patch 以及 linux-5.4.34.tar.xz 拖入虚拟机中,解压后打上补丁,安装相关程序后编译内核并安装 qemu虚拟机,配置好linux5.4.34内核代码、mykernel补丁、相对应的依赖库文件和qemu仿真系统,以下是课程PPT里面的命令,逐条执行即可:
1 wget https://raw.github.com/mengning/mykernel/master/mykernel-2.0_for_linux-5.4.34.patch 2 sudo apt install axel 3 axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz 4 xz -d linux-5.4.34.tar.xz 5 tar -xvf linux-5.4.34.tar 6 cd linux-5.4.34 7 patch -p1 < ../mykernel-2.0_for_linux-5.4.34.patch 8 sudo apt install build-essential gcc-multilib 9 sudo apt install qemu # install QEMU 10 sudo apt install libncurses5-dev bison flflex libssl-dev libelf-dev 11 make defconfifig # Default confifiguration is based on 'x86_64_defconfifig' 12 make -j$(nproc) 13 qemu-system-x86_64 -kernel arch/x86/boot/bzImage
axel是一个多线程高速下载命令,支持多种文件传输协议,可以从多个地址或者与一个地址建立多个连接来下载同一个文件。make defconfig命令的作用是按照默认的配置文件.arch/arm64/configs/defconfig对内核进行配置,在当前目录下生成.config用作初始化配置。用ls -a命令可以在编译完成配置文件之后查看到该文件。最后,make -j$(nproc)命令作用是编译内核,这里nproc是一个内核参数,指系统上的最大进程数。$(nproc)是获取安装系统的该内核参数,指定该参数,完成内核编译。

执行qemu-system-x86_64 -kernel arch/x86/boot/bzImage结果如下:

2.基于mykernel 2.0编写一个操作系统内核,实现进程的切换。
在Linux内核中,实际上是struct task_struct结构体。在struct PCB中,包含进程标识符pid、进程状态state(-1表示阻塞状态,0表示正在运行中,大于0则表示停止执行)、栈空间stack、指示CPU特征状态(sp和ip)的线程结构体thread、进程入口task_entry以及指向下一个进程的指针*next,最后的指针,说明进程是以链表的形式组织起来的,mypcb.h内容如下。
#define MAX_TASK_NUM 16
#define KERNEL_STACK_SIZE 1024
/* CPU-specific state of this task */
struct Thread
{
unsigned long ip;
unsigned long sp;
};
typedef struct PCB
{
int pid;
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进⾏修改,mymain.c是mykernel内核代码的⼊⼝,我们在这里对内核的各个组成部分进行初始化操作,在函数my_start_kernel中,首先是初始化第0个进程,然后开始对1~MAX_TASK_NUM中的进程依次进行初始化,其中进程状态初始化为0,表示所有进程都处在就绪状态等待调用,且所有的进程初始化之后成为一个循环链表。在mymain.c中添加了my_process函数,用于模拟进程执行的过程。此外,my_need_sched变量初始化为0,但是因为my_time_handler函数会定期更新其为1,表示需要调度新的进程,调度完毕之后,重新切换为0,执行1个时间片,依次循环,mymain.c内容如下:
#include "mypcb.h"
2
3 tPCB task[MAX_TASK_NUM]; /*task数组*/
4 tPCB *my_current_task = NULL; /*指向当前进程的指针8*/
5 volatile int my_need_sched = 0; /*是否需要调度,1表示需要,0表示不需要*/
6
7 void __init my_start_kernel(void)
8 {
9 int pid = 0;
10 int i;
11 /* Initialize process 0*/
12 task[pid].pid = pid;
13 task[pid].state = 0; /* -1 unrunnable, 0 runnable, >0 stopped */
14 task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
15 task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE - 1];
16 task[pid].next = &task[pid];
17 /*fork more process */
18 for (i = 1; i < MAX_TASK_NUM; i++)
19 {
20 memcpy(&task[i], &task[0], sizeof(tPCB));
21 task[i].pid = i;
22 task[i].state = 0;
23 task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE - 1];
24 task[i].next = task[i - 1].next;
25 task[i - 1].next = &task[i];
26 }
27 /* start process 0 by task[0] */
28 pid = 0;
29 my_current_task = &task[pid];
30 asm volatile(
31 "movq %1,%%rsp\n\t" /* set task[pid].thread.sp to rsp */
32 "pushq %1\n\t" /* push rbp */
33 "pushq %0\n\t" /* push task[pid].thread.ip */
34 "ret\n\t" /* pop task[pid].thread.ip to rip */
35 :
36 : "c"(task[pid].thread.ip), "d"(task[pid].thread.sp) /* input c or d mean %ecx/%edx*/
37 );
38 }
39
40 void my_process(void)
41 {
42 int i = 0;
43 while (1)
44 {
45 i++;
46 if (i % 10000000 == 0)
47 {
48 printk(KERN_NOTICE "this is process %d -\n", my_current_task->pid);
49 if (my_need_sched == 1) /*表示需要从当前进程切换到下一个进程*/
50 {
51 my_need_sched = 0; /*重新置0,执行一个时间片*/
52 my_schedule(); /*切换进程*/
53 }
54 printk(KERN_NOTICE "this is process %d +\n", my_current_task->pid);
55 }
56 }
57 }
然后是myinterrupt.c的内容,进程运⾏过程使用时钟中断处理过程记录时间⽚,每次time_count % 1000 == 0的时候,就置my_need_sched为1,表示当前进程时间片用完,需要调度下一个进程。当next->state为0的时候,表示下一个进程可以被调度,只要先将指向当前进程的指针指向下一个进程,然后保存上一个进程的rsp和rip,重新设置rsp和rip指向当前进程的栈空间,就完成了进程的切换。对myinterrupt.c中修改如下:
1 extern tPCB task[MAX_TASK_NUM];
2 extern tPCB *my_current_task;
3 extern volatile int my_need_sched;
4 volatile int time_count = 0;
5
6 /*
7 * Called by timer interrupt.
8 */
9 void my_timer_handler(void)
10 {
11 if (time_count % 1000 == 0 && my_need_sched != 1)
12 {
13 printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
14 my_need_sched = 1; /*执行一个时间片之后,重新置my_need_sched为1,因为当前进程时间片用完,需要调度下一个进程*/
15 }
16 time_count++;
17 return;
18 }
19
20 void my_schedule(void)
21 {
22 tPCB *next;
23 tPCB *prev;
24 printk(KERN_NOTICE ">>>my_schedule<<<\n");
25 /* schedule */
26 next = my_current_task->next;
27 prev = my_current_task;
28 if (next->state == 0) /* -1 unrunnable, 0 runnable, >0 stopped */
29 {
30 my_current_task = next;
31 printk(KERN_NOTICE ">>>switch %d to %d<<<\n", prev->pid, next->pid);
32 /* switch to next process */
33 asm volatile(
34 "pushq %%rbp\n\t" /* save rbp of prev */
35 "movq %%rsp,%0\n\t" /* save rsp of prev */
36 "movq %2,%%rsp\n\t" /* restore rsp of next */
37 "movq $1f,%1\n\t" /* save rip of prev */
38 "pushq %3\n\t"
39 "ret\n\t" /* restore rip of next */
40 "1:\t" /* next process start here */
41 "popq %%rbp\n\t"
42 : "=m"(prev->thread.sp), "=m"(prev->thread.ip)
43 : "m"(next->thread.sp), "m"(next->thread.ip)
44 );
45 }
46 return;
47 }
3.操作系统内核核心功能及运行工作机制
操作系统内核一个核心的功能是进程管理,包括进程的创建与销毁、处理与外部设备的联系、进程间通信等等,最主要的进程管理操作主要有实现进程间的切换执行、对各种设备的调度使用以及CPU的共享,以下以进程调度为例阐述进程切换机制:
1 asm volatile( 2 "movq %1,%%rsp\n\t" /* 将进程原堆栈栈顶的地址存⼊RSP寄存器 */ 3 "pushq %1\n\t" /* 将当前RBP寄存器值压栈 */ 4 "pushq %0\n\t" /* 将当前进程的RIP压栈 */ 5 "ret\n\t" /* ret命令正好可以让压栈的进程RIP保存到RIP寄存器中 */ 6 : 7 : "c"(task[pid].thread.ip), "d"(task[pid].thread.sp) 8 );
进程切换代码如下:


浙公网安备 33010602011771号