Linux内核分析之二——时间片轮转多道程序上下文切换机制之堆栈分析
来源:互联网 发布:成都网络推广公司电话 编辑:程序博客网 时间:2024/05/17 08:40
作者:姚开健
原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
在计算机中,能够保证计算机程序运行的一些重要机制包括:存储程序,函数调用堆栈机制,中断机制以及多道程序上下文切换机制。本文要介绍的就是在时间片轮转的情况下,多道程序上下文切换的机制是如何工作的。以一个小小的kernel,运行着几个程序,然后在时间片消耗完之后如何来切换程序,来模拟操作系统的进程切换机制。
首先由三个程序文件:
1、mypcb.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 longip; unsigned longsp;};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 longtask_entry; struct PCB *next;}tPCB;void my_schedule(void);
2,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*/ 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].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( "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*/);} 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); } }}3、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; if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */ { /* 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) ); 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;}程序运行过程如:
从上面的文件可以看到,我们定义一个PCB,里面有进程的标号,堆栈指针,堆栈,eip,下一进程指针等等,程序首先在void __init my_start_kernel(void)函数执行,先初始化0号进程,即task[0],
/* 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];接着初始化1,2,3号进程,
/*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]; }接着运行0号进程,这时需要详细说明进程如何开始运行:
/* 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 */ "popl %%ebp\n\t" : : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)/* input c or d mean %ecx/%edx*/);}0号进程首先运行上面的汇编代码,分别是1,把进程PCB初始化时的栈顶指针送到CPU的esp,2,ebp压栈,因为是0号进程,ebp与esp相同,所以可以用pushl %esp,3,初始化的eip压栈再出栈,此时程序eip为函数void my_process(void)的地址,程序跳转到此函数运行。如果系统的时钟中断函数my_timer_handler时间片运行完,则把可以切换程序标志置为true,所以当my_process函数运行足够时间后,便会访问切换标志,如果为true,则进行程序切换,访问my_schedule(void)函数。
当从0号进程切换至1号进程时,执行
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) ); }这是进程切换的关键步骤,首先1,pushl %ebp,pushl %esp,保存刚才运行的进程的ebp和esp,2,接着把新进程原来初始化的esp(包括ebp)送到CPU的esp,ebp,接着保存旧进程的eip($1f,1的意思是标号1的代码段的地址,f意思是forward,即标号1下面的地址)3,接着把新进程的eip压栈再出栈,程序跳转至my_process函数执行,接着以此类推至3号进程切换至0号进程,执行:
if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */ { /* 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) ); my_current_task = next; printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid); }这时的进程切换也是与之前的切换大同小异,也是先保存旧进程的ebp,esp,接着把新进程保存的esp恢复,然后eip压栈再出栈,跳转至1:处执行,ebp出栈。
通过此程序我们知道,进程的运行开始时需要把栈顶地址(即栈数组的末尾地址)送至CPU的esp,并初始化ebp,接着把进程需要运行的函数地址压栈,再ret(即popl %eip),程序就能进入函数运行,进程启动完毕。
在程序进行切换时,需要保持旧进程的ebp,esp,然后再按照新进程的运行开始过程(如上面所说)一样初始化新进程的相关数据。
0 0
- Linux内核分析之二——时间片轮转多道程序上下文切换机制之堆栈分析
- Linux内核分析-2/时间片轮转多道程序
- Linux内核分析(二) 一个简单的时间片轮转多道程序内核代码
- Linux内核分析 实验二:完成一个简单的时间片轮转多道程序内核代码
- Linux内核分析二:一个简单的时间片轮转多道程序内核代码
- 机制分析:基于简易的时间片轮转多道程序的 linux 内核
- Linux内核分析2:一个简单的时间片轮转多道程序内核代码分析
- 简单的时间片轮转多道程序内核代码分析
- Linux 内核分析 第二次作业 完成一个简单的时间片轮转多道程序内核代码
- Linux内核分析:完成一个简单的时间片轮转多道程序内核代码
- Linux内核分析-完成一个简单的时间片轮转多道程序内核代码
- Linux内核分析,完成一个简单的时间片轮转多道程序内核代码
- 第2节 一个简单的时间片轮转多道程序内核代码【Linux内核分析】
- 通过一个简单的时间片轮转多道程序内核代码,分析linux操作系统系统
- Linux内核分析之时间片轮转调度
- [网易云课堂]Linux内核分析(二)—— mykernel内核部署及简单时间片轮转程序分析
- 一个简单的时间片轮转多道程序分析
- 通过内嵌汇编实现模拟时间片轮转多道程序的内核代码分析
- 插入排序法 php
- [leetcode] 297. Serialize and Deserialize Binary Tree 解题报告
- 思考下:工作沟通工具的选择(QQ?、微信?、邮件?、m2m还是其他的?)
- 55. Jump Game
- 如何设置约束让三个按钮平分UIView容器的宽度
- Linux内核分析之二——时间片轮转多道程序上下文切换机制之堆栈分析
- [leetcode] 325. Maximum Size Subarray Sum Equals k 解题报告
- 面试题
- 3698: XWW的难题 有源汇上下界最大流
- MVC, MVP与MVVM
- 2055: 80人环游世界 有上下界的费用流
- java 爬虫
- C++ 动态绑定的实现机制
- Python图像处理库PIL的ImageStat模块介绍