操作系统设计

本文探讨了并发执行在编程模型中的应用,涉及多道编程、分时与实时的区别,以及信号量在生产者消费者问题中的解决方案。文章还详细讲解了操作系统中的调度、上下文切换、内存管理、虚拟内存、中断处理、通信端口、实时时钟管理和定时消息接收等关键概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

并发执行和操作系统服务

多活动的编程模型

  • 同步事件循环
  • 异步事件处理程序
  • 并发执行 多道编程的思想

多道编程中分时和实时的区别

分时:所有运算具有相同的优先级,一项计算所获得的CPU时间与这个系统的负载成反比,也就是计算越多,效率越低

实时:各项计算具有不同的优先级,总是将具有高优先级的先运算

原则

每个进程都占用一块独立的地址空间,操作系统允许用户创建一个或多个进程,每一个进程都是运行于独立的内存区域,并进一步允许用户在一个给定进程中创建多个控制线程。

生产者消费者问题解决方式——信号量抽象

  • 一个信号量有一个整数值和一组进程构成,系统调用wait()递减信号量的值,当结果为负时,调用wait的进程将被加入到等待进程集合中,
  • 系统调用singal执行的是相反的操作,将信号量递增并允许等待进程之一继续运行
  • 互斥:两个或多个进程在协作时需要互斥,以保证在某一时刻他们中只有一个进程能够得到对一个共享资源的访问权

硬件环境

内存缓存和KSEG1

KSEG0/KESG1的区别:

当处理器引用KSEG0的地址时,将引用传递到总线之前,首先送入LI内存缓存,当处理器引用KESG1的地址时,将它直接传送到总线,所以为了避免从内存中获取旧的值,每一个IO引用(包括DMA)必须使用KSEG1

调度和上下文切换

操作系统通过在计算时频繁切换处理器给人以并发执行的幻觉,由于处理器很快——所以给人一种看上去就像在同时处理一样

上下文切换

就是停止当前进程,保存足够的信息以便可以在稍后将该进程重启,为了实现这个作用,操作系统使用进程表来记录每一个进程的相关信息,这其中包括当前进程的状态,调度优先级,栈指针的值,进程名字。

在操作系统中,进程能够通过其进程ID来引用,在进程表中,保存了进程状态信息的表项的索引就是进程的ID

调度策略

这一过程需要两个步骤:从有资格使用CPU 的进程中挑选一个,然后将CPU的控制权交给该进程,执行选择进程策略的软件称为调度器

resched使用这样一个策略:在任何时候,执行有资格获得CPU服务的优先级最高的进程,在优先级相同的情况下,采用时间轮转调度策略,该策略使得这组进程的每个成员能够依次获得服务,所以每个成员都会在其他成员获得第二轮执行之执行

因为寄存器和硬件状态不能直接通过高级语言来进行操作,所以resched调用一个用汇编语言编写的函数ctxsw来执行从一个进程到另一个进程的上下文切换

并发执行和null进程

调度器的设计反映了下面的准则:调度器的唯一功能就是使处理器正在执行和一系列准备运行的进程之间切换。

因为操作系统只能将CPU从一个进程切换到另一个进程,所以在任何时候都至少存在一个进程准备执行,为了保证至少有一个进程总是准备执行,系统在启动时创建一个null进程的额外进程,null进程包含无限循环。

ready函数

因为使进程获得CPU服务发生的非常频繁,所以我们需要设计一个函数来执行有资格执行这个状态。

ready函数的第二个参数有两个选择RESCHED_YES,RESCHED_NO,一个是执行是调用reashed完成调度,另一个是不会使用该调度器,这两个都必须遵循调度不变式,

进程挂起与恢复

操作系统函数有时候需要暂时停止一个进程的执行,并在以后恢复其执行。我们说已经停止的进程处于“假死”状态,在假死情况下,理论上紧靠两个操作就可以提供所有需要的功能:挂起和恢复

挂起允许一个进程挂起另一个,而不仅仅是作用在当前进程上,恢复必须允许一个正在执行的进程恢复一个之前被挂起的进程

禁止中断和恢复中断

在操作系统执行任何计算之前,要保证中断不会发生,所以操作系统定义了这样一对函数:

  • 函数disable禁止中断并且返回先前的中断状态给调用者
  • 函数restore从先前保存的值中重新载入中断状态

进程终止和进程退出

系统调用kill通过从系统中完全移除进程来实现进程的终止一旦进程被杀死,将无法被加载运行

技术信号量的概念

并发执行的进程需要相互协作来实现共享全局变量,操作系统必须保证在任何时候只有一个进程企图改变一个指定变量的值,在前面的知识中我们知道操作系统通过调用禁止中断和中断恢复来实现这一过程。

但禁止中断对整个操作系统都有着负面的影响,禁止中断使操作系统只有一个进程在执行,其他所有活动都停止,而且还限制了进程的行为

因此需要一种更为通用的机制,使得在不长时间禁止中断的前提下,允许进程之间协调数据项的使用,这种机制必须包括一下内容:

  • 允许一部分进程未访问某一资源进行竞争
  • 提供一种策略来保证竞争的公平性
  • 互斥
  • 生产者-消费者模型

计数信号量机制有着相当优雅的实现,信号量s有一个整数和一系列阻塞进程构成,当信号量创建后,进程对信号量使用wait和signal两个函数,关于这两个函数的特点,前面的已经有说过:如果没有进程调用signal,则阻塞的进程会一直等待下去

避免忙等待

为了在实现信号量时避免忙等待,造作系统为每个信号量关联了一个进程链表,只有当前进程可以选择等待信号量,当一个进程等待信号量s时,系统将信号量s对应的值减1,如果为负,进程被阻塞,操作系统将该进程放入信号量关联的进程链表中,然后调用resched调用其他进程,singal根据最长等待时间的信号量策略选择需要执行的进程

动态信号量

通过semcreate,semdelete函数实现信号量的动态创建和删除

多核处理器

多处理器使用称为旋锁的互斥机制,尽管需要一个处理器重复测试是否能够访问,但是相比于一个处理器中断另一个处理器来进行系统调用的方法,自旋锁还是很有效的

消息传递

我们使用消息传递来表示进程间交互的形式,消息传递有两种方式:

  • 同步 如果接受者准备在消息到达前接收,那么他就会阻塞,反之亦然
  • 异步 消息可以在任意时刻到达,消息到达后通知接受者,接受者不需要知道有多少消息到达或者有多少发送者将发送消息

对于同步消息传递,进程需要调用依次系统函数,这个调用会阻塞直到消息到达,相反,异步消息传递系统要求轮询,或者要求一个允许操作系统暂停进程的机制

xinu底层的消息传递机制限制信息的大小为一个字,限制每一个进程最多有一个未完成的消息,并使用第一消息语义,消息的存储和进程表相关——发送到进程P的消息存储在P的进程表项中,第一消息语义的使用允许进程决定先触发那些事件

底层的消息组件包括的函数

send,receive和recvclr,这三个函数中,主要receive是阻塞的——他阻塞调用进程直到有消息到达,再开始使用消息传递来进行交互之前,进程使用recvclr移除一个旧的消息

内存基本管理

大型操作系统提供复杂的内存管理方案,允许对内存的请求超过实际内存的大小,这种系统吧数据保存在二级存储器中,在引用数据是将数据移到主存中,高级内存管理机制支持多虚拟地址空间,允许每个应用程序从零开始对内存进行取址,我们使用术语重量级进程来指代运行在独立地址空间中的应用程序,轻量级进程抽象允许在一个虚拟空间中运行一个或多个进程,分页是最为普遍使用的技术,用于提供虚拟空间和多路复用它们到物理内存上

内存分区:

  • 文本区:里边存有代码和常量,一般放置在ROM中
  • 数据区:一般存放已经初始化的全局变量和静态变量
  • bss区:存放未初始化的全局变量
  • 堆区:由程序员自己申请,通过new,malloc,realloc,calloc
  • 栈区:每个进程都有自己的栈空间,这是由系统自动调用以及释放的

XINU中的底层内存管理器维护一个空间内存块链表,堆和栈存储根据从链表上进行分配,对存储的分配通过寻找第一个瞒住请求的空闲内存块来完成,栈存储的分配则选取满足请求且具有最高地址的内存块,由于空闲块链表按照地支顺序单项连接,因此分配栈空间需要搜索整个空闲链表

底层内存管理器不包含防止一个进程分配完所有的可用的内存的机制,所以存在内存耗尽的危险

高层内存管理机制允许将内存分为独立的区域,保证单个子系统不会用尽所有的可用内存

xinu高层内存关联函数使用缓冲池范式,在这个范式中,给每个缓冲池分配一组固定的缓冲区,一旦建立缓冲池,一组进程便可以动态的分配和释放缓冲区,缓冲池接口只支持同步访问:一个进程阻塞,直到缓冲池可用

大型操作系统采用虚拟内存机制来为应用程序进程分配独立的地址空间,使用最广泛的虚拟内存机制——分页,将地址空间分为固定大小的页,并且按需加载。分页需要硬件的支持,因为每个内存引用都需要经过从虚拟地址到相应物理地址的映射

高层消息传递

xinu引入了一个高层消息传递机制,称为通信端口,该机制允许进程间通过指定的端口交换信息,每个端口包含固定长度的消息队列,函数pstend处理在队列末尾的消息,而ptrecv获取队列头部的消息,当进程试图从一个空端口接收消息时,该进程会被阻塞,直至有消息到达,当进程试图往一个队列已满的端口发送消息时,该进程会被阻塞,直至队列中有空的空间

中断处理

当中断发生时,处理器中的硬件执行三个基本步骤:

  • 当中断正在处理时,立刻改变处理器的状态以阻止其他的中断发生
  • 当中断处理完后,保存足够的状态以允许处理器继续执行
  • 分配出预先定义好的内存单元,操作系统可以将处理中断的代码防止在这些单元

中断向量号的分配

使用套接字为每个设备可以通过套接字加入到系统中,这样中断向量号和每个套接字相关联而不是和设备相关联

总况

为了处理中断,操作系统需要保存处理器状态、确定发出中断的设备并为相应的设备提供处理程序。由于高级语言(比如 C 语言)不提供直接操纵处理器或者协处理器寄存器的方法,所以一些中断进程代码并不是用 C 语言写的。当然示例系统也不是所有的中断代码都用汇编语言写的,它把中断分配器分成了两个部分:低层部分是用汇编写的,高层部分则是用 C 语言写的。 分配器捕获中断,保存寄存器状态,确定请求中断的设备并把控制权交给合适的高层中断处理程序。当高层中断处理程序返回时,控制权返回到重新加载寄存器的分配器并执行恢复状态的特殊指令,最后返回给被中断的程序 多个规则控制着中断过程。首先,中断代码不能让中断禁止任意长的时间,这样会使设备无法使用。中断时间的长度取决于连接到系统的设备,而不是运行的设备本身。其次,中断代码可能有空进程运行,它决不能调用会使执行程序离开当前或就绪状态的函数。再次,中断处理程序不能显式地开启中断

尽管开启中断是禁止的,但一个等待的进程变成就绪状态时,中断处理程序必须进行重新调度。这样做就能够保证调度不变原则,同时这也意味着一个进程等待IO操作结束后会被唤醒,当然,系统必须保证在重新调度前,全局数据结构是合法的,重新调度不会引起连续中断,因为每个进程在自己的栈中最多只有一个中断

实时时钟管理

实时时钟以固定的间隔向 CPU 发出中断请求。本书的设计使用计时器来模拟时钟中断。中断处理程序负责处理中断和对下一个中断复位计时器。 操作系统使用时钟来处理抢占和进程延迟。当进程使用了 QUANTUM 个时钟滴答时间的处理器后,每次系统切换上下文时被调度的抢占事件就会强制调用调度器。抢占能够确保任何进程均不能永久占有 CPU ,并且能够通过保证同等优先级进程间的轮转服务来实现调度机制。 当进程请求计时延迟时,系统就会调度一个事件,该事件使得一个正在运行的进程将自己放人进程的增量睡眠链表中。并且分配进程的状态为PR_SLEEP,中断程序通过将进程移人就绪链表并重新调度来唤醒一个计时器到期的睡眠进程。增量链表为管理睡眠进程提供了一个非常有效的方法。

在xinu中拥有一个在计算机网络中非常有用的机制:定时消息接收,定时消息接收机制的基本原理是分离等待:进程阻塞直到两个事的其中之一发生,

当一个进程请求定时接收时,该进程就进入睡眠进程队列中,但系统将进程配置状态PR_RECTIM中以指明它已加入到拥有计时模式的接收中,如果睡眠定时到期,进程就像其他进程一样被唤醒 ,如果一个消息在延迟到期之前对打,函数send就调用unsleep将进程从睡眠进程中移除,然后继续传送消息。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值