2016年12月31日 星期六

如何在Pull request 被merge/revert 後再送 pull request

自從換了智慧型手機,又搭配1.5G的網路之後,用手機上網的機會大增,在用Firefox 刷Facebook 的時候,很容易跑出廣告來,於是發想把「在Yahoo 台灣大殺四方驚動萬教的人生溫拿勝利組強者我同學 qcl 大神」所寫的神專案QClean 移植到Firefox Mobile 上面。

這部分後來還在努力中,目標是把QClean 的Add-On 移植到Mobile上,研究時,Mozilla 的網頁都跑出一直跑出提醒:Add-on 寫的 plugin 很快就會被淘汰,請改用 Web extension 來寫,想說就順便,在寫mobile version 前,先把本來用 add-on 寫的QClean for Firefox 搬移到 Web-extension 上面。
總體來說是沒什麼難處,把原本用 Web-extension 寫的QClean for Google Chrome,複製一份,然後把裡面web extension 的API 從 chrome 取代為 Mozilla 所用的 browser ,結果就差不多會動了,真的是超級狂,整體來說沒什麼工作。

到時再送出Pull Request 之後,發現寫的Makefile 裡面有typo,雖然緊急在Pull Request 下面留言<先不要merge>,但在作者看到之前已經被merge 了。
接下來出現一個很有趣的狀況,直接幫我把 typo 修好並commit 之後,對上游的master branch送出新的 Pull request,github 卻顯示這個 Pull Request 有衝突,沒辦法像之前一樣直接合併。

後來想了一下才發現問題在哪裡,目前repository 的狀況大概像這個樣子。


問題在於送出新的 Pull Request 的時候,github 會比較兩個 branch 最近的共同祖先,從那個 commit 開始算pull request,在上圖中會是那個”Add features 2” 的commit。
這時我們從”fix some typo” 送了PR,github 會比較這個新的commit ,跟revert 過後的commit 作比較,因為revert 這個commit 修改的檔案(等同我們Pull Request的修改只是剛好反向),兩者因此有衝突;同時,之前的 commit “Add features 1, 2” 都不會算在這個Pull Request 裡面。

要修正這個問題,我們必須讓 upstream 跟現在這個 develop branch完全沒有共同祖先,我所知的有兩個解法:
一個是從開始的master branch開一個新的branch newDev,然後用cherry-pick 把develop 上面的 commit 都搬到這個branch 上,再重送 Pull Request;就如這篇文中所示:
http://stackoverflow.com/questions/25484945/pull-request-merged-closed-then-reverted-now-cant-pull-the-branch-again

$ git checkout master
$ git checkout -b newDev
For every commit in develop branch do
$ git cherry-pick commit-hash

另外一個方法比較方便,直接在 develop branch 上面切換到 newDev branch,然後使用 interactive rebase ,然後把所有的commit 都重新commit 一篇,newDev 就會變成一個全新的分枝了:
$ git checkout develop
$ git checkout -b newDev
$ git rebase -i master
Mark every commits as reword (r) in interactive setting

修正完之後的 repository 大概會長這樣


這時候就能由 newDev branch 對上游的 master 送出Pull Request,把這次的修改都收進去了。

題外話:
寫這篇時發現了這個工具,滿好用的,可以輕鬆畫各種git 的圖,雖然說試用一下也發現不少bugs XD,不過這篇文中的圖都是用這個工具畫的:
https://github.com/nicoespeon/gitgraph.js

2016年12月30日 星期五

使用git hook在commit 前進行unittest

使用 git 做為版本控制系統已經有一段時間了,最近在寫Facebook message viewer 的時候,就想到能不能在本地端建一個 CI,每次 git commit 的時候都會時自動執行寫好的測試?
查了一下就查到這一個網頁,裡面有相關的教學:
https://www.atlassian.com/continuous-delivery/git-hooks-continuous-integration
在這裡記錄一下相關的設定:

Git hook 可以想成git 的plugin system,在某些特定的指令像是commit, merge的時候觸發一個script。
Hook 可分為 client side 和server side,又有 pre-hook 跟 post-hook 的分別,pre-hook 在指令執行前觸發,並可取消行為;post-hook 得是在指令結束後執行,它無法取消行為只能做其他自動化的設定。

網頁中有給一些使用範例
Client-side + Pre-hook: 檢查coding style
Client-side + Post-hook: 檢查repository 的狀態
Server-side + Pre-hook: 保護master
Server-side + Post-hook: broadcast 訊息

所有的hook 會放在.git/hook 資料夾裡面,在開啟專案時就有預設的一些範例,只要拿掉檔名的 .sample就能執行,它們是從 /usr/share/git-core/templates/hooks/ 複製而來。
望檔名生義,這些檔名大多直接了當的指名它們的功用;有些script 如 pre-commit 在回傳非零值時,能阻止git 接受這次commit;詳細的使用方法跟觸發時機請見參考資料。

這裡我想做的是用 Client-side + Pre-hook ,在每次commit 前都先跑過一次測試,如果測試不過就拒絕此次commit,所以我們用到的是 pre-commit。
首先先在project 裡面加上一個test.py,用來統整所有的test script,這樣就可以用 python test.py 跑過所有測試,可以先在command line 測一下:
python test.py || echo $?

pre-commit script 就很簡單:
#!/bin/sh

python test.py || exit 1
這個|| exit 1 是要確保pre-commit script 一定以錯誤結束,如果這個測試之後就沒有其它測試就不需要這段,因為script 會回傳最後一行command 的回傳值。
另外,如果不想在打下commit 的時候出現 unittest 的輸出,可以改寫成:
#!/bin/sh

python test.py 2>/dev/null
if [[ $? -ne 0 ]]; then
  echo "> Unit tests DID NOT pass !"
  exit 1
fi
注意後面的判斷跟echo 是必要的,否則python 輸出導向null之後,使用者打git commit 會變成完全沒有反應,這顯然不是個好狀況;完成之後,試著下git commit,就會發現test 不過,而不像正常流程一樣跳出commit message編輯器:
Garbage@GarbageLaptop $ git commit
.F
======================================================================
FAIL: test_zh_tw (__main__.REdictParseTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test.py", line 17, in test
assert(ans == parse)
AssertionError

----------------------------------------------------------------------
Ran 2 tests in 0.007s

FAILED (failures=1)

我的建議是在 test case 有一定規摸的時候,足以進行TDD 的時候才引入此流程,或者要先把test 裡都加上expected failure ,否則有test 不過就會讓git 根本commit 進不去,如果又因為過不了就每次都下git commit --no-verify,就失去TDD 的意義了
另外,請記得所有要跑的 script 一定要有執行權限,之前試了一段時間,每次commit 還是都commit 進去,後來發現是 pre-commit 沒有執行權限lol;hook 內的東西clone 的時候也不會複製到 client side,要把它們包到repository 裡面,再由使用者自己把script 加到.git/hook 裡。

這裡大致介紹 git hook 的簡單用法,網路上能輕易找到許多亂七八糟的script 來做各種事,例如用autopep8, cppcheck 檢查語法是否合標準,自動跑npm test 等等。

參考資料:
git hook in gitbook
https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
stackoverflow about pre-commit and unittest
http://stackoverflow.com/questions/2087216/commit-in-git-only-if-tests-pass

2016年12月13日 星期二

那些在工作上看到的各種東西

十二月小弟剛結束手邊的一個從七月延續到現在的案子,在這為期5個月的工作過程中,個人也有不少收穫,在這裡寫篇文章記錄一下。

溝通:

這次的案子是在不同的平台間移植,工作上沒遇到太多技術問題,在對方公司原有架構上,把平台相關的程式碼換成目標平台的程式即可,撰寫的程式碼不超過300行,只花大約一週半的時間,相關的程式碼就已經寫完了。
後來之所以會拖這麼久,一個原因是對方公司的程式碼還在不斷的更新,而我們必須將這些更新都整進我們的程式中,並且還要在機器上測試過。
這次真的是見證軟體工程,最重要的是團隊間的溝通,少了溝通再怎麼會寫也沒有用,整合上很多問題,諸如架構、測試、環境設定、測試硬體,都是由我的頭頂上司出面,多多溝通就解掉了,溝通的能力有時比技術能力還要重要。


架構:

整個過程中不斷撞牆的部分,在於整個Project 的開發過程中幾乎看不到架構設計的概念,感覺上就是東改一些西改一些,沒有一個系統性的規劃;甚至到現場準備產品發佈的前兩週,突然出現架構的大幅修正,我們整合的部分當然也要收進去,也因此在週末加了一天班(yay。
後來在言談中得知,對方公司的確沒有架構工程師這個職位,那個<架構的大幅修正>只是某個資深工程師這麼寫,其他人看看覺得不錯,其他專案也就跟著這麼做。
對方公司也算台灣有點大的硬體廠,軟體部門的狀況卻是如此,幾乎接近各程式設計師各自為政,知道了還是覺得有些心寒,不知道更上面的廠商狀況會不會好一點?

開發過程中,我自己也犯了類似的錯,應對方要求,我們這邊要整合兩個不同平台的程式碼,我竟然把所有的程式碼放在同一個檔案再用編譯macro 隔開,這當然不符合軟體工程的概念,只要加上更多平台,程式碼必定會增長到難以維護。
後來反省,的確是我的經驗不足才會這樣寫,真是幾年程式寫下來,軟體架構的概念還是沒練成,也難怪要成為架構工程師,都要先經過好幾年的軟體工程師的訓練。

測試:

這是專案中移植的目標平台,使用了一家中國廠商提供的SDK,無奈這家廠商有點兩光,通常會送有問題的程式碼過來,文件也寫得不清不……啊不對是通常沒什麼文件。
一開始在做整合的時候,因為sdk有問題的關係,我們的程式碼執行不定時間就會造成其他的process crash,從log 上來說就是系統上某process (而且每次的process 還不一定一樣)segmentation fault 了,跟這篇文章描述的有 87 % 像
當下無論是我們亦或對方公司覺得是自己的錯,但程式碼怎麼看都沒有問題,無奈它又是個不穩定的錯誤,在實際環境上測試非常花時間,在這個問題上面就花了近一個月的時間。
後來是利用一支之前用過的測試程式,可以用迴圈不斷執行核心功能,透過執行這條測試時也會crash,確認問題是出在核心功能而不是其他地方。

實際測試需要手動碰觸控螢幕,測試程式只需要用終端機執行,透過測試程式才能快速的註解掉可能有問題的部分,再測試會不會crash 來判定問題在哪,最後才定位出問題出在對方的sdk中,回報上去才拿到修正這個問題的新版sdk 。
覺得就算沒有要用到TDD,在專案中維持一些簡單的測試,光是簡單的回歸測試都能預防開發中可能的問題,就算真的出問題找不到在哪時,也能利用測試來定位錯誤。

能力:

這次發覺,自己的能力和對方公司的工程師比起來並沒有差太多,一些方面可能還超前對方。

很多工具例如git 的使用,如何透過Format patch同步兩個git repository 的狀態(當然對方公司內部是用svn ,所以這部分比較勝之不武),shell script 一些工具,我也比對方公司的工程師知道多一些些,工作的效率自然更高
我個人是認為,對方工程師雖然有較長的工作經驗,卻沒有-亦或沒有辦法-持續的和外圈技術圈交流,學習和知道一些最新的小工具:例如我在Code & Beer 聊天中知道的 autojump
一些無名的小工具能大幅提高生產力,卻未必能大紅大紫,是在小眾的技術圈中流傳,多多交流還是能有效擴展自己的見聞。

工具:

這次也學到,平時多多熟悉一些工具的使用,用到的時候可以發揮很大的助益
案子的結尾,有機會跟對方公司的工程師一起到客戶現場去準備產品上線,到現場是個很新奇的經驗,但工作環境實在不太好,程式碼不讓我們用版本控制就算了,連網路也不讓我們使用,遇到問題都只能問男人。
沒辦法查網路的時候真的是在比底力,平常有沒有熟悉shell 工具和程式語言這時立分高下。
我們又被現場公司雷:他們的環境裡有他們自己的設定,造成我們的code 放上去,clean 重build 的時候,把他們的設定移除,造成機器當掉,又是感覺起來是我們的程式有問題
其實只要把他們的設定再裝一次就解決掉了-但從沒人告訴我們,為了抓這個問題花了兩個工作天,大部分時間都在比較build log,在不同版本間進退,也因此用到不少 shell 工具。

我整理記憶上用得上的工具:
一套版本控制系統,至少要能做到在兩台機器上同步,我是用 git
至少一款diff 工具,我是用 vimdiff
檔案關鍵字的搜尋工具:shell grep
找檔案工具:shell find
簡單的 shell script,一旦發現到有重複性的工作,可以的話就盡快改寫成shell script,可以加速工作的效率。
上面這些除掉網路還要能熟練地使用。

總結上面五個,這五個月來接這案子的小小心得,記錄一下。


註:照片有不少篇幅都是用Google Doc, Voice Typing 的功能打的,覺得直接用講的雖然可以省下打字的時間,還有簡省手力,不過事後校稿也滿耗時的。
感覺用說的句子跟用手打出來的風格不太一樣,句子拉得比較長,贅字跟語助詞較多,如果是先寫在紙上再打到電腦又會是另一種風格,挺有趣的。

2016年11月26日 星期六

minecraft 創作:莒光樓

小弟是2198T 的退伍金門兵,當兵時放島休,自然會到金門島上四處走走,覺得莒光樓是個不錯的建築題材,退伍後花了一點時間在minecraft 裡面蓋了一座,斷斷續續的蓋,工期大概6個月。
在陰間沒相機,只能畫畫草圖,幸好google 找圖片就有很多人拍的相片可以看,庭園的部分用google 地圖也有實景可以「鍵盤旅遊」,很多細節都是這樣補齊的。

外觀好像沒什麼好說的,照著照片依比例選用磚塊、雕紋磚塊、玻璃等打造,室內擺設除了關鍵位子的盆景可以放之外,其他大部分都只有掛畫,都快變畫廊了。


二樓外部裝飾,同樣用Banner 掛在spruce fence來實作,缺點是超級浪費染料,染料也不像方塊挖一挖就有了,用掉一堆骨粉跟打一堆草,紅染料需求大產量又小,打草打到OOXX
後來才想到伺服器上其實有人蓋了鐵人農場,紅花都用不完_(:з」∠)_
其他部分:
藍旗 + (白染料 + 磚塊)+ (紅染料456)
藍旗 + (白染料 + 磚塊)+ (紅染料456)+ (白染料123)+(黃染料789)+(白染料468)

莒光樓的匾的部分,寫字真的就無法了:
白旗 + (藍染料741/963)+(藍染料123)+ (紅染料456)
藍旗 + (紅染料456)+(白染料258369)+(藍染料123)

屋頂的藍色後來發現是海中prismarine 的顏色最像,其他部分就用spruce 跟dark oak 來打造。

遠眺福建省……嗯……算了當我沒說,只會看到巨大農場……

天花板大概是最麻煩的東西了,原本的天花板是雕梁畫棟五彩繽紛,如照片所示,這在Minecraft 裡面根本做不到,只能用各種色彩的羊毛意思意思一下;只能說這中華民國美學你敢嘴?像素太低了沒辦法啦(yay

庭園的部分,沒什麼就是一直種樹,超級花時間;在實際莒光樓,沒人會去坐,不知道幹什麼的樓梯,都靠這個幫我算圓形怎麼蓋
http://donatstudios.com/PixelCircleGenerator
圍牆的部分就選用End Stone Brick ,顏色跟質料都很像:


花園蓋得比需期得還要大了一點,原本估計的正方形 72x72,後來蓋到快110x110,大超多owo,幸好server 上有安裝自己寫的fastbuild (自肥一下),在整地、填土跟挖建材上省了很多時間。
https://github.com/yodalee/FastBuild

來張全景:


建築物蓋完用shader 來一張是一定要的:
正面:
後面:
 

這個建案大概就到這邊了,還有很多細節部分無法盡善盡美Orz
金門其實是個漂亮的小島(只要不是當兵的話),歡迎大家去金門旅遊,小小建案希望大家喜歡owo

2016年11月21日 星期一

利用lxml 實作高效率的parser

最近實作facebook message viewer 的時候,需要去處理相當大的html 檔案,原始檔案大小約50 MB,beautify 之後會加到近80 MB。
實作上我用lxml 來實作,用了最基本的寫法,最直覺而簡單的寫法:
from lxml import etree
parser = etree.HTMLParser(encoding='UTF-8')
root = etree.parse("largefile.htm", parser)
問題是什麼呢,這樣lxml 一開場就把整個htm 給載到記憶體裡面,消耗一堆記憶體。
我們執行一下,看到的結果是這樣:
group count 525
parse 280895 entries
./parser.py   25.80s  user 0.21s system 99% cpu 26.017 total
  avg shared (code):         0 KB
  avg unshared (data/stack): 0 KB
  total (sum):               0 KB
  max memory:                710 KB
  page faults from disk:     0
  other page faults:         181328
710KB!根本記憶體不用錢一樣在浪費…,我把這個丟到網路服務上,光記憶體就害自己被砍了。

要打造節省記憶體的 xml parser,相關的資訊都來自這篇:
http://www.ibm.com/developerworks/xml/library/x-hiperfparse/

文中提供了兩種方式,一種是在parse 中指定我們的target class,我們要實作一個class 包含下列四個method:
1. start(self, tag, attrib): 有tag open的時候會呼叫這個method,這時只有tag name跟attribute 是可用的
2. end(self, tag): tag close 時呼叫,此時這個elem 的child, text都可以取用了
3. data(self, data): 在parser 收到這個tag 中的text child的時候,會將text 變為作為參數呼叫這個method
4. close: 當parse 結束時呼叫,這個method 應該回傳parse 的結果:
之後取用
results = etree.parse(target=TargetClass)
results 就會是close method return 的結果,這個方法不會消耗大量記憶體,不過它的問題是每一個遇到的tag 都會觸發一次事件,如果大檔中之有少量elements 是想剖析的,這個方法就會比較花時間;我的案例比較沒這個問題,因為我要剖析的內容幾乎橫跨整份文件。

另一個方法是用lxml 本身提供的 iterparse,可是傳入指定events的tuple跟一個有興趣的tag name的list,它就只會關注這些tags 的events:
context = etree.iterparse(infile, events=('end',), tag='div')
for events, element in context:
  print(elem.text)
文中並有說,iterparse 雖然不會把整個檔案載入,為了加速整份檔案可能被多次讀取的狀況,它會保留過去element 的reference,因此剖析會愈跑愈慢;解決方法是在iterparse 中,取完element 的資料就把它給刪除,同時還有element已經剖析過的sibiling,在iteration 最後加上這段:
element.clear()
while element.getprevious() is not None:
  del element.getparent()[0]
要注意的一個是,使用iterparse 的時候,要避免使用如getnext() 方法,我用這個方法,有機會在剖析的時候遇到它回傳None,應該是iterparse 在處理的時候,next element 還沒有被載入,因此getnext 還拿不到結果;從上面的end 事件來說,每個iteration,就只能取用element 裡面的元素。

改寫過之後的parse 的執行結果:
group count 525
parse 280895 entries
./parser.py   22.59s  user 0.07s system 99% cpu 22.669 total 
  avg shared (code):         0 KB
  avg unshared (data/stack): 0 KB
  total (sum):               0 KB
  max memory:                68 KB
  page faults from disk:     0
  other page faults:         21457
跟文中不同,因為我有興趣的tag 比例較高,執行時間沒有下降太多,但記憶體用量大幅下降。

另外文中也有提到其他技巧,像是不要用find, findall,它們會用XPath-like expression language called ElementPath。
lxml 提供iterchildren/iterdescendents 跟XPath,前兩者相較 ElementPath速度會更快一些;對於複雜的模式,XPath可以經過事先compile,比起每次呼叫元素的xpath() method,經過precompile 的XPath在頻繁取用下會快很多,兩種寫法分別是:
# non-precompile:
xpathContent = "//div[@class='contents']"
content = root.xpath(xpathContent)
# precompile:
xpathContent = etree.XPath("//div[@class='contents']")
content = xpathContent(root)
下面是我實測的結果,在都是完全載入記憶體下,有無precompile 的速度差接近兩倍,不過這也表示我用了iterparse 其實速度是下降的,因為我iterparse 其實是有使用precompile 的:
./parser.py 14.99s user 0.35s system 99% cpu 15.344 total
./parser.py 25.82s user 0.40s system 99% cpu 26.232 total

以上大概是使用lxml 時,一些減少記憶體用量的技巧,記錄一下希望對大家有幫助。

題外話:
在寫這篇的過程中,我發現Shell script 的time 其實是一個相當強大的指令,除了時間之外它還能記錄其他有用的數值,像是 memory usage, I/O, IPC call 等等的資訊。
我的time 是用zsh 下的版本,我也不確定它是哪裡來的,用whereis 找不到,用time --version 也看不到相關資訊,但man time 有相關的說明,總之copy and paste from stack overflow,把我的TIMEFMT 變數設定為:
export TIMEFMT='%J   %U  user %S system %P cpu %*E total'$' \
  avg shared (code):         %X KB'$'\
  avg unshared (data/stack): %D KB'$'\
  total (sum):               %K KB'$'\
  max memory:                %M KB'$'\
  page faults from disk:     %F'$'\
  other page faults:         %R'
其他的變數選項可以在man time 的format string 一節找到,這裡就不細講了,算是寫code 的一個小小收穫吧。

人生第一次參加 hackthon

故事是這樣的,最近手邊在寫一個網頁相關的facebook message viewer,然後…嗯…我根本不會寫網頁:前.端.超.難 。
正好這時候,學校的開源社在校內舉辦 hackthon,覺得這是一個大好機會,可以認識其它強者幫忙我作這個project,於是就報名了。

這是我第一次參加hackthon,想說先從比較小的 hackthon 開始,累積一點經驗,看有沒有機會參加玩真的hackthon,畢竟如隔壁棚qcl 大神都已經在參加真的黑客松,還遠征芬蘭大殺四方,嚇得老外屁滾尿流,在大神面前我根本只是業餘來亂的,不努力追一下怎麼行?

在hackthon 之前,我已經著手在這個project 一段時間,把後端的parser跟資料庫都寫完了,在hackthon上面只要開API跟寫前端就好了
第一天首先上台簡報一下自己想做的東西, 結果就徵到一名隊友,傳說中的昆立大神,雙方溝通一下API的格式;然後他埋頭寫了一下下,就用Vue.js 把前端整個寫完了,現在的user interface 坑全部都是隊友填的。

哇靠這到底是什麼神速,太強請受小弟一拜 m(_ _)m

然後…試著的所以我的Code 到GAE 上面,結果它用掉太多記憶體跟資料庫,記憶體是我parser 沒有寫好, 總之不付錢的話就沒辦法執行,啊我就是沒錢嘛咬我啊= =...
記憶體的問題,把parser改過之後就解掉了;資料庫真的就無法,資料量就是這麼多,最後放棄直接先睡了。

第二天為了demo ,決定直接把後端的parser 跟資料庫都從GAE 上請下來,整個改成local server,伺服器跟資料庫用Bottle.py 跟sqlite3重寫。
因為GAE 的 ndb 和 sqlite 的介面幾乎不同,平常也沒有在寫SQL 的相關的Code ,第二天重寫後端就用掉一整個早上,幸好到了下午2點左右,全部修完之後作品就會動了,上台demo 也得到不少迴響,第一次hackthon 就這樣堪稱順利的結束了。

我覺得參加hackthon,在參加前最好先有個題目的雛形,自己也可以先寫一些,這樣到了會場,一早 demo 過點子之後就能立即的尋求幫助和建議,遇到的問題也能問人,很快的得到答案。
到了hackthon 現場,就是專注在寫Code的上,在那個專注工作氣氛下,工作效率相當高,像我在第二天,很快的就把SQL相關的語法還有bottle.py 摸熟了,也為了趕出成品,第一天晚上,把我原本消耗超過600 MB memory 的parser重寫成只需要60 MB,算是把工作壓到兩個全天的成果。

當然現在的code 滿雜亂的,需要好好整理整理才行,最後的成品大概就是這樣,已經可以爬訊息跟去瀏覽特定時間的訊息:


這次hackthon 的氣氛算滿輕鬆,也有很多玩笑話,在這裡節錄一些語錄:

A: Steam 雖然出很多單機遊戲,但還是提供了成就系統……
B: Steam 根本是理財遊戲吧XD
C: 遊戲收集遊戲(XDDD

某:前後端分離?前端起手式:Bootstrap

某:Golang寫起來跟C 有 87 %像

2016年11月18日 星期五

使用GAE 回應fetch API

最近在寫GAE 上面的服務,把東西存進資料庫之後,開點API 讓使用者拿資料;這感覺就好像先蓋個大水槽裝滿水,然後在上面打洞讓水流出來讓使用者們喝(X
因為都2016 年了,被人說別再用XMLHttpRequest,趕快用fetch API 吧,就來研究一下GAE 要怎麼服務fetch API。

fetch API 用起來很直覺,對著一個網址 fetch 下去就是了,我開了個API 叫fetch,就是這麼直覺,回來的東西會丟進then裡面處理:
fetch("/fetch?type=data").then(funciton(response) {
  console.log(response);
}
其實概念是一樣的,fetch 就是去取某一個位址,所以我們可以開一個fetch 的handler,然後把對fetch 的請求都導到那邊去,然後建一個handler:
app = webapp2.WSGIApplication([
  ('/fetch', MessageFetchHandler),
], debug=True)

class MessageFetchHandler(webapp2.RequestHandler):
  def get(self):
    reqType = self.request.get("type") // reqType == "data“
剩下的就是handler的事了,想回什麼就回什麼,文字就文字,binary 就把binary讀出來寫回去,像是:
self.response.write("response message") # return text
with open("img/servo.png") as f:
  img_content = f.read()
self.response.content_type = 'image.png'
self.response.write(img_content) # return binary
一開始的時候,因為我有用GAE 的user API,在寫fetch 的時候,我的fetch API都會被redirect 到login 的網址,後來發現fetch API 預設不會包含必要的資訊,以致GAE 查不到已登入的資訊,要修好就是在fetch 時多加一點東西
fetch 提供了 Headers(), Request()函式來建立fetch,fetch 並可代入init 參數做更多設定。
例如我這邊的狀況,使用自定義的Init 參數,設定credentials 為same-origin,GAE 就不會再把我們導向login了。
另外也可以設定如mode, cache 等設定,比使用XMLHttpRequest來得彈性。Header則可建立request的header,例如content-type,詳細的內容可以參考後面附的參考文件。
var userInit = {
  method: "GET",
  credentials: "same-origin" };
var userRequest = new Request('/fetch?type=user', userInit);
fetch(userRequest, userInit).then(function(response) {
  if (!response.ok) {
    console.log(response);
  }
}

以上大體就是關於GAE 回應fetch API 的寫法,再來就是寫各種API 了。

相關資料:
https://developer.mozilla.org/zh-TW/docs/Web/API/GlobalFetch/fetch
https://developer.mozilla.org/zh-TW/docs/HTTP/Access_control_CORS
https://fetch.spec.whatwg.org/#concept-request-credentials-mode

2016年11月9日 星期三

整理一些增進效率的shell 工具

最近整理了一下自己工作上有在用的一些shell 工具或指令,在這裡分享一下,也許能讓大家工作更有效率。

shell:

沒錯不要懷疑,就是shell,如下這篇shell 本身就有不少快捷鍵可用:
https://blog.longwin.com.tw/2006/09/bash_hot_key_2006/
不過其實不少快捷鍵都有更好記的單鍵可代替,例如到行首尾可以用Home 跟End,平常比較常會用到的其實只有:

  • Ctrl + W 刪除一個詞
  • Ctrl + R 後打關鍵字,可以找上一個拿關鍵字的命令,持續Ctrl + R 持續找,光是用Ctrl + R 就海放只用↑↓在找指令的了(X,不過用了下面的fzf ,它會用fzf 代替單純的 Ctrl + R搜尋
  • Ctrl + L 可以清除畫面,跟下clear 是一樣的效果,而且clear 打起來超級快,最近還在習慣用Ctrl+L;我覺得這個重要的是在gdb 裡面沒有clear 指令,要用Ctrl+L來清除畫面。

autojump

本指令感謝AZ 大神的推薦,一個記錄你cd 過的地方,以後可以直接jump 過去的指令
https://github.com/wting/autojump
主流的linux 都有套件支援了,安裝後在shellrc 裡面source 它即可
if [[ -e /usr/share/autojump/autojump.zsh ]]; then
  source /usr/share/autojump/autojump.zsh
fi
接著它需要一點時間train 一下,平常cd 的時候它就會把cd 的資料夾記下來,之後就能用
j foo
jo foo
前者cd到名稱含有或近似foo 的資料夾,後者直接開視窗的file manager檢視該資料夾。
也可以下多個參數,例如:
victim/alice/data
victim/bob/data
如果只下j data 可能會跳去alice的資料,但下了 j bob data 就能跳到後者
我記得我在寫minecraft plugin 的時候,差點沒被java 氣死,為啥source code 一定要藏這麼多層勒,光cd ../../../ 就累死了,那時沒用autojump 深感相見恨晚;後來寫一些包含不少小專案,還要source sdk 的android project ,這個指令也幫了大忙。

fzf

本指令感謝士銘大神推薦,fzf 是一個模糊搜尋工具,讓你列出很多的東西,然後模糊查找目標:
https://github.com/junegunn/fzf
目前還在習慣中,安裝的話透過它github 頁面上的script 安裝即可:
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install
archlinux 則有收到repository 中,安裝repository 之後在shellrc 裡面加上
source /usr/share/fzf/completion.zsh
source /usr/share/fzf/key-bindings.zsh
最一般的使用方式,是把其他會吐出一堆東西的指令當作fzf 指令的輸入,fzf 會把輸出全列出來,然後讓你打字進行模糊搜尋,或者用上下選取,用一下就覺得快若閃電,絲毫沒有延遲,例如:
locate servo | fzf
find -type f | fzf
加了key-bindings.zsh 之後就連上面的Ctrl+R 也會變成fzf 模式,潮爽der;fzf 也有跟vim 綁定,不過這部分我沒試用,暫時先持保留意見。

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 上傳資料: 

2016年9月27日 星期二

筆電重灌全記錄

最近家目錄 100 GB 的硬碟被我塞滿了。

雖然後來刪掉一些東西,例如之前玩一些<資料>分析contest 的資料,還有之前玩虛擬貨幣primecoin載了整個區塊鏈,也全刪掉;清了大概30 GB 的空間出來,不過覺得還是不夠好,畢竟現在的電腦從2013年4月用到現在,硬碟分割不是很好,有太多長期留下來的東西,設定檔愈來愈亂,空間分配也不是很好;覺得是時候重灌系統調整體質,以迎接下個十年,逆風高灰(X。

重灌系統,麻煩的就是要先備份系統,我選擇的是把家目錄備份出來,備份使用rsync,用rsync 的好處是快,而且可以增量備份,隨時加一點東西上去也OK,例如週末有空的時候做全資料夾備份,直到重灌前一刻再增量備份:
http://newsletter.ascc.sinica.edu.tw/news/read_news.php?nid=1742
rsync -av --exclude=”.*” --exclude=”.*/” src_dir dest_dir
不過如果你的資料夾裡有 git 的話,就要小心,上面這個不會備份隱藏的資料夾,要把那個exclude 拿掉。

硬碟分割的部分,這次決定採用激進的分割方式,雖然電腦上會保留Windows…用來……打遊戲,不過這次不分割所謂的D槽給Windows,資料分割區從NTFS 改為ext4。

以下是這次的分割狀況,沒特別註明就是GB:
  • /boot 200 MB
  • windows 150 之前的windows 被塞了不少acer 預設的程式在裡面,這回全部清得乾乾淨淨,給150 應該很夠用了。
  • Linux root 75,現在是給50 GB,我也從來沒想過它會裝滿,直到有一天要燒FPGA裝了Xilinx Vivado,瞬間root 剩下零頭,每回開機都會警告空間不足…
  • Linux /var 30 曾看archlinux 的wiki 頁面分12 G 給它,結果安裝一堆套件之後立刻就滿了
  • Linux /home 200 一口氣倍增到200 G,應該絕對夠用了吧
  • Linux /data 硬碟剩下的空間大概500 G就全分給data,之前用NTFS 現在改用ext4,用來放一些像是東方啦動畫之類的東西。

另外備份所有已裝套件:archlinux 使用pacman -Q > allpackage 或 yaourt -Q > allpackage 把所有安裝的套件包都列出來

然後就可以放心的把磁碟區全部clean 掉,然後先裝windows,再裝我的不敗開發環境:archlinux;颱風天一個人躲在房間,任窗外狂風暴雨,我的電腦也是狂風暴雨XD。不過我太久沒裝了,還是參考一下強者我同學的安裝記錄,大體上沒什麼不同,我是選用mate 作為我的桌面環境:
http://johnjohnlys.blogspot.tw/2016/06/archlinux.html
小試一下,我的archlinux就回來啦,重裝套件的部分,就用剛剛上面的reinstall 文件,搭配vimdiff,很快就能把套件全部裝回來了…才怪。

老實說,因為選用的是archlinux 的關係,有不少比較不那麼正式卻又很常用到的東西,都要從AUR 去裝;而AUR 跟pacman相比就是很吃網路跟編譯時間,結果我整個裝下來,花的時間不比windows 的少,雖然說爽度還是有差,AUR 只要yaourt 裝好,指令打下去就是了;windows 還要自己去找安裝檔,用滑鼠一個一個安裝,只能說重灌就是麻煩……希望這次轉骨完之後,下次可以等到很久很久之後再重灌了。

ps. 後來發現在 /etc/makepkg 裡面的 -j 選項沒有開,等於是一直用單核心編譯,不知道速度上能差到多少

2016年9月21日 星期三

國際觀大補帖

最近某位教授覺得大家竟然不知道希特勒是誰,覺得大家都沒國際觀,台灣未來一定超沒競爭力,大家都只能領22K。
他還提供了一個網站<國際觀檢測網>:「每次點進去都會出現10道題目,例如尼克森、翁山蘇姬是哪國人、做過什麼事,並提供詳解」,連結在此:
http://doc.boyo.org.tw/gp/

老實說我覺得這樣太沒效率了,一次只有10題不小心還會重複,豈不是浪費應考人的時間!?因此我就做了一份增進國際觀大補帖,把網站的題庫全部拉出來,讓大家題目可以一看再看,準備完了再上線考試,就像考駕照一樣。
相信有了這份大補帖,一定能幫大家有效的提升國際觀,讓台灣人才競爭力逆風高灰超英趕美。

概念其實不難,就跟之前做的東西一樣,我是用python2.7 來實作,著眼點是它的lxml 套件,不過python2.7 對utf-8 的支援沒那麼好,script 開始前要把default encoding 設定為utf-8,不然常會印出亂碼。
reload(sys)
sys.setdefaultencoding('utf-8')
首先用urllib2把網頁抓下來,因為這個網頁會cache 要求過的內容,因此要用這樣來開啟網頁,讓HTTP proxy server不要cache 回傳的資料。
request = urllib2.Request(TARGET)
request.add_header("Pragma", "no-cache")
response = urllib2.build_opener().open(request)
再來用lxml把裡面問題和答案的部分給挖出來,填到一個python dictionary 即可,每抓一次都會比對不要抓到重複的東西。
因為爬蟲有時候會遇到網頁錯誤,因此一定要把爬過的東西先存下來,不然跑一跑壞掉了,重跑又要重抓資料,會哭的;幸好在python 上面有pickle 的支援,serialize 資料算相當方便,只要在開始的時候,先用pickle 讀入一個字典檔,沒有的話就回一個空的:
def openPickle():
  try:
    return pickle.load(open("global", "rb"))
  except (EOFError, IOError):
    return {}
然後存檔也很簡單,一行就解決了:
pickle.dump(data, open("global", "wb"))
再來就是一直跑一直跑一直跑,題目就會如洪水般湧進來,老實說挖出來的東西比我想得還要多…好多,wc 一下有1500多行,估計大約就是750 題左右吧。
Well 至於我爬出來的東西,我就無償公開好了,反正這種東西你它的網站一直按F5 也可以抓全,我只是請機器人幫我抓,原始碼跟大補帖放在這裡:
原始碼:https://github.com/yodalee/globalizaion
大補帖:https://github.com/yodalee/globalizaion/blob/master/global
其實我對這個題…沒什麼意見啦,不過我還是不樂見有人真的把這個拿來背(是…應該不會有人這麼蠢吧…應該啦……),畢竟與其死背希特勒是哪國人,還不如去了解他崛起的背景,與其多看大補帖還不如念念「希特勒回來了」。

不過自從我把大補帖公平出來之後,受到各方熱烈的回應,在此僅節錄幾則:
使用者評價一:自從用了國際觀大補帖,頭腦就靈光了很多,考試都考87分呢!
使用者評價二:上學的時候我國際相關的東西都答不出來,自從用國際觀大補帖,成績突飛猛進,現在已經準備要考托福跟日檢N1了。
使用者評價三:原本人生一片黑暗,直到遇到國際觀大補帖,和家人關係變好不說,上周不但交到了女友,老闆還幫我加薪25元呢。
使用者評價四:看國際觀大補帖,平常和朋友聊天信手拈來就是一堆國際知識,朋友們都改用欽佩的眼光看我,覺得我人生從此都不一樣了。
使用者評價五:之前男朋友都看不起我,自從把國際觀大補帖印下來帶在身邊,除了平時閱讀增進國際知識,男朋友欺負我的時候還可以用厚重的大補帖打他的臉,被沉重的歷史感打到一定很痛!
使用者評價六:難得一見的好書,我把每一題都記得滾瓜爛熟,上周考汽車駕照一次就通過了,謝謝你,國際觀大補帖。

Ps. 其實這篇寫程式的時間遠不及寫上面那堆嘴砲文的時間lol

2016年9月20日 星期二

在Archlinux 安裝arduino 開發環境

最近開始接觸arduino開發板
在archlinux 上開發arduino 也相當簡單,照著wiki上面做便是:
https://wiki.archlinux.org/index.php/arduino
先用yaourt 裝arduino
然後把一般使用者加到uucp 跟lock 群組裡面,讓使用者可以取用serial 的權限:
# gpasswd -a $USER uucp
# gpasswd -a $USER lock
再來登出登入就設定完了,打開arduino 就有介面可以使用了。

另外有遇到一個問題,因為手上拿到的arduino 使用的是Intel Edison的板子,因此打開arduino 之後,要用它的board manager 或中譯板子管理員安裝Intel Edison 的板子。
而安裝時會出問題,大致的錯誤訊息會類似這樣:
Setting it up...find: invalid mode ‘+111’
/tmp/tmp.ioGoYaYhZu/relocate_sdk.sh /home/yodalee/.arduino15/packages/Intel/tools/core2-32-poky-linux/1.6.2+1.0/i686/relocate_sdk.sh
SDK could not be set up. Relocate script failed. Abort!

然後如果不理它直接去arduino 裡面編譯看看的話,會出現類似這樣的錯誤:
$HOME/.arduino15/packages/Intel/tools/core2-32-poky-linux/1.6.2+1.0/i686/sysroots/x86_64-pokysdk-linux/usr/bin/i586-poky-linux/i586-poky-linux-g++: No such file or directory

總之在家目錄的超深的地方,某個g++ 檔案找不到,但如果你去它所寫的路徑看一下,它確確實實的就在那裡,直接在shell 下執行會得到一樣的結果。
第一次遇到這個問題是在管工作站的時候,某個EDA 公司給的執行檔,也是怎麼跑都跑不起來,當下完全就是見鬼了,檔案就在那裡linux 你是跟我開玩笑嗎?後來那個EDA 好像是給錯版本,忘了是32 bits 的工作站給了64 bits 執行檔,還是64 bits 工作站給了32 bits 執行檔,總之後來強者我同事解掉了。

解法一(不完整,請用解法二)
如果用file 去看那個執行檔就會看出一點端倪,裡面有這句:
interpreter /opt/poky-edison/1.6.1/sysroots/core2-32-poky-linux/lib/ld-linux.so.2
意即這個執行檔需要這個dynamic linker來執行,而安裝時,這個路徑並沒弄好,因為在這個安裝包($HOME/.arduino/packages/Intel/tools/core2-32-poky-linux/1.6.2+1.0/i686)下面有找到這個檔案sysroots/core2-32-poky-linux/lib/ld-linux.so.2
因此解法就是在opt下面,用softlink連結1.6.1到i686 下面,編譯就能跑得起來了。
上述工作站的問題也是類似,大概是忘了裝32/64 bits 的libc,在ubuntu的話應該要補裝libc6-i386 lib32stdc++6 等。

解法二:
這個才是正常的解法,在i686 資料夾下,會找到安裝時執行的script:install_script.sh,實際執行的話也會報錯,它在呼叫relocate_sdk.sh裡面執行relocate_sdk.py後會出錯,原因是給relocate_sdk.py 參數太少了。
後來查到真正的原因跟解法:
http://askubuntu.com/questions/764715/unable-to-install-intel-i586-library-intel-galileo-gen-2-in-arduino-ide-on-ubu

把install_script.sh 裡面的
executable_files=$($SUDO_EXEC find "$native_sysroot" -type f -perm +111 -exec printf "\"%s\" " {} \; )
的+111 改成 /111就行了,就是find 出錯導致executable_files 沒找到檔案,relocate_sdk.sh 就沒把該修改interpreter 的執行檔傳給relocate_sdk.py,最後就變成interpreter 還指向/opt 的狀況。

確實去找manpage find,會找到這行
-perm +mode
This is no longer supported (and has been deprecated since 2005). Use -perm /mode instead.
所以大概是這個script 太久沒維護了吧。

後話:
現在筆電的archlinux 自從我2013/4/24 10:26:04 灌好之後,經歷了無數程式開發,無論系統程式、機器學習、Android、Arduino,從來沒讓我失望過,archlinux果然是不敗開發神機。
至於我怎麼知道這個時間,是參考這個:
http://unix.stackexchange.com/questions/9971/how-do-i-find-how-long-ago-a-linux-system-was-installed
用tune2fs 去偵測例如root 或boot 分割區的創立時間,大致就是系統灌好的時間了。

2016年9月16日 星期五

不為人知的gdb 使用方式系統-gdb pretty printer auto load

前言:
最近因為jserv 大神的關係,看了下面這部Become a GDB Power User
https://www.youtube.com/watch?v=713ay4bZUrw
覺得裡面還不少生猛的用法之前都不會用,決定把它整理一下,寫個系列文:

上回我們提到了gdb 的pretty printer,現在我們就來看個範例:寫Rust 用的Rust-gdb。

其實Rust-gdb 跟gdb 本質上沒什麼不同,裡面將python pretty printer 的路徑加到gdb source 的路徑,然後執行gdb…
gdb -d "$GDB_PYTHON_MODULE_DIRECTORY" \
-iex "add-auto-load-safe-path $GDB_PYTHON_MODULE_DIRECTORY" \
"$@"
這樣感覺什麼都沒加進去呀,事實上,rust 是透過比較隱晦的機制,在編譯的時候將要引入的資料插在執行檔的 .debug_gdb_scripts 裡面,這個section 會包含null-terminated 的條目(entry)指明gdb 要載入哪些script,每個script 的開頭會有一個byte 指明這是哪一種entry,拿我之前寫的racer 來試試,把它的section 印出來:
$ readelf -S racer
[17] .debug_gdb_script PROGBITS         0000000000743af8  00743af8
     0000000000000022  0000000000000000 AMS       0     0     1
然後我們把這個區段給印出來:
$ readelf -x .debug_gdb_scripts racer
「.debug_gdb_scripts」區段的十六進位傾印:
0x00743af8 01676462 5f6c6f61 645f7275 73745f70 .gdb_load_rust_p
0x00743b08 72657474 795f7072 696e7465 72732e70 retty_printers.p
0x00743b18 7900                                y.

開頭的01,指明它是在script in python file,內容則指明要載入gdb_load_rust_pretty_printers.py,一般我覺得是用不著這麼搞工啦,畢竟要用這招就要在編譯器上面動手腳,大概在自幹語言的時候用上這招比較方便,相關文件可見這兩個連結:
https://sourceware.org/gdb/onlinedocs/gdb/Auto_002dloading-extensions.html#Auto_002dloading-extensions
https://sourceware.org/gdb/onlinedocs/gdb/dotdebug_005fgdb_005fscripts-section.html#dotdebug_005fgdb_005fscripts-section

rust-gdb 所用的方法,如這份文件str_lookup_function之後的部分所示:
https://sourceware.org/gdb/onlinedocs/gdb/Writing-a-Pretty_002dPrinter.html#Writing-a-Pretty_002dPrinter

把pretty printer 寫完之後,首先要先寫一個gdb 的lookup_function,這個function 會負責回傳適當的pretty printer,不然就回傳None。
文件建議這個pretty printer 放到單獨的python package 裡面,如果這個pretty_printer是針對某個函式庫寫的,那更建議package 的名字要加上函式庫的版本號,這樣gdb 能載入不同版本的pretty printer;最後在auto load 的script 中import 這個pretty printer,用gdb.pretty_printers.append ,把這個lookup_function插入gdb的objfile 中即可。

Rust-gdb 就是這樣做的:
auto load 的script gdb_load_rust_pretty_printers.py 中,引入gdb_rust_pretty_printing,然後呼叫它的register_printers,把rust_pretty_printer_lookup_function插入目前的objfile 中。
一個lookup_function 會接受一個gdb Value,我們可以從中取得它的type的tag,從tag 中辨別它是什麼型別的資料,藉此回傳適當的pretty printer。

我用之前的QString 做了個簡單的範例,首先我們要改造一下我們的qstring.py,加上lookup function 跟register_printer,lookup function 在型別為QString 時會將這個值傳入我們寫的QStringPrinter,並回傳它:
def qstring_lookup_function(val):
  lookup_tag = val.type.tag
  if lookup_tag is None:
    return None
  regex = re.compile("^QString$")
  if regex.match(lookup_tag):
    return QStringPrinter(val)
  return None

def register_printers(objfile):
  objfile.pretty_printers.append(qstring_lookup_function)
接著我們寫一個autoload script : hello-gdb.py,雖然我們用到了gdb,但gdb 內的python 會自動引入這個gdb 這個module,所以不引入也沒關係:
import qstring
qstring.register_printers(gdb.current_objfile())
另外要注意的是,一定要使用自動載入的方式,因為gdb.current_objfile() 只有自動載入的時候才會設定為當前的objfile。
之所以叫hello-gdb.py,是因為我們採用另一個機制:objfile-gdb.py的檔案會被自動載入,我執行檔名叫hello ,hello-gdb.py會在gdb 開啟時自動載入,其他有用到的dynamic library ,檔名叫libXXX-gdb.py也可以用類似的方法載入。
https://sourceware.org/gdb/onlinedocs/gdb/objfile_002dgdbdotext-file.html#objfile_002dgdbdotext-file

當然故事不是這樣就完了,首先hello-gdb.py會用到qstring.py,所以我們必須把qstring的位置加到PYTHONPATH 變數之中;第二gdb 不會隨意自動載入第三方的script,以免造成危害(雖然說到底能有啥危害我也不知道……),因此我們必須在gdb 開始自動載入script 前,把這個script 加到安全名單之中;因此,我們的gdb 呼叫會變成下列兩者擇一,也就跟rust-gdb 相差無幾了:
PYTHONPATH="$PYTHONPATH:/tmp" gdb -iex "set auto-load safe-path /tmp" hello
PYTHONPATH="$PYTHONPATH:/tmp" gdb -iex "add-auto-load-safe-path /tmp/hello-gdb.py" hello

大概的故事就是這樣了,Rust-gdb 當然是lookup function 那邊搞了很多,把型別什麼共同的部分獨立出來,以便支援gdb 跟lldb。
一般的lookup function 就不用寫這麼多了。
又說回來,要寫到大型的lookup function 應該也只有開發語言跟函式庫的時候需要,一般人更用不到這個功能,所以這篇比起上篇其實更是廢文一篇(蓋章,感謝大家又貢獻兩分鐘的時間給小弟的blog (炸。

2016年9月14日 星期三

不為人知的gdb 使用方式系統-gdb pretty printer

前言:
最近因為jserv 大神的關係,看了下面這部Become a GDB Power User
https://www.youtube.com/watch?v=713ay4bZUrw
覺得裡面還不少生猛的用法之前都不會用,決定把它整理一下,寫個系列文。

這篇來介紹 gdb 的pretty printer:
https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing.html#Pretty-Printing

假設我們有一個客製的struct 或是class,例如Qt4 的QString,如果我們用原本gdb 的print 去印出QString的話:
QString s("Hello World");
(gdb) p s
$2 = {static null = {<No data fields>}, static shared_null = {ref = {_q_value = 2}, alloc = 0, size = 0, data = 0x7ffff7dd70fa <QString::shared_null+26>, clean = 0,
simpletext = 0, righttoleft = 0, asciiCache = 0, capacity = 0, reserved = 0, array = {0}}, static shared_empty = {ref = {_q_value = 1}, alloc = 0, size = 0,
data = 0x7ffff7dd70da <QString::shared_empty+26>, clean = 0, simpletext = 0, righttoleft = 0, asciiCache = 0, capacity = 0, reserved = 0, array = {0}},
d = 0x603dc0, static codecForCStrings = 0x0}

因為QString 是個結構的關係,我們無法單純印個char array ,反而會印出裡面所有的資訊,它真的資訊是存在那個 d 裡面,要真的印字串就要從那個d 裡面去印。
之前在寫Qt 專案的時候有遇到類似的問題,那時有查到一個printqstring 的自訂函式,把下面這段加到 ~/.gdbinit 裡面:
define printqstring
  printf "(QString)0x%x (length=%i): \"",&$arg0,$arg0.d->size
  set $i=0
  while $i < $arg0.d->size
    set $c=$arg0.d->data[$i++]
    if $c < 32 || $c > 127 
      printf "\\u0x%04x", $c
    else
      printf "%c", (char)$c
    end 
  end
  printf "\"\n"
end

就可以在debug 時使用printqstring來印出QString
(gdb) printqstring s
(QString)0xffffe650 (length=12): "Hello World!"

當然我們可以用gdb 的pretty printer 來做到類似的事,而且不需要自訂函式,單純用p s 也能做到一樣的效果。
gdb v7 開始加入對python的支援,可以讓我們透過python和gdb 互動,在python 之中import gdb 之後,就可以利用一連串定義完善的介面去跟gdb 互動,詳細上用法還不少,這裡我們只先試著寫個pretty_printer。
Python 的pretty printer 就是實作一個class,至少要實作__init__跟to_string() 兩個介面,其他的函式可以參考:
https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing-API.html#Pretty-Printing-API
之後使用gdb.printing的RegexpCollectionPrettyPrinter,產生一個叫QString 的printer,利用add_printer,只要型態名稱符合^QString$ 的物件,就用QStringPrinter來處理,最後用regeister_pretty_printer把這個RegexpCollectionPrettyPrinter 塞進去:
# qstring.py
import gdb.printing

class QStringPrinter(object):
  def __init__(self, val):
    self.val = val
  def to_string(self):
    return "I'm Qstring"
  def display_hint(self):
    return 'string'

pp = gdb.printing.RegexpCollectionPrettyPrinter('QString')
pp.add_printer('QString', '^QString$', QStringPrinter)
gdb.printing.register_pretty_printer(gdb.current_objfile(), pp)
進到gdb 之後,首先source qstring.py ,接著去print 一個QString 的話,gdb 就會呼叫我們寫的QStringPrinter 的to_string 來處理,就會得到垃圾訊息:I'm QString。

to_string 回傳的規則如下:
python 的integer, float, boolean, string 都是gdb 可處理的(可轉換為gdb.Value),會由gdb 直接印出;回傳None的話則不會印東西。
傳回其他的gdb.Value,那gdb 會接續處理這個value,處理中也會呼叫其他註冊的pretty printer;真的無法處理,gdb就會丟出exception。
另外我們可以實作display_hint 的介面,告知gdb 要如何處理這個值的輸出,這會影響到gdb 如何接續處理這個值;這裡我們表示這個輸出是個字串。

上面只是舉例,要印出同樣的結果,我們的to_string 要稍微改寫:
addr = self.val.address
size = self.val['d']['size']
i = 0 
content = []
while i < length:
  c = self.val['d']['data'][i]
  if c > 127:
    content.append("\\u%x" % (int(c)))
  else:
    content.append(chr(c))
  i = i + 1 
return '(QString)%s (length=%d): "%s"' % (addr, size, "".join(content))
當然我在查這個的時候,發現有另一個人的實作生猛得多:
http://nikosams.blogspot.tw/2009/10/gdb-qt-pretty-printers.html
dataAsCharPointer = self.val['d']['data'].cast(gdb.lookup_type("char").pointer())
content = dataAsCharPointer.string(encoding='utf-16', length=size*2)
它直接用cast 得到一個新的gdb.Value(),型態為char pointer;再呼叫string()方法用utf-16 的方式encoding,就把字串給拿出來了,更多用法可以參考文件,這裡暫時不詳細說明:
https://sourceware.org/gdb/onlinedocs/gdb/Values-From-Inferior.html

不過,這樣的pretty printer 其實有個問題的,因為它寫死了一定會去讀取self.val['d']裡面的值,但這個值未必是有初始化的,這時使用pretty printer 就會出錯:
<error reading variable: Cannot access memory at address 0xa>

其實要用pretty printer 的話,在.gdbinit 裡面加上 set print pretty on 就是個不錯的開始,光這樣就能讓一些輸出漂亮很多(其實也就是塞在一行裡跟自動換行的差別XDDD),相對開了這個print 也會佔掉比較多行數。
一般除非是客製的資料結構又有大量debug 的需求,才會需要客製化pretty printer,像是gdb 內應該已內建C++ std 的pretty printer,Qt 的開發套件應該也有提供Qt 的;寫rust時所用的rust-gdb也只是對gdb 進行擴展,在開始時先引入設定檔,裡面也有Rust 的pretty printer。
# Run GDB with the additional arguments that load the pretty printers
PYTHONPATH="$PYTHONPATH:$GDB_PYTHON_MODULE_DIRECTORY" gdb \
  -d "$GDB_PYTHON_MODULE_DIRECTORY" \
  -iex "add-auto-load-safe-path $GDB_PYTHON_MODULE_DIRECTORY" \
  "$@"

但一般人似乎不太用得到這個功能,所以這篇其實是廢文一篇(蓋章,感謝大家貢獻兩分鐘的時間給小弟的blog (炸。

2016年9月5日 星期一

使用 ctags 增強vim 的功能

vim 搭配 ctags 是一款生猛的工具組,可以快速在trace 專案尋找定義和實作,大幅增加vim 瀏覽程式碼的效率。
安裝方法,首先要安裝ctags ,archlinux 的話是ctags,ubuntu 的話是exuberant-ctags,其他的就…自己找。
在專案的根目錄中使用:
ctags -R

產生tags 檔,在瀏覽原始碼的時候,就能用:
Ctrl + ] 跳到該名稱的定義
Ctrl + t 跳回到剛離開的位置

另外在搜尋的時候,找到了一個taglist 的替代品tagbar,可以使用Vundle 安裝:
https://github.com/majutsushi/tagbar
在vimrc 裡面加上:
Plugin 'majutsushi/tagbar'
map <F12> :TagbarToggle<CR>
就能用F12 開關Tagbar 的視窗,第一眼看來還不錯,比taglist 還要漂亮跟清楚很多,據說相對taglist 對Cpp 的支援也更好;雖然以個人之前的經驗,taglist 沒有想像中的好用…也可能是我不會用吧。

有關Vundle 的相關資訊,請參考之前的文章:
http://yodalee.blogspot.tw/2015/03/vundle-vim.html

其實ctags 上使用一直有個問題,導致我之前都不太使用它:一般稍大一點專案都不會是一層,而是程式碼分到樹狀的資料夾中,用ctags -R 只會在根目錄上產生tags 檔,而通常寫code 的時候都不會在根目標上作業,否則要開檔的時候光打目錄就飽了;但如此一來vim 就抓不到tags 檔了。
後來查了一下vim wiki,發現只需要在.vimrc 裡面加上一行文就可以解決這個問題……
http://vim.wikia.com/wiki/Single_tags_file_for_a_source_tree
set tags=tags;
這樣vim 就會一路往上找tags 檔。

ps. 這樣就能修掉也太詭異了吧…

2016年8月13日 星期六

新Top 介紹

top 一直是command line 上相當重要的工具,一打開就列出幾個高消耗的行程,等著kill (X,有時候不小心把電腦給炸掉的時候還滿好用的。
不過不知啥時更新後,新版的top 介面大改之後,我就不會用了OAO

最近決定把top 的用法給好好研究一遍,其實也就是把manpage 看過一遍啦,這裡做點筆記:

進到top 後,主要分成
1) Summary Area; 
2) Fields/Columns Header; 
3) Task Area

幾個沒變的命令:
h是help,q是quit沒變。
上下左右,page up/down, home, end,調位置

Summary Area 沒啥好說,顯示uptime, load average, Task 和CPU 的狀態
CPU 的狀態可用 t 來toggle顯示方式,可以關掉CPU 顯示,下面資料全部顯示,或者只顯示us+ni / sy total
用 1 來toggle 顯示全部的CPU 亦或合成一個

顯示的縮寫意思:
us, user : time running un-niced user processes
sy, system : time running kernel processes
ni, nice : time running niced user processes
id, idle : time spent in the kernel idle handler
wa, IO-wait : time waiting for I/O completion
hi : time spent servicing hardware interrupts
si : time spent servicing software interrupts
st : time stolen from this vm by the hypervisor

Memory 的狀態用 m 來選擇顯示方式 Used/Avail graph 或都純文字顯示

Field/Column 大概是跟舊版比起來變最多的,這裡可以用f 進到managing fields來設定要顯示的欄位,按f 後在想要的資訊用 space 或 d 來選擇要不要印出,用s 來設定用哪個欄位排序。

欄位基本上都有註解,我預設沒特別設定印出的欄位會是:
PID USER PR NI VIRT RES %CPU %MEM TIME+ S COMMAND
事實上可以印的東西很多,可以自行看manpage的介紹,不過我覺得實際上需要的其實也就預設這幾個。
其實寫這篇最主要的目的就是sort 了,因為開了top 都不知道要kill 誰了;現在自行設定用CPU 來sort,之後新的top 就像舊的top 一樣,把佔用最多CPU 的行程放在最上面,等著我們kill (X

另外也有些快捷鍵能設定排序欄位:
M: %MEM
N: PID
P: %CPU
T: TIME+

另外還有一些有趣的global command:
d: 設定更新的頻率
E/e: 設定Summary Area/Task Window記憶體的單位,從KiB 到EiB (真的有人有這麼多記憶體嗎XD)都行
g: 新的top 可以開四個不同的顯示視窗,可以有各自設定,用g 來選擇
k: 大殺四方行程,這跟原本的top 是一樣的
r: renice, 就…就是renice
L: 定位字串,如果要highlight 某個關鍵字可用,類似vim 裡面的 '/',找下一個則是 &

如果要改變畫面的顏色mapping,可以用Z 進到互動設定
用b/B toggle粗體顯示,z 設定是否彩色顯示,另外能設定各欄位的顏色:
S = Summary Data, M = Messages/Prompts,
H = Column Heads, T = Task Information
不過我是沒什麼美感的人,去改配色大概只會悲劇,所以就放著讓它用預設顏色就好。

初看新版的top ,應該是舊的top 功能不夠了,所以整個大翻修,還有許多功能本篇沒有介紹,剩下的大概都是平常用不太到的功能吧,就留給有興趣的人去研究了。

2016年8月2日 星期二

使用Facebook bot on GAE自動監控網頁更新

故事是這樣的,最近我有一位同學在申請日本留學試驗(EJU)的獎學金,最近會在網站上公佈複試的錄取名單,不過他八月又要去美洲大殺兩個星期,想託我幫他看一下複試結果什麼時候出來,出來的話跟他通知一下(說實話就算出來了在美洲是能幹嘛,還不如專心大殺四方)
不過you know,我這個人嘛,懶~~,每天上去看網頁多麻煩…要是哪天忘了看那可是賠不起呀,畢竟強者我同學成績猛高,成績都比平均高了兩個標準差,我去考大概只能考他的零頭出來QQ。
靈機一動,為啥我不找我的好友「葉闆大師」幫忙呢?心情不好的時候跟他聊聊天,他一字一句都是鼓勵的話,十足的激勵人心,檢查網頁這樣的小事能不能拜託他呢?決定就來試試看了。

概念其實很簡單,Google App Engine 本身就有cron 的設定,可以定時觸發一個function,最快每一分鐘觸發一次,到一個月一次都行;還有各種複雜的設定文件:
http://yhhuang1966.blogspot.tw/2013/03/gae-cron-job.html

首先我們先把向facebook 某user 發送訊息的函式獨立出來,再寫一個新的class 處理cron 的狀況,至於receiver 的ID (也就是我的ID) 是多少,那在之前寫bot 的時候,從log 裡面撈出來的:
CONSTANT_RECEIVER = "Fan ID Here"

class FBNotify(webapp2.RequestHandler):
    def get(self):
        logging.info("Fire periodically hello")
        send_fb_message(CONSTANT_RECEIVER, "Periodically Hello")

 app = webapp2.WSGIApplication([
     ('/webhook', FBwebhook),
     ('/fbnotify', FBNotify),
     ('/', MainPage),
 ], debug=True)

有了上面的設定,在project 中加上cron.yaml 先設定每分鐘觸發一次fbnotify get(注意雖然是 1分鐘可是要寫 minutes),時區在以分鐘為單位的狀況就不用設了,詳細請見參考資料:
cron:
- description: check eju website automatically
  url: /fbnotify
  schedule: every 1 minutes
  timezone: Asia/Taipei

用appcfg.py update之後葉闆大師就會熱情的每分鐘定期向你問好:

如果覺得它很煩,只要把cron.yaml 裡面的內容刪到剩下cron: 一行,再appcfg.py update_cron把cron 取消掉就行了。

當然這樣還不行,重點是要監看,EJU 的結果會公佈在這裡:
https://www.koryu.or.jp/taipei-tw/ez3_contents.nsf/14
方法就很正規了,把第一列的資料給拉出來,xpath 的部分先用chrome 的檢查看過,決定xpath 為:
//tr[@valign='top']/td/a[@title]/@title
受限於gae 的關係,我們只能用lxml (其實應該也可以裝 beautifulsoup 不過我有點懶),搭配urllib2把資料拉下來,取出第一列的資料若是和現在的資料不同就給我發送訊息,測試用時,就算沒變也會發一則:
URL = "https://www.koryu.or.jp/taipei-tw/ez3_contents.nsf/14"
FIRST_ROW = u"2016年度第二期日本交流協會獎學金(短期留學生)--合格發表"
res = urlfetch.fetch(self.URL)
s = res.content

root = lxml.html.fromstring(s)
firstTitle = root.xpath("//tr[@valign='top']/td/a[@title]/@title")[0]
isNew = (firstTitle != self.FIRST_ROW)
if isNew:
    send_fb_message(CONSTANT_RECEIVER, "Notification: There is new message")
else:
    send_fb_message(CONSTANT_RECEIVER, "Notification: There is no new message")
把它加到fbnotify 的get 裡面,先用cron 為1 minute 測試,確定真的會送出訊息;也可以直接開瀏覽器,造訪 xxxx.appspot.com/fbnotify 觸發檢查,看看有沒有發訊息給你:



接著就可以把cron 改為30 minutes了,安心去睡覺等葉闆大師的通知了……


才怪!

我還是超緊張的,還是會開網站檢查一下它有沒有更新,要是code 沒寫好怎麼辦,或者葉闆大師下線了呢?這個網站為啥不弄個RSS 之類的就好了Orz。老實說,寫這個花了一兩個小時,其實只要每天早上花 1 秒鐘開網頁看一下就好了,我覺得我這樣根本多此一舉。

參考文件:

GAE cron 相關文件,cron的佈署和 schedule format:
https://cloud.google.com/appengine/docs/python/config/cron
https://cloud.google.com/appengine/docs/python/config/cronref#schedule_format

其他相關文件,python built-in-libraries
https://cloud.google.com/appengine/docs/python/tools/built-in-libraries-27


ps :想靠北一下,GAE 用了兩年覺得網頁愈改愈亂,要找想要的東西都找不到,每每花一堆時間在找一些小東西。

----

按:剛剛在中午左右,已經收到葉闆大師的通知了,實驗成功,謝謝你葉闆大師XD


2016年7月31日 星期日

背離親緣 Far From the Tree

這本書是壹壹捌天大地大台科大多益990沙暴大家的莘予大神推薦,看了幾頁覺得不錯,回家立馬上網買下來,分上下冊的超級巨頭書,就算不看買來當磚塊也不錯(X

淺白一點來說,背離親緣就是本超級故事書是作者跟許多不同家庭的訪談筆記,訪談家庭的子女落在以下十個面向:聽障、侏儒、唐氏症、自閉症、思覺失調(過去稱精神分裂症)、重度殘障、音樂神童、犯罪、因姦成孕、跨性別。

第一章,作者在書中用了淺顯的分類來展現這樣的關係:由父母而來的「垂直身分」,例如種族、國籍、性別……,另外還有子女由社會而來的「水平身分」,像是罕見疾病、價值觀等,兩者愈不相同,對雙方的衝擊和挑戰也愈強大,書中選出的十項都是這樣兩個身分強烈衝突的例子。
作者訪談的動機,便是想了解這些生下獨特子女的父母,要如何忍受子女的水平身分,與他們的最愛相處;子女又要如何在衝突中建立自己的身分認同。

作者 Andrew Solomon 的TED 演講,也算是本書不錯的導讀:
https://www.ted.com/talks/andrew_solomon_love_no_matter_what

本書中間每章除了每個家庭的故事之外,另外都會加上當代對那些罕病的歷史,現在最新的研究與理解,社會各界各類的看法。最後,都會歸結到,這個群體是否能產生自我身分認同,及相關進行中的運動。
透過作者的文筆和真實訪談的故事,交錯其中艱澀的部分如科技、醫學、心理學,都能輕鬆吸收;社會、文化、政治上的正反爭議也有完整的呈現,這本書不單單是一本訪談錄,而是針對這些罕見病症的完整記錄。其中包含大量正反意見的呈現,種種生命與道德的問題穿插在故事間,就像電車兩難一樣,這樣的問題絕對沒有正確答案,卻能讓我們體見生命和社會的複雜性,以及道德決定之困難。

以下僅節錄一句開始閱讀時,令我覺得讓人印象深刻的句子:某位聽障父親不時會指導他聽障小孩的手語,有人問他為何小孩已經會手語了,還要繼續教?他的回答是:「你的小孩會說英語,但他上學還是要學習英語,持續精進;我小孩的手語比得並不好。」
這句那時留下了深刻的印象,也許跟作者開始訪談時一樣,我們習於把聽障視為一種病症,而將手語視為一種幫助聽障用的<工具>,而工具是會用就好,何需精進?可是如果我們把聽障視為特殊族群,而手語是他們的<語言>和<文化>,說它是工具,不但過於輕視手語,多少有點歧視的意味了;本書就會這樣,不斷去挑戰你的價值觀和想法,從頭開始反思我們對特殊身分的刻板印象。

我無法評斷作者在選文之中是否刻意保留美好部分,儘管故事裡充滿了各種挑戰,各種血淚,在故事的最後,作者都不忘提起「這世界即使如此還是很美」,儘管孩子似乎永遠無法回應父母對他們的愛,父母還是能從中找到愛,從再怎麼不堪的的經歷中找到每天前進的原動力。

我知道有些人很不喜歡所謂「充滿著太多正向能量的作品」,我聽到這個形容詞時,第一個想到的是戴晨志的作品XD,充滿各種奇怪的小故事,從每個故事帶出一個理想的解,照著這個解去做,問題不是就不會發生了嘛?平安無事囉可喜可賀,滿滿正向能量。
當然不是說這樣不好,只是現在看就會覺得很假:世事怎可能如此一帆風順?好吧當這些問題都接踵而來了,這可不是說句「這樣這樣做」就算了呀。

背離親緣裡,每則故事都是事實,大多數它只是述事;書裡並沒有幻想中的「解」存在,就如同唐氏症的母親幻想著不存在的攝子,能夠挑盡60兆細胞中多出來的染色體,一夕之間治好子女的疾病,這就是做不到。
而書中的道德難題,父母何時應該肯定子女、何時該放手讓子女單飛?一個問題問十個人,可能會有二十個答案,我們無意求解,而是在了解的過程中,重塑自己對世界的觀點。
而那些家庭中身分的衝突、內心的煎熬、照護的無耐,解不開也放不下,只能和它共處;到這最後,這些問題將變成他們更美滿人生的一部分,也是從這樣的掙扎、奮鬥,才能看見真正的「正向能量」。

在最後一章,作者書寫自己的故事,透過捐精、代理孕母和種種高科技,他與他的同性伴侶、另外一對女性的同性伴侶和他的某位女性好友,五位家長用遠超過想像的複雜關係(至少我真的沒想過)生了四位子女,他用自身育兒美好的經驗提醒我們,社會的光譜如此寬廣,而接納和包容的力量又是如此偉大。
透過故事,我們能對世界上的差異和多樣更了解一些,並且接納了世界上各種可能性,平心靜氣的面對差異,讓世界更加多元。

前幾天剛到誠品,隨意翻閱了架上的「希特勒回來了」的導讀,他評論在一個走向瘋狂的社會,人類的良知能起多大的作用?很悲觀的,經歷無數的社會學實驗,像是服從權威的電擊實驗、假想監獄實驗,答案是很少、很少,人類天生就是社會裡的動物,個人的良知想反抗社會的前進宛如螳臂擋車一般無望。
但反過來看,這也表示當我們的社會愈堅強、愈能包容異己,瘋狂的個人能造成的影響力也很少、很少,如果我們相信民主、相信開放、相信多樣性,那就拿著他們打造一個更加柔韌的社會,令那些想方設法瓦解我們的邪惡無從見縫插針。

末尾,我想要引用一句,我身邊一位非常重要的人所說的話:「我也無法理解為什麼我會如此堅持?這種堅持毫無理由,但若說世界上有什麼是愛,我想這就是了」本書中的故事再再說明,父母親的愛如海之深,足以抹平世上一切障礙。

這些年看過最令人深思的書,推薦給大家。

2016年7月28日 星期四

(有電)星際爭霸戰-浩瀚無垠Star Trek XIII

上周四新世代Star Trek 的第三部,電影第13部浩瀚無垠終於上映,距離上部電影已經過了三年,這裡還有上集的心得
http://yodalee.blogspot.tw/2013/05/star-trek-xii.html
每次都要加防電頁,這一次先來講一些不會電人的東西

比如說:
企業號曲速航行變得更炫了,喔喔看看那個波紋。
企業號又壞了QQ,這個看預告片大概就知道了。
––––真好奇如果沒有重開機的話A到Z夠不夠企業號用 (誤

以下有電,包括本作Beyond,上作Into Darkness,和隔壁的Star Wars: The Force Awaken
不想被電請慎入





這集電影上映前,有人的評論是 Fast and Furious in Space,well,其實不算太糟的評論XD
和前兩部比起來,這部的主要劇情都在地面,星際的內容相對少一點,FF 風格的無槍打鬥、飆車、搖滾樂都沒缺,還有各種喔喔喔差一公分一秒鐘你就死惹的超高難度極限動作。
無疑是部爽片(蓋章,蜂群艦被打爆,配上激烈的搖滾樂,十足的快感。

當然星際的內容也沒有少,另外感謝血汗的特效公司,製作了精美的York Town 基地(Deep Space 9 表示:我輸了QQQQ)雖然基地再精美防禦力好像都沒有隨之提高,我覺得這才是看科幻電影最值的地方:打造一個想像的世界。
儘管現在這是大量的血汗特效堆出來的,但舊系列的模型搭配大腦想像力也能有同樣的效果,但單就科幻這點上我認為Beyond 的科幻值還是滿高的,看著York Town 動畫,連內心都不自覺的激動起來了。

內容的部分,有些負雷認為本片的文戲不足,我並不這麼認為。

Beyond 利用船毀人散的方式把組員分成兩人一組,在組間有更多的對話空間,讓觀眾們能和角色互動,畢竟Enterprise 七人一組,全員登場固然必要,但人多嘴雜的情況下,角色的印象就不容易突顯,分成小組下去就能分別展現各人的特色
片中可以從Spock 和McCoy的對談看見Spock內心的掙扎;從Kirk 和Chekov 的冒險看到Chekov 的機靈;Scotty 引入路人Jaylah 跟星艦;被俘虜的Uhura 和Sulu 代入反派角色,看到反派對聯邦那種不尋常的深入了解,自然導入後面的劇情。

如果比較11, 12, 13三集,這部的角色對話反而是最多的,說文戲太少實在不能認同。

若真要說Beyond 是武戲夠了文戲不足,Into Darkness 就是武戲不到位文戲更加單薄,只看到Khan 大發神經鬼吼鬼叫,主要角色除了Kirk, Spock, Uhura 外,其餘幾乎沒有出場機會。

----

這集Kirk 的表現令人激賞,無論是企業號被打爆的處置、擬定反擊方針、奮力拯救基地,都比上兩集沉著穩定,五年任務過了三年,我們的Kirk總算成為優秀的艦長,不像隔壁棚的魯夫練了兩年還是一樣白痴
Sulu 在片中也有代理艦長的機會,但不知是否導演刻意安排,其實Sulu 代理的時間內沒有做出重大命令,看得出Kirk 和Sulu 的指揮能力仍有差距。

----

另外從原設定來看的話,這部也遠比Into Darkness 優秀,這集大致上沒亂改原設定,頂多為了致敬原系列演員,幫Sulu 加了個男友。
與其說這部不亂改,不如說上集實在太不在意原設定,隨意消費一個擁有豐富語言和文化的克林貢人,把航行時間的設定改得亂七八糟,亂玩Khan 梗卻又沒有Star Trek 2: The Wrath of Khan的表現來得好。

隔壁的The Force Awaken 亦同,電影裡沒有解釋這把光劍怎麼來的,但若照原設定,它是The Empire Strike Back 的時候跟著Luke 的手一起掉下去,那隻手後來好像被帝國撿去弄了個Luke 的複製人,還跟Luke 本人幹過架。這要到 Star Wars 8 或 Star Wars 9才會解釋,依 JJ Abrams 拍攝立場來看,他很有可能直接無視這樣的設定,其他像是賜死Han Solo等等,不及備載。
有時覺得JJ Abrams 根本原作粉碎機。

----

若要列缺點的話,我不滿意的大致有下列幾項:
音樂表現不足:和Star Wars: The Force Awaken 一樣,並沒有特別令人印象深刻的配樂,除了預告片使用的片尾曲 Sledgehammer,大部分仍是沿用XI 留下來的Enterprising Yong Men,這裡表現不足。

 連個MV 都要搞這麼多特效是有必要嗎……

過場和結尾太過快速,雖然把人分群可以增加角色互動,也會增加不斷切換場景對觀眾造成的負擔,中場我覺得場景切換太快了,上個對話群還沒完全結束,突然硬生生被切到下一組。
片中在York Town 基地時打開了兩個支線,分別是Kirk 跟Spock 都想要離開艦隊,卻都還在猶豫;經過了電影中段的冒險,在收尾應該能有更多的鋪陳回心轉意的過程,電影卻在Kirk 的生日派對上嘎然而止,也造成末段的對話有點破碎。

結尾瞬間帶到1701-A 的快轉下水過程和To boldly go where no one has gone before 的發言,對照原系列的Star Trek 4: The Voyage Home,加上眾人齊聚艦橋,一同迎接新企業號的下水:
 
新版只剩下動畫和旁白的下水過程,少了人物的元素,看來實在免洗,而To boldly go where no one has gone before 生怕觀眾不知道一樣,連續11, 12, 13 都用同一個結尾,第一次用覺得感動,第二次覺得重複,到了第三次就覺得老套了,連Star Trek 6: The Undiscovered Country 一代的結尾都還處理得比較好。

最後還有,我覺得Star Trek 裡面,除了人類之外,遇到較人類不進步種族的機會也太多了,這集的開頭仍然是Kirk 在向其他種族接觸,照Prime Directive這個種族應該已經發明曲速才對,可是接觸的地點仍然是星球上古老的石造建築物中,並試圖以肉身攻擊的方式對待來客。
試想要是人類發明了曲速,接待外星人會是用這樣的方式?
個人覺得上集消費克林貢人以降,電影中就沒有好好描述過其他的外星文明,連瓦肯的地位都大不如前,好似只有地球充滿了城市和優哉的人群,這和Star Trek 本來的設定似乎頗有出入。

----

有人問我心目中理想的Star Trek 電影……
只能選一部的話,我應該會選Star Trek 9: Insurrection 吧,沒有過多激烈的衝突場面,毫不誇大的述說企業號的冒險故事和道德的衝突;如果能選其他的,Star Trek 6: The Undiscovered Country 我也覺得不錯。
論Star Trek 系列橫跨50 年,到目前13 部電影各有特色,由老至新題材多變,不像隔壁棚的Star Wars 總是同個調調,影迷們各有所好,也是不錯。

這部Star Trek: Beyond爽則爽,片中藉由Prime Spock 的隨身物品,代出第一代的Star Trek,照片中演員多已作古,但新的一代也已蓄勢待發,傳承新一代的Star Trek,作為重開機三部曲的最後一部,在三部曲中也能坐二望一,推薦喜歡爽片的大家一同觀賞。

2016年7月24日 星期日

使用clap-rs 建構程式介面

最近小弟在改 rust completion tool: racer 的code ,發現它用的程式介面crates: clap-rs還不錯,值得專文介紹一下:

其實這不是第一個類似的套件,事實上已知有以下這幾個選項:
https://github.com/rust-lang-nursery/getopts
https://github.com/docopt/docopt.rs
https://github.com/kbknapp/clap-rs

程式介面的構造,最重要的就是:剖析使用者輸入的參數

getopts 是在研究 rust coreutils 的時候遇到的,它的chmod 使用getopts 來剖析參數,問題是getopts 只能功能有限的參數剖析程式,它的參數選項有限,由 - 開頭的設定也是很大的限制,這在chmod 遇到問題,因為chmod 允許大量的選項變化:

chmod -x file, chmod -0700 file, chmod -rwx file 都是可接受的
而getopts 遇到 '-',就把後面的內容視為參數,若不曾設定任一字母的 optflag 就會丟出UnrecognizedOption error
但我們也不能把 01234567rwx 都加到getopts 的flags 裡面,它們會出現在opts.usage 裡面,下面這段程式碼就是要排除這個問題,在opts 處理剖析args 之前先手動剖析它,並把符合 -rwxXstugo01234567 的選項先移除掉:
https://github.com/uutils/coreutils/blob/master/src/chmod/chmod.rs#L43-L60

曾經把這個問題回應到getopts 那邊,作者的建議是修改getopts 讓它在遇到 unrecognized option時,可以接到使用者定義的函式;或者讓unrecognized argument 轉為free argument。
https://github.com/rust-lang-nursery/getopts/issues/43
不過後來這些都沒有解XD,getopts 好像也有段時間沒有維護了。

我們還是說回clap-rs好了

clap-rs 作者自己有說了,getopts 不是不好,在簡單的小程式上getopts 足以應付大多數需求,不太需要配置記憶體也讓getopts 做到極簡,但缺點是很多東西要自幹,像是檢查參數、自訂help 訊息等,實作額外功能時,不配置記憶體的優勢隨即消失。
另外跟docopt 相比的話,docopt 讓你「寫help 訊息,parse 後幫你產生程式介面」,缺點是比較難客製化,parser 也比直接設定還要肥一點(雖然處理argument這通常不是什麼問題)

來看看clap-rs 怎麼用:

首先引入clap的App, Arg,clap 的設計是一層層加上去,宣告App::new之後,要加什麼功能就呼叫對應函式,最後於末尾放上 get_matches把選項爬一遍,程式介面就建完了。
先來看看最簡單的例子,建一個空殼子claptest:
extern crate clap;

use clap::{App, AppSettings};

fn main() {
let matches = App::new("Test program")
  .version("1.0")
  .author("yodalee")
  .about("My suck program").get_matches();
}
它會自動產生 help, version, usage:
claptest --help:

Test program 1.0
yodalee
My suck program

USAGE:
    claptest

FLAGS:
    -h, --help Prints help information
    -V, --version Prints version information
這裡可選的內容,可見:http://kbknapp.github.io/clap-rs/clap/struct.App.html

當然這樣太乾了,介面通常要加上Argument參數,大體跟app 一樣,串接一個
.arg(Arg::with_name('name')),Arg 要哪些功能一樣一一往後串接,以下介紹幾種Argument 的設定方式:

1. 是讓程式判斷相關參數是否出現:
.arg(Arg::with_name(“debug”).short(“d”).help(“execute in debug mode”))
使用matches.is_present()可叫出是否有這個參數。

2. 取得參數後的值:
設定參數takes_value(true)
.arg(Arg::with_name(“debug”).long(“debug”).short(“d”).takes_value(true))
參數值可接受下列方式設定:
-d value, --debug value
-d=value, --debug=value
-dvalue
利用matches.value_of(“debug”) 取得其值

3. 非參數的值,這是針對「不是hyphen」開頭的參數,不用設定long, short,可以直接抓:
.arg(Arg::with_name(“arg”))
matches.value_of(“arg”)
如果設定multiple ,可以一次抓一排:
.arg(Arg::with_name(“arg”).multiple(true))
let trail: Vec<&str> = matches.values_of(“arg”).unwrap().collect()

其他還有:
  • 設定某個參數是否一定要出現(required)
  • 要不要在如有debug 參數時才出現(required_unless)
  • 是否跟其他參數有衝突(conflicts_with)
  • 參數是否要蓋掉其他參數(overrides_with)
  • 是否需要其他參數(requires)
  • 從help 訊息中將參數說明隱藏(hidden)
  • 設定參數後可能的值(possible_values)
  • 取得參數重複的次數(number_of_values)
  • 設定help 中參數更多的資訊(next_line_help)

作者似乎從docopt搬來一些指令,像是在Arg 可以用from_usage 打入argument 的使用方式來產生Arg(from_usage),個人還是不喜歡這種方式啦,感覺怪詭異的,也就不介紹了,文件可見:
http://kbknapp.github.io/clap-rs/clap/struct.Arg.html

在App 中使用subcommand設定子命令,就像git add 這樣:
.subcommand(SubCommand::with_name(“add”))
subcommand 下就跟App 設定一樣,可以用.arg 設定給subcommand 的argument

App也有各種選項能設定subcommand 的參數跟主程式參數要如何互動,像是subcommand的alias(alias),在help 裡出現的順序(display_order)
http://kbknapp.github.io/clap-rs/clap/struct.App.html
App 內設定AppSetting 也有許多相關設定可選,可見文件:
http://kbknapp.github.io/clap-rs/clap/enum.AppSettings.html

另外clap-rs 還有一些可用的特性:
支援YAML 方式來設定程式介面(from_yaml, unstable),這可以支援多語系,在編譯時期決定要選擇哪個YAML 來編譯,這部分小弟就無心鑽進去了,畢竟程式都沒有寫到這麼複雜過。
http://kbknapp.github.io/clap-rs/clap/struct.App.html#method.from_yaml

另外是支援產生bash-completion,這部分可參閱相關文件,步驟大致如下:
首先把一般寫在main 裡面的
let matches = App::new()
獨立為一個function
pub fn build_cli() -> App<'static, 'static> {…}
let m = build_cli().get_matches();
在Cargo.toml 裡面加上
build = "build.rs"
在build.rs 中,就可以使用build_cli()產生App,然後呼叫gen_completions 來產生completions。
http://kbknapp.github.io/clap-rs/clap/struct.App.html#method.gen_completions

雖然自己都沒有用,不過還是大致把clap-rs 的功能介紹了一遍,大概就是比getopts 還要強大的介面產生工具,功能繁多,不過能讓打造介面輕鬆許多
當然一開始提到的,chmod 的介面在getopts 上面有問題,clap-rs 也不例外,目前這個問題無解,雖然在AppSetting 上面有選項為AllowLeadingHyphen,但開了這個選項似乎會打破一些parse 規則,變成parse 錯誤,我已經file bug 了,還待修正

ps說實話其實clap-rs 維護頻率也下降了,有沒有人要fork 一下(X

2016年7月21日 星期四

NAND2Tetris Part1

五月的時候看到coursera 上了傳說中鼎鼎大名的課程:Nand2Teris,就給它選修下去了。結果後來遇到七月考N1,課程大停擺(yay,最近才慢慢一週週的把課程聽完。
課程網址:
https://www.coursera.org/learn/build-a-computer
剛發現 7/18-9/4 有再開一次課程,有興趣的大夥進攻囉~~


Week 1: 邏輯閘

介紹Nand是什麼,如何用Nand做出其他邏輯閘,以及課程在Hardware 模擬用的工具和HDL。

作業要用課程提供的Hardware Simulator,從Nand堆出其他的邏輯閘。

這裡說難真的不難,線畫一畫,填個電路就完成了;大部分都是麻煩而已,像是16 路的Or gate,又沒有像verilog 的for loop 或bus 可以用,填接線的數字就飽了(雖然用vim 的block select 可以省一點工夫)

對應課程:交換電路與邏輯設計。


Week 2: ALU

概念講解:用位元表示數字、負數,如何做二進位加法

這個作業要用上星期作業完成的邏輯閘寫一顆ALU出來,ALU的功能規格是固定的。
比較煩人的是,它裡面的使用的HDL 跟平常習慣的Verilog 有點差異,導致在寫作業的時候,它一直報錯
像是它對Bus signal 取值不能在input ,而是在output;同個output 訊號也不能多次使用,這兩個跟verilog 不同。
例如某個mux output 的MSB (15)為sign bit,我們需要輸出sign bit;同時要產生zr 訊號,表示output 是否全為零,在verilog 裡會是類似這樣:
Mux16(out=out, a=result, b=notResult, sel=no);
Or16Way(out=nez, in=out);
Not(out=zr, in=nez);
And(out=ng, a=out[15], b=true);

答案是不行,因為out這個訊號已經當成輸出,不能再接給Or16Way,同時不能在input 的時候對out 訊號這個bus 取它15 的值
正確的寫法會是這樣:
Mux16(out=out, out=out2, out[15]=sign, a=result, b=notResult, sel=no);
Or16Way(out=nez, in=out2);
Not(out=zr, in=nez);
And(out=ng, a=sign, b=true);
知道這個差別,作業同樣不算太難。

對應課程:交換電路與邏輯設計。


Week 3: Sequential logic

介紹Latch 和Flip-flop 兩個sequential 電路,如此一來我們可以用記錄下來的狀態做什麼?最後的perspective 中有介紹如何用Nand 來實作Latch,D Flip-flop 課程中假定為黑盒子。

作業的電路模擬器中內建DFF 和DRegister ,要實作Program Counter, Memory 等電路元件。

RAM 的架構基本上都一樣,寫好最底層的RAM8之後,一路上去的RAM 64, 512, 4K, 16K 都只是複製貼上而已,只是它的Hardward Simulator 怪怪的,有時候會摸擬到 \Explosion/,建議在跑script 的時候可以用慢一點跑.;把veiw/animation選為No Animation也或多或少能防止它爆掉。

Sub bus 用double dot .. 而非Verilog 裡的 comma,同時接線時是接在被assign 那端,例如要把15 pins的wire 接到元件16 pins 的input上。
Element(input[0..14] = wire, input[15] = xxx);

對應課程:交換電路與邏輯設計。


Week 4: 組合語言

上周已經把該有的硬體:ALU,PC,RAM都寫完了,這周離開硬體,開始介紹Machine Language 及組合語言(Hack Assemble),組合語言要如何對應到機器碼,記憶體如何取存用,利用最基本的memory map 對應到screen, keyboard 的操作方式。

這周作業可能比較讓人崩潰,畢竟
Unlike other language like Java, machine language is not designed to make people happy.
不過我還是覺得滿輕鬆的啦XDDD

這周跳脫出來先寫組合語言,下周才會把所有的硬體組起來,寫組合語言等於是知道,機器如何去實習這些功能。

對應課程:計算機結構。


Week 5: 建構一台電腦

介紹電腦整體架構,有這上一週的基礎,我們大致知道電腦會有哪些功能,這周就是要把能夠執行這樣功能的電腦給作出來。
一些基礎元件:Memory, PC, ALU 第三周都已經實現了,這周只是要把它們放起來,寫出自己的CPU跟電腦。

要注意的,電路中包含以下的元件:Memory 的Screen 跟Keyboard取用螢幕跟鍵盤、存程式碼的ROM32K 、以及CPU 裡的DRegister 和ARegister,這些可以在builtInChips裡面找到;要用這些不然硬體模擬器無法抓到這些元件的值。
如果思考一下,就會發現課程都設計好了,控制訊號就是instruction 逐一填入即可,非常輕鬆;想比較多的反而是何是要讓PC jump,但基本上線接一接,Mux 輸入的訊號調整一下就會動了。

對應課程:計算機結構。


Week 6: 組譯器

Part 1 最後一周,介紹組譯器

作業就是寫自幹CPU 的組譯器,什麼語言都可以;為了non-programmer ,課程也設計了手寫作業:人工assembler 給…呃…有毅力的挑戰者。
這部分我使用Rust 來開發,不然我Rust 都快忘光了,被在台灣謀智隻手撐起servo project 半邊天的Rust 台柱呂行大神(又是個好長的稱號XD)屌打,都快無地自容了。
最後當然是寫出來了,可惜只是作業程度,格式錯掉一點點加個空白就會直接噴射,離工業強度大概還差30 dB,超級容易壞掉,叫它草莓組譯器或者玻璃心組譯器(X,這裡也就不拿出來獻醜了。

對應課程:計算機結構,編譯器。


TL;DR

整體來說的話,因為我本身就是電機背景,硬體跟程式都略懂皮毛,寫作業輕鬆愉快,聽課也像在複習忘掉的東西XD
如果沒有相關背景的話也許會困難一點,但修完的那刻,真能領略電腦這複雜又簡明的發明。
能這樣從Nand 往上一路打造出電腦也是很有趣。整體作業非常精巧,沒有多餘的浪費,week 1打造的邏輯閘,在week 2 ALU 、week 3 PC、week 5 CPU 都會用到,不需要特別打造自訂的邏輯閘,一步步照著說明也能自幹一台電腦出來,可以想見規劃這門課程的時候,是真的經過千錘百鍊的設計。

修完這門課,以後看到電腦,儘管它做了那麼多事,接了除了課程沒提到的各種周邊,我們還是可以說:「假的!不過只是相關概念延伸罷了,嚇不倒我滴」

啊不過寫到這就覺得我只不過在小打小鬧,真強者我同學都是跳下去改變世界,哪像我還在這裡聽課程QQQQ