2016年10月27日 星期四

Google App Engine 使用taskqueue 在背景處理工作

最近小弟在用Google App Engine 開發一個網頁的服務,大體的內容是讓使用者上傳一個檔案,伺服器處理過後,讓使用者可以瀏覽處理後的內容。
因為檔案的大小一般都滿大的,處理起來一定會有延遲,如果handler 直接開始處理的話使用這一定會感受到網頁沒有回應,最後請教了強者我同學 NNNN 大大,經大大指點,才知道Google App Engine其實有提供taskqueue 來達成我要的功能。

簡單來說 task queue可以讓我們在背景執行耗時的工作,不影響到其他的服務,這篇是個簡單的範例:情境如下,使用者上傳要處理的檔案,處理過後會把處理的結果送到 ndb 的資料庫中儲存,因此使用者的 ndb model 中,加上一個 isReady 的boolean,用來記錄資料是否已經處理好,taskqueue 處理完之後就會把這一個boolean設為 True;如果他還是 false,前端就會顯示處理中的畫面(例如一個圓圈一直轉)。

Gae 的taskqueue 分為 push 跟 pull 兩種
  • Push:你push 工作到queue 之後,你設定多久釋放一個工作,時間到了那個工作就會開始執行,push 的工作限定要在 10 分鐘內結束。
  • Pull:這讓你決定何時從queue 中釋放工作出來執行,但你也要負擔更多管理queue 的工作。
一切都先從呯叫開始,在一般的get/post handler 中,由taskqueue.add
from google.appengine.api import taskqueue
task = taskqueue.add(
  url='/parse',
  target='worker',
  params={'user_id':user_id})
https://cloud.google.com/appengine/docs/python/config/queueref
Target 指要執行 task 的module 是誰,另外可以指定instance, version,這裡我是依著範例叫做 worker,如果未指定module 的話,就會是預設的app.yaml 這個module:
文件是這麼寫:
A string naming a module/version, a frontend version, or a backend, on which to execute all of the tasks enqueued onto this queue. … If target is unspecified, then tasks are invoked on the same version of the application where they were enqueued.
url 用來在服務中,選擇對應的handler。
params 則可帶入字典,指明要有哪些參數,亦可直接用 ?key=value 附加在/url 後,不過我喜歡用 params的帶入,比較清楚。

接著我們加入 worker.yaml,跟 app.yaml 一樣,這個module 用來分配task 的執行,注意handler 的URL 要用login: admin設為secure:
runtime: python27
api_version: 1
threadsafe: true
module: worker

handlers:
- url: /.*
  script: worker.app
  login: admin

最後就可以寫真正的handler 了,我們寫在worker.py中,因為我們要操作使用者的資料,因此先把使用者資料庫的model UserMessage獨立到單獨檔案dbmodel.py 中:
Class UserMessage(ndb.Model):
    user = ndb.StringProperty()
    isReady = ndb.BooleanProperty()

Worker.py 一樣用 wsgi ,將對 /parse的要求交給 ParseHandler 處理,ParseHandler 可以用 self.request.get(‘key’) 拿到由caller 傳來的資料,我們這裡沒做什麼事,就是取出使用者資料然後把isReady 改為 True 再存回去;為了模擬耗時工作我加了個 sleep(10)
from google.appengine.ext import ndb
import webapp2
import logging

from dbmodel import UserMessage

class ParseHandler(webapp2.RequestHandler):
    def post(self):
        time.sleep(10)
        user_id = str(self.request.get('user_id'))
        logging.info("user_id: {}".format(user_id))

        query = UserMessage.query(UserMessage.user == user_id)
        userdata = query.fetch()

        if len(userdata) != 0:
            userdata = userdata[0]
            userdata.isReady = True
            userdata.put()

app = webapp2.WSGIApplication([
    ('/parse', ParseHandler)
], debug=True)

執行是最關鍵的一步了,平常是要測試的話只要
dev_appserver.py .
就好,因為我們多了一個 worker.yaml ,所以要指定它把 worker.yaml 也考慮進來,又因為兩個yaml 在application ID上會衝突,會出現 “More than one application ID found: dev~None, dev~application_id”,所以要明確指定ID:
dev_appserver.py -A application_id app.yaml worker.yaml
這樣才能正常的執行,這花了我超多時間,最後是看了google 範例code 裡,standard/taskqueue/counter 的readme 才知道要這樣執行…
https://github.com/GoogleCloudPlatform/python-docs-samples

實際上來說,我們也可以在add taskqueue 的時候不指定target,然後在app.yaml 的handler 把/parse 交由 worker.app 處理,這樣就不需要分兩個yaml 了。
驗證有沒有動的話,我是有個 /view 的頁面,會去監看那個資料庫裡isReady 的狀態,並顯示這個狀態,在發動taskqueue 之後,重新整理一下網頁,就會看到狀態更新了。

另外taskqueue 也可以透過 queue.yaml 來設定queue 的名稱,update rate ,每個task 可以使用的空間上限等屬性,不過我們還是個小服務所以沒用上這個設定,這裡就不細講了。
https://cloud.google.com/appengine/docs/python/config/queueref

以上大概就是taskqueue 的小小整理,之後就是把 sleep(10) 換成真正重要的工作了。

本文感謝強者我同學 NNNN 大大的指導。

2016年10月24日 星期一

錢的聯想-後半段

其實這篇理應是跟著上一篇:錢的聯想寫的
http://yodalee.blogspot.tw/2015/11/blog-post.html
不過後來就拖稿到現在,最近用了失眠的時間補完,其實就是篇無意義的mur mur。

記得曾經看過類似的文章(雖然現在已經找不到了),大意就是在人工智慧跟物聯網那種大量感測器的狀況下,人類社會能夠達成過去共產經濟所達不到的目標,大量的感測器能無所不在的監看每一筆交易和金錢流動;人工智慧則能最有效率的去分配資源並調節價格,達到共產社會的理想境界。

我的答案是辦不到。

先假設這種無所不知的系統存在好了,他可以做到,例如:監控所有市場上的交易,察覺到商品過熱時,就進行宏觀的調節,利用增加供給量或是增課交易稅來抑制過熱的交易,壓制隨過熱交易而來的暴利。
拿房市來說,房市上漲時,房屋交易稅或房屋稅就會對應調高,壓抑稍熱的買氣,促使房價回跌;任何稍有賺頭的東西都可以被適當調節,透過制度抵消。
聽起來很理想,但問題就在,並不是所有的交易都會在所謂的「市場」上發生,交易才是經濟的本體,有交易的地方才是市場;一般以為的市場只是指受到監管的交易場所,相對也有不受監管的「黑市」交易存在。
除非這個系統能夠有效偵測每一個商品的流向,並在任何商品轉移的時候對交易雙方自動以公訂價進行付款,我想這樣的系統老早就超過了一般人能接受的監控程度,也是現下根本無法想像的系統密度。

另外,我不認為系統能夠有效因應泡沫經濟、通貨膨脹這類非理性的經濟事件。
泡沫吹起來的時候,因為有利可圖,大家會盡全力的尋求交易機會,早買一天可以賺更多,價格存在於人們的預期心理,而不是系統定價,出問題的不是系統,而是信心脫離常軌的集中;即便系統可以對房市交易隨熱度推出極高的重稅,也只會讓交易遁入黑市之中;另一方
面當泡沫破裂,每個人都瘋狂拋售手上的物件,只因為其他人也在拋售,物件放到明天就比今天更沒價值。

把錢看成一種貨物,極速通貨膨脹就是人民對政府信用失去信心的後果,大量印鈔則是政府與央行要保證它價值的後果;從現下社會的緩慢通膨來看,手中的10元到了幾年後可能只剩9元的價值,當然,所有的物品都會隨時間經過而失去價值,所謂的「這東西保值」說難聽一點,其實就是這個物件失去價值的速率,比法幣因通貨膨脹失去價值的速率還要慢。

的確,泡沫經濟完全就是集體狂熱和集體相信,毫不理性,但我們的貨幣跟經濟就是建立在這樣的不理性上面,「相信」這件事情,本來就沒多少理性的成份。

就跟聽得懂鳥語的公冶長一樣,鳥兩次告知它哪裡有動物可以吃,第一次是真的有肉可以吃,第二次卻是死人屍體害公冶長入獄,那麼公冶長到底該不該相信鳥?同樣的,貨幣充其量只是廢五金,我把全身財產全換用貨幣,它要是沒了價值我不就一無所有?那我究竟是信還是不信?這完全是一種集體行為的結果,如果我信你信大家都信,那我信就有用;如果大家都不信了,我去信就是我蠢。

「相信」這件事情遠在文明發生之前,你的同伴叫你去前面搬剛打的獵物搬回來,可能是真的有東西要跟你分享,也可能他要把你引開然後炸你全家,你信或不信?

政府管得到人們的行為,卻管不了人們的信心,系統也是,如果我們要求這個系統有total control,就得讓它接管生活中任何能影響信心的事物,它得接管包括黑市在內的所有交易、控制戰亂造成的政府動盪、控制流言四散;我們大概要假設:這個系統擁有的控制能力,比過去任何極權政府所能做到的還要強上數十數百倍。
事實上,歷史早有明證,政權在面臨經濟危機時,總是能端出各種存亡救急的方法,參考一下這篇國府在面對通膨時的手段:
https://www.ptt.cc/bbs/Warfare/M.1456638482.A.569.html
像黃金準備率,利用定量的貴金屬來保證法幣的價格;利用黃金引誘存錢收回法幣;管制外匯避免透過進出口以外幣結匯,加強市面的法幣使用。其他還有很多,平抑物價管制市場交易?限價法;市場上商品不足?對重要物資採配給;但最重要的還是安定集體的信心,有了信心法幣才有價值;極端狀況下,就算再如何極權的政府也拉不回人民的信心,貨幣就像一個國家的股票,守住法幣的價值則依靠國民對國家這個符號的凝聚力,那句老話:民心如水,水能載舟亦能覆舟,實在是至理真言。

2016年10月22日 星期六

使用vim script 改進blogger 寫作流程

故事是這樣子的,今年不知道為什麼靈感大爆發,一直不斷的寫blog。

用blogger 最麻煩的就是它的介面,不太能像jekyll 之類的由文字檔轉成blog,要直接用它的編輯器介面寫文章,如果是一般的文章就很方便,插圖、連結都能一鍵完成,但要插入tag 就麻煩大了。
像我的blog 常會有程式碼或一些執行結果要highlight,我通常會插入兩種不同的tag,一種是單純的highlight <div class="hl"></div>;另一個是包住程式碼用的 <pre class="prettyprint lang-xx">,要在blogger 上面加上這兩個tag ,就必須切換到html 編輯模式,自己到適當的地方加上open tag,然後找到末尾加上close tag,這是一個很累人的過程。

特別像是上一篇rust helloworld 的文章,充滿許多程式碼跟執行結果,編輯起來要十幾分鐘,還要不斷的交稿,每每寫到這種文章就想要放棄blogger換到其他平台…但想想還是不太想放棄blogger 平台,雖然有無痛轉移到其他平台wordpress,但其實blogger 上傳圖片等等還是滿好用的。

那編輯耗時這點總不能這樣下去吧…最近死腦筋終於想到:可以利用vim script 改善這個流程。
首先是HTML跳脫的部分,其實這個可以把文字貼去blogger 再從HTML 抓回來就行了,也可以用vim script 代勞,例如這個指令:
http://vim.wikia.com/wiki/HTML_entities

另外自幹一個Make Blogger funciton,會用這個function對整份文件執行EscapeHTML 取代,並在每個行尾插入<br /> 換行tag:
function! Blogger()
  let range = 'silent ' . 0 . ',' . line("$")
  call EscapeHTML(0, line("$"))
  execute range . 'sno/$/<br \/>/eg'
  endfunction

command! MakeBlogger call Blogger()
另外有兩個help funciton MakePre 跟MakeDiv,功用是可以用vim 執行range 指令,在前後加上tag,如果是Pre的話會把當中的<br /> 給代換掉,利用vim 優秀的整行選取,方便加入各種tag。
//Pre, add <pre class="prettyprint"> and </pre>
function! MakePre(line1, line2)
  call cursor(a:line2, 0)
  :normal o</pre><ESC>
  call cursor(a:line1, 0)
  :normal O<pre class="prettyprint">\r<ESC>
endfunction

command! -range Pre call MakePre(<line1>, <line2>)
//Div, add <div class="hl"> and </pre>
function! Div(line1, line2)
  call cursor(a:line2, 0)
  :normal o</div> <ESC>
  call cursor(a:line1, 0)
  :normal O<div class="hl"> <ESC>
endfunction

command! -range MakeDiv call Div(<line1>, <line2>)
如此一來就能有效的加速blog 發文的流程了,使用時先把libreoffice 裡的文件貼到文字檔裡面,然後用vim script 編輯過,就能整篇由blogger的文字介面中貼入,這篇文用這個方式發,不到十分鐘就發完了。 話說在這個FB, LINE, HackMD 當道的年代,這樣一直寫blogger 感覺怪土裡土氣的,不知各位看倌怎麼想呢?

2016年10月19日 星期三

Rust 開發迷你ARM kernel 系列 0:Hello world

故事是這樣的,很久以前曾經在rust 上面實作hello world 的arm 程式,不過那時候的作法現在已經不能用,而且除了輸出x 之外其實不能幹嘛,更別提後面更多的東西了。

其實網路上也查得到不少Rust OS 的實作,沒道理我做不到,於是就來試一試了。

這裡不會說明程式碼為什麼要這樣寫,請參考之前寫的筆記:
https://yodaleeembedded2015.hackpad.com/Lab42-Note-YKuTRvCMYYx
mini-arm-os 的程式碼:
https://github.com/jserv/mini-arm-os
或者參考金門大學傳說中的鍾誠教授的<用十分鐘 向jserv學習作業系統設計>

--

要跑這個首先要安裝stm32 的qemu
https://github.com/beckus/qemu_stm32
注要在configure 的時候加上—disable-werror才能成功編譯,不然會遇到deprecated 的warning,完整的編譯參數
./configure --enable-debug --target-list="arm-softmmu" --python=/usr/bin/python2.7 --disable-werror

另外要將rust 編譯為arm,我們需要安裝rust 的cross compile tools,這裡有一篇文章把相關會遇到的問題都講得差不多了。
https://github.com/japaric/rust-cross

就算是一般使用我也推薦使用rustup,可以快速的在stable, beta, nightly 中切換;用rustup 處理cross compile 的問題也很容易,如上頁所述的五個步驟:
1. 安裝對應的C toolchain
2. 用rustup 安裝編譯好的目標library
3. 在~/.cargo/config指定特定target 的linker 要用誰,我猜這是因為LLVM 的linker 尚未就諸的緣故?這樣就可以用cargo build –target=$(target) 來指定編譯目標了;為了這個測試,我選用armv7-unknown-linux-gnueabihf,gcc 則是選用arm-linux-gnueabihf-gcc
rustup target add armv7-unknown-linux-gnueabihf
cat >> ~/.cargo/config < EOF
> [target.armv7-unknown-linux-gnueabihf]
> linker = "arm-linux-gnueabihf-gcc"
> EOF
cargo build --target=armv7-unknown-linux-gnueabihf
第一步是實作Hello world,雖然網路上有些純Rust 的實作,但這次想要自己重頭自幹,試圖完全用rust 代替c ,一些dirty work 總是少不了的,在最底層的部分還是先用 assembly 實作,找到適合的方法再用rust 改寫。

Assembly 的部分參考(複製貼上)這裡的code
https://community.arm.com/docs/DOC-8769

先寫一個最簡單的startup.S,isr 的部分只定義reset handler,並且用它的FUNCTION, ENDFUNC macro 實作defaultResetHandler和defaultExceptionHandler,內容物都是單純的迴圈:
.weakref            Reset_Handler,defaultResetHandler

.section            isr_vector
.align              2
.long               0  //initial stack pointer
.long               Reset_Handler // startup-code,系統上電時第一個執行的位址

.text
.align
FUNCTION            defaultResetHandler
b                   defaultExceptionHandler
ENDFUNC             defaultResetHandler

FUNCTION            defaultExceptionHandler
wfi                 // wait for an interrupt, in order to save power
b                   defaultExceptionHandler // loop
ENDFUNC             defaultExceptionHandler
編譯則是採用arm-none-eabi-gcc,參數使用 -fno-common -O0 -mcpu=cortex-m3 -mthumb -T hello.ld -nostartfiles,直接編譯就會動了,程式碼的hash 為 fdc836

當然只有assembly 是不夠的,我們要rust! 這裡參考之前看到這個神blog,它在x86 上面用asm 跟rust 自幹了一個頗完整的kernel,現在我的狀況跟他在接上rust 的地方有 87 % 像
http://os.phil-opp.com/set-up-rust.html

首先是寫一個Cargo.toml
[package]
name = "mini_arm"
version = "0.1.0"
authors = ["yodalee <lc85301@gmail.com>"]

[lib]
crate-type = ["staticlib"]

然後新建檔案 src/lib.rs
#![no_std]
#![feature(lang_items)]

#[lang = "eh_personality"]
extern fn eh_personality() {}
#[lang = "panic_fmt"]
extern fn panic_fmt() -> ! {loop{}}

#[no_mangle]
pub unsafe fn __aeabi_unwind_cpp_pr0() -> () { loop {} }

#[no_mangle]
pub extern fn rust_main() {
    loop {}
}
開頭先用#! 指定這個crate 的特性;指定 no_std免得rust std 那堆需要OS支援的檔案、system call 等東西跑進來亂;指定lang_items feature 讓我們可以去調整rustc 的一些行為,官方文件是這麼說的:
https://doc.rust-lang.org/book/lang-items.html
The rustc compiler has certain pluggable operations, that is, functionality that isn't hard-coded into the language, but is implemented in libraries, with a special marker to tell the compiler it exists.

大意是需要透過lang marker 來告訴rustc,這裡我們有實作(或說更改)了某項功能,例如下面的 eh_personality 跟panic_fmt;把feature 拿掉,我們實作 eh_personality會造成錯誤 language items are subject to change;把eh_personality 實作拿掉,則會變成language item required, but not found;有點…詭異。

eh_personality負責的是Rust在panic 時 unwinding 的工作,目前OS還不會unwinding 所以留白沒差;panic_fmt 則是panic! 的進入點,同樣不需要實作。
https://doc.rust-lang.org/nomicon/unwinding.html
__aeabi_unwind_cpp_pr0也是類似的狀況,如果不寫直接編譯,會發生undefined reference的錯誤,要使用 #[no_mangle] 避免function 名字被改掉;最後就是我們的main function,同樣要用no_mangle 來避免asm 找不到對應的function。

再來我們就能在reset handler 裡面動手腳了,把原本的迴圈改掉加上跳到rust_main 的指令:
FUNCTION            defaultResetHandler
bl rust_main
b  defaultExceptionHandler
ENDFUNC             defaultResetHandler
執行到這裡它就會進來執行我們的rust_main ;在Makefile 中加上cargo 的命令,就能成功編譯出執行檔,反編譯中也會看到對應的程式碼:
00000034 <rust_main>:

#[no_mangle]
pub extern fn rust_main() {
34: e24dd004  sub sp, sp, #4
  loop {}
38: eaffffff  b 3c <rust_main+0x8>
3c: eafffffe  b 3c <rust_main+0x8>

後面的內容就跟神blog 的內容講得差不多,需要在Cargo.toml 中加上rlibc的dependencies,並且在linker 參數加上 --gc-sections,才能使用一些rust 的code。
[dependencies]
rlibc = "1.0.0"

現在我們試著用qemu 執行時,qmeu 它爆炸了:
emu: fatal: Trying to execute code outside RAM or ROM at 0xe12fff1e

R00=00000000 R01=00000000 R02=00000000 R03=00000000
R04=00000000 R05=00000000 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=ffffffe0 R14=fffffff9 R15=e12fff1e
PSR=40000153 -Z-- A svc32
FPSCR: 00000000

使用qemu 搭配gdb 來檢查一下:
qemu-system-arm -M stm32-p103 -nographic -kernel hello.bin -S -gdb tcp::9453
$(gdb) file hello.elf
$(gdb) target remote localhost:9453

它一進到rust_main 之後就死機了,當下的第一個指令是:
34: e24dd004 sub sp, sp, #4

很奇怪的,這行指令就是一直讓它當掉,比對了C version之後,發現可能是eabi 的問題:C用的是arm-none-eabi;我們則用了arm-linux-gnueabihf,於是我們要改用thumbv7em-none-eabi 的rustc;首先是從網路上拿到thumbv7em-none-eabi.json:
{
  "arch": "arm",
  "cpu": "cortex-m4",
  "data-layout": "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64",
  "disable-redzone": true,
  "executables": true,
  "llvm-target": "thumbv7em-none-eabi",
  "morestack": false,
  "os": "none",
  "relocation-model": "static",
  "target-endian": "little",
  "target-pointer-width": "32",
  "no-compiler-rt": true,
  "pre-link-args": [
    "-mcpu=cortex-m4", "-mthumb",
    "-Tlayout.ld"
  ],
  "post-link-args": [
    "-lm", "-lgcc", "-lnosys"
  ]
}
參考這篇裡面的作法: http://antoinealb.net/programming/2015/05/01/rust-on-arm-microcontroller.html
先把rust 的git repository 載下來,利用rust --version -v找到rustc 的build hash,將rust checkout 到相同的hash value
git clone https://github.com/rust-lang/rust
cd rust
git checkout $HASH
cd ..
把thumbv7em-none-eabi 存下來,就可以build 了:
mkdir libcore-thumbv7m
rustc -C opt-level=2 -Z no-landing-pads --target thumbv7em-none-eabi \
 -g rust/src/libcore/lib.rs --out-dir libcore-thumbv7em
先用rustc --print sysroot 找到rustc 的根目錄位置:
~/.multirust/toolchains/nightly-x86_64-unknown-linux-gnu
把編譯出的 libcore-thumbv7em/libcore.rlib,放到對應的資料夾中:$(rustc root dir)/lib/rustlib/thumbv7em-none-eabi/lib 裡面
$ pwd
~/.multirust/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib
$ tree thumbv7em-none-eabi
thumbv7em-none-eabi
└── lib
    ├── libcore.rlib
    └── rustlib
現在就可以用cargo build –target=thumbv7em-none-eabi 來編譯了;編譯完之後qemu 也不會當機了;同樣只有loop 的main,linux-eabi 跟none-eabi 的結果差異:
armv7-unknown-linux-gnueabihf, failed:
1c: e24dd004 sub sp, sp, #4
20: eaffffff b 3c <rust_main+0x8>

thumbv7em-none-eabi, worked:
10: b081 sub sp, #4
12: e7ff b.n 14 <rust_main+0x4>
到這裡我們就能來寫code 了,首先我們要把裡面的register 都獨立出來到一個reg.rs 裡面
#![allow(dead_code)]

/* RCC Memory Map */
pub const RCC: u32 = 0x40021000;
pub const RCC_APB2ENR: u32 = RCC + 0x18;
pub const RCC_APB1ENR: u32 = RCC + 0x1C;

/* GPIO Memory Map */
pub const GPIOA: u32 = 0x40010800;
pub const GPIOA_CRL: u32 = GPIOA + 0x00;
pub const GPIOA_CRH: u32 = GPIOA + 0x04;

/* USART2 Memory Map */
pub const USART2: u32 = 0x40004400;
pub const USART2_SR: u32 = USART2 + 0x00;
pub const USART2_DR: u32 = USART2 + 0x04;
pub const USART2_CR1: u32 = USART2 + 0x0C;
在main 裡面就能對各register 進行操作了,因為rust 對安全性的要求,所有對定位址的操作都是unsafe 的;另外之前支援的 number as *mut _ 已經不能用了,現在要指明哪一種型別的pointer:
https://doc.rust-lang.org/book/casting-between-types.html
const USART_FLAG_TXE: u16 = 0x0080;

pub fn puts(s: &str) {
    for c in s.chars() {
        unsafe {
            while !(*(reg::USART2_SR as *mut u32) & USART_FLAG_TXE as u32 != 0) {}
            *(reg::USART2_DR as *mut u32) = c as u32;
        }
    }
}

#[no_mangle]
pub extern fn rust_main() {
    unsafe { *(reg::RCC_APB2ENR as *mut u32) |= 0x00000001 | 0x00000004 };
    unsafe { *(reg::RCC_APB1ENR as *mut u32) |= 0x00020000 };
    unsafe { *(reg::GPIOA_CRL as *mut u32) = 0x00004b00 };
    unsafe { *(reg::GPIOA_CRH as *mut u32) = 0x44444444 };
    unsafe { *(reg::USART2_CR1 as *mut u32) = 0x0000000C };
    unsafe { *(reg::USART2_CR1 as *mut u32) |= 0x2000 };

    puts("Hello World!\n");
}

終於,我們看到傳說中的 Hello World! 啦,為了這一步可是歷經千辛萬苦呀

程式碼在此:
https://github.com/yodalee/rust-mini-arm-os/tree/master/00-Helloworld
請先進指教。

下一步:
我們依 mini-arm-os 的步調,下一步要加上更多的 register,我們要想辦法儘量不要增加編寫的負擔,能快速開發針對不同平台register 位址。

2016年10月9日 星期日

人生第一次參加 coding contest

最近郵件收到leetcode weekly contest 8 的信,又收到Top international 也要辦coding contest 的消息,撇開原本就有的ACM 或是Google Code Jam 不說,怎麼現在大家都在辦coding contest,是某種新時代的潮流嗎owo
看了這麼多信我都有點心動,決定這次來參加一下。不過我從來沒有參加過Coding contest 的經驗,畢竟我軟體是半路出家,演算法什麼的根本是十竅通了九竅-一竅不通,不像強者我同學外號武藤遊戲的郝神,都已經在Code Jam大殺四方,還可以打到世界前幾名(yay,連Jeff Dean 都不是對手。

第一次參加這種contest ,得名什麼的就算了,志在參加不在得名。

總之0930 時開始,時間到把題目先看一遍,總共有四題;看完完全確定解法的只有一題 415. add string,我還在寫雛型的時候,大概五分鐘網頁上已經有人解完一題了= =,這到底是什麼鬼速度RRRR。
把add string 解掉之後,繼續下一題,416. Partition Equal Subset Sum,這題我先用了最直覺的greedy algorithm 去寫,因為是approximate algorithm因此有測資是錯的,只好改用dynamic programming 去解,在1 hr 的時候拿下第二題。

第三題Sentence Screen Fitting,用最直覺的解法解掉之後,上傳遇到 time limit exceed。
做了最佳化,在 2 hr 時拿下第三題。解完剩下30 分鐘,開始寫最後一題,雖然知道演算法,不過有些地方碰上問題,剩五分鐘的時候上傳有測資出現錯誤,查了一下想出問題到底在哪裡?再上傳就通過了,只可惜已經超時,這題最後就沒有拿分。

Leetcode coding contest 的規則,除了要快還要對,每次錯誤都會加上10 分鐘的penalty,這讓submit 的時候壓力超大的,看到Accept 跳出來都在電腦前又叫又跳的,幸好房間裡沒其他人。
這種比賽真的滿吃經驗跟熟練度的,在比賽中還在翻c++ forum或cppman 就low 了,根本是上了戰場後再來查武器使用說明書Orz。
最終結果:四題完成三題(如果不算上那五分鐘理論上是四題啦)在2:06:45 時完成,但因為一次超時跟一次錯誤,各加上10分鐘,總時間是2:26:45,名次:200 / 869。

人生第一次coding contest 就這樣結束了,根本悲劇,演算法的什麼好難啊QQQQ

進行arm 開發時遇到的各種狀況

約一週前重灌了電腦,重灌這檔事最麻煩的就是一些開發環境會消失不見,平常用得順手的東西突然不見了,例如我的arm 開發環境就是一例:
首先在archlinux 上面,跟之前這篇不同,現在只剩下gcc49, gcc53跟gdb 還在
http://yodalee.blogspot.tw/2015/04/llvmclang.html
arm-none-eabi-gcc53-linaro
arm-none-eabi-gdb-linaro

替代品是AUR中下面這些套件(順帶一提,安裝這幾個套件之前,請先去/etc/makepkg.conf 把MAKEFLAGS 改成 -jn,不然用單核心編譯這幾個近100 MB的程式會編譯到想翻桌):
arm-linux-gnueabihf-binutils
arm-linux-gnueabihf-gcc
arm-linux-gnueabihf-glibc
arm-linux-gnueabihf-linux-api-headers
arm-linux-gnueabihf-gdb

安裝完之後當然就來用一下了,測試當然就用最簡單的helloworld.c 去測:
$ arm-linux-gnueabihf-gcc helloworld.c -o hello
exec format error: ./hello
這是當然的囉,因為我的機器是x86_64,而arm-gcc 編出來的執行檔要在arm 架構上執行,幸好這年頭我們有qemu-arm 可以用(老實說,悲劇就是從這裡開始的= =):
qemu-arm hello
/lib/ld-linux-armhf.so.3: No such file or directory
這又是為什麼呢?我們可以file 它一下,可以看到它的interpreter 是/lib/ld-linux-armhf.so.3,而這個檔案是不存在的。
file hello
hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.32, BuildID[sha1]=716a92a4985090baa83f8b762c5f9844e197ed83, not stripped

它真正的位置在arm-gcc 的安裝位置:/usr/arm-linux-gnueabihf/lib,創個symbolic link過去
ln -s /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3 /lib/ld-linux-armhf.so.3
ll ld-linux-armhf.so.3
lrwxrwxrwx 1 root root 48 Oct 5 19:10 ld-linux-armhf.so.3 -> /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3

這時候就不會有No such file or directory,雖然會換成另一個錯誤
hello: error while loading shared libraries: libc.so.6: wrong ELF class: ELFCLASS64 http://blog.csdn.net/sahusoft/article/details/7388617
原因是dynamic linker 在連結動態函式庫時,找到的libc.so.6 是錯誤的格式,它找不到它要的arm 格式的函式庫,這步有時會有其他的錯誤像是:
cannot open shared object file: No such file or directory
這就是電腦上沒有安裝相對應的函式庫,可以用ldd 來確認這件事,沒有就要安裝該函式庫;或者函式庫安裝在/usr/lib 以外的特殊路徑,就要利用ld.so.conf去設定,像我的/etc/ld.so.conf.d裡面就有:
android-sdk.conf
cuda.conf
fakeroot.conf
ffmpeg2.8.conf
lib32-glibc.conf
octave.conf
openmpi.conf

不過我們的狀況比較複雜一點,在這之前試著把 /usr/arm-linux-gnueabihf/lib 加到ld.so.conf 裡面,執行ldconfig 時就出問題了
$ ldconfig
ldconfig: /usr/lib/ld-linux-armhf.so.3 is for unknown machine 40.
我們x86_64 的ldconfig 壓根不認arm 的函式庫,這些函式庫也沒加到ld.conf.cache,自然也不會在執行時被 ld-linux-armhf.so.3 連結,所以上面的執行還是失敗了。

所以說了這麼多,我們到底要怎麼樣才能把我們的hello world 跑起來?

首先是rpath
http://stackoverflow.com/questions/2728552/how-to-link-to-a-different-libc-file
在呼叫gcc 的時候使用
arm-linux-gnueabihf-gcc -Xlinker -rpath=/usr/arm-linux-gnueabihf/lib hello.c
這樣編譯出來的執行檔就會以/usr/arm-linux-gnueabihf/lib 為第一優先,就能夠直接以qemu-arm 去執行。

另一個方法是設定LD_LIBRARY_PATH,這個的優先順序高過 ldconfig 設定的路徑,也能讓qemu-arm 跑起來:
export LD_LIBRARY_PATH=/usr/arm-linux-gnueabihf/lib/
當然這不是一個好方法,也是老話題了,請見:
http://xahlee.info/UnixResource_dir/_/ldpath.html

或者是利用qemu-arm 的-L flag,這個的位階低於LD_LIBRARY_PATH,因此用這個要確定LD_LIBRARY_PATH沒有設值。
qemu-arm -L /usr/arm-linux-gnueabihf hello

試了這麼多方法,最後一種其實才是最有效的做法,也是我忘掉的方法(yay

寫文的同時我大概整理一下 man ld 裡面,在選項-rpath=dir跟-rpath-link=dir的說明:
首先是rpath ,這是設定runtime 時期的library search path,這些字串會完整的被複製到runtime linker (也就是ld.so)用來尋找共享函式庫;如果rpath 沒有設定,則linker 會使用LD_RUN_PATH 的值。
因此透過rpath 我們有兩招:
第一個是上面寫的,用rpath 設定搜尋路徑:
arm-linux-gnueabihf-gcc -Xlinker -rpath=/usr/arm-linux-gnueabihf/lib hello.c
arm-linux-gnueabihf-gcc -Wl,-rpath=/usr/arm-linux-gnueabihf/lib hello.c

第二個是直接編譯,但先設定LD_RUN_PATH的值:
export LD_RUN_PATH=/usr/arm-linux/gnueabihf/lib
arm-linux-gnueabihf-gcc hello.c

我們可以用readelf -d 把dynamic sections 給讀出來,就能看到我們設定的rpath 了:
readelf -d hello
0x0000000f (RPATH) 函式庫路徑:[/usr/arm-linux-gnueabihf/lib]

另外還有一種是 -L,它會設定連接時搜尋共享函式庫的目錄,這裡只給一個最粗淺的例子:
arm-linux-gnueabihf-gcc -c hello.c -o hello.o
arm-linux-gnueabihf-ld hell.o -o hello
會發生undefined reference to puts 的錯誤,因為我們沒有連接所需要的 c library,另外我們也沒有指定程式的進入點為何,要能連結通過至少要:
arm-linux-gnueabihf-ld hell.o -o -lc -L/usr/arm-linux-gnueabihf/lib hello –entry main

當然這樣不代表可以執行,試著執行會發現dynamic linker 並沒有正確設定,除此之外還有各種runtime 的library 需要連結進去才會動;要看到可運作的呼叫方式,可以用gcc -v (-verbose) 來觀察。

-rpath-link 則只指定link 時搜尋shared library的路徑,這個路徑不會包含到executable 裡面,這個我一時之間給不出例子,但在這裡有找到,利用 -rpath-link=. 在編譯時指定在編譯時目錄尋找 shared library,另外關鍵的差別都寫在這幾句話了:
在 gcc 中使用 rpath/rpath-link 指定 shared library 搜尋路徑
在 -rpath-link 裡指定 "." (當前目錄) 還算正常,因為我們可以控制現在的工作目錄,
但是在 -rpath 裡指定 "." 就有點奇怪,因為你不知道別人會在哪個目錄執行你的程式...

使用-rpath-link 須知它也會蓋掉原本的搜尋路徑,因此用-rpath-link有個危害是:link time linker(ld)跟runtime linker (ld.so) 可能會使用不同的shared library,因為後者並沒有設定這個路徑,而是去預設的路徑尋找。

man ld 有列出 link time linker 的搜尋路徑,不過它未寫明有沒有先後關係,但以-rpath 來說顯然是有的:
https://my.oschina.net/shelllife/blog/115958
http://blog.lxgcc.net/?tag=dt_runpath
1. 透過 -rpath-link 指定的資料夾
2. 透過 -rpath 指定的路徑,如上所說,這跟 -rpath-link的差別是它會被包括到runtime linker中
3. 如果前兩者都沒有指定,尋找LD_RUN_PATH環境變數的值
4. 在SunOS,如果 -rpath 未指定,尋找 -L 選項設定的值
5. 尋找LD_LIBRARY_PATH 設定之路徑,這裡有句 For a native linker,不確定native linker是在指什麼特別的linker
6. For a native ELF linker,會查找現在引入的共享函式庫中設定的DT_RUNPATH 跟DT_RPATH section,後者的優先度高於前者(其實這就是在編譯shared library 時,以rpath 編進去的路徑:)
7. 預設路徑 /lib, /usr/lib
8. /etc/ld.so.conf 設定的路徑、

整體大概就是這樣,從編譯到執行,有許許多多的地方都能讓我們有各種方法對執行檔「上下其手」,去改動裡面連結、動態連結引入的東西
不過說老實話,其實這篇文…是呃…我忘了 qemu-arm 使用上就是用 -L 去指定library path 的產物,花了我一堆時間… 這真的是:Hello World 年年都會寫,每一年都有不同的體會呢XD。

2016年10月2日 星期日

使用網路線直接連線進行資料傳輸

這篇講的其實不是什麼大不了的事情
總之先前重灌電腦的時候,筆電裡面的平常放著不少動畫檔,為了求備份方便(時間與空間考量)我沒將它們備份到隨身硬碟上,而是把它們全部刪掉了。
反正我桌機上還有同樣的動畫資料夾,想說重灌過後再把它們複製一份就是了。

在複製一份的時候,試了一下用網路線的方式來傳檔案,查一些文件,有說2002 之後的網路卡,配合網路跳線都能連得上,也有相關文章在做這件事,於是就來試試看:
http://askubuntu.com/questions/22835/how-to-network-two-ubuntu-computers-using-ethernet-without-a-router
我有一台筆電跟一台桌電,大致的設定如下: 首先是在兩端都接上網路線,分別在筆電和桌電的eth0(也可能是其他名字,反正就乙太網路介面)上設定不同的static ip:
我筆電設定為:192.168.66.99/24 gateway:192.168.66.254
桌電設定為:192.168.66.100/24 gateway:192.168.66.254

在筆電上用ifconfig 就會看到介面ip 已設定:
enp2s0f0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
  inet 192.168.66.99 netmask 255.255.255.0 broadcast 192.168.66.255

連接上網路線的話,也會看到雙方的有線網路都顯示連線,這時候可以先用ping 去測一下,確定已連線
ping 192.168.66.100 -c 3
PING 192.168.66.100 (192.168.66.100) 56(84) bytes of data.
64 bytes from 192.168.66.100: icmp_seq=1 ttl=64 time=12.4 ms
64 bytes from 192.168.66.100: icmp_seq=2 ttl=64 time=7.74 ms
64 bytes from 192.168.66.100: icmp_seq=3 ttl=64 time=2.46 ms

--- 192.168.66.100 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 2.468/7.566/12.488/4.092 ms

接著在筆電上設定ssh,我在archlinux 上是用openSSH,相關的設定就參考: http://smalldd.pixnet.net/blog/post/24627330-arch-linux-%E5%AE%89%E8%A3%9D-openssh
最後開啟服務:
systemctl start sshd
就能透過該ip ,從桌電ssh 進入筆電了:
ssh username@192.168.66.99

確認連線之後,就能用任何你想得到的方法,透過網路備份,像什麼sftp, fillzilla 的,我最後同樣用rsync 來處理,這應該會要求目標電腦上也有安裝rsync,它會透過一個rsync --server來接收檔案:
rsync -av --progress -e ssh /media/data/Animate 192.168.66.99:/media/data
log 我就不貼了,反正就一直傳一直傳一直傳,均速大概是 50 MB/s,雖然連線資訊寫的速度是1000 Mb/s,不過也算相當快了,一集動畫100M 在1-2 秒間就會傳完,我自己用隨身硬碟的經驗是,雖然usb2.0 速度理論上有 30 MB/s,3.0 當然更快,但是實際在複製的時候,通常都達不到這麼快,甚至有時會降到3-5 MB/s,這點我懷疑有可能是我隨身硬碟用的格式是ntfs,而linux 上的driver ntfs-3g 有時效能不太好,相對來說網路傳輸反而穩定得多。
另一方面,隨身硬碟備份要來回複製跟貼上,不像rsync按下去就行了;相對網路讀寫能同時進行就可以一次複製完,外接硬碟再怎麼快,讀跟寫還是分開,外接硬碟要能相匹敵,讀寫均速至少要是網路速度的兩倍才行,問題是現下可能連網路的1/5 都不一定達得到,網路的優勢實在太過顯著。

附一張連線的實際照片,隱約可見的藍色線就是網路線,桌電遠端進入筆電,正在用rsync 上傳資料: