2013年4月26日 星期五

初探 ﹣ 驗收測試驅動開發 (ATDD)






記得去年在JuluOSDev 與 Tossug 合辦聚會活動,曾探討了Tdd with python unittest for embedded c 這個主題,非常多朋友熱情參與。

最近這一段時間,筆者又接觸到ATDD (Acceptance Test-Driven Development),
之前大略看了,最近再花些時間研讀,溫故知新一下。

其中主要差異在於TDD 針對單元測試,
而 ATDD 則是對客戶要驗收的需求來實作單元測試。

有個真實小案例:

王小明是一個很盡責的工程小組長,他和小組完成整個程式專案,也都一一通過各成員所撰寫單元測試,整個程式運作穩定正常。

驗收當天,元大頭為主要客戶代表來驗收,交付程式前,產生如下的對話:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

元大頭說:「小明,可以讓我來試試程式符不符合我們的要求。」

王小明很有信心地說:「可以,程式經過我們嚴格的單元測試,目前版本已是穩定版了!」

元大頭試著輸入一些資料,他不小心把網路線弄掉,結果程式跑出一個錯誤,

他皺著眉說:「這是什是意思“Error:ECONNREFUSED”,我看不懂,這是火星文嗎?」

王小明立刻看了一下把線接上,不加思索,直接回說:

「因為網路連不上,網路線鬆掉了,現在接好,您再試試。」

元大頭按下OK後,待重新連線訊息完成,即能正常輸入後,過了一會有些不滿地說:

「咦?剛輸入資料怎麼全不見,小明你不說沒問題嗎?怎麼會這樣!
之前開需求時,不是有提到要讓使用者方便在常用頁快速輸入,也要讓使用者看得懂啊!」

王小明心虛地說:「抱歉!這是我們的失誤,會儘速修正。」
⋯⋯

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

小結:

以上述案例而言,小明的團隊其實有錯誤處理網路斷線的問題(曾見到沒處理的app程式直接閃退),但卻漏掉兩個需求。
  • 易懂的錯誤訊息
  • 常用頁的輸入時,錯誤處理應儘量保持使用者輸入的資料
若使用 ATDD,設法把需求轉成驗收測試單元,再寫程式來通過這些單元測試;
就比較不會發生遺漏使用者需求的情況。當然這個案例只是冰山一角,
ATDD 也不見得是萬靈丹,有任何想法,歡迎一同討論。



參考資料:



2012年12月30日 星期日

Cubieboard 耗電量實測





源起,最近在玩一個板子,Cubieboard (就一塊有帶SATA 的 A10 板子),心想到效能尚可,想拿來當多媒體播放中心,大家雖然知道它應該很省電,但耗電量多少也沒有看到測試報告,好奇想知道到底如何?於是有了本文。

測試一 (使用測電計, 藍光機殼溫度計)



執行環境

使用linaro desktop with sunxi-bsp


參考這一篇:Confirming that the Linaro Desktop build for the Hackberry works on CubieBoard

git clone https://github.com/linux-sunxi/sunxi-bsp/
cd sunxi-bsp
wget http://dl.linux-sunxi.org/amery/sunxi-3.0/20121109-1024/cubieboard_hwpack.tar.xz
wget http://releases.linaro.org/12.11/ubuntu/precise-images/alip/linaro-precise-alip-20121124-519.tar.gz
./scripts/sunxi-media-create.sh /dev/YOURSDCARD YOURHWPACK YOURROOTFS

製作了一個linaro desktop 的 SDCARD,試了這個版本總算 SATA 及2D 影片(RMVB 640x480 RV40 OK)播放功能都能正常支援。

測試時,硬體連接方式

測電計 --> 5v 變壓器(iphone 附)--> cubieboard → HDMI → HDMI-to-DVI 轉接頭 → CHIMEI LCD 22”

Part 1
初始耗電功率: 2.2w  (linaro 啟動到HDMI 輸出畫面)

Part 2
GNOME MPlayer 執行三個 (352x262 FFMPEG1, FFMP2FLOAT) 電影檔案 透過 samba intranet
CPU load: 86~92.7%





啟動時間 (分鐘後)A10 溫度: ℃耗電功率
2743.8℃3.6W
4346.4℃3.6W
4647.3℃3.6W
4947.9℃3.6W
5247.7℃3.6W
5647.8℃3.6W
6147.6℃3.6W
6447.4℃3.6W
7047.3℃3.6W


Part 3
將播放程式全部停止後,只有Linaro 桌面環境時



啟動時間 (分鐘後)A10 溫度: ℃耗電功率
9040.4℃2.2W
9238.5℃2.1W
9437.4℃2.2W


結論



Cubieboard 真是個省電好物,拿來當 media center (影音播放器)很適合。
linaro 桌面待機也不過2.2W而已。

接下來,目前有一點有待解決的問題如下:
* 就是youtube 在瀏覽器播放,會lag 的問題。
* 有啟動ibus時,會讓鍵盤輸入變得很卡,就算沒有有中文輸入也一樣。

附註:目前聲音用音源線接是OK的,從HDMI 還沒試過。

(如有同好使用的話,歡迎互相交流問題,把它弄得更好用吧。)


2013﹣01﹣01 更新:
中文輸入法部分改用 gcin 輸入子系統,調整一下 Locales 相關的設定後,中英文輸入得到不錯的速度。


2013﹣01﹣02 更新:
加入 ASUS N10 USB Wireless 支援 (patch sunxi-bsp),當無線網路啟用時,觀察CPU 使用約10~20﹪(@.@)。(更完整報告整理中。)

2012年12月27日 星期四

我們可能使用更高階的語言來寫OS嗎?


今天我看在 JuluOSDev 論壇上看到 

Descent 問道:寫 os 真的只有組合語言, c/c++ 可以完成嗎?別的語言無法用來寫 os 嗎?
(本文同步回覆於論壇)

以下是我參考一些文章翻譯及一點自已小心得總和而來。

其實是也不是,對所有語言來說最終會轉換machine code (大部分會產生中間的assembly)。
換個精確一點的問法:「我們可能使用更高階的語言來寫OS嗎?」

答案不是絕對的,因人而異。

排列比較答案後,大部分想使用高階語言來寫OS,是想簡化學習的曲線,避掉一些瑣碎細節,來享受快速開發的好處。

但憑心而論,學習沒有捷徑,天道酬勤;
還是需要大量的作業系統設計的基礎(如網路通信協定、記憶體管理、執行緒、驅動程式等等),
畢竟不會使用舊的方法的問題所在;無法創造新的路出來。

從技術的觀點來看,電腦其實並不知道你使用的是C#,還是JAVA;

首先你第一個要的是一個 bootloader, 
如果略過bootloader 的部分,你可以用任何語言來寫OS,

假設所使用的語言可以編譯出機器語言(而不是byte code,但有人可能反駁說硬體有JVM chip,可能也是成立的,視環境而定)。

另一方面,從效能的觀點,OS常需要精實而高效率的,
這部分高階語言無法為你做的,很有可能你會需要自行開適合的編譯器或其他的子系統來輔助,因為在和硬體溝通的部分若沒有低階語言存取能力,事情可能變得更複雜一些。

最後,就我個人而言,也夢想有一天,自已能用 python 或 nodejs 等語言來寫出OS來。

以下是大略列出(但不限),其他語言可用來寫OS:


* Forth -- http://www.forth.org/svfig/osf.html
ref: http://en.wikipedia.org/wiki/Forth_(programming_language)

* Lisp -- Genera is a commercial operating system
ref: http://en.wikipedia.org/wiki/Genera_(operating_system)

* JAVA: JNode
ref: http://www.jnode.org/
ref: http://www.jopdesign.com/ (Java Optimized Processor)

* C#: Singularity
ref: http://research.microsoft.com/en-us/projects/singularity/

* Ada: MaRTE OS
ref: MaRTE OS: An Ada Kernel for Real-Time Embedded ... - CiteSeerX
ref: http://marte.unican.es/


參考資料:
*stackoverflow - 627095/can a whole operating system be written without using even one line of c c code

*High level Languages and IO Access in Computer Interfacing﹣ osdever.net/tutorials/pdf/high_level_io.pdf

* System Programming in a High Level Language - Andrew D Birrell ﹣ http://birrell.org/andrew/papers/thesis.pdf

* wikipedia Operating system

* stackoverflow - 3317329/what language is used to write operating systems windows
* quora.com/What programming language are operating systems written in


以上寫了一堆,希望有所幫助。

2012年7月25日 星期三

F4OS與STM32F407的那些事兒?


源起,最近看到STM32F407這塊板子CPU 168MHz,性價比很高,手滑買了下來。


這塊板子上有個高手(Michael Pratt)正在認真地開發上面的作業系統,專案名為F4OS,源碼開放在GitHub。歷經三個多月,目前成品可直接在USART1跑一個只有一個uname 指令的shell。

在嵌入式無螢幕的裝置上,開發OS,最難的地方便是在沒有任何輸出之前的階段,把Terminal Console弄出來。

首先,Michael 開發是最先利用板子上的LED的明暗速度來進行偵錯。

筆者大約在6月份時,第一次用F4OS便是將LED 的明暗做調整,
接著用ARM的GDB來觀察暫存器值的變化,這真是黑暗前期。

直到最近,當看到Terminal輸出的畫面的一刻,真是令人十分感動。

USB2TTL

筆者使用的是  FT232 USB-Serial 的裝置
使用lsb 命令結果

~$ lsusb
Bus 002 Device 022: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC
Bus 002 Device 021: ID 0483:3748 SGS Thomson Microelectronics
Bus 002 Device 002: ID 0e0f:0002 VMware, Inc. Virtual USB Hub
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

STM32F407 + F4OS輸出畫面


執行uname -a 的輸出結果:

$ uname -a
F40S rev 143 Wed Jul 25 22:36:20 CST 2012

USB2TTL 與 STM32407 的連接

使用杜邦端子線來連接USB2TTL及STM32,之前一直卡在這裡;故特別對接法做個記錄。

STM32 Pin   ----    USB2TTL
PB6 (TX)  <------------> RX
PB7 (RX)  <------------> TX
GND  <-----------------> GND
(註:GND 線不接也可正常運作)

小結

有了Terminal 輸出的功能之後,開始進行其他實驗就十分的方便。
以上記錄目前的學習進度。


2012年5月21日 星期一

實作第一個BenOS 系統呼叫 puts

最近很久沒有更新BenOS的訊息,最近專注在研讀MIT-OSE 課程。

本次仿造 MIT-OSE JOS 中的系統呼叫,
筆者為自己小OS加新功能,算是對最近學習的一個驗證。

實作程式碼在 https://github.com/benwei/bos (develop branch)。

讓我們先來看JOS的系統呼叫的概念圖:
 可參考Lab3 JOS 實作程式碼在branch lab3 https://github.com/benwei/MIT-JOS

Lab3 JOS syscall calling sequence

執行步驟:
  1. lib/libmain.c:17 呼叫 sys_getenvid()
  2. lib/syscall.c:61 呼叫 syscall(SYS_getenvid)
  3. lib/syscall.c:23 呼叫48號中斷 帶入系統呼叫SYS_getenvid(2) 號
  4. 發生中斷 kern/trapentry.S:98, 將返回值0及 系統呼叫SYS_getenvid(2) 號放入堆疊,
  5. 跳至 kern/trapentry.S:312 _alltraps中,將相關的暫存器放入堆疊,接著執行332行trap(%esp), %esp == struct Trapframe *tf
  6. 為避免重覆執行中斷, kern/trap.c:200, eflags & FL_IF == 0 必須為真,204行檢查只否由使用者空間進入(tf->tf_cs & 3) == 3, 將tf 複製到核心空間保存;執行220行trap_dispatch()
  7. 執行trap.c:172 核心的syscall() 在 kern/syscall.c
  8. 跳至kern/syscall.c:87 執行 sys_getenvid(), 取得 curenv->env_id
  9. 返回trapdispatch, 執行kern/env.c:520 env_run 中 env_pop_tf()
  10. 呼叫kern/env.c:502 重新設置返回值及相關暫存器
  11. 於kern/env.c:507 執行iret 結束系統呼叫中斷
  12. 回到使用者空間,lib/syscall.c:34 檢查回傳值等等,完成一個呼叫流程
(注意:在lib/syscall.c:23及 TRAPHANDLER_NOEC&_alltrap 放入堆疊的數量與env_pop_tf 取回堆疊的數值必須一致,否則系統將發生不可預的行為。)

BenOS 實作


本次只先實作部分 kernel thread 間的系統呼叫,尚無ring3 至ring 0之間的權限切換。

詳見 user/libsyscall.c:10 系統呼叫:int TRAP_SYSCALL(68) , 其中 BSYS_PUTS(8)

1 #include "user/syscall.h"
2 #include "stdio.h"
3
4 /*
5  * num: BSYS_PUTS, a1 is string for display, a2 is undefined.
6  */
7 unsigned int syscall(int num, unsigned int a1, unsigned int a2)
8 {
9         unsigned int ret = 0;
10          __asm __volatile(
11                 "int %1\n"
12                 :"=a" (ret)
13                 :"i" (TRAP_SYSCALL),
14                  "a" (num),
15                  "d" (a1),
16                  "c" (a2)
17                 :"cc", "memory");
18         printf("syscall with ret=%d\n", ret);
19         return ret;
20 }
21
22 void uputs(const char *msg)
23 {
24         syscall(BSYS_PUTS, (unsigned int)msg, 202);
25 }

在kernel/osfunc.s:126 使用nasm 語法寫成,IDT 中斷呼叫256個函式庫。

其中osfunc.s:400 為減少堆疊使用,僅使用三個參數。
(註:將JOS 中的env_pop_tf 改寫成409~413,較易於理解)

400 _all_trap_handler:
401         push eax
402         push edx
403         push ecx
404
405         push esp
406         call trap_handler
407         add  esp, 4
408
409         pop ecx
410         pop edx
411         add esp, 4 ; no pop eax for return code
412         add esp, 8
413         iretd

kernel/trap.c
70 int trap_handler(struct trapframe *tf)
71 {
72         switch(tf->trapno) {
73         case TRAP_SYSCALL:
74                 print_tf(tf);
75
76                 return kern_syscall(tf->regs.eax,
77                              tf->regs.edx,
78                              tf->regs.ecx);
79                 break;
80         default:
81                 print_tf(tf);
82                 panic("panic: system halt\n");
83         }
84         return 0;
85 }

kernel/ksyscall.c
6 int kern_syscall(uint32_t num, uint32_t a1, uint32_t a2)
7 {
8         int ret = 0;
9         if (num == BSYS_PUTS) {
10                 do {
11                 struct task *t = get_now_task();
12                 printf("task_id(%d) syscall(BSYS_PUTS,a2=%d),a1: %s\n", t->pid, a2, a1);
13                 ret = strlen(a1);
14                 } while(0);
15         }
16         return ret;
17 }

執行結果:

開機完成至bos$ 下,執行run 來執行task 2 (events: hello_main)

0521_BOS


小結


本次的系統呼叫的基本框架,以最精簡的部分,實作出來。

筆者正在嘗試加入ring3 至 ring0 時,
在windows的qemu 0.13版,執行run 命令,qemu系統自動重新啟動bos;
但在MAC OS X 中使用qemu 0.12.5 執行同樣測試時,
qemu 會顯示正確存取權限失敗,
因為bos在ring3 中直接呼叫ring0函式。之後,會對使用者權限切換部分繼續實作。

有任何問題建議,歡迎至 http://www.juluos.org or http://julu.staros.mobi  中的Juluosdev google groups 參與討論。

2012年5月20日 星期日

python + shellcode 實測 64/32

0520-shellcode_py

最近看到Shellcode文章,手癢把它改相容64bit Ubuntu及32bit cygwin 了。

由此下載原始碼修改過smc.py


Ubuntu 11.10 (GNU/Linux 3.0.0-12-virtual x86_64)
使用Python 2.7.2+,進行測試

Linux pylab 3.0.0-12-virtual #20-Ubuntu SMP Fri Oct 7 18:19:02 UTC 2011 x86_64 x86_64 x86_64 GNU/Linux
# python smc.py
linux2_add64(99, 1) = 100


Linux xubuntu 2.6.35-32-generic #65-Ubuntu SMP i686 GNU/Linux
使用Python 2.6.6,進行測試

$ python smc.py
linux2_add32(99, 1) = 100


Windows XP+cygwin+gcc(4.5.3) + python 2.7.2
使用dump_machine_code.sh, ASM 的結果:

add.o:     file format pe-i386
Disassembly of section .text:
00000000 <_add>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   8b 45 0c                mov    0xc(%ebp),%eax
   6:   03 45 08                add    0x8(%ebp),%eax
   9:   5d                      pop    %ebp
   a:   c3                      ret
   b:   90                      nop
cygwin中,執行smc.py結果
$ python smc.py
cygwin_add32(99, 1) = 100


載入平台相依的libc

if sys.platform == "cygwin":
    libc = cdll.LoadLibrary("/bin/cygwin1.dll")
else:
    libc = CDLL('libc.so.6')


判斷32或64位元的方法之一


import sys
from math import log
def is64bit():
    return log(sys.maxsize, 2) == 63


小結


這技巧可以使用smc主程式,隨意在執行時修改程式碼的方法,也提供一條路讓python直接與硬體溝通。

如這是一個24小時不能停機的程式,意味著筆者可利用這方法做出不需要重新執行python主程式來動態載入修改過的函式庫。

當然這種方法也會影響Python 跨平台的可攜性,
解決方法只能為各種所需執行平台編繹對應的版本(若其平台安裝有gcc,則可以在執行檢查有無對應的platform_func.o 檔案,動態產生並載入它,類似pyc的做法)。

2012年3月31日 星期六

使用 array-label-goto 來模擬 while-switch-case 語法






最近看到在 Jserv 他的簡報中的某些Kernel原始碼,

使用 array+label + goto 來模擬while-switch-case的指令。

目的是讓整個程式片段能完整載入到CPU的L1快取中,

以提高程式效能。

以下是筆者所做的小實驗,程式碼可由此下載

testla.c 用 array+label+goto 來實作


#include "testla.h"
#define dispatch(s, h)  { int n = (*++s) ? (s - h) % 3 : 3; \

                          goto *tdispatch_list[n]; }

int run(const char *s)
{
        const char *head = s;
        void *tdispatch_list[] = {&&la, &&lb, &&lc, &&ld};
        int a = 1;

        la:
               dmsg("a:%c\n", *s);
               a++;
               dispatch(s, head);
        lb:
               dmsg("b:%c\n", *s);
               a++;
               dispatch(s, head);
        lc:
               a-=2;
               dmsg("c:%c\n", *s);
               dispatch(s, head);

        ld:
        return a;
}

testsw.c 用while-switch-case 來實作

 #include "testla.h"

int run(const char *s)
{
        const char *h = s;
        int n = 0;
        int a = 1;
        do {

            switch(n) {
            case 0:
                   dmsg("a:%c\n", *s);
                   a++;
                    break;
            case 1:
                   dmsg("b:%c\n", *s);
                   a++;
                    break;
            case 2:
                   a-=2;
                   dmsg("c:%c\n", *s);
            };

            n = (*++s) ? (s - h) % 3 : 3;

        } while(n != 3);
        return a;
}


共用的主程式部分



#include "testshared.h"

int main(int argc, char **argv) {
        const char *s = "hello";
        long i;
        for (i = 0 ; i < 250000000; i++) {
                run(s);
        }
        return 0;
}


編譯及測試用Makefile

SHELL=bash

OS=$(shell uname -s)

ifeq ($(CROSS),1)
ifeq ($(OS),Darwin)
        CROSS_COMPILE=/usr/local/gcc-4.5.2-for-linux64/bin/x86_64-pc-linux-
        CC=$(CROSS_COMPILE)gcc
        AS=$(CROSS_COMPILE)as
        LD=$(CROSS_COMPILE)ld
        ifeq ($(DUMP_AD),1)
        CFLAGS = -Wa,-a,-ad
        endif
endif
endif


ifeq ($(NO_O2),1)
CFLAGS += --save-temps
else
CFLAGS += -O2 --save-temps
endif

all: testla testsw

.c.o:
        $(CC) -c $< -I. $(CFLAGS)

testla: main.o testla.o
        $(CC) -o $@ $^

testsw: main.o testsw.o
        $(CC) -o $@ $^


run: testla testsw
        time ./testla
        @echo
        time ./testsw

執行結果


(以執行環境:windows xp, cygwin 為例)
無最佳化 -O2參數,testla 檔案大小 20136 Bytes; testla 檔案大小 19624 Bytes

testlabelsw$ make run

time ./testla

real    0m5.596s
user    0m5.546s
sys     0m0.092s

time ./testsw

real    0m6.034s
user    0m5.968s
sys     0m0.077s

有最佳化 -O2 參數, 檔案大小 20136Bytes; testla 檔案大小 19624Bytes

testlabelsw$ make run
time ./testla


real    0m3.172s
user    0m3.077s
sys     0m0.061s

time ./testsw

real    0m3.219s
user    0m3.140s
sys     0m0.093s


結論

目前測試只在User Space中,執行函式2.5億次

無最佳化下,大約有 7.25 % 的效能提升。

有最佳化下,大約只有 1.46 % 的效能提升。

以目前的結果來看,若去除誤差值約0.2%,使用最佳化的情況下,

在user-space下,可能不太有價值去做這樣的更動;

但就系統Kernel來說,這個方法提供一種優化演算法的小技巧,值得玩玩。

更新04-19


根據Jserv的回覆:筆者添加到5個switch-case,在有最佳化的情況下,效能提升至 9~10% 之間(測試數據 ),所以在此更正結論。原始碼已更新,請由此下載