操作系统是如何运行的(时间片轮转多道程序内核代码)
操作系统是如何运行的。
继上一篇计算机是如何运行的
使用实验楼的虚拟机(直接使用3.9.4内核)使用qemu 启动
cd LinuxKernel/linux-3.9.4
qemu -kernel arch/x86/boot/bzImage
打开终端
从qemu窗口可以看到my_start_kernel在执行 同时my_timer_handler时钟中断处理程序周期性执行
mymain.c和myinterrupt.c
vi mymain.c
跳过之前include 头文件(硬件初始化)
下面函数 实际为操作系统入口 运行100000次打印一次语句
初始化好的cpu从 my_start_kernel执行
void __init my_start_kernel(void)
{
inti = 0;
while(1)
{
i++;
if(i%100000 == 0)
printk(KERN_NOTICE "my_start_kernel here %d \n",i);
}
}
vi myinterrupt.c 中断发生时进程中断处理 下面函数实现中断处理
即时钟中断机制周期执行
void my_timer_handler(void)
{
printk(KERN_NOTICE"\n>>>>>>>>>>>>>>>>>my_timer_handlerhere<<<<<<<<<<<<<<<<<<\n\n");
}
分析进程的启动和切换机制
从github下载 代码
git clone https://github.com/mengning/mykernel
定义文件头 方便多个C文件引用头 达到 公用的目的
1、mypcb.h
#defineMAX_TASK_NUM 4
#defineKERNEL_STACK_SIZE 1024*8
/*CPU-specificstate of this task */
structThread{
unsignedlong ip;
unsignedlong sp;
};
typedefstructPCB{ //定义进程管理相关的数据结构
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; //包括一个ip 一个sp
unsigned long task_entry; //调用程序入口
struct PCB *next; //进程链表
}tPCB;
void my_schedule(void);//函数调度器
2、mymain.c
#include"mypcb.h"
tPCBtask[MAX_TASK_NUM];
tPCB*my_current_task = NULL;
volatileintmy_need_sched = 0;
void my_process(void);
void__initmy_start_kernel(void)
{
int pid = 0;
int i;
/* Initialize process 0*/
task[pid].pid = pid;
task[pid].state = 0;/* -1 unrunnable, 0runnable, >0 stopped */
task[pid].task_entry = task[pid].thread.ip= (unsigned long)my_process;
task[pid].thread.sp = (unsignedlong)&task[pid].stack[KERNEL_STACK_SIZE-1];
task[pid].next = &task[pid];
分析
首先启动0号进程
任务的进程当前pid为0状态设置为0(运行状态),设置任务的入口为线程Thread的ip寄存器为my_process函数 定义堆栈的站定,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 = (unsignedlong)&task[i].stack[KERNEL_STACK_SIZE-1];
task[i].next = task[i-1].next;
task[i-1].next = &task[i];
}
fork很多进程 复制0号进程的一些状态过来 不一样的在于state为-1未启动
每个进程有自己的堆栈(创建堆栈)
并把上一个task next 指向创建的task,当前的tasknext指向原来task的next (类似链表中插入一个节点)
/* start process 0 by task[0] */
pid = 0;
//启动0号进程
my_current_task = &task[pid];
asmvolatile(
"movl%1,%%esp\n\t" /* settask[pid].thread.sp toesp */
"pushl%1\n\t" /* push ebp */
"pushl%0\n\t" /* push task[pid].thread.ip */
"ret\n\t" /* poptask[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*/
);
调用嵌入式汇编
1、当前进程SP放入ESP
2、EBP压栈(此时是初始情况ebp和esp指向同一位置,相当于esp压栈)
3、当前taskip压栈相当于myprocess调用处压栈
ret 后启动0号进程(poptask[pid].thread.ip to eip)开始执行性myprocess
内核初始化工程完成
}
void my_process(void)
{
int i = 0;
while(1)
{
i++;
if(i%10000000 == 0)
{
printk(KERN_NOTICE "this isprocess %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);
}
}
}
所有进程均执行的均是my_process方法
里面代码含义为执行1千万次查看是否需要调度
3、myinterrupt.c
#include"mypcb.h"
引入公用结构体
externtPCBtask[MAX_TASK_NUM];
externtPCB* my_current_task;
externvolatileint my_need_sched;
volatileinttime_count = 0;
引入全局变量
/*
*Called by timer interrupt.
*it runs in the name of current runningprocess,
*so it use kernel stack of current runningprocess
*/
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;
}
执行1000次判断是否为不需要调度切换需要调度状态
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, 0runnable, >0 stopped */
{
/*switch to next process */
asmvolatile(
"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 %dto%d<<<\n",prev->pid,next->pid);
}
else
{
next->state = 0;
my_current_task = next;
printk(KERN_NOTICE">>>switch %dto%d<<<\n",prev->pid,next->pid);
/*switch to new process */
asmvolatile(
"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;
}
判断当前进程的下一个进程是否已经启动
来分别执行两段不同的嵌入式汇编
如果下个进程的状态为已运行
则保存当前进程的EBP,保所esp到threadsp
把下个进程的ESP放到当前的ESP中
报标号为1的位置保存到eip
压下个进程的eip入栈
ret 下个进程开始执行
如果下个进程为新进程从未运行过
首先进程状态置为运行状态,把这个进程作为当前执行的进程
然后执行嵌入式汇编
保存ebp 保存esp 因为没有执行过这个进程 EBP和ESP 指向同一个位置,所以把要切换的的ESP分别存入当前进程的ESP EBP
SAVE EIP
要切换的进程入口保存起来
ret 开始执行要切换的进程的入口
嵌入式汇编主要是进程状态的切换保存当前进程上下文切换下一个进程到当前进程,切换执行完后可以出栈再切换回来
对比计算机意见三大法宝,存储程序计算机,函数堆栈调用,中断
操作系统也有两大法宝 就是保存现场和回复现场(中断上下文和进程上下文切换),
作者:李嘉
原创作品
转载请注明出处 《Linux内核分析》MOOC课