首页 > 技术文章 > 2019-2020-1 20199321《Linux内核原理与分析》第三周作业

20199321zjy 2019-09-28 15:01 原文

第一部分 学习笔记

  • 计算机的“三大法宝”:程序存储计算机、函数调用堆栈以及中断机制。
  • 堆栈的作用是:记录函数调用框架、传递函数参数、保存返回值地址、提供函数内部局部变量的存储空间。
  • 堆栈相关的寄存器:
    ESP:堆栈指针,指向堆栈栈顶
    EBP:基址指针,指向堆栈栈底
  • 堆栈操作
    push: 栈顶地址减少4个字节,将操作数放入栈顶存储单元
    **pop ** :将操作数从栈顶存储单元移出,栈顶地址增加4个字节
  • 其他关键寄存器
    CS:EIP 总是指向下一条指令地址。CS是代码段寄存器, EIP是指向下一条指令的地址。
  • C语言中内嵌汇编语言的写法
asm volatile (
       汇编语句模版;
       输出部分;
       输入部分;
       破坏描述部分;
       );
  • 内嵌汇编语言的写法举例分析
#include <stdio.h>

int main()
{
    unsigned int val1 = 1;
    unsigned int val2 = 2;
    unsigned int val3 = 0;
    pritnf("val1:%d,val2:%d,val3:%d\n",val1,val2,val3);
    asm volatile(
        "movl $0,%%eax\n\t"
        /*  将eax寄存器清零  */
        "addl %1,%%eax\n\t"
        /* %1 是指下面的输入输出部分,从0开始编号,所以%1指的是val1*/
        /* 这条语句的就是就是将ecx中存储的val1的值与eax寄存器中的值相加,结果为1*/
        "addl %2,%%eax\n\t"
        /* %2 是指val2存在edx寄存器中*/
        /*这条语句就是将val2与寄存器eax中的值相加,放回eax中*/
        "movl %%eax,%0\n\t"
        /* val1+val2的值写入到%0中去,也就是val3*/

        /*输出部分 */
        :"=m"(val3)
           /* =m”代表内存变量,m就是memory,也就是直接把变量写到内存val3中*/

         /*输入部分 */
        :"c"(vall),"d"(val2)
            /* c代表%二次项,d![](https://img2018.cnblogs.com/blog/1800798/201909/1800798-20190928193228032-1611440033.png)
代表%edx,就是使用这一存储器存储相应变量的值*/
    );
    pritnf("val1:%d,val2:%d,val3:%d\n",val1,val2,val3);

    return 0;
}
- asm 是GCC的关键字asm的宏定义,是内嵌汇编的关键字。
- _volatile_是GCC的关键字,告诉编译器不要优化代码,汇编指令保留原样。
- %作为转义字符,寄存器前面会多一个转义符号
- %加一个数字代表输入、输入和破坏描述的编号。

第二部分 实验报告

  • 第一步:

mymain.c一直在循环输出“my_start_kernel here”这句代码,而myinterrupt.c中的my_timer_hardler函数将上述函数打断,而执行自己的代码。

  • 第二步:将mykernel操作系统的代码进行扩展:
    1.添加mypcb.h头文件,用来定义进程控制块
//mypcb.h
#define MAX_TASK_NUM  4
#define KERNEL_STACK_SIZE 1024*8
 
  struct Thread {
    unsigned long   ip;
    unsigned long   sp;
};

typedef struct PCB{ 
    int pid;                                        //进程的编号
    volatile long state;                            //进程的状态
    char stack[KERNEL_STACK_SIZE];               
    struct Thread thread;                          
    unsigned long   task_entry;                     //进程的起始入口地址
    struct PCB *next;                                //进程用链表连接起来
 }tPCB;

void my_schedule(void);                          // 进程调度器

2.修改mymain.c,作为内核代码的入口,负责初始化内核的各个组成部分

//mymain.c
#include <linux/types.h>#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];//定义4个进程
tPCB * my_current_task = NULL;
volatile int my_need_sched = 0;

void my_process(void);   //每10000000 来进行进程调度,调用my_schedule

void __init my_start_kernel(void)
{ 
    int pid = 0; 
    int i;
    task[pid].pid = pid;   //初始化0号进程
    task[pid].state = 0;   
    task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;//0号进程的ip和入口地址设为my_process();
    task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
    task[pid].next = &task[pid];  //next指针指向自己
    for(i=1;i<MAX_TASK_NUM;i++)  //1,2,3号进程复制0号进程
        {
            memcpy(&task[i],&task[0],sizeof(tPCB));
            task[i].pid = i;
            task[i].state = -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];  //所有进程成为一个循环链表
        }
    pid = 0;
    my_current_task = &task[pid];   //启动0号进程
     asm volatile(
        "movl %1,%%esp\n\t" //esp指向stack数组的末尾
        "pushl %1\n\t"  //将task[0].thread.sp压栈
        "pushl %0\n\t"  //将task[0].thread.ip压栈
        "ret\n\t"   //eip指向0进程起始地址,启动0号进程
        "popl %%ebp\n\t"//释放栈空间
        : 
        : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) 
    );
}   

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_shcedule();
        }
            printk(KERN_NOTICE"this is process %d +\n",my_current_task->pid);
        }
    }
}

3.修改myinterrupt.c,增加进程切换代码,模拟基于时间片轮转的多道程序。

#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;

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(next->state == 0)  //下一个进程可运行,执行进程切换
    {
        /* switch to next process */   
        asm volatile(
            "pushl %%ebp\n\t"  
            "movl %%esp,%0\n\t" /
            "movl %2,%%esp\n\t" 
            "movl $1f,%1\n\t"  
            "pushl %3\n\t" 
            "ret\n\t"  
            "1:\t"  
            "popl %%ebp\n\t"
            : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
            : "m" (next->thread.sp),"m" (next->thread.ip)
           ); 
        my_current_task = next; 
        printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);  
    else
    {
        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;
}

修改后,make 进行重新编译。

推荐阅读