欢迎使用CSDN-markdown编辑器

完成一个简单的时间片轮转多道程序内核代码

周恺祺 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

一、 实验要求
  1. 完成一个简单的时间片轮转多道程序内核代码,代码从视频中或从mykernel中找
  2. 详细分析该内核的源代码,并给出实验截图,写一篇博客,内容围绕操作系统是如何工作的,请说出自己的理解。
二、实验步骤及相关分析(简易内核)
  1. 打开实验楼的虚拟机,运行如下代码
    运行简易内核并编译
  2. 编译过程及运行结果如下图
  3. 内核运行结果如图所示
    这里写图片描述
  4. 打开mykernel文件夹中的mymain.c可以看到,当mykernel自制的操作系统启动后,即qemu命令执行后,系统会首先调用mymain产生一个进程,该进程内while循环条件恒为1所以永远执行输出“my_start_kernel here”+累计循环次数。循环次数到规定的次数以后,会产生响应输出。并且无限循环
    这里写图片描述
    接着,打开myinterrupt.c可以看到,此时由系统时钟中断触发调用myinterrupt产生一个新进程,并输出“my_timer_handler here”,结束后返回mymain产生的进程继续执行,
    通过实验结果可以得知,中断结束返回继续执行原来的进程时,中断输出的次数是连续的,并没有中断或者置0,说明CPU在中断的时候拥有保护现场和恢复现场的功能,会把一些重要的值通过寄出去你保存起来,以便中断结束后继续从原来中断的地方执行。
三、修改内核步骤
  1. 实验楼中孟宁老师的代码库里面获取新的 mymain.c myinterrupt.c mypcb.c,将原先的代码用这三个新代码文件替换。通过二中的步骤,重新编译内核运行。
  2. 内核编译结果如图
    这里写图片描述

这里写图片描述

这里写图片描述

从第一张图图中可以清晰看到进程的切换过程:在0号进程运行过程中,先是my_timer_handler()被执行,然后是myschedule()被执行,myschedule()在运行过程中会打印switch 0(被切换出去的进程号) to 1(切换到的进程号)。然后就跳到新的进程1继续执行。 之后的两张图同理。

四、代码分析
  1. myinterrupt.c
/*
 *  linux/mykernel/mypcb.h
 *
 *  Kernel internal PCB types
 *
 *  Copyright (C) 2013  Mengning
 *  定义进程控制块TCB
 */

#define MAX_TASK_NUM        4
#define KERNEL_STACK_SIZE   1024*2 # unsigned long
/* CPU-specific state of this task */
struct Thread {
    unsigned long       ip;                      //定义Thread,用来存储ip,esp
    unsigned long       sp;
};

typedef struct PCB{
    int pid;                                    //定义进程的ID、进程的状态
    volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
    unsigned long 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);                         //调度器
  1. mymain.c
/*
 *  linux/mykernel/mymain.c
 *
 *  Kernel internal my_start_kernel
 *
 *  Copyright (C) 2013  Mengning
 *
 */
#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];             //声明任务的数组、指针、是否需要调度
tPCB * my_current_task = NULL;
volatile int my_need_sched = 0;

void my_process(void);


void __init my_start_kernel(void)    //内核初始化、0号进程的启动
{
    int pid = 0;                     //0号进程
    int i;
    /* Initialize process 0*/
    task[pid].pid = pid;
    task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped  状态正在运行*/
    task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
    task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
    task[pid].next = &task[pid];      //堆栈的栈顶,指向它进程本身
    /*fork more process */
    for(i=1;i<MAX_TASK_NUM;i++)       //创建更多进程
    {
        memcpy(&task[i],&task[0],sizeof(tPCB));  //copy 0号进程的状态
        task[i].pid = i;
        task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];
    *(task[i].thread.sp - 1) = task[i].thread.sp;
    task[i].thread.sp -= 1;
        task[i].next = task[i-1].next;  //把新fork的进程加入进程链表的尾部
        task[i-1].next = &task[i];
    }
    /* start process 0 by task[0] 运行0号进程*/
    pid = 0;
    my_current_task = &task[pid];
    asm volatile(
        "movl %1,%%esp\n\t"     /* set task[pid].thread.sp to esp %1表示当前sp参数,可认为函数的1号参数*/
        "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  ret之后0号进程就正式启动,构建了CPU的运行环境*/
        "popl %%ebp\n\t"
        : 
        : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)   /* input c or d mean %ecx/%edx*/
    );
}   
void my_process(void)          //所有的进程采用相同的代码
{
    int i = 0;
    while(1)
    {
        i++;
        if(i%10000000 == 0)   //循环10000000次才会调度一次,主动调度
        {
            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);
        }     
    }
}
  1. mytcb.c
/*
 *  linux/mykernel/mymain.c
 *
 *  Kernel internal my_start_kernel
 *
 *  Copyright (C) 2013  Mengning
 *
 */
#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];             //声明任务的数组、指针、是否需要调度
tPCB * my_current_task = NULL;
volatile int my_need_sched = 0;

void my_process(void);


void __init my_start_kernel(void)    //内核初始化、0号进程的启动
{
    int pid = 0;                     //0号进程
    int i;
    /* Initialize process 0*/
    task[pid].pid = pid;
    task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped  状态正在运行*/
    task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
    task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
    task[pid].next = &task[pid];      //堆栈的栈顶,指向它进程本身
    /*fork more process */
    for(i=1;i<MAX_TASK_NUM;i++)       //创建更多进程
    {
        memcpy(&task[i],&task[0],sizeof(tPCB));  //copy 0号进程的状态
        task[i].pid = i;
        task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];
    *(task[i].thread.sp - 1) = task[i].thread.sp;
    task[i].thread.sp -= 1;
        task[i].next = task[i-1].next;  //把新fork的进程加入进程链表的尾部
        task[i-1].next = &task[i];
    }
    /* start process 0 by task[0] 运行0号进程*/
    pid = 0;
    my_current_task = &task[pid];
    asm volatile(
        "movl %1,%%esp\n\t"     /* set task[pid].thread.sp to esp %1表示当前sp参数,可认为函数的1号参数*/
        "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  ret之后0号进程就正式启动,构建了CPU的运行环境*/
        "popl %%ebp\n\t"
        : 
        : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)   /* input c or d mean %ecx/%edx*/
    );
}   
void my_process(void)          //所有的进程采用相同的代码
{
    int i = 0;
    while(1)
    {
        i++;
        if(i%10000000 == 0)   //循环10000000次才会调度一次,主动调度
        {
            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);
        }     
    }
}
五、实验总结(参考网络及相关同学报告)
  1. 本次实验虽然小有难度,但还是完成了,了解到了操作系统的工作方式、了解了计算机的三大法宝,尤其是对于进程调度和中断的机制了解的更深了。
  2. 本次实验首先首先通过编译运行简单的内核代码,知道了操作系统运行时候的中断机制的机理,接着跟着视频一步步的学习,完成了一段时间片轮转的操作系统内核代码的运行。
  3. 操作系统是运行在相应的硬件平台上的一个特别程序,它的任务是实现任务(也就是进程)的创建、运行和调度;同时实现对运行在相应的平台的资源管理和分配,实现整个硬件和软件系统处于最优的工作状态。Linux操作系统由内核来实现它的具体工作的,一个进程是通过系统调用fork()函数来创建的,他先是将先前CPU正在运行的进程的进程上下文保存在内核态堆栈中,包括有eip,esp,ebp,cs等寄存器的数据;然后加载创建的进程的上下文信息到相应的寄存器中,运行当前新建进程;运行完毕后根据系统的调度继续执行相应的进程。在这个过程中的执行流程是:SAVE_ALLà进建PID对应的task_structàrestore allàiret。而Linux操作系统是多进程的操作系统,不同的进程就是基于以上的方式有操作系统实现调度运行的。同时,操作系统以一种中断的机制实现与用户的交互。操作系统中的IDT描述好各个中断对应的处理程序,当发生相对应的中断时,由硬件来实现中断信号的传递,CPU接收到相应的IRQ信号后,由操作系统如调度进程那样调度相应的处理程序,来完成相应的中断请求,实现与用户的交互。整个操作系统就是如此实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值