2019-2020-1 20199307《Linux内核原理与分析》第三周作业
操作系统是如何工作的
一:函数调用堆栈
计算机的三个“法宝”:
1.存储程序计算机:基本上是所有计算机的基础性框架。
2.函数调用堆栈:堆栈是C语言程序运行时必须使用的记录函数调用路径和参数存储的空间,堆栈的具体作用有:记录函数调用框架、传递函数参数、保存返回值的地址、提供函数内部局部变量的存储空间等。
3.中断机制:中断的流程大概是产生中断、响应中断、关闭中断、保护中断、识别中断源、现场保护、中断服务、现场恢复。
堆栈相关的寄存器:
1.ESP:堆栈指针
2.EBP:基址指针,在C语言中用作记录当前函数调用基址。
堆栈操作:
对于x86体系结构来讲,堆栈空间是从高地址向低地址增长的。
二:借助Linux内核部分源代码模拟存储程序计算机工作模型及时钟中断
实验步骤:(因为实验楼已经为我们搭配好了qemu环境,并且搭建了虚拟的x86 CPU。所以可以直接进行试验)
1.使用实验楼的虚拟机打开shell,并且进入到内核目录中:
>cd ~/LinuxKernel/linux-3.9.4
>rm -rf mykernel
>patch -p1 < ../mykernel_for_linux3.9.4sc.patch
>make allnoconfig
>make
2.查看搭建起来的内核启动效果:
> qemu -kernel arch/x86/boot/bzImage
3.进入到mykernel目录,查看mymain.c和myinterrupt.c:
> cd mykernel
4.查看mymain.c文件:
>vi mymain.c
在mymain.c中,my_start_kernel是操作系统的入口,在该函数体中,有一个循环,每循环100000次,就会打印一条语句。因为如今的CPU速度都很快,所以,呈现在我们眼前就是在很短的时间打印一次。
5.查看myinterrupt.c文件:
> vi myinterrupt.c
在myinterrupt.c中,每次的始终中断,都会打印语句“my_timer_handler here”。
6.通过实验所给的代码链接,建立一个简单的时间片轮转多道程序:
7.分析进程的启动和进程的切换机制:
mypcb.h:
#define MAX_TASK_NUM 4 //定义总有4个任务/进程
#define KERNEL_STACK_SIZE 1024*2 //每个进程的堆栈大小为2KB
/*用于保有存当前进程的esp和eip*/
struct Thread {
unsigned long ip;
unsigned long sp;
};
/*定义进程结构体*/
typedef struct PCB{
int pid; //进程id
volatile long state; //进程状态
unsigned long stack[KERNEL_STACK_SIZE]; //进程堆栈
struct Thread thread; //保存esp、eip
unsigned long task_entry; //进程入口
struct PCB *next; //在进程链表中指向下一个进程
}tPCB;
/*声明调度函数*/
void my_schedule(void);
mymain.c:
#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;
/* 初始化0号进程*/
task[pid].pid = pid;
task[pid].state = 0;
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]; //进程链表中,next指针指向自己
/*创建其他进程*/
for(i=1;i<MAX_TASK_NUM;i++)
{
/*把0号进程的状态拷贝给当前进程,不同成员值再单独赋值*/
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" /*把当前进程的栈指针赋给esp*/
"pushl %1\n\t" /* 把当前进程的栈指保存到栈中 */
"pushl %0\n\t" /* 保存当前进程的eip到栈中 */
"ret\n\t" /* 返回操作 */
:
: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /*ecx=当前进程的eip, 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);
/* 判断是否需要切换进程,1需要切换,0不需要切换*/
if(my_need_sched == 1)
{
my_need_sched = 0;
my_schedule(); //切换进程
}
printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
}
}
}
myinterrupt:
#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(my_current_task == NULL
|| my_current_task->next == NULL)
{
return;
}
printk(KERN_NOTICE ">>>my_schedule<<<\n");
next = my_current_task->next;
prev = my_current_task;
if(next->state == 0) /*state:-1表示不可用,0表示正在执行,>0表示没有运行 */
{
/*当前进程指针指向下一下进程*/
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" /*保存当前进程的ebp*/
"movl %%esp,%0\n\t" /*保存当前的栈指针到上一个进程的sp中*/
"movl %2,%%esp\n\t" /*把下一个进程的sp赋给esp*/
"movl $1f,%1\n\t" /*保存当前进程的eip到上进一个进程的ip中*/
"pushl %3\n\t" /*将下一个进程的ip压到栈中*/
"ret\n\t" /* 返回操作 */
"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;
}
三:总结
通过本章的学习,我对计算机的三大法宝有了进一步的了解,清楚它们各自的作用是什么。并且对C语言内嵌入汇编代码有了一定的了解,但从解读内嵌代码到自行编写还需要很长的时间来学习。实验楼给我们提供了良好的实验环境,供我们学习如何进行借助Linux内核部分源代码模拟存储程序计算机工作模型及时钟中断,它的重难点就是进程的切换,进程在执行的过程中,当时间片用完需要进行进程切换时,预压先存储当前进程的上下文环境,下次被调度时,需要恢复进程的上下文环境,才可以实现多道程序的并发性执行。