搜索
bottom↓
回复: 15

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

[复制链接]

出0入0汤圆

发表于 2010-8-6 11:43:27 | 显示全部楼层 |阅读模式
编程模式比较之一
1.        线程调度和事件调度的比较

(原文件名:调度模式比较.jpg)

传统的操作系统,是以线程为调度目标的,无论是简单的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却跟线程编程有同样的效率。

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入0汤圆

发表于 2010-8-6 11:48:53 | 显示全部楼层

出0入0汤圆

发表于 2010-8-6 12:59:54 | 显示全部楼层
学习一下,呵呵,不知道DJYOS 会不会出GUI,什么时候会出

出0入0汤圆

发表于 2010-8-6 17:00:57 | 显示全部楼层
传统:
while(1)
{
   mouse_event_func();
}

DJYOS:
while(1)
{
   if(click_event_flag==true)
   {
        mouse_event_action();
   }
   else
   {
        ;
    }
}
是不是这样?

出0入0汤圆

发表于 2010-8-6 17:40:48 | 显示全部楼层
打酱油的路过。

出10入0汤圆

发表于 2010-8-6 17:56:31 | 显示全部楼层
粗看了一下,真的没发现DJY有优势!

楼主做为原创者,对自己的OS非常之了解,自然会觉得易用!!!这个也算优势?

出0入0汤圆

 楼主| 发表于 2010-8-6 22:01:48 | 显示全部楼层
回复【3楼】zcl843 起航
-----------------------------------------------------------------------

错了,仅就这种情况而言,传统和DJYOS在事件(线程)入口函数是一样的。

出0入0汤圆

发表于 2010-8-6 23:13:37 | 显示全部楼层
没有用过操作系统的路过。

出0入0汤圆

发表于 2010-9-30 09:19:48 | 显示全部楼层
所谓的调度器判断依据不同,还是感觉不明白你所说的线程和事件的区别。事件驱动和IAR的那个Visualstate状态机的驱动原理类似吗?

出0入0汤圆

发表于 2011-8-31 18:40:07 | 显示全部楼层
”传统操作系统“ 的线程也可以动态创建啊。

出0入0汤圆

发表于 2011-8-31 18:40:45 | 显示全部楼层
如果是按事件,动态创建线程去处理,不也差不多了吗?

出0入0汤圆

发表于 2011-9-19 08:41:24 | 显示全部楼层
事件驱动最麻烦就是同步了,如果业务流程比较复杂的话,写出来的代码到处都是状态,异常难懂

出0入0汤圆

发表于 2011-10-22 08:02:12 | 显示全部楼层
学习中。

出0入0汤圆

发表于 2011-10-22 10:23:14 | 显示全部楼层
mark

出0入0汤圆

发表于 2012-1-7 18:38:05 | 显示全部楼层
请问djy作者,看了的蛮多帖子,觉得你的事件相比于ucos的任务,有啥子区别呢?
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-23 15:50

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表