DJYOS与传统操作系统编程模式比较

DJYOS与传统操作系统编程模式比较


1. 线程调度和事件调度的比较

DJYOS与传统操作系统编程模式比较_第1张图片

    传统的操作系统,是以线程为调度目标的,无论是简单的UCOSII,还是复杂的windows、linux,无论是单进程还是多进程的系统,调度器所调度的,都是线程。调度器的作用,简单地说,就是决定哪个线程、什么时候占有CPU、什么时候让出CPU,如何决定由哪个线程占有CPU的方法很多,不同的操作系统都有自己的策略,这里暂且不提。

    DJYOS的调度是以事件为目标的,调度器直接决定哪条事件应该被处理,在内核中,调度队列是事件队列,而不是线程队列。DJYOS提供的API,也全部是针对事件的,没有针对线程的。虽然DJYOS也有线程,但用户完全看不到,也且线程也不作为调度依据。


2.两种编程模型对比

    DJYOS的调度方式也传统调度方式上的差异,必然导致在DJYOS上编程和传统操作系统上编程模型的差异。传统方式下,程序员编写函数以处理需要计算机完成的任务,然后建立线程来运行这些函数,创建线程、启动线程、线程同步、线程暂停、线程休眠、线程终止、线程删除等操作,均由程序员亲自完成。在DJYOS下,认为计算机执行某一段程序,必定是发生了使计算机执行这段程序的事件,认为计算机的所有操作,均由事件引发。程序员所需要做的工作是,以适当的粒度定义事件类型,为各类型事件编写处理函数,并在需要的时候弹出事件,在必要的时候调用操作系统提供的事件同步功能。由此而导致了两种截然不同的编程模式,下面,我们通过不同场景的对照,来直观了解一下这两种编程方式。    

2.1.场景1

程序启动后即开始运行,直到程序终止运行的模块,例如通信口监视模块,它必须在整个生命周期内不间断地监视。

2.1.1.传统操作系统

int thread_comm(int para)
{
    //无限循环;
}
int main(void)
{
    //创建线程;
    //启动线程;
}

2.1.2.DJYOS

int event_comm(int para)
{
    //无限循环;
}
int main(void)
{
    //登记evtt_comm事件类型;
    //弹出evtt_comm类型事件;
}

2.1.3.点评

    可以看到,这种场景下,两种编程方式看似没有区别。但实际上是有区别的,因为事件是直观的,与人类思维模式比较接近;线程是抽象的,与计算机的执行过程接近。越接近人类自然思维,就越容易学习和掌握,编程就越不容易出错。

    现代计算机已经进入“ubiquitous/Pervasive Computing”时代,即普适计算。触手可及的计算产品里面也包含着触手可及的计算机程序,这些程序由大量的嵌入式程序员编写。设计这些产品需要大量的软件工程师,这使得嵌入式程序员的队伍迅速增大,新增的程序员,可能来自各行各业,他们原来在各自的行业中,可能都是非常了不起的专家,比如化工专家、医疗专家等。这些不同领域的专家,却未必是计算机领域的专家,让他们去掌握晦涩难懂的线程技术并灵活应用,恐怕要花费不少的人才培养成本,而使用传统的操作系统开发嵌入式产品,不理解这些复杂的概念根本就寸步难行。DJYOS操作系统不要求程序员操纵线程和进程,程序员只需把需要计算机处理的任务划分为一个个事件类型,并为各种不同类型的事件编写独立的事件处理函数,并且把它登记到系统中就可以了。当事件发生时,发现(检测到)该事件的模块只要告诉操作系统“某类型事件发生了”,不需要管劳什子的线程。从这个角度,DJYOS降低了程序员培训要求,客观地为企业节约了人力资源费用。

2.2.场景2

    如场景1的通信口监视模块,接收来自外部的请求,把接收到的请求交由另一个模块提供服务。为了确保通信口不丢数据包,thread_comm的优先级应该比服务模块高。

2.2.1.传统操作系统

2.2.1.1.方案1

int thread_comm(int para)
{
    while(1)
    {
       // if(收到请求)
       //    唤醒thread_server线程or 释放信号量;
       // 其他代码;
    }
}
int thread_server(int para)
{
    while(1)
    {
       // 提供收到的请求对应的服务;
       // 线程暂停or 请求信号量;
    }
}
int main(void)
{
   // 创建thread_comm线程;
   // 启动thread_comm线程;
   // 创建thread_server线程;
   // 启动thread_server线程;
}

    从上述代码中我们可以看到,thread_server线程无论如何,都占据内存的,即使通信线没有插上,一辈子都收不到请求,也是这样。那么,我们有改进方法吗?能不能收到请求后再创建线程,像方案2的代码这样:

2.2.1.2.方案2

int thread_comm(int para)
{
    while(1)
    {
        if(收到请求)
        {
            if(thread_server线程未创建)
            {
            //   创建thread_server线程;
            //   启动thread_server线程;
            }else
            //   唤醒thread_server线程or 释放信号量;
        }
       // 其他代码;
    }
}
int thread_server(int para)
{
    while(1)
    {
       // 提供收到的请求对应的服务;
       // 线程暂停or 请求信号量;
    }
}
int main(void)
{
   // 创建thread_comm线程;
   // 启动thread_comm线程;
}

    一般来说,这种方法是不合理的,因thread_comm线程是高优先级线程,而创建线程需要很长时间,并且分配栈需要使用动态分配内存,时间和结果都不确定,而所有这些事情都是为运行低优先级的thread_server线程做的,相当于在高优先级的线程中,花很多时间为低优先级线程做事,这是不合理的。

2.2.2.DJYOS

int event_comm (int para)
{
    while(1)
    {
        if(收到请求)
            弹出evtt_server类型事件;
        其他代码;
    }
}
int event_server (int para)
{
    while(1)
    {
        提供收到的请求对应的服务;
        事件同步;
    }
}
int main(void)
{
    登记evtt_comm事件类型;
    弹出evtt_comm类型事件;
    登记evtt_server事件类型;
}

    表面上,DJYOS下的代码和传统操作系统下方案1的代码类似,但在水面以下,是完全不同的。因为如果event_comm没有收到服务请求,就不会弹出evtt_server事件类型,其所对应的线程就不会被创建,也就不会占用计算机资源。

    有读者可能会问了:这样不会出现2.2.1.2节的问题吗?DJYOS下是不会的,因为弹出事件仅仅是把事件控制块推进事件调度队列中,需要的时间是非常少的。而创建线程的操作,只有等到该事件必须被处理时,才执行。因event_comm的优先级高于event_server,故一定会在event_comm休眠的时候,才会执行创建线程的操作。

    那又有读者可能要问,我要收到服务请求后,迅速可靠响应怎么办?的确,因为要分配内存,创建线程有失败的可能。这时候,你可以把evtt_server的优先级定高于128,但低于evtt_comm,这样,在登记事件类型的时候,系统会自动创建线程,等弹出事件的后,就无须创建线程,而是分配线程了。

2.2.3.点评

    传统操作系统下,无论任务是否产生,都要占用资源。

    DJYOS下,不实际产生任务,就不占用资源。

    孰优孰劣,就不用我说了吧。

    举个简单例子,某通信产品,依通信口数量不同,有两种型号,A8型有8个口,A16型有16个口,其他功能一模一样。

    传统操作系统实现该产品的话,要么A8和A16型配置相同的硬件资源,可使软件版本保持一致,但须付出更高的硬件成本;为了节省成本,A8和A16型会按实际需要配置资源,此时,两个型号的软件版本是不一样的,至少差一个配置常量是不一样的。即使只有一个配置常量不一样,企业也要管理两个不同的版本。

    在DJYOS下,未安装的通信口,自动就不占用资源,完全没有上述问题。

2.3.场景3

    在场景2的基础上,如果频繁收到服务请求,但各个请求之间没有内在的关联,各个请求是可以独立处理的,有许多网络服务器接收的请求,以及云计算中的服务请求,就有这个特点。

2.3.1.传统操作系统

    如果使用2.2.1.1节的方法,CPU就只能串行地处理一个个接收到的请求,无论CPU的主频多么高,无论计算机有几个CPU核,都只能如此。

    改善的办法是,创建多个线程,把不同的请求分配到不同的线程,但这样做的问题是,你不知道需要创建多少线程,创建的线程要是少了,服务请求密集到达的时候,就不够用;创建多了,就要占用很多资源,即使没有接收到请求,或者请求很少,这些资源也必须占用着。况且,这些活,操作系统内核时不会帮你做的,一切都要应用程序自己张罗。尤其是,在多核环境下,尤其麻烦。于是,就发展出了复杂的线程池技术,有兴趣的可以google“线程池”,这里就不再赘述了。

2.3.2.DJYOS

    在DJYOS下,天生就能优化支持这种应用。DJYOS的事件类型控制块中,有三个成员是为此类应用服务的:

    vpus_res:繁忙时系统为本类型事件保留的线程数量,由应用程序设置。

    vpus_limit:该类型事件同时拥有的线程数量上限,由应用程序设置。

    vpus:本类型事件已经拥有的线程数量。

    在这种情况下,只要把2.2.2代码中的event_server函数改成如下即可:

int event_server (int para)
{
    提供收到的请求对应的服务;
}

    当事件类型登记后,vpus=0或1(如果事件类型的优先级高于128),若事件频繁弹出,即频繁收到服务请求,操作系统将为此类事件创建多个线程,直到线程数量达到上限vpus_limit,达到上限后,如果再有新事件弹出,新事件就将阻塞在调度队列中。线程执行完所需服务后,操作系统会查看事件队列中有没有evtt_server类型的事件,如果有,就直接把线程转交给它。如果没有,再查看evtt_server类型事件拥有的线程数量是否超过vpus_res,若超过就销毁该线程,否则保留该线程。

    这样,如果频繁收到服务请求,操作系统就为evtt_server类型事件保持较多线程,否则就维持在比较低的水平。特别是,在多核环境下,操作系统可以自由选择这些线程放在哪个核,在分布式环境下,操作系统可自由选择放在哪个CPU,哪台计算机,所以说,DJYOS调度算法天生适合分布式和多核环境。

2.3.3.点评

    传统操作系统面对这种应用时,需要在调度器之外,用复杂的线程池技术,整个程序的复杂度大大增加,而DJYOS却在调度器这一级别直接实现了需要用线程池技术才能解决的问题,仅用了非常少的代码量,系统复杂度几乎没有增加。我们都知道,系统越简单,可靠性越高,bugs越少。

    另外,在多核和分布式计算支持上,DJYOS可以由调度器直接完成多核支持,而这个调度器简单到可以运行在单片机上,这点,传统操作系统是很难做到的。目前,多核化的趋势已经蔓延到单片机领域,ADI已经有多核单片机,其他一些厂商也即将推出多核单片机。

2.4.场景4

    如何实现事件触发式编程,我们知道,微软的GUI是事件触发式编程的,VB、VC等可视化编程工具下是事件触发式编程的,这说明,事件触发式编程是我们所需要的。在传统操作系统和DJYOS下,实现事件触发式编程有什么异同呢?

2.4.1.传统操作系统

    让我们来看看CBuilder下的一个编程场景:

    先优雅地拖一个按钮放到桌面上。

    为鼠标点击该按钮的事件编写处理函数。

    编码工作就这样完成了,很简单很强大吧。

    接着编译、执行,用鼠标点击该按钮,就会执行处理函数。

    这种优雅高效的编程方式是怎样实现的。原来,由开发工具创建的一个或多个线程一直在后台候着,等待操作系统弹出鼠标点击事件。操作系统则负责检测并弹出事件,潜伏的线程一旦收到鼠标点击事件,便被唤醒执行。我们可以看到,无论该按钮是否被点击,甚至一辈子都不点击,该线程依然要占用系统资源。

    在事件触发是编程环境下,程序员只与程序需要处理的具体事件打交到,其编程过程完全与线程、进程等无关。我们也知道,在传统操作系统上,只有在PC上或者能运行linux、wince等的高端嵌入式平台上才能使用上述便利。为什么呢?究其原因,是因为操作系统是按照线程调度的,必须经过开发工具的包装后,才能转换成事件触发。而这种包装,需要耗费大量的计算机资源,所以只适合在高端平台上使用。

2.4.2.DJYOS

    无须多说了,DJYOS只提供事件触发式编程模式,无论在高端平台还是在单片机上,都只能用事件触发式编程,区别仅在于,单片机上可能不能提供可视化编程方式。

2.4.3.点评

    传统操作系统要的面向事件编程,只能在高端平台上实现,是不折不扣的奢侈品,而DJYOS却可以在单片机上,使用简单的开发工具就可以实现,摇身一变成为日用品;

    传统OS,即使使用VC、VB之类的工具支持,事件触发式编程主要用于界面编程,不能覆盖所有需求,而DJYOS只需要单片机开发工具,就覆盖全部需求;

    传统OS在VB、VC之类工具包装后,用事件触发式编程产生的目标程序尺寸庞大、效率低下,而DJYOS却跟线程编程有同样的效率。


你可能感兴趣的:(学习心得,编程杂谈)