Linux内核分析实验二:完成一个简单的时间片轮转多道程序内核代码

陈琛 + 原创作品转载请注明出处 +
《Linux内核分析》MOOC课程

一、基础知识-C代码中嵌入汇编代码

内嵌汇编语法

__asm__ __volatile__(
    汇编语言模板;
    输出部分:
    输入部分:
    破坏描述部分);
    
#include <stdio.h>
int main()
{
    unsigned int val1 = 1;
    unsigned int val2 = 2;
    unsigned int val3 = 0;
    
    asm volatile(
            "movl $0,%%eax\n\t"
            "addl %1,%%eax\n\t"
            "addl %2,%%eax\n\t"
            "movl %%eax,%0\n\t"
            :"=m"(val3)     //输出部分,%0表示,=m表示输出到内存变量val3
            :"c"(val1),"d"(val2)//输入部分,%1,%2
                //val1变量放入ecx,val2变量放入edx寄存器
            );
    
    printf("val1:%d+val2:%d=val3:%d\n",val1,val2,val3);

    return 0;
}
 嵌入汇编模板中的输入部分和输出部分作用就是与c代码进行数据交换的通道   
  1 #include <stdio.h>                   
  2 
  3 int main()
  4 {
  5     int input, output, temp;
  6     input = 1;
  7 
  8     __asm__ __volatile__(
  9         "movl $0, %%eax;\n\t"
 10         "movl %%eax, %1;\n\t"
 11         "movl %2, %%eax;\n\t"
 12         "movl %%eax, %0;\n\t"
 13         :"=m"(output),"=m"(temp)//输出到内存变量
 14         :"r"(input)     //输入放入任意寄存器
 15         :"eax");        //破坏eax
 16 
 17         printf("temp=%d,output= %d\n", temp, output);
 18 
 19         return 0;
 20 }

内嵌汇编常用限定符

二、基础知识

  • 存储程序计算机
  • 函数调用堆栈
  • 中断

1. 程序切换:一个中断信号发生时,CPU和内核共同实现了保存现场和恢复现场

2. 将eip,esp,ebp压入到内核堆栈,然后eip指向中断处理服务程序

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

1.实验环境

2.实验说明

借助Linux内核源码,老师已经打好了内核补丁,我们暂时不需要关注系统为什么开始从my_start_kernel函数开始执行我们编写的内核代码。核心函数如下:

  • my_start_kernel函数,内核启动入口
  • my_process函数,进程执行函数
  • my_timer_handler函数,时钟中断处理函数,提供时间片
  • my_schedule函数,进程切换

3.时间片轮转内核代码执行流程图

码执行流程图

4.运行结果

image

5.关键代码分析

//mypch.h

//定义了PCB结构,关于进程的信息定义在PCB结构体中

#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;   //记录进程执行时eip,esp等值
    unsigned long	task_entry;
    struct PCB *next;
}tPCB;

void my_schedule(void);     //调度函数
//mymain.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*/
    /*初始化0号进程,状态为运行,进程逻辑为process函数*/
    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 */
    /*fork多个进程,构成一个循环进程链表*/
    for(i=1;i<MAX_TASK_NUM;i++)
    {
        memcpy(&task[i],&task[0],sizeof(tPCB));
        task[i].pid = i;
        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的进程
    *分别让cpu寄存器esp,ebp指向本进程的堆栈空间
    *eip寄存器指向my_process函数,这样进程被启动之后就开始执行my_process代码
    */
    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 */
    	"popl %%ebp\n\t"
    	: 
    	: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)	/* input c or d mean %ecx/%edx*/
	);
}
/*
*通过全局变量my_need_sched配合时钟中断处理函数,实现进程调度
*/
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

#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;
    /*下一进程状态为0*/
    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 */
    	/*
    	*1.保存当前进程环境ebp,esp,eip
    	*2.恢复下一个进程堆栈环境
    	*3.eip指向下一个进程的入口
    	*/
    	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)
    	); 
    }
    else
    {   /*
        *从未被调度过的进程
        *除了保存当前进程(prev)的堆栈环境,还需要新建(不是恢复)下一个进程的堆栈环境
        */
        next->state = 0;
        my_current_task = next;
        printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid); 

        /* switch to new process */
        asm volatile(  
            "pushl %%ebp\n\t" /* save ebp */
            "movl %%esp,%0\n\t" /* save esp */
            "movl %2,%%esp\n\t" /* restore esp */
            "movl %2,%%ebp\n\t" /* restore ebp */
            "movl $1f,%1\n\t" /* save eip */   
            "pushl %3\n\t"
            "ret\n\t" /* restore eip */
            : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
            : "m" (next->thread.sp),"m" (next->thread.ip)
        );  
    }
    return;	
}
代码总结:

在实验代码的基础上,添加了自己对代码理解的注释,重点部分是对进程启动、切换汇编码的理解,需把握住进程切换时现场保护(堆栈信息和eip)。

5.我对“操作系统怎么工作”的理解

本实验通过时钟中断驱动的进程切换模拟了现代操作系统中重要的两个部分:中断、进程切换。其中进程切换时上下文保护是实验重点关注的内容。通过本实验,os在运行过程中,响应中断,引起进程切换,保存当前cpu环境,执行中断服务程序。这个过程就够成了多道程序运行环境,是现代操作系统得以运行的基本原理。

posted @ 2017-03-05 09:44  tb1over  阅读(239)  评论(0)    收藏  举报