一个简单的时间片轮转多道程序

一个简单的时间片轮转多道程序

1.代码分析
首先上代码,代码主要有三个
my interrupt.c. mymain.c mypcb.h
最开始阅读mypcb.h

#define MAX_TASK_NUM        4
#define KERNEL_STACK_SIZE   1024*2 # unsigned long
/* 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 */
    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);

从中可以达到以下信息:最多只能有四个进程,进程栈大小为2K,Thread结构体中包含有指令指针ip与栈指针sp。结构体PCB为进程控制块,其中含有描述进程的一些信息,包括进程id,进程运行状态,进程栈,thread结构体,进程入口地址和指向下一个进程控制块的指针。

接下来时myinterrupt.c

/*
 *  linux/mykernel/myinterrupt.c
 *
 *  Kernel internal my_timer_handler
 *
 *  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"

extern tPCB task[MAX_TASK_NUM];
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = 0;

/*
 * Called by timer interrupt.
 * it runs in the name of current running process,
 * so it use kernel stack of current running process
 */
void my_timer_handler(void)
{
#if 1
    if(time_count%1000 == 0 && my_need_sched != 1)
    {
        printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
        my_need_sched = 1;
    } 
    time_count ++ ;  
#endif
    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");
    /* schedule */
    next = my_current_task->next;
    prev = my_current_task;
    if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
    {        
    	my_current_task = next; 
    	printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);  
    	/* switch to next process */
    	asm volatile(	
        	"pushl %%ebp\n\t" 	    /* save ebp */
        	"movl %%esp,%0\n\t" 	/* save esp */
        	"movl %2,%%esp\n\t"     /* restore  esp */
        	"movl $1f,%1\n\t"       /* save eip */	
        	"pushl %3\n\t" 
        	"ret\n\t" 	            /* restore  eip */
        	"1:\t"                  /* next process start here */
        	"popl %%ebp\n\t"
        	: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
        	: "m" (next->thread.sp),"m" (next->thread.ip)
    	); 
    }  
    return;	
}

这一段代码主要实现了进程调度功能。
my_timer_handler实现了每一千个时间片,强制进行进程切换。
my_schedule实现了进程调度。思想如下:若下一个进程状态是可运行的,即就绪,则保存当前进程的上下文,将 ebp 压栈, esp 和 eip 的内容保存到 Thread 结构体中,从下一个进程控制块的 Thread 中取出 sp 和 ip 放到 esp 和 eip 中,从栈中弹出 ebp,完成进程切换;若下一个进程不可运行,先要将其标记为可运行,然后再进行进程的上下文切换,这里与上面有一点不同的地方是此进程没有运行过,栈是空的,栈顶指针和栈基指针相同,所以是把 Thread 中的 sp 赋给ebp。

最后分析main.c

#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)
{
    int pid = 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));
        task[i].pid = i;
	//*(&task[i].stack[KERNEL_STACK_SIZE-1] - 1) = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];
	task[i].thread.sp = (unsigned long)(&task[i].stack[KERNEL_STACK_SIZE-1]);
        task[i].next = task[i-1].next;
        task[i-1].next = &task[i];
    }
    /* start process 0 by task[0] */
    pid = 0;
    my_current_task = &task[pid];
	asm volatile(
    	"movl %1,%%esp\n\t" 	/* set task[pid].thread.sp to esp */
    	"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 */
    	: 
    	: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)	/* input c or d mean %ecx/%edx*/
	);
} 

int i = 0;

void my_process(void)
{    
    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);
        }     
    }
}

my_process运行任务处理函数,循环一千万次,打印出自己pid检查my_need_schedule的标志,决定是否需要切换进程。
my_start_kernel为内核启动后第一个运行的函数,他的作用是初始化一个进程,并用这个初始进程的信息去填充后面新创建的进程的一些字段,当初始化第一个进程时还要设置一些寄存器的值。
2.实验步骤
2.1安装qemu
一、安装交叉编译工具链
1.创建工作目录
$ mkdir ~/qemu_linux
$ cd ~/qemu_linux
2.安装arm交叉编译工具(可以通过crosstool-ng开源软件构建,本方法直接通过apt下载)
$ sudo apt-get install gcc-arm-linux-gnueabi
3.安装qemu模拟器
下载源码
$ git clone git://git.qemu-project.org/qemu.git
$ cd qemu
检出分支
$ git checkout remotes/origin/stable-2.4 -b stable-2.4
编译前依赖库安装
sudo apt-get install zlib1g-dev
sudo apt-get install libglib2.0-0
sudo apt-get install libglib2.0-dev
sudo apt-get install libtool
sudo apt-get install libsdl1.2-dev
sudo apt-get install autoconf
sudo apt-get install libpixman-1-dev
二、安装qemu模拟器
1.编译安装
$ mkdir build
$ cd build
2.将配置生成中间文件放到build中
$ …/qemu/configure --target-list=arm-softmmu --audio-drv-list=
$ make
$ make install
三、制作文件系统
形成根目录结构
1.下载busybox
$ wget https://busybox.net/downloads/busybox-1.20.2.tar.bz2
make defconfig
make CROSS_COMPILE=arm-linux-gnueabi-
make install
安装完成后,会在busybox目录下生成_install目录,该目录下的程序就是单板运行所需要的命令。
先在Ubuntu主机环境下,形成目录结构,里面存放的文件和目录与单板上运行所需要的目录结构完全一样,然后再打包成镜像(在开发板看来就是SD卡),这个临时的目录结构称为根目录
2.创建rootfs目录(根目录),根文件系统内的文件全部放到这里
$ sudo mkdir rootfs
3.拷贝busybox命令到根目录下
$sudo cp busybox-1.20.2/_install/* -r rootfs/
4.从工具链中拷贝运行库到lib目录下
$sudo cp -P /usr/arm-linux-gnueabi/lib/* rootfs/lib/
5.创建4个tty端终设备
sudo mknod rootfs/dev/tty1 c 4 1
sudo mknod rootfs/dev/tty2 c 4 2
sudo mknod rootfs/dev/tty3 c 4 3
sudo mknod rootfs/dev/tty4 c 4 4
制作根文件系统镜像

  1. 生成32M大小的镜像
    $ dd if=/dev/zero of=a9rootfs.ext3 bs=1M count=32
  2. 格式化成ext3文件系统
    $ mkfs.ext3 a9rootfs.ext3
  3. 将文件拷贝到镜像中
    $sudo mkdir tmpfs
    $sudo mount -t ext3 a9rootfs.ext3 tmpfs/ -o loop
    $cp rootfs/* tmpfs/ -r
    $sudo umount tmpfs
    四、编译arm架构内核
    下载内核
    $ wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.16.1.tar.gz
    解压
    $ tar zcvf linux-3.16.1.tar.gz
    $ cd linux-3.16.1
    生成vexpress-a9 的config文件 (建议先执行make clean && make mrproper)
    $make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm O=./out_vexpress_3_16 vexpress_defconfig
    执行内核配置
    $make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm O=./out_vexpress_3_16 menuconfig
    选项位于:
    System Type —>
    [ ] Enable the L2x0 outer cache controller
    取消该项

其他保持默认不变
编译
$make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm O=./out_vexpress_3_16 zImage -j2
生成的内核镱像位于./out_vexpress_3_16/arch/arm/boot/zImage
完成编译

$ cp ./out_vexpress_3_16/arch/arm/boot/zImage ~/qemu_linux
$ cd ~/qemu_linux
测试qemu 能否正常启动
$qemu-system-arm -M vexpress-a9 -m 512M -kernel /home/ivan/kernel_git/linux/arch/arm/boot/zImage -nographic -append “console=ttyAMA0”
五、系统启动运行
完成上述所有步骤之后,就可以启动qemu来模拟vexpress开发板了,命令参数如下:
$ qemu-system-arm -M vexpress-a9 -m 512M -kernel /the/path/to/your/kernel/dir/arch/arm/boot/zImage -nographic -append “root=/dev/mmcblk0 console=ttyAMA0” -sd a9rootfs.ext3
3.结果展示
在这里插入图片描述
4.对操作系统功能的一点理解
该实验实现了一个简易的linux操作系统,因为它已经具备创建进程的基本功能,虽然它最多只能创建四个进程,每个进程都有单独的结构体描述进程的各种信息,并还实现了进程切换的功能,实现了简单的保存现场,栈指针,栈基址,指令指针的切换,并在最后实现了中断功能,麻雀虽小,五脏俱全。

**447原创作品转载请注明出+https://github.com/mengning/linuxkernel/ **

猜你喜欢

转载自blog.csdn.net/yn359992335/article/details/88399366