我是如何学习写一个操作系统(六):进程的调度

前言

既然引进了多进程,其实也就是在进程之间来回切换,那么就会有进程之间的调度问题。实则是在可运行进程之间分配有限的处理器时间资源的内核子系统。

几个简单的CPU调度算法

  • First Come, First Served(FCFS)

其实就是一个先进先出队列了,也就是说先申请的进程,先执行。当CPU空闲时,它会分配给位于队列头部的进程,并且这个运行进程从队列中移去。FCFS调度代码编写简单并且理解容易。

但是对于一个需要和用户进行交互的进程,这种调度算法就会造成体验非常不好,因为周转时间需要完成一整个队列的任务,非常的长

但FCFS调度算法是非抢占的。一旦 CPU 分配给了一个进程,该进程就会使用 CPU 直到释放 CPU 为止,即程序终止或是请求I/O。

  • Shortest Job First(SJF)

SJF调度算法就指对短作业或者短进程优先调度的算法,将每个进程与其估计运行时间进行关联选取估计计算时间最短的作业投入运行。这样就可以缩短周转时间

最短作业优先(SJF)调度算法将每个进程与其下次CPU执行的长度关联起来。当 CPU 变为空闲时,它会被赋给具有最短 CPU 执行的进程。如果两个进程具有同样长度的 CPU 执行那么可以由FCFS来处理。

  • RR

该算法中,将一个较小时间单元定义为时间量或时间片。时间片的大小通常为 10~100ms。就绪队列作为循环队列。CPU调度程序循环整个就绪队列,为每个进程分配不超过一个时间片的CPU。

为了实现RR调度,我们再次将就绪队列视为进程的 FIFO 队列。新进程添加到就绪队列的尾部。CPU 调度程序从就绪队列中选择第一个进程,将定时器设置在一个时间片后中断,最后分派这个进程。

调度算法的折中

在运行的许多进程里,有的进程更关心响应时间,有的进程更关心周转时间,所以调度算法就需要折中,但是如何折中又是一个问题。

Linux0.11 调度算法

schedule

schedule是Linux0.11里最主要的调度算法,但是十分简洁

  • task_struct是用来描述一个进程的结构体

    task_struct的counter是调度算法实现折中的一个关键,它既用来表示分配的时间片,又用来表示进程的优先级

  • 首先从任务数组中最后一个任务开始循环检测一些域

    如果设置过任务的定时值alarm,并且已经过期(alarm<jiffies),则在信号位图中置SIGALRM信号,即向任务发送SIGALARM信号。然后清alarm。还有一些信号量相关的会在后面再提

  • 找到数值最大的一个couter,也就是运行时间最少的一个进程,切换到它的进程
  • 当执行完一回的轮转就会重新分配一次时间片,这时候对于已经轮转过的进程,时间片将会被设置为初值,但是对于那些阻塞的进程,时间片将会增加,也就是进行了一次折中的调度。
void schedule(void)
{
    int i,next,c;
    struct task_struct ** p;

/* check alarm, wake up any interruptible tasks that have got a signal */

    for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
        if (*p) {
            if ((*p)->alarm && (*p)->alarm < jiffies) {
                    (*p)->signal |= (1<<(SIGALRM-1));
                    (*p)->alarm = 0;
                }
            if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
            (*p)->state==TASK_INTERRUPTIBLE)
                (*p)->state=TASK_RUNNING;
        }

/* this is the scheduler proper: */

    while (1) {
        c = -1;
        next = 0;
        i = NR_TASKS;
        p = &task[NR_TASKS];
        while (--i) {
            if (!*--p)
                continue;
            if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
                c = (*p)->counter, next = i;
        }
        if (c) break;
        for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
            if (*p)
                (*p)->counter = ((*p)->counter >> 1) +
                        (*p)->priority;
    }
    switch_to(next);
}

init

我们在顺便来看一下sched_init,这个函数内核调度程序的初始化子程序,就是初始化一些中断和描述符

  • 首先调用set_tss_desc和set_ldt_desc设置进程0的tss和ldt
  • 清任务数组和描述符表项
  • 之后初始化8253定时器
  • 最后是设置时钟中断和系统调用中断
void sched_init(void)
{
    int i;
    struct desc_struct * p;

    if (sizeof(struct sigaction) != 16)
        panic("Struct sigaction MUST be 16 bytes");
    set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
    set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
    p = gdt+2+FIRST_TSS_ENTRY;
    for(i=1;i<NR_TASKS;i++) {
        task[i] = NULL;
        p->a=p->b=0;
        p++;
        p->a=p->b=0;
        p++;
    }
/* Clear NT, so that we won't have troubles with that later on */
    __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
    ltr(0);
    lldt(0);
    outb_p(0x36,0x43);      /* binary, mode 3, LSB/MSB, ch 0 */
    outb_p(LATCH & 0xff , 0x40);    /* LSB */
    outb(LATCH >> 8 , 0x40);    /* MSB */
    set_intr_gate(0x20,&timer_interrupt);
    outb(inb_p(0x21)&~0x01,0x21);
    set_system_gate(0x80,&system_call);
}

设置描述符

_set_tssldt_desc其实就是根据描述符各个位的作用来进行设置

#define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),((int)(addr)),"0x89")
#define set_ldt_desc(n,addr) _set_tssldt_desc(((char *) (n)),((int)(addr)),"0x82")

#define _set_tssldt_desc(n,addr,type) __asm__ ("movw $104,%1\n\t"     "movw %%ax,%2\n\t"     "rorl $16,%%eax\n\t"     "movb %%al,%3\n\t"     "movb $" type ",%4\n\t"     "movb $0x00,%5\n\t"     "movb %%ah,%6\n\t"     "rorl $16,%%eax"     ::"a" (addr), "m" (*(n)), "m" (*(n+2)), "m" (*(n+4)),      "m" (*(n+5)), "m" (*(n+6)), "m" (*(n+7))     )

设置中断

#define _set_gate(gate_addr,type,dpl,addr) __asm__ ("movw %%dx,%%ax\n\t"     "movw %0,%%dx\n\t"     "movl %%eax,%1\n\t"     "movl %%edx,%2"     :     : "i" ((short) (0x8000+(dpl<<13)+(type<<8))),     "o" (*((char *) (gate_addr))),     "o" (*(4+(char *) (gate_addr))),     "d" ((char *) (addr)),"a" (0x00080000))

#define set_intr_gate(n,addr)     _set_gate(&idt[n],14,0,addr)

#define set_system_gate(n,addr)     _set_gate(&idt[n],15,3,addr)    

小结

这一篇主要看了一下Linux0.11里的调度算法,十分的简洁,但是又照顾了响应时间,又照顾了周转时间。

然后提了一下内核调度程序的初始化子程序,其实就是根据之前说的设置一些描述符和中断处理

原文地址:https://www.cnblogs.com/secoding/p/11422525.html

时间: 2024-10-29 01:02:53

我是如何学习写一个操作系统(六):进程的调度的相关文章

我是如何学习写一个操作系统(五):故事的高潮之进程和线程1

前言 为什么取这个标题呢?一是进程和线程是作为操作系统里最重要最核心的一部分.二是确实吃冰棍拉冰棍,没话,强行凑标题和之前的标题差不多字数. 前一章写了系统调用的过程,算是一个小插曲,这个部分不管在哪里应该都是可以的. 现在的这个系列已经和之前的标题渐行渐远了,原本是想以之前写的一个玩具型操作系统FragileOS为主线,但是在看书学习的过程中稍微改了一下方向,已经不是特别关注一个操作系统的实现的完整流程和内部的联系,更多的是想系统的学习操作系统的各个模块然后辅以一些代码,但是不管怎么样,都是属

我是如何学习写一个操作系统(三):操作系统的启动之保护模式

前言 上一篇其实已经说完了boot的大致工作,但是Linux在最后进入操作系统之前还有一些操作,比如进入保护模式.在我自己的FragileOS里进入保护模式是在引导程序结束后完成的. 实模式到保护模式属于操作系统的一个大坎,所以需要先提一下 从实模式到保护模式 实模式和保护模式都是CPU的工作模式,它们的主要区别就是寻址方式 实模式出现于早期8088CPU时期.当时由于CPU的性能有限,一共只有20位地址线(所以地址空间只有1MB),以及8个16位的通用寄存器,以及4个16位的段寄存器.所以为了

Android学习--写一个发送短信的apk,注意布局文件的处理过程!!!

刚开始写Android程序如图发现使用了findViewById方法之后输出的话居然是null(空指针错误),也就是说这个方法没有成功.网上说这样写是在activity_main .xml去找这个ID所代表的控件,而现在使用的ADT在layout下除了activity_main .xml还多生成了一个fragment_main.xml.我就是把控件写在了后一个布局文件中.想请问下,使用什么样的方法是在fragment_main.xml去寻找控件呢? 回答:在PlaceHolderFragment

Linux内核学习--写一个c程序,并在内核中编译,运行

20140506 今天开始学习伟大的开源代表作:Linux内核.之前的工作流于几个简单命令的应用,因着对Android操作系统的情愫,"忍不住"跟随陈利君老师的步伐,开启OS内核之旅.学习路径之一是直接从代码入手,下面来写一个hello.c内核模块. 说明: 这个路径/usr/src/linux-headers-2.6.32-22/include/linux是引用的头文件. 内核模块固定格式:module_init()/ module_exit(),module函数是从头文件中来的.

初级篇第八期:学习写一个Model

学习建议:自己动手,丰衣足食 学习周期:1周 学习目的:熟练使用Obejct-C中基于NSObject的Model类 学习答疑:欢迎来技术群里提问并做分享 学习工具:Xcode开发环境 学习内容:熟悉Model的作用以及基本用法 我们在开发中为什么要创建Model,主要因为我们在接收服务器给我们返回的数据时候,我们需要用来保存这些数据,那么我们就会创建一个所谓的Model,其实就是基于NSObject类,然后创建一些它的属性,来保存相应的值,也方便其他的类来调用,当然了,我们要考虑在这个Mode

学习写一个能够将文件复制、解压到指定文件夹中的批处理文件

场景1:afoldersource中有若干文件夹和若干压缩文件,现在将afoldersource中的文件全部复制到afolderdist中. afoldersource: 脚本: @ECHO OFF ##关闭命令回显 echo copy file ##显示一句话 SET sourcepath=E:\afoldersource ##定义源地址 SET destpath=E:\afolderdist ##定义目标地址 rmdir %destpath%\folder /s /q ##删除目标地址中的文

近期小感——一个残疾人写的操作系统

因为自己也在写一个操作系统的内核,虽然没完成,也遇到很多困难,参考了诸如Linux 0.01 ,minix3.0, Orange's等,总算有点起色吧,属于走一步看一步那种,从实模式怎么跳到保护模式,从怎么打开关闭中断,再到怎么读取内存大小,都是一步一步来,很多内容都是从网上搜索,看资料等,而且我的引导扇区也是用的nasm汇编,实在反感ATT格式汇编,毕竟大学学得汇编是在windows下的的intel格式. 扯了这么多犊子,还是想说,我在搜索决定写宏内核还是微内核时,碰到了一个人从10年就开始搞

Webpack-源码三,从源码分析如何写一个plugin

经过上一篇博客分析webpack从命令行到打包完成的整体流程,我们知道了webpage的plugin是基于事件机制工作的,这样最大的好处是易于扩展.社区里很多webpack的plugin,但是具体到我们的项目并不一定适用,这篇博客告诉你如何入手写一个plugin,然后分析源码相关部分告诉你你的plugin是如何工作.知其然且知其所以然. 该系列博客的所有测试代码. 从黑盒角度学习写一个plugin 所谓黑盒,就是先不管webpack的plugin如何运作,只去看官网介绍. Compiler和Co

从零写一个编译器(三):语法分析之几个基础数据结构

项目的完整代码在 C2j-Compiler 写在前面 这个系列算作为我自己在学习写一个编译器的过程的一些记录,算法之类的都没有记录原理性的东西,想知道原理的在龙书里都写得非常清楚,但是我自己一开始是不怎么看得下来,到现在都还没有完整的看完,它像是一本给已经有基础的人写的书. 在parse包里一共有8个文件,就是语法分析阶段写的所有东西啦 Symbols.java Production.java SyntaxProductionInit.java FirstSetBuilder.java Prod