最近把手头的SD卡更新NK到NAND项目完成,总算抽空上网瞎逛游了。正好群里面有人推荐了一个系统,说是对小资源的单片机非常适合。这里我就推荐2篇网文:
http://www.daxia.com/bibis/moredata30_1234764_43130.shtml
http://blog.csdn.net/t_larry/archive/2008/10/18/3097191.aspx
这2篇网文一个讲系统简介,一个是里面宏的说明。熟悉一下是很不错的。
然后就是官网了:
http://www.sics.se/~adam/pt/index.html
我翻译了里面的under the hood 章节:How protothreads really work
What goes on behind the magical macros in the C protothreads library? Why are they macros? How do the macros work? Read on for the explanation.
在C protothreads库里面的神奇宏到底发生了什么?他们为什么都是宏?这些宏是如何工作的?请接着读解释:
In the C implementation of protothreads, all protothread operations are hidden behind C macros. The reason for building the protothread library on C macros rather than C functions is that protothreads alter the flow of control. This is typically difficult to do with C functions since such an implementation typically would require low-level assembly code to work. By implementing protothreads with macros, it is possible for them to alter the flow of control using only standard C constructs.
在Protothreads系统C的实现里,所有的protothread操作都被隐藏在C的宏里面。生成protothread库用C的宏而不是用C函数的原因是protothreads需要改变程序的跳转,这通常是很难用C函数做,因为这样的实现通常需要低级别的汇编代码的工作。通过protothreads宏的实施,它有可能只使用标准C结构为他们改变程序的跳转。
This page explains how protothreads work under the hood. We do this by taking a look at how the C preprocessor expands the protothread macros, and by looking at how the resulting C code is executed.
这页介绍了protothreads在这个hood下如何工作。我们来看看protothread C预处理宏如何工作,如何生成C代码被执行。
First, we'll introduce a simple example program written with protothreads. Since this is a simple program we can show the entire program, including the main() function from which the protothread is driven. The code, shown below, waits for a counter to reach a certain threshold, prints out a message, and resets the counter. This is done in a while() loop that runs forever. The counter is increased in the main() function.
首先,我们将介绍一个简单的用protothreads写的例子程序。由于这是一个简单的程序就可以显示整个程序,包括main()函数启动protothreads。代码如下所示,等待计数器达到一定的阈值就打印出一条消息,并重置计数器。这是在一个while()循环里运行下去的。计数器是在main()函数里面增加。
Before we let the C preprocessor expand the above code, we'll take a look at how the protothread macros are defined. In order to make things easier, we use a simpler definition than the actual definition from the protothreads source code. (The definition used here is a combined version of the protothread macros and the local continuation macros implemented with the C switch statement.) This definition looks like:
在我们让C预处理器解析表面代码之前,我们先讨论下protothread的宏是如何定义的。为了方便起见,我们使用一个比protothreads源代码的实际代码简单的代码例子。(这里使用的定义是protothread宏和本地延续宏与C switch语句结合的组合版本。)这个定义是这样的:
We see that the struct pt consists of a single unsigned short called lc, short for local continuation. This unsigned short variable is the source of the "two byte overhead" frequently mentioned on the protothread web pages. Furthermore, we see that the PT_THREAD macro simply puts a char before its argument. Also, we note how the PT_BEGIN and PT_END macros open and close a C switch statement, respectively. But the PT_WAIT_UNTIL macro is the most complex looking of them all. It contains one assignment, one case statement, one if statement, and even a return statement! Also, it uses the built-in __LINE__ macro twice. The __LINE__ macro is a special macro that the C preprocessor will expand to the line number at which the macro is issued. Finally, the PT_INIT macro simply initializes the lc variable to zero.
我们看到这个结构体pt包括一个unsigned short 变量lc,对应本地short类型。这unsigned short变量是protothread网页里面提到的2字节内存开销。此外,我们看到PT_THREAD宏简单的把char放在它的参数前面。另外我们注意到PT_BEGIN与PT_END宏是如何打开与关闭一个C swithc结构的起始、结束。但是PT_WAIT_UNTIL宏是这些宏里面最复杂的。它包括一个任务,一个case语句,一个if语句甚至有一个return语句!另外,它使用2次内置的_LINE_宏。_LINE_宏是一个特殊的能够让C预处理器解析当前使用宏的行号的宏。最后,PT_INIT宏简单的将lc变量初始化为0。
Many of the statements used in the protothread macros are not commonly used in macros. The return statement used in the PT_WAIT_UNTIL macro breaks the flow of control in the function the macro is used. For this reason, many people dislike the use return statements in macros. The PT_BEGIN macro opens a switch statement, but does not close it. The PT_END macro closes a compound statement that it has not itself opened. These things does look weird when looked at without the perspective of protothreads. However, in the context of protothreads these things are absolutely essential to the correct operation of protothreads: the macros has to change the flow of control in the C function in which they are used. This is indeed the whole point of protothreads.
在protothread宏里面使用的很多语句在宏运行状态是不常使用的。return语句在PT_WAIT_UNTIL宏中的使用跳出了当前使用宏的程序流程。因此,很多人不喜欢在宏里面添加return语句。PT_BEGIN宏打开了一个switch语句,但是没有关闭它。PT_END宏关闭一个不是它打开的复合语句。这些代码如果不是从protothread的角度来看会很奇怪。然而,在protothreads的背景下,这些代码是绝对必不可少的正确操作:这些在C函数中使用的宏改变了控制流,这的确是protothreads的重点。
Ok, enough of talk about how the protothread macros look weird to seasoned C developers. We now instead look at how the protothread in the example above looks when expanded by the C preprocessor. To make it easier to see what is happening, we put the original and the expanded versions side by side.
好了,现在可以谈论下protothread宏在C语言开发里面是如此的怪异。我们现在不要去看例子里面扩展了C预处理命令的protothread。为了让它看起来更容易一些,我们把源代码与替换了宏的代码放一起。
At the first line of the code we see how the PT_THREAD macro has expanded so that the example() protothread has been turned into a regular C function that returns a char. The return value of the protothread function can be used to determine if the protothread is blocked waiting for something to happen or if the protothread has exited or ended.
在代码的第一行我们看到PT_THREAD宏展开使得protothread 的example()变成一个返回char类型的普通C函数。 该protothread函数的返回值能够用来确定protothread是在线程等待或者是该protothread已经退出或结束。
The PT_BEGIN macro has expanded to a PT(switch) statement with an open brace. If we look down to the end of the function we see that the expansion of the PT_END macro contains the closing brace for the switch. After the opening brace, we see how the PT_BEGIN expansion contains a case 0: statement. This is to ensure that the code after the PT_BEGIN statement is the first to be executed the first time the protothread is run. Recall that PT_INIT set pt->lc to zero.
PT_BEGIN宏展开一个包含大括号的PT switch结构。如果我们往下看就能看到PT_END宏包含对应switch结构的结束大括号。在开始大括号后面,我们能够看到PT_BEGIN包含一个case 0:部分。这是确保protothread运行以后,在PT_BEGIN部分后面的代码第一次执行。调用PT_INIT设置pt->lc为0
Moving past the while(1) line, we see that the PT_WAIT_UNTIL macro has been expanded into something that contains the number 12. The pt->lc variable is set to 12, and a case 12: statement follows right after the assignment. After this, the counter variable is checked to see if it has reached 1000 or not. If not, the example() function now executed an explicit return! This all may seem surprising: where did the number 12 come from and why does the function return in the middle of the while(1) loop? To understand this we need to take a look at how the code is executed in the example() function the next time it is called.
越过while(1)那行代码,我们看到PT_WAIT_UNTIL宏已经展开一些包含数字12的内容。pt->lc的值被设置为12,同时一个case 12:部分紧随后面。在此之后,对变量counter的值进行检测看它是否达到了1000。如果没有,example()函数执行一个明确的返回。这一切看起来令人吃惊:这个数字12是哪里来的同时为什么这个函数在while(1)循环中跳出?要理解这一点我们需要看看代码在example()函数里面是怎么运行的。
The next time the example() function is called from the main() function, the pt->lc variable will not be zero but 12, as it was set in the expansion of the PT_WAIT_UNTIL macro. This makes the switch(pt->lc) jump to the case 12: statement. This statement is just before the if statement where counter variable is checked to see if it has reached 1000! So the counter variable is checked again. If it has not reached 1000, the example() function returns again. The next time the function is invoked, the switch jumps to the case 12: again and reevaluates the counter == 1000 statement. It will continue to do so until the counter variable reaches 1000. Then, the printf statement is executed and the counter variable is set to zero, before the while(1) loop loops again.
下次example()函数被main()函数调用,变量pt->lc的值就不是0而是12,因为它在PT_WAIT_UNTIL宏里面已经被设置过。这使得switch(pt->lc)跳转到case 12:部分。这部分是在判断变量counter值是否达到1000这个if语句前面。所以变量counter再次被检测。如果它没有达到1000,example()函数就继续返回。下次这个函数被调用,这个switch语句就继续跳转到case 12:部分并且继续判断counter==1000。它会一直这样运行下去直到变量counter达到1000。然后在while(1)循环继续循环之前printf()语句被执行并且counter被置0。
But where did the number 12 come from? It is the line number of the PT_WAIT_UNTIL statement (check it by counting lines in the original program on your screen!). The nice thing with line numbers are that they are monotonically increasing. That is, if we put another PT_WAIT_UNTIL statement later in our program, the line number will be different from the first PT_WAIT_UNTIL statement. Therefore, the switch(pt->lc) knows exactly where to jump - there are no ambiguities.
但是这个数字12是哪里来的?它是PT_WAIT_UNTIL宏的代码段数字(检查你程序里面的源程序行就知道了)。用行号的好处就是他们是单调递增的。也就是说,如果我们在项目里面添加另外一个PT_WAIT_UNTIL申明,行号会与第一个PT_WAIT_UNTIL声明有所不同。因此,这个switch(pt->lc)明确知道跳转到哪里。
Did you notice anything slightly strange with the code above? The switch(pt->lc) statement jumped right into the while(1) loop. The case 12: statement was inside the while(1) loop! Does this really work? Yes, it does in fact work! This feature of the C programming language was probably first found by Tom Duff in his wonderful programming trick dubbed Duff's Device. The same trick has also been used by Simon Tatham to implement coroutines in C (a terrific piece of code).
你有没有发现上面的代码很奇怪呢?switch(pt->lc)语句跳转到while(1)循环的右边。Case 12:部分在while(1)循环里面。这能工作吗?是的,他确实能工作!C语言的这种功能是被Tom Duff在他的项目里面首次发现的,这种戏法被称作为Duff`s Device。同样的把戏也被Simon Tatham
用C实现协同程序(了不起的代码)。
Ok, now that you know how protothreads work inside its wicked macros, download the library and try it out for yourself!
好了,现在知道protothreads是如何利用它怪异的宏来工作的吧,下载库并且自己尝试一下吧。
---------------------------------------------我是华丽的分割线-------------------------------------------
上面只是翻译总结一下原文,如果想深入了解还是需要看源代码并且自己测试。我已经把ProtoThreads移植到了51上面,并且加入了时间延时函数。这些都可以根据源代码自己来修改达到。