LINUX内核分析第二周学习总结:操作系统是如何工作的?

时间:2016-03-05 16:18:38   收藏:0   阅读:219

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

一、函数调用堆栈

1. 小结:计算机是怎样工作的

三个法宝:存储程序计算机、函数调用堆栈、中断机制。

2. 堆栈

3. 堆栈操作

 

call指令的两个作用:
- 将下一条指令的地址A保存在栈顶
- 设置eip指向被调用程序代码开始处

4. 函数堆栈框架

5. 注:函数调用约定

函数调用约定参数传递顺序负责清理参数占用的堆栈
__pascal 从左到右 调用者
__stdcall 从右到左 被调函数
__cdecl 从右到左 调用者

二、函数堆栈框架

三、C代码中嵌入汇编代码的写法

0. 内嵌汇编语法

__asm__(汇编语句模板: 输出部分: 输入部分: 破坏描述部分)

1. 汇编语句模板

汇编语句模板由汇编语句序列组成,语句之间使用“;”、“\n”或“\n\t”分开。指令中的操作数可以使用占位符引用C语言变量,操作数占位符最多10个,名称如下:%0,%1,…,%9。

2. 输出部分

输出部分描述输出操作数,不同的操作数描述符之间用逗号格开,每个操作数描述符由限定字符串和C语言变量组成。

3. 输入部分

输入部分描述输入操作数,不同的操作数描述符之间使用逗号格开,每个操作数描述符由限定字符串和C语言表达式或者C语言变量组成。

4. 破坏描述部分

破坏描述符用于通知编译器我们使用了哪些寄存器或内存,由逗号格开的字符串组成,每个字符串描述一种情况,一般是寄存器名;除寄存器外还有“memory”。

5. 限制字符

限制字符有很多种,有些是与特定体系结构相关,它们的作用是指示编译器如何处理其后的C语言变量与指令操作数之间的关系。 
常用限制字符
分类限定符描述
通用寄存器 “a” 将输入变量放入eax
“b” 将输入变量放入ebx
“c” 将输入变量放入ecx
“d” 将输入变量放入edx
“s” 将输入变量放入esi
“d” 将输入变量放入edi
“q” 将输入变量放入eax,ebx,ecx,edx中的一个
“r” 将输入变量放入通用寄存器,也就是eax,ebx,ecx,edx,esi,edi中的一个
“A” 把eax和edx合成一个64 位的寄存器(use long longs)
内存 “m” 内存变量
“o” 操作数为内存变量,但是其寻址方式是偏移量类型,也即是基址寻址,或者是基址加变址寻址
“V” 操作数为内存变量,但寻址方式不是偏移量类型
“ ” 操作数为内存变量,但寻址方式为自动增量
“p” 操作数是一个合法的内存地址(指针)
寄存器或内存 “g” 将输入变量放入eax,ebx,ecx,edx中的一个或者作为内存变量
“X” 操作数可以是任何类型
立即数 “I” 0-31之间的立即数(用于32位移位指令)
“J” 0-63之间的立即数(用于64位移位指令)
“N” 0-255之间的立即数(用于out指令)
“n” 立即数
“p” 立即数,有些系统不支持除字以外的立即数,这些系统应该使用“n”而不是“i”
匹配 & 该输出操作数不能使用过和输入操作数相同的寄存器
操作数类型 “=” 操作数在指令中是只写的(输出操作数)
“+” 操作数在指令中是读写类型的(输入输出操作数)
浮点数 “f” 浮点寄存器
“t” 第一个浮点寄存器
“u” 第二个浮点寄存器
“G” 标准的80387浮点常数
其它 % 该操作数可以和下一个操作数交换位置
# 部分注释,从该字符到其后的逗号之间所有字母被忽略
* 表示如果选用寄存器,则其后的字母被忽略

6. 例子

(1)例一

技术分享

(2)例二

技术分享

 

结果:temp=0,output=1。

四、mykernel实验

1. 部分内核代码模拟

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

(1)主要改写文件

mypcb.h : 进程控制块PCB结构体定义。
mymain.c: 初始化各个进程并启动0号进程。
myinterrupt.c:时钟中断处理和进程调度算法。

(2)mypcd.h源代码

/*
*  linux/mykernel/mypcb.h
*  Kernel internal PCB types
*  Copyright (C) 2013  Mengning
*/

#define MAX_TASK_NUM        4
#define KERNEL_STACK_SIZE   1024*8

/* CPU-specific state of this task */
struct Thread {
    unsigned long       ip;//保存eip
    unsigned long       sp;//保存esp
};

typedef struct PCB{
    int pid;
    volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
    char 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);//调度器

(3)mymain.c:内核初始化和0号进程启动

/*
 *  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)
{
    int pid = 0;
    int i;
    /* Initialize process 0 (初始化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;//定义0号进程的入口:myprocess
    task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
    task[pid].next = &task[pid];//由于0号进程初始化时只有这一个进程,所以next指向自己
    /*fork more process (创建更多其他的进程)*/
    for(i=1;i<MAX_TASK_NUM;i++)
    {
        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];
    }
    /* start process 0 by task[0] */
    pid = 0;
    my_current_task = &task[pid];
    asm volatile(
        //%0表示参数thread.ip,%1表示参数thread.sp。
        "movl %1,%%esp\n\t"     /* set task[pid].thread.sp to esp 把参数thread.sp放到esp中*/
        "pushl %1\n\t"          /* push ebp 由于当前栈是空的,esp与ebp指向相同,所以等价于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*/
    );
}
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);
        }     
    }
}

(4)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 //task为空,即发生错误时返回
    || my_current_task->next == NULL)
    {
        return;
    }
    printk(KERN_NOTICE ">>>my_schedule<<<\n");
    /* schedule */
    next = my_current_task->next;//把当前进程的下一个进程赋给next
    prev = my_current_task;//当前进程为prev
    if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
    {
        /* switch to next process */
        /*如果下一个进程的状态是正在执行的话,就运用if语句中的代码表示的方法来切换进程*/
        asm volatile(   
            "pushl %%ebp\n\t"       /* save ebp 保存当前进程的ebp*/
            "movl %%esp,%0\n\t"     /* save esp 把当前进程的esp赋给%0(指的是thread.sp),即保存当前进程的esp*/
            "movl %2,%%esp\n\t"     /* restore  esp 把%2(指下一个进程的sp)放入esp中*/
            "movl $1f,%1\n\t"       /* save eip $1f是接下来的标号“1:”的位置,把eip保存下来*/ 
            "pushl %3\n\t"          /*把下一个进程eip压栈*/
            "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)
        ); 
        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; 
}

五、总结

操作系统“两剑”:中断上下文、进程上下文的切换
  1. 操作系统的核心功能就是:进程调度和中断机制,通过与硬件的配合实现多任务处理,再加上上层应用软件的支持,最终变成可以使用户可以很容易操作的计算机系统。
  2. Linux是一个多进程的操作系统,所以,其他的进程必须等到正在运行的进程空闲CPU后才能运行。当正在运行的进程等待其他的系统资源时,Linux内核将取得CPU的控制权,并将CPU分配给其他正在等待的进程,这就是进程切换。
  3. 进程切换机制中包含esp的切换、堆栈的切换。从esp可以找到进程的描述符;堆栈中ebp的切换,确定了当前变量空间属于哪个进程。

原文:http://www.cnblogs.com/mqy123/p/5244858.html

评论(0
© 2014 bubuko.com 版权所有 - 联系我们:wmxa8@hotmail.com
打开技术之扣,分享程序人生!