基于mykernel 2.0编写一个操作系统内核——Linux操作系统实验一

一、实验环境配置

系统环境:阿里云 ubuntu_16_04_x64

参照https://github.com/mengning/mykernel配置

配置完成后,可以看到进程在不断执行

基于mykernel 2.0编写一个操作系统内核——Linux操作系统实验一_第1张图片

二、精简实现mykernel操作系统

1、进程基本结构定义

先在mykernel目录下增加一个mypcb.h 头文件,来定义进程控制块(Process Control Block),也就是进程结构体的定义,在Linux内核中是struct tast_struct结构体。

基于mykernel 2.0编写一个操作系统内核——Linux操作系统实验一_第2张图片

 1 //定义最大进程数和进程栈空间大小
 2 #define MAX_TASK_NUM        4
 3 #define KERNEL_STACK_SIZE        1024*8
 4 
 5 struct Thread {
 6     unsigned long ip;
 7     unsigned long sp;
 8 };
 9 
10 typedef struct PCB{
11     //进程号
12     int pid;
13     
14     // -1:就绪态, 0:运行态, >0:阻塞态 
15     volatile long state;
16     //当前进程的栈
17     char stack[KERNEL_STACK_SIZE];
18     //当前线程
19     struct Thread thread;
20     //进程入口地址
21     unsigned long task_entry;
22     //指向下一个进程的指针
23     struct PCB *next;
24 }tPCB;
25 
26 void my_schedule(void);

2、模拟进程

对mymain.c进行修改,这是mykernel内核代码的入口,负责初始化内核的各个组成部分。在mymain.c中添加了my_process函数,来作为进程的代码模拟一个个进程。

 1 void __init my_start_kernel(void){
 2     int pid = 0;
 3     int i;
 4     
 5     //初始化0号进程
 6     task[pid].pid = pid;
 7     task[pid].state = 0;
 8     task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
 9     task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
10     task[pid].next = &task[pid];
11     
12     //创建后续进程,最大数为4
13     for(i=1;i){
14         memcpy(&task[i],&task[0],sizeof(tPCB));
15         task[i].pid = i;
16         task[i].state = 0;
17         task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];
18         task[i].next = task[i-1].next;
19         task[i-1].next = &task[i];
20     }
21     
22     //启动进程
23     pid = 0;
24     my_current_task = &task[pid];
25     
26     asm volatile(
27         "movq %1,%%rsp\n\t" /* set task[pid].thread.sp to rsp */
28         "pushq %1\n\t" /* push rbp */
29         "pushq %0\n\t" /* push task[pid].thread.ip */
30         /* pop task[pid].thread.ip to rip
31         这一步没有直接将thread.ip放入rip中,而是通过ret操作实现,是因为rip寄存器
32         不能直接访问
33         */
34         "ret\n\t"
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     int i = 0;
42     while(1){
43         i++;
44         //减少输出,每1千万次输出一个
45         if(i%10000000 == 0){
46             printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);
47             if(my_need_sched == 1){
48                 my_need_sched = 0;
49                 my_schedule();
50             }
51             printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
52         }
53     }
54 }

 

3、进程调度和切换

在myinterrupt.c中修改my_timer_handler用来记录时间片,同时在myinterrupt.c中增加进程切换代码。

 1 void my_timer_handler(void) {
 2     if(time_count%1000 == 0 && my_need_sched != 1){
 3         printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
 4         my_need_sched = 1;
 5     }
 6     time_count ++ ;
 7     return;
 8 }
 9 
10 void my_schedule(void){
11     tPCB *next;
12     tPCB *prev;
13 
14     if(my_current_task==NULL||,my_current_task->next==NULL){
15         return;
16     }
17 
18      printk(KERN_NOTICE ">>>my_schedule<<<\n");
19      /* schedule */
20      next = my_current_task->next;
21      prev = my_current_task;
22      if(next->state == 0){
23          my_current_task = next;
24          printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);/* 切换进程 */
25          asm volatile(
26              "pushq %%rbp\n\t"
27              "movq %%rsp,%0\n\t"
28              "movq %2,%%rsp\n\t"
29              "movq $1f,%1\n\t"
30              "pushq %3\n\t"
31              "ret\n\t"
32              "1:\t"
33              "popq %%rbp\n\t"
34              : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
35              : "m" (next->thread.sp),"m" (next->thread.ip)
36         );
37      }
38      return;
39  }

 重新编译后,再次运行可以发现进程正在不断切换进行

 基于mykernel 2.0编写一个操作系统内核——Linux操作系统实验一_第3张图片

4、关键代码分析

重点在于进程的切换,整个进程切换过程,主要体现在rbp,rsp的存储和切换

1)"pushq %%rbp\n\t"

将rbp中的值压入堆栈中,这步完成来rbp的存储(当前进程栈基地址)
2)"movq %%rsp,%0\n\t"

将rsp中的值存入thread.sp中,完成rsp的存储
3)"movq %2,%%rsp\n\t"

将next.thread.sp的值传入rsp中,也就是rsp表示的栈顶指针指向下一个进程的内存区
4)"movq $1f,%1\n\t"

将$1f存入thread.ip中
5)"pushq %3\n\t"

将thread.ip的值($1f)压入栈中,因为要完成rip的存储切换,但rip不能随意更改,只能通过堆栈操作进行更改,所以使用这样的方法来保存

6)"ret\n\t"

pop next.thread的rip,执行next.thread也就是$1后面的语句
7)"popq %%rbp\n\t"

 pop出的值存入rbp中,也就是rbp指向当前进程的栈底

你可能感兴趣的:(基于mykernel 2.0编写一个操作系统内核——Linux操作系统实验一)