2014年2月28日 星期五

coroutine 初試鍊

從 jserv 的課程得知 coroutine: http://wiki.csie.ncku.edu.tw/embedded/Lab2

The Art of Computer Programming, Volume 1 1.4.2 coroutines 這節就是在討論 coroutine。

test env:
debian/x86 32 bit

我對 coroutine 很有興趣, 想把這東西搞懂。練習這個作業時花了不少時間, 每次解開一個 bug, 後面的 bug 又接踵而來。

作業 (B)

  • 移植 coroutine 到 STM32 平台 (p103)
  • 必須修改 SAVE_STACK_POINTER_ASM 與 RESTORE_STACK_ASM 這兩個 macro
  • 系統最少要有兩個 routine,一者傾聽 UART 端來自使用者的輸入,另一者將輸入透過 ROT13 編碼演算法印列在 UART 上
  • 注意:ROT13 應以 inline assembly 實做
  • 倘若使用者按下 button 時,系統結束運作,不再畫面作輸出,同時也該清掉任何 buffer,直到使用者再次按下 button 時,才如同之前描述般繼續運作
  • 可參考 demo 的 Makefile,其已存在 ‘emu’ 的 target,可接受來自 stdio 的輸入,自動控制 QEMU 的執行。於是應該在 coroutine/Makefile 加入 ‘emu’ target
  • make emu
  • emulate.sh
.. code-block:: prettyprint
emulate () {
  $QEMU_STM32 \
    -M stm32-p103 \
    -kernel $1 \
    -serial stdio \
    -parallel none \
    -monitor tcp:localhost:4444,server,nowait <&0 & pid=$!
}
  • telnet localhost 4444
  • 提示:可修改 emulate.sh 與 test.script

這篇並不是在介紹 coroutine, 我還沒有搞懂, 只是紀錄我做這個作業遇到的問題。

難關一:

在使用 register variable 時, 用我自己實作的 setjmp/longjmp 會有錯誤 (segment fault), 但是標準程式庫的 setjmp/longjmp 不會有錯。

這個 register unsigned long savedstack; 可害死我, 花了兩個晚上才找出來為什麼會讓我的 setjmp/longjmp 當掉, 在我的系統中 savedstack 會用 ebx, 結果我的 setjmp/longjmp 沒有保留好 ebx, 就發生了 ebx 的值被改掉, 也就是 savedstack 這變數被在執行 setjmp 之後被改了。

https://github.com/descent/progs/blob/master/coroutine/my_setjmp.S 我已經做了一點修正。

cm3 的 setjmp/longjmp 沒遇到這樣的問題。

難關二:

movl    (%esp), %eax 改成 movl    4(%esp), %eax
因為 %esp 保存的函式返回值變成在 4(%esp), 因為我多了 push %ebx 的動作。
這個 bug 造成我用 setjmp 存下來的位址無法讓 longjmp 順利跳回。

難關三:

還有一個問題, 為何 Routine 1 count 是 1, 2 , 4 的值, 而不是 1, 2, 3 ?
[Routine 1] pass 1
[Routine 2] pass 4
[Routine 1] pass 2
[Routine 2] pass 2
[Routine 1] pass 4
[Routine 2] pass 0

要追這問題可讓我大傷腦筋, 使用 gdb single step 到 longjmp 會有問題, 直接設定 b __longjmp 在這裡觀察 __longjmp 的動作即可。


void routine1()
void routine2()
的 count 宣告改成 static 就符合預期了。

//volatile unsigned count = 0;
static unsigned count = 0;

執行結果:
[Routine 1] pass 1
[Routine 2] pass 4
[Routine 1] pass 2
[Routine 2] pass 3
[Routine 1] pass 3
[Routine 2] pass 2
[Routine 1] pass 4
[Routine 2] pass 1
[Routine 1] pass 5
[Routine 2] pass 0

不過原因是什麼呢? 似乎是:
L19 ~ 21, L37 ~ 39 的 macro 有錯,  改成 L74 ~ 83, L88 ~ 92 就正常了。

sample.c
  1 /* use setjmp/longjmp to implement coroutines */
  2 
  3 #include <stdio.h>
  4 #include <setjmp.h>
  5 #include <stdlib.h>
  6 //#include "my_setjmp.h"
  7 
  8 //#define setjmp my_setjmp
  9 //#define longjmp my_longjmp
 10 
 11 #define STACK_SIZE 4096
 12 
 13 /*
 14  * Change SP prior to calling setjmp so that longjmp will
 15  * start the routine with 'stackptr'.
 16  */
 17 #if defined(__i386__)
 18 //#error "386"
 19 #define SAVE_STACK_POINTER_ASM(savedstack, stackptr) \
 20 "movl %%esp, %[savedstack]\n" /* savedstack <- SP */ \
 21 "movl %[stackptr], %%esp"    /* SP <- stackptr */
 22 #elif defined(__x86_64__)
 23 #define SAVE_STACK_POINTER_ASM(savedstack, stackptr) \
 24 "movq %%rsp, %[savedstack]\n" /* savedstack <- SP */ \
 25 "movq %[stackptr], %%rsp"    /* SP <- stackptr */
 26 #else
 27 #error "Unsupported architecture!"
 28 #endif
 29 
 30 #define SAVE_STACK_POINTER(savedstack, stackptr) \
 31 do { \
 32 asm volatile ( SAVE_STACK_POINTER_ASM(savedstack, stackptr) \
 33 : [savedstack] "=r" (savedstack): [stackptr] "r" (stackptr) ); \
 34 } while (0)
 35 
 36 /* Restore "normal" stack prior to return */
 37 #if defined(__i386__)
 38 #define RESTORE_STACK_ASM(savedstack) \
 39 "movl %[savedstack],%%esp"
 40 #elif defined(__x86_64__)
 41 #define RESTORE_STACK_ASM(savedstack) \
 42 "movq %[savedstack],%%rsp"
 43 #else
 44 #error "Unsupported architecture!"
 45 #endif
 46 
 47 #define RESTORE_STACK(savedstack) \
 48 do { \
 49 asm volatile ( RESTORE_STACK_ASM(savedstack) \
 50 : : [savedstack] "r" (savedstack)); \
 51 } while (0)
 52 
 53 jmp_buf routine1_buf;
 54 jmp_buf routine2_buf;
 55 
 56 void routine1()
 57 {
 58 volatile unsigned count = 0;
 59 
 60 /* Start of Routine #1 */
 61 setjmp(routine1_buf);
 62 
 63 printf("[Routine 1] pass %d\n", ++count);
 64 
 65 longjmp(routine2_buf, 1);
 66 }
 67 
 68 void create_routine1(const void *stackptr)
 69 {
 70 register unsigned long savedstack;
 71 
 72 //SAVE_STACK_POINTER(savedstack, stackptr);
 73 
 74         asm volatile ( 
 75                        "movl %%esp, %0"
 76                        :"=r"(savedstack)
 77                        :
 78                      );
 79         asm volatile ( 
 80                        "movl %0, %%esp"
 81                        :
 82                        :"r"(stackptr)
 83                      );
 84 
 85 
 86 if (setjmp(routine1_buf) == 0) {
 87 //RESTORE_STACK(savedstack);
 88         asm volatile ( 
 89                        "movl %0, %%esp"
 90                        :
 91                        :"r"(savedstack)
 92                      );
 93 }
 94 else {
 95 /* We got here through longjmp */
 96 routine1();
 97 }
 98 }
 99 
100 #define FINAL_NUM 5
101 void routine2()
102 {
103 volatile unsigned count = 0;
104 
105 /* Start of Routine #2 */
106 setjmp(routine2_buf);
107 
108 printf("[Routine 2] pass %d\n", (FINAL_NUM - ++count));
109 
110 if (count < FINAL_NUM)
111 longjmp(routine1_buf, 1);
112 
113 exit(0);
114 }
115 
116 void create_routine2(const void *stackptr)
117 {
118 register unsigned long savedstack;
119 
120         asm volatile ( 
121                        "movl %%esp, %0"
122                        :"=r"(savedstack)
123                        :
124                      );
125         asm volatile ( 
126                        "movl %0, %%esp"
127                        :
128                        :"r"(stackptr)
129                      );
130 
131 
132 if (setjmp(routine2_buf) == 0) {
133 //RESTORE_STACK(savedstack);
134         asm volatile ( 
135                        "movl %0, %%esp"
136                        :
137                        :"r"(savedstack)
138                      );
139 }
140 else {
141 routine2();
142 }
143 }
144 
145 int main(void)
146 {
147 create_routine1((char *) malloc(STACK_SIZE) + STACK_SIZE);
148 create_routine2((char *) malloc(STACK_SIZE) + STACK_SIZE);
149 longjmp(routine1_buf, 1);
150         #if 0
151 routine1();
152 routine1();
153 routine1();
154 routine1();
155 routine1();
156 routine1();
157         #endif
158 
159 return 0;
160 }

這兩個 macro 沒有正確的將 stack 轉移到 malloc 出來的記憶體區塊, 造成兩個 count 變數其實是同一個, 會互相影響。

執行結果:

[Routine 1] pass 1
[Routine 2] pass 4
[Routine 1] pass 2
[Routine 2] pass 3
[Routine 1] pass 3
[Routine 2] pass 2
[Routine 1] pass 4
[Routine 2] pass 1
[Routine 1] pass 5
[Routine 2] pass 0

呼 ... 結束了嗎? 還沒, 我要 port 到 stm32f4Discovery 版子上, 這就簡單不少了, 將之前的努力集合起來, 把 usart 的 code 貼過來, 做些小修改, 從 minicom 就可以得到和 x86 上相同的結果。

source code:
https://github.com/descent/stm32f4_prog/tree/master/coroutine

程式碼沒有用到標準程式庫的東西, 所有的 c 檔案就是所有的程式碼了。

這樣結束了嗎? not yet, 作業題目可不是這樣 ..., rot13 我用了 c 語言而不是組合語言, 先饒了我吧!

ref:

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。