DJYOS事件调度解析:事件、事件调度和线程调度关系

本篇文章详细的解析DJYOS事件调度中事件、事件调度和线程调度的之间的关联。

1 事件

       计算机处理的是现实世界中的具体任务,有因才有果,现实生活中的任务不会无缘无故地产生,人们做某一件事肯定是因为发生了某种事件使其需要去做这件事情,这就是事件。计算机中的事件与现实生活中的事件是一致的,CPU不会无缘无故地执行某一段代码,就算是一段包含在一个if语句里的代码被执行,肯定是因为发生了使该条件成立的事件。人走到沙发前是一个事件,智能沙发上的计算机发现这个事件后然后处理这个事件,处理结果是执行调整坐垫到合适位置的操作;人转身面对电视机是一个事件,智能电视机里的计算机发现这个事件然后处理这个事件,处理结果是执行打开电视机的操作;人躺在床上并闭上眼睛,智能家居的计算机发现这个事件然后处理这个事件,处理的结果是执行关灯的操作。以上所述的事件,就是DJYOS操作系统中“事件”的原型。所有这些原型中,都有一个“发现”(或称“检测”)事件和执行一定操作以处理事件的过程,现实系统中,这两个过程可能非常复杂,甚至处于两个不同的学科,其软件实现模块可能会由两个不同专业方向的程序员编写。DJYOS软件模型是:由一个软件模块专门用于监测人的行为,另外一些模块执行开关灯、开关电视机、调整沙发坐垫的操作。检测模块发现人靠近沙发的事件后,不是去调整沙发坐垫,而是把“事件”报告给操作系统了事。操作系统收到该事件后,先把该事件记录在调度队列中,再依据调度算法,当决定要处理该事件时,就分配或创建用于处理该类型事件的线程,并启动该线程,再由这个线程去执行调整沙发坐垫的操作。这样,就使“检测”和“执行”相互独立开来。进程、线程之类的东西只是操作系统内部的秘密,线程作为一个资源,是创建新资源还是使用现有资源来处理事件,完全由操作系统自动完成,应用程序的程序员不知道也不需要关心这些。
      DJYOS下程序运行的过程,就是新事件不断发生,然后被处理的过程。在此过程中,操作系统组织、创建、分配线程、进程以及其他资源去满足处理事件所需。每弹出一条事件,DJYOS操作系统就为它分配一个事件控制块,事件处理完毕后收回事件控制块。未处理完毕的事件就会堆积在队列中,操作系统对队列的容量有一定的限制,当队列中事件数量达到上限时,操作系统将拒绝接受新事件。core_config.c文件中的gc_u32CfgEventLimit常量用于确定队列容量,用户可以修改,但不能在程序运行过程中动态改变,最多允许16384个事件。

2 事件类型

      程序运行期间会发生各种各样的事件,不同种类的事件由各自的线程处理,需要用事件类型去加以区分。DJYOS操作系统为每一类事件分配一个唯一的事件类型ID号,并为每个事件类型分配一个事件类型控制块(struct EventType)。事件类型控制块是静态分配的,其数量须按照内存量和应用程序实际需求合理设定,由工程目录下的core_config.c文件中的gc_u32CfgEvttLimit常量确定,用户可以修改,但不能在程序运行过程中动态改变,最多允许16384个事件类型。
事件类型必须调用Djy_EvttRegist函数登记才能使用,登记时要为该事件类型指定一个事件处理函数,该函数将成为线程的入口函数,还要设定该类型的默认优先级,并且告诉操作系统该函数运行需要多少栈空间。在某些情况下,操作系统会为部分类型事件至少保留一个线程(参见5.4节),当有该类型的事件发生(调用Djy_EventPop)时,操作系统可能会自动创建线程处理该事件,也可能会指挥现有线程(或创建线程)去处理该事件。

2.1独立型和关联型事件

      如果某类型事件重叠发生(即事件未处理完成,又发生相同类型事件),产生的多条事件可以用多个线程并行处理的,称为独立型事件。典型的独立型事件是web服务,当web服务器收到多个客户端的服务请求后,这些请求一般是独立的,服务器会创建多个线程独立处理多个请求。独立型事件用EN_INDEPENDENCE表示,该类型的每条事件都需要单独处理,每次弹出独立型事件,都会在事件队列中添加一条事件,事件反复发生而又来不及处理时,事件队列中将积压多条同一类型的事件。独立型事件也有特例,如果在调用Djy_EventPop函数时使用的参数是事件ID,也不会添加新事件。
      反之,如果重叠事件互相关联,必须在一个线程中顺序处理,称之为关联事件。该类型事件一般表示的是物理世界的一种状态,若此类型的事件重复发生,它也只代表系统处于某种状态,不是每次发生的事件都需要单独处理。关联型事件用EN_CORRELATIVE标记,这种事件无论重复发生多少次,事件队列中都只会保留一条事件。这是一种很重要的事件,因为现实世界中有太多的关联型事件,试举数例如下:

  1. 快件投递中,当客户有快件需要投递,就会给快递公司电话,同一个地址,无论重复多少次电话,只需派收件员上门一次把积累的快件全部取走就可以了。
  2. 在LCD面板显示软件中,在内存中设计了一个显存镜像,应用程序修改显示内容时,修改的是镜像显存,然后发出“显示刷新”类型事件,处理该事件的过程就是把镜像显存中的图像搬到物理显示器上。这是一个典型的关联型事件,无论应用程序修改了多少次镜像显存,都表示“显存被修改”这一物理状态,处理一次“显示刷新”事件将把历史上积累的事件完全清理。
  3. 串口通信软件中,缓冲区接收到数据,会发出“缓冲区有数据需处理”类型的事件,无论该类型事件重复发出多少次,都表示这样一个状态,事件处理时只要把缓冲区的所有数据取走,就可以了。
    独立型事件的例子也很多,例如:
  4. 在百货商店,每进来一个顾客算发生了一个“顾客来了”类型的事件,由于每个顾客都是独特的,所以必须单独服务。
  5. 同样是LCD面板显示软件中,当用户需要绘制时,就会发出“屏幕绘制”事件,因为每次绘制的内容都可能不同,所以每条事件都必须单独处理。
  6. 通信软件中,应用程序需要发送数据,就把数据准备好,弹出“发送数据”类型的事件,并把数据缓冲区作为事件参数,由于每次事件的数据缓冲区都是独立的,不能把多条事件统一处理,而是每条事件都要单独处理。

3线程

      DJYOS以事件为调度单元,理论上,操作系统可以用任何方法处理事件,只要能够调用事件处理入口函数就可以了,当前版本的DJYOS操作系统使用的方案是,创建一个线程执行事件处理入口函数来实现事件处理。注意,线程是操作系统自行创建的、用于执行用户提供的事件处理入口函数的手段,对程序设计者是不可见的,这就隐含了一个事实:操作系统还可以选择其他方案代替线程方案,遗憾的是,笔者至今也没有想到可以替代线程的可行方案。要处理事件,操作系统就要为该事件创建线程,用户在登记事件类型时必须告诉操作系统执行该入口函数需要多少栈空间。在DJYOS系统中,线程是处理事件的执行者,也作为事件的资源而存在——完成该事件需要许多资源,线程是诸多资源之一。

4事件、事件类型与线程的关系

      套用面向对象的方法,事件类型相当于C++的类,登记事件类型相当于声明一个类数据类型;事件相当于对象,弹出事件相当于定义对象,同时做一些构造函数的工作;事件处理完成相当于撤销一个对象,同时做一些类似析构函数的工作。
DJYOS中应用程序的运行过程,就是不断地弹出新事件和处理事件的过程。每个事件都必须属于已经登记的事件类型,相同类型的事件使用相同的线程进行处理。
DJYOS操作系统中,线程作为事件的资源而存在,而该事件就是线程的拥有者,因此,任何线程,都不能无缘无故地出生、存在和死亡,它必定与某一类型的某一条事件联系在一起。线程随事件的需要而生,随事件完成而消亡。线程无需登记,也无需有用户建立和启动,它的创建、启动和删除都是由操作系统自动完成的。这与传统操作系统不一样,传统操作系统可以由用户任意创建线程,创建一个毫无意义的线程是允许的。DJYOS系统的调度依据是一个就绪事件队列和若干个同步事件队列,而不是线程队列(有的操作系统也称其为任务队列),DJYOS中根本就没有线程队列。DJYOS的调度针对事件而不是线程,创建一个线程是因为它的拥有者需要处理,线程被切入是因为该线程的拥有者需要被切入,线程被切离是因为它的拥有者被挂起。把线程作为事件的资源的积极意义在于,当某类型的事件连续发生,操作系统将调集更多的资源,为其创建多个线程来处理该事件,如果在多处理机(多核)系统中,把这些线程分配给不同的处理器,处理器本身也就成为一种资源,将极大地方便多处理器系统的管理。而传统的编程方法中,程序员创建若干线程待机,每个线程对应一种或数种事件,待相应的事件发生后,唤醒线程予以处理,这种方式在管理多处理器方面要复杂得多。因此,DJYOS在多处理器系统中,有先天的优越性。
      线程的属性必须与事件类型对应,相同类型的事件使用相同的线程处理,不同类型的事件使用不同的线程处理。用户登记一个事件类型时,必须传入操作系统创建用于处理该类型事件的线程所需要的两个关键参数:事件处理入口函数和该函数需要的栈空间。当某类型的事件发生后,操作系统就会在适当的时候创建线程(或分配存在的线程)执行该类型对应的事件处理函数。事件处理完成之后,操作系统会自动回收该线程所占用的资源,必要时还会删除线程。
每一条事件对应一个线程,如果有多条同一类型的事件需要处理,操作系统会创建多个相同的线程同时处理多个相同的事件。这可以更合理地使用计算机资源。虽然在单处理器的情况下,建立多个线程并不会比单个线程长期霸占处理器更充分利用处理器,但可以产生多个相同类型事件并行处理的效果,例如同时绘制多个窗口,特别是,在多处理机(或多核)系统中,可以把频繁发生的同一类型事件分配到不同的处理器上。而传统操作系统下,线程是由程序员创建的,如果程序员只为某项工作创建了一个线程,则该工作再繁忙也只有一个线程为它工作,该线程处理的多项任务只能串行执行。但是这样反复创建线程可能导致资源枯竭,比如处理某事件时需要使用串口,而串口又被其他线程占用,在串口被占用期间发生该事件,操作系统就会再次为其创建线程,该线程开始执行后会因串口资源繁忙而进入阻塞状态,如果事件反复发生,操作系统就会反复为其创建线程,直到消耗完所有内存,造成内存枯竭。为了防止发生资源枯竭事故,在事件类型控制块中提供了vpus_limit成员,表示该类型事件可以同时建立线程的个数。

5线程调度和事件调度

      DJYOS系统中,调度是以事件为依据,线程是事件的资源,线程本身是没有优先级的,但上述理论依然适用,只要把其中的“线程”两字改为“事件”就可以了。
在实时系统中,事件的优先级是能否实现实时指标的关键,现实中,大多数事件会继承事件类型的默认优先级,因此确定每一类事件的默认优先级是系统设计的重中之重。
基于事件进行调度,这是DJYOS与传统操作系统最大的区别。图 51是从传统操作系统抽象出来的,无论是简单的OS还是复杂OS,其调度都是基于线程的。图 51中,无论是初始化创建线程,还是线程执行过程中创建线程,都是在创建线程的时候分配线程所需要的资源,栈是其中的必备件。图 52中,弹出事件时,除非新事件的优先级足够高,需要立即处理,否则弹出时不会分配或创建线程,直到该事件应该被处理的时候才创建或分配线程。
传统操作系统的调度示意图

图 5-1传统操作系统调度示意图
DJYOS事件调度示意图

图 5-2事件调度示意图

      看起来,除了不能从中断处理函数中创建线程外,两图有很大的相似性,其实不然,传统OS下编程时,不应该频繁地在线程执行过程中创建新线程的,为什么呢?为了创建线程而导致阻塞,而被阻塞的却是当前线程。如果新线程的优先级低于当前线程,那当前线程就冤了,因为一个低优先级的线程,而导致高优先级线程阻塞,这在RTOS中是不允许的,从系统方案设计角度,也是不合理的;即使不被阻塞,高优先级线程执行过程中,花大量时间用于创建低优先级的线程,也是不合理的。所以,传统OS下编程时,所有线程总是在初始化阶段创建,极少在运行过程中创建。而DJYOS则没有这个问题,如果新弹出的事件优先级不够高,根本就不会为他创建线程。这使得DJYOS能够更加优化配置计算机的资源,假设有一个产品,有uart串口,在传统线程模式下, uart通信线程是在初始化时创建的,即使uart通信线没有连接,uart线程也必定占用内存资源。如果uart通信非常频繁地发生,也只能由初始化时创建的那个线程一点一点来处理,即使你有多个cpu核,其他cpu核也只能干着急。那不能创建多个线程吗?可以,线程池技术就是这样干的,究竟创建多少线程才合适,是一个非常令人头疼的问题。在传统OS下,线程池技术可是一门专门的学科哦。而DJYOS的事件调度呢?如果uart通信线上没有数据,则根本不会弹出该事件,也就不会占用任何资源。如果uart口频繁通信,就会频繁弹出事件,若计算机有多个cpu核,则自然而然地会把事件分散到不同的cpu核上,程序员根本不知道线程为何物,就能进行优化的多核编程。
      大家都知道vc、cbuilder下编程吧,在桌面上放上一个按钮,然后为按钮点击事件编写处理函数,当用户点击该事件时,处理函数就被执行。这就是事件触发式编程,这是怎么实现的呢?原来,有一个线程一直在后台候着,等待windows发出的消息,一旦收到点击消息,线程便被唤醒执行。我们可以看到,无论该按钮是否被点击,甚至一辈子都不点击,该线程依然要占用系统资源。而DJYOS的方法则不同,只要该事件不发生,则不会占用任何系统资源。VC为什么要实现事件触发式编程呢?是因为现实需要,与传统操作系统需要像VC这样的工具支持才能实现事件触发式编程相比,DJYOS只需要文本编辑器就可以实现。VC这样的工具只能用于较大的系统中,而DJYOS却可以用在单片机中!
      传统的基于线程(进程)的调度模式下,操作系统只知道哪个线程(进程)正在占有CPU,却不能知道该线程(进程)在干什么,调度器也只能针对线程(进程)分配CPU,不能针对计算机所处理的具体事务分配CPU。因此,传统操作系统下,程序员必须熟练掌握有关线程(进程)的知识,必须自己为程序需要处理的事务创建线程(进程),清楚在哪些状况下有哪些线程(进程)正在运行。而事件调度则不同,用户可能根本不知道线程(进程)的存在,以及谁正在运行,谁正在等待,实际上,程序员根本不需要关心这些。DJYOS系统中,程序员只知道哪个事件正在被处理,哪些事件已经处理完成,哪些事件正在队列中等待处理。定义一个一个的事件类型并登记到系统中,为每类事件编写处理函数,便是编程的全部工作。当相同类型的多条事件,具有不同的优先级时,在传统操作系统下,要么为每一种可能的优先级建立一个线程(进程)来实现,要么在执行中动态改变线程(进程)的优先级,不管哪种方式,程序员都需要花费大量的时间和精力,以确保事件按正确的优先级得到处理。而DJYOS不同,它先天就是以事件优先级作为调度的基础,只要在产生事件时,直接在事件中做优先级标志就可以了。例如一个串口通信程序,中断函数负责底层接收,当接收到完整数据包后,就发给上层应用程序处理,上层应用程序处理接收到的所有数据包,而数据包中有一个命令字域,不同的命令要求的优先级不同。在传统操作系统下,要么创建一个comm_app线程,在中断函数中把数据传送给该线程的同时根据命令字改变comm_app线程的优先级,这种在中断函数中改变线程优先级的做法,在许多操作系统中是不允许的;另一种方法是,为命令字对应的每一种可能的优先级,均创建一个相同的线程,这些线程除优先级不同外,其它部分完全相同,当中断函数接收到完整数据包时,就根据数据包中的命令字,发消息给相应优先级的线程。这种方法虽然可行,但是会消耗很多CPU资源,且难于在编码阶段穷举所有可能的优先级,一旦命令字发生变化,很可能就需要修改软件源码。而DJYOS系统不一样,程序员只需要定义一个事件类型,并为之编写事件处理函数proc_uart(),当中断函数接收到完整数据包,弹出事件时直接以命令字对应的优先级作为参数就可以,DJYOS的调度系统自动会根据事件优先级域进行调度。
      另外,嵌入式系统多是应激系统,应激系统的主要任务是对外界的事件做出正确且及时的反应,从这点看,程序员根本就不用知道进程和线程这些东西,为处理外界事件而建立线程(进程)实际上是不得已而为之,传统操作系统下,你必须做这些工作,你的系统才能正确地为你做些事情。基于事件的调度非常适合这种应激系统,被调度的目标就是反映外部刺激的事件,而不是处理这些外部刺激的线程,符合人们的习惯性思维。即使是非应激系统,或者是非实时系统,基于事件的调度仍然有其优势,“有事就做,没事就坐”是人们最为习惯的思维方式,以事件为调度单位的调度策略显然符合这种思维方式,而与人们习惯性思维相同的调度方式,又是避免人为错误,减少软件bug的有效方法。
      现代计算机已经进入“ubiquitous/Pervasive Computing”时代,即普适计算。在普适计算时代,触手可及的计算产品里面也包含着触手可及的计算机程序,这些程序由大量的嵌入式程序员编写,是的,十年前的硬件工程师可以不懂软件,软件工程师可以不懂硬件,而今天的嵌入式技术,除了在一些很特殊的方向如射频设计,已经没有纯粹意义上的软件工程师和硬件工程师了。让这些队伍迅速壮大的、软硬兼顾的“普适计算工程师”们去掌握晦涩难懂的进程和线程技术并灵活应用,恐怕要花费不少的人才培养成本,而使用传统的操作系统去开发嵌入式产品,不理解这些复杂的概念根本就寸步难行,而人才的匮乏又将限制嵌入式产业的发展。DJYOS操作系统不要求程序员操纵线程和进程,程序员只需把需要计算机处理的任务划分为一个个事件类型,并为各种不同类型的事件编写独立的事件处理函数,然后把它登记到系统中就可以了。当事件发生时,发现(检测到)该事件的程序员只要告诉操作系统“某类型事件发生了”,操作系统自动地创建或唤醒合适的线程去处理该事件,而无须程序员亲自创建或者唤醒相应的线程。当然,“普适计算工程师”即使是在DJYOS系统下编程,深入理解线程和进程技术,对开发工作也是很有帮助的。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值