操作系统是如何工作的

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了操作系统是如何工作的相关的知识,希望对你有一定的参考价值。

实验2:运行并分析一个精简的操作系统内核,理解操作系统是如何工作的

姓名:李冬辉

学号:20133201

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

 

1.计算机是如何工作的?(总结)——三个法宝

存储程序计算机工作模型,计算机系统最最基础性的逻辑结构;

函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的时候堆栈机制对于计算机来说并不那么重要,但有了高级语言及函数,堆栈成为了计算机的基础功能;

enter 

pushl %ebp

 movl %esp,%ebp

leave 

movl %ebp,%esp

popl %ebp

函数参数传递机制和局部变量存储

中断,多道程序操作系统的基点,没有中断机制程序只能从头一直运行结束才有可能开始运行其他程序。

 

2.mykernel实验指导

使用实验楼的虚拟机打开shell

cd LinuxKernel/linux-3.9.4

qemu -kernel arch/x86/boot/bzImage

然后cd mykernel 您可以看到qemu窗口输出的内容的代码mymain.c和myinterrupt.c

使用自己的Linux系统环境搭建过程参见mykernel,其中也可以找到一个简单的时间片轮转多道程序内核代码

 

分析代码
打开mymain.c
技术分享
 
代码显示为:代码前部分——Linux头文件
技术分享
 
执行的是my_start_kernel:
之前是硬件初始化的工作,从这里开始是操作系统的入口,即启动操作系统:
技术分享
 
 
 打开myinterrupt.c文件:
技术分享
 
技术分享
技术分享
 每次时钟中断,都被调用一次,打印。
 

进程控制块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 { //用于保存erp,esp
  unsigned long ip;
  unsigned long sp;
  };
   
  typedef struct PCB{
  int pid; //进程id,进程的状态
  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); //调度器
mymain.c
/*
  * 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; //初始化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 */ //%1指task[pid].thread.sp
  "pushl %1\n\t" /* push ebp */ //当前栈为空,pushesp
  "pushl %0\n\t" /* push task[pid].thread.ip */ //%0指task[pid].thread.ip
  "ret\n\t" /* pop task[pid].thread.ip to eip */ //ret之后0号进程正式启动
  "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) //循环10000000次,判断是否需要调度
  {
  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);
  }
  }
  }
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
  || my_current_task->next == NULL)
  {
  return;
  }
  printk(KERN_NOTICE ">>>my_schedule<<<\n");
  /* schedule */
  next = my_current_task->next; //把当前进程的下一进程赋给next
  prev = my_current_task;
  if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */ //如果下一进程的状态为0(正在执行)
  {
  /* switch to next process */ //则切换进程
  asm volatile(
  "pushl %%ebp\n\t" /* save ebp */ //保存当前进程的ebp
  "movl %%esp,%0\n\t" /* save esp */ //把当前进程的esp赋给prev->thread.sp,保存
  "movl %2,%%esp\n\t" /* restore esp */ //把下一进程的next->thread.sp赋给esp
  "movl $1f,%1\n\t" /* save eip */ //保存eip
  "pushl %3\n\t" //把下一进程的next->thread.ip进栈
  "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;
  }
 

总结:
操作系统是如何工作的:操作系统是运行在相应的硬件平台上的一个特别程序,它的任务是实现任务(也就是进程)的创建、运行和调度;同时实现对运行在相应的平台的资源管理和分配,实现整个硬件和软件系统处于最优的工作状态。Linux操作系统由内核来实现它的具体工作的,一个进程是通过系统调用fork()函数来创建的,他先是将先前CPU正在运行的进程的进程上下文保存在内核态堆栈中,包括有eip,esp,ebp,cs等寄存器的数据;然后加载创建的进程的上下文信息到相应的寄存器中,运行当前新建进程;运行完毕后根据系统的调度继续执行相应的进程。在这个过程中的执行流程是:SAVE_ALLà进建PID对应的task_structàrestore allàiret。而Linux操作系统是多进程的操作系统,不同的进程就是基于以上的方式有操作系统实现调度运行的。同时,操作系统以一种中断的机制实现与用户的交互。操作系统中的IDT描述好各个中断对应的处理程序,当发生相对应的中断时,由硬件来实现中断信号的传递,CPU接收到相应的IRQ信号后,由操作系统如调度进程那样调度相应的处理程序,来完成相应的中断请求,实现与用户的交互。整个操作系统就是如此实现。  
 

流程如下:

1.将当前的用户态堆栈的espebp指针保存在当前进程内核栈

2.执行SAVE_ALL,保存进程a的各个寄存器的值到其内核栈中;

3、进入中断处理程序;

4、操作系统调用schedule()函数来进行进程调度,进入进程b的内核栈;

5、执行RESTALL_ALL,恢复现场,恢复进程b的寄存器的值;

6、执行IRET,恢复EIPESP以及EFLAGS寄存器;

7、系统从内核态返回到用户态。

 

 

以上是关于操作系统是如何工作的的主要内容,如果未能解决你的问题,请参考以下文章

创建自己的代码片段(CodeSnippet)

片段组合在 Relay 中是如何工作的?

如何从片段外部清除/重置地图?

导航抽屉backstack,如何让actionbar标题在点击后随片段改变

如何将此 JavaScript 代码片段翻译成 Parenscript?

在哪里以及如何使用片段填充我的标签