2019-2020-1 20199305《Linux内核原理与分析》第三周作业
操作系统的秘密
(一)计算机的三大法宝
-
存储程序计算机;
-
函数调用堆栈机制;
-
中断机制。
(二)堆栈
(1)堆栈的作用
-
记录函数调用框架;
-
传递函数参数;
-
保存返回值的地址;
-
提供局部变量存储空间。
(2)堆栈操作
-
push:栈顶地址减少4个字节,然后将操作数亚茹栈顶存储单元;
-
pop:栈顶地址增加4个字节,然后将栈顶存储的内容放回原寄存器。
(3)相关寄存器
-
ESP:堆栈指针(stack pointer),用于指向栈顶。
-
EBP:基址指针(base pointer),用于记录当前函数调用的基址。
-
EAX:累加寄存器(Extended accumulator register),用于存储返回值。
-
CS:EIP:用于指向下一条指令地址,它的实用有几种不同情形:
顺序执行:总是指向地址连续的下一条指令;
跳转/分支:执行这样的指令时,CS:EIP的值会根据程序需要被修改;
call:将当前存储的值压入栈顶,然后指向被调用函数的入口地址;
ret:原来保存的值从栈顶弹出,回到CS:EIP中。
-
现在谈一下个人对调用函数框架的理解
调用者用call指令来实现函数调用,接下来生成一个新的堆栈给被调用者使用,
这个过程涵盖两步——建立堆栈框架和清空堆栈拆除框架,它们由enter和leave
两个指令实现。
(三)中断
没有中断机制之前,计算机只能一个个地执行程序(批处理),而无法实现并发工作。
现在写一个时间片轮转调度的操作系统内核(使用到函数调用堆栈和内嵌汇编):
(1)使用实验楼的虚拟机打开shell,启动内核
(2)运行效果如下
(3)查看mymain.c和myinterrupt.c
(4)代码分析
-
mymain.c
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;
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];/*ESP指向栈顶*/
task[pid].next = &task[pid];
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];
}
pid = 0;
my_current_task = &task[pid];
asm volatile(
"movl %1,%%esp\n\t"
"pushl %1\n\t" /*EBP入栈*/
"pushl %0\n\t" /*task[pid].thread.ip入栈*/
"ret\n\t"
"popl %%ebp\n\t" /*EBP出栈*/
:
: "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);
/*循环10000000次之后判断是否需要调度*/
if(my_need_sched == 1)
{
my_need_sched = 0;
my_schedule();
}
printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
}
}
}