leavic 发表于 2012-12-12 10:24:25

发一个自己用C++写的简单任务调度器

本帖最后由 leavic 于 2012-12-12 10:29 编辑

说明一下,这个基本是基于ProtoThread来做的,
这两天刚学C++,试着仿造ProtoThread的方式做了一个调度器,最重要的几个宏基本都是抄的。
还是不太会C++里面对类成员的属性的控制,基本都是Public,这方面各位就将就着忍受一下吧。

1、这个是基于GCC编译的,用到的是Gcc的Label As Value特性,不过我看到好像有人说Visual C++也支持这个。
理论上讲,这个是和硬件平台无关的,只和编译器是否支持GCC这个特性有关。我在STM3240G_EVAL上验证过可行。
2、实现了任务调度,延时,信号量。互斥量没有做,不过这个应该很简单,只是信号量的一个变种而已。
3、任务中的变量如果定义成static属性,那么变量值是可以保存的,不需要全部都写成全局变量。
4、严重的问题,延时和信号量这两个东西都只能在任务函数中使用,而不能在任务函数中调用的子函数中使用,这可能是基于Label方式恢复上下文的调度器都无法避免的一个问题,

因为调度器可以恢复到任务函数,而任务函数中的第一个宏可以通过上次退出时保存的label恢复到上次执行的位置继续执行,
但如果上次的退出是在子函数里发生,保存的label就是子函数的label,那么从任务函数开始的地方是跳不过去的。

这个我没有试过,只是觉得理论上应该是这样的结果。要想避免这种问题,还得是保存PC指针和建立私有堆栈,就和一般的FreeRTOS、ucOS一样了,然后就一定是平台相关了。
基于Label的方式,主要是节约RAM,不需要建立私有堆栈,每个任务只需要占用一个thread_c类的大小(基本就是几个整形变量加一个函数指针),一个信号量占用一个bool型变量的大小,
对于资源非常紧缺,不需要太复杂应用的环境来说,还是有点用的。
http://v.youku.com/v_show/id_XNDg3MjE0Nzg4.html

takashiki 发表于 2012-12-12 10:45:58

但如果上次的退出是在子函数里发生,保存的label就是子函数的label,那么从任务函数开始的地方是跳不过去的。

为什么?
Label As Value扩展的目的就是为了跨函数跳转,你居然说跳不过去,那用它还有什么作用,直接goto 标号就可以了。

leavic 发表于 2012-12-12 11:08:59

本帖最后由 leavic 于 2012-12-12 11:14 编辑

takashiki 发表于 2012-12-12 10:45 static/image/common/back.gif
为什么?
Label As Value扩展的目的就是为了跨函数跳转,你居然说跳不过去,那用它还有什么作用,直接got ...

void * pt;
int subtest(void)
{
        DebugPrintf("Sub function start\r\n");


labelTest:
        pt = &&labelTest;
        DebugPrintf("label test start\r\n");
        return (1);
}


int main(void)
{
        vSetupEnvironment();

        goto *pt;

        subtest();

        while (1)
        {}

}


如果子函数内的标号可以直接跳到,那么这段代码应该会直接跳转到labelTest处输出:
label test star

然后执行subtest,接着输出:
Sub function start
label test star

你可以试试实际上这样写会不会跳转,结果肯定是一句输出都没有。如果你不用label as value,直接goto labeltest,编译的时候就已经会报错了。

takashiki 发表于 2012-12-12 11:16:28

leavic 发表于 2012-12-12 11:08 static/image/common/back.gif
void *pt;
int subtest(void)
{


哥们,你错了,主要是你的思想错了。

goto是无条件跳转,他当然会跳过去啊。问题是,你这个函数它返回了,返回到什么地方去了?按道理就应该返回到堆栈中原来所保存的地址,问题是你根本就没有把原来的状态装入堆栈,所以一旦返回,程序就会跑飞。
主要是因为goto和函数调用是不一样的,你不能简单地把goto当做函数调用。

leavic 发表于 2012-12-12 11:16:36

takashiki 发表于 2012-12-12 10:45 static/image/common/back.gif
为什么?
Label As Value扩展的目的就是为了跨函数跳转,你居然说跳不过去,那用它还有什么作用,直接got ...

http://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html

You may not use this mechanism to jump to code in a different function. If you do that, totally unpredictable things happen.

leavic 发表于 2012-12-12 11:18:50

本帖最后由 leavic 于 2012-12-12 11:25 编辑

takashiki 发表于 2012-12-12 11:16 static/image/common/back.gif
哥们,你错了,主要是你的思想错了。

goto是无条件跳转,他当然会跳过去啊。问题是,你这个函数它返回了 ...

返回自然是返回main。
还有,这个void *pt是全局变量,我不认为一个子函数的返回,会让一个全局变量发生变化。还有,串口输出是在返回之前执行的,就算返回之后怎么跑飞,第一句话至少应该输出,实际上第一句都不会输出。

如果我先从调度器返回subtest,然后在subtest里跳转到label,那肯定是可以的,但如果subtest里还有一个subsubtest,那个函数里还有一个label,我怎么可能直接从subtest里面跳到subsubtest里面的label上。

leijiayou 发表于 2012-12-12 11:25:28

牛逼    顶!!!!

takashiki 发表于 2012-12-12 11:26:55

另外,你的pt没有初始化,作为全局变量,他的值=0,于是复位。
Label As Value扩展明显打乱了程序的结构,并且不保护任何寄存器,使用时一定要小心又小心。磨刀恨不利,刀利伤人指。
在C语言中,有一种稳妥的跳转方式,是setjmp/longjmp(包含setjmp.h)就可以用了,没有副作用,但是生成的代码非常臃肿。

takashiki 发表于 2012-12-12 11:49:46

我写一个,你自己看看,能不能跳转过去。
这个程序写的很烂,一点实用价值都没有。我只是向你说明,Label As Value完全是可以跳到其他函数中去的。void * pt, *cur;

int subtest(void)
{
        DebugPrintf("Sub function start\r\n");

labelTest:
        pt = &&labelTest;
        DebugPrintf("label test start\r\n");

        if(cur)
                goto *cur;
        return (1);
}

int main(void)
{
   subtest();                                  //初始化pt,会打印Sub function start\r\nlabel test start\r\n
   cur = &&lblMain;   
   goto *pt;                                 //直接跳转过去,会打印label test start\r\n
lblMain:
   cur = (void*)0;                           //擦屁屁

    subtest();                                 //普通函数调用,会打印Sub function start\r\nlabel test start\r\n

    while (1);
}

leavic 发表于 2012-12-12 12:21:01

本帖最后由 leavic 于 2012-12-12 13:00 编辑

takashiki 发表于 2012-12-12 11:49 static/image/common/back.gif
我写一个,你自己看看,能不能跳转过去。
这个程序写的很烂,一点实用价值都没有。我只是向你说明,Label A ...

我想这不是你期望的结果,我看了一下,上面链接中的说法是可以跳转到containing function,但实际跑下来确实不行。
而且就算可以,也会引发一个问题,代码里的label是基于行号来建立的,完全可能出现在thread_function和sub function里面出现两个完全一样的label,这样还是会出问题。

dr2001 发表于 2012-12-12 13:21:36

本帖最后由 dr2001 于 2012-12-12 13:24 编辑

takashiki 发表于 2012-12-12 11:49 static/image/common/back.gif
我写一个,你自己看看,能不能跳转过去。
这个程序写的很烂,一点实用价值都没有。我只是向你说明,Label A ...

Labels as Values是一个函数内跳转用的功能,主要针对ProtoThread这样的需求设计。
似乎更多的是用在字节码解释上?似乎有文章提到过不少JIT编译器依赖于类似的实现。

说白了,Label本身代表的是地址,但是C语言规定,这种地址只能goto,而不能获取了保存在void *中供下次调用使用。为了特定的需求,开发了这个功能;但是它小众,所以没进标准。
Labels as Values在读Label的时候就是直接获得指令地址并且写道变量;跳转的时候就是Jmp到变量中的地址去执行。
不涉及任何现场保存/恢复的工作。

所以,任何跨函数的跳转都可能会导致堆栈不平衡,从而*可能*导致异常。

takashiki 发表于 2012-12-12 13:27:22

dr2001 发表于 2012-12-12 13:21 static/image/common/back.gif
Labels as Values是一个函数内跳转用的功能,主要针对ProtoThread这样的需求设计。
似乎更多的是用在字节 ...

是的,这几乎是肯定的。因为他不保护任何现场,保护现场的是setjmp/longjmp。但是这两个函数似乎用得比Labels as Values还少。
我只是指出,Labels as Values完全可以跨越函数的界限跳转,但必须非常小心的去使用,或者根本不使用。

dr2001 发表于 2012-12-12 13:28:04

Sorry,没有认真看代码。

但是Google Code有一个基于C++实现的ProtoThread库,不妨参考一下。
另外,GNU有一个轻量级的调度器,pth好像是,也可以参考一下。

至少,对实现一个自己的库开说是一个很好的起点。

dr2001 发表于 2012-12-12 13:30:51

takashiki 发表于 2012-12-12 13:27 static/image/common/back.gif
是的,这几乎是肯定的。因为他不保护任何现场,保护现场的是setjmp/longjmp。但是这两个函数似乎用得比La ...

setjump和longjump用于异常也需要非常小心,限制多,所以用的少。

Labels as Values其实是两个部分,一个是Label可以当值,赋给别人;一个是Goto的参数可以是一个void *变量,这个其实有意思。

leavic 发表于 2012-12-12 13:55:32

takashiki 发表于 2012-12-12 13:27 static/image/common/back.gif
是的,这几乎是肯定的。因为他不保护任何现场,保护现场的是setjmp/longjmp。但是这两个函数似乎用得比La ...

是的,测试下来是可以跳转,但是是直接跳到了subtest函数的开始,而不是指定的label处,所以这样的话没法拿来用。
我想这也是为什么proto thread里面的wait函数都是写成宏定义而不是函数的原因之一,写成函数,就是建立了一个子函数内的标签了。

leavic 发表于 2012-12-12 13:59:58

dr2001 发表于 2012-12-12 13:28 static/image/common/back.gif
Sorry,没有认真看代码。

但是Google Code有一个基于C++实现的ProtoThread库,不妨参考一下。


谢谢,google code上的那个好像是C做的,c++的我也找到一个,还在看。
页: [1]
查看完整版本: 发一个自己用C++写的简单任务调度器