the 1st edition: 20131017
the 2nd edition: 20141112
我不確定是不是和 linux list 一樣, 這是
一步步写嵌入式操作系统:ARM编程的方法与实践提到的 list 資料結構, 應該是從 linux 那個取經而來。有點複雜, 花了點腦力才搞懂。
基本原理:
以 list.c L7 struct page 來說, 只要知道 list 成員這個位址, 就可以計算出這個 struct page 的位址, 假設 unsigned int/int 佔 4 byte, list 位址是 28, 那這個 struct page 的位址是 28 - 4 * 3 = 16, 就是這麼簡單, 但是那些計算的 macro 就不簡單了。
我並不是要說明 list_entry macro, 書上解說的很詳細 (p148~154, 8 頁的說明, 我怎麼可能在 blog 解釋的比書上還好呢), 但我還是沒能理解, 寫個小範例來配合書上的說明。
如果只是要使用這個 function, 那就簡單多了, 參考
Linux kernel linked list for user space
L80 ~ L89 真是令人害怕的 macro, 還有一個不認識的
typeof, 不認識很正常, 這是 gcc 的 c extension, 很討厭這種 extension 吧, 難道沒用上 extension 就做不到同樣的事情嗎? 當然不是, 只是選擇不同。
L112 展開是 L200, L201 的結果 (還是讓人害怕, 書上用了 8 頁的說明現在你覺得有道理了吧), 我分別從 L105~110 做了單獨的測試。最主要是要補充書上沒提到的小例子, 有了這個小例子, 搭配書上的講解 (6.1.2.2 page 148~154), 應該就能了解。
我沒注意到 ({}) 這用法, 這也是 gcc extension:
http://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
http://www.ptt.cc/bbs/C_and_CPP/M.1383271390.A.B23.html
一開始被 0 取址所迷惑, 以為這樣會 core dump,
不過應該是要做寫入的動作才會 core dump, 讀取位址 0 是沒問題的。有點問題, 在我的 linux 環境下, 就算只是讀取位址 0 的內容也是有問題的。這裡應該要這樣說: &((struct page *)0)->list) 把這個 list 的位址取出來是沒有問題的, 縱使是用 address 0 去強制轉型出來。
struct list_head addr = (((struct page *)0)->list);
有問題
struct list_head *addr = &(((struct page *)0)->list);
沒問題
一個更短的例子:
這在我的 linux 下會得到 Segmentation fault。
執行到 L149 會產生 Segmentation fault, 看來 linux 把讀取 address 0 也當作是一個 memory access exception。
descent@debian-vm:part2$ ./list
0xc
0xbfe84500
0xbfe844f4
0xbfe844f4
真是厲害, 我看過的版本是使用 c macro, 造出類似 c++ template 的技巧; c++ 有 template, 就不用這麼轉腦袋了, 當然一樣有型別檢查, 而且和課本的內容像多了。下面的程式碼是 c++ template 的版本。
跑在 os 下使用這功能沒什麼特別的, 這是要在 os 層級使用的, 來試試看能否成功。
arm-none-eabi-g++ -fno-common -O0 -g -mcpu=cortex-m3 -mthumb -fno-exceptions -fno-rtti -std=c++11 -c list.cpp
arm-none-eabi-g++ -Wl,-T./stm32.ld -nostartfiles -fno-common -mcpu=cortex-m3 -mthumb -o list.elf list.o
arm-none-eabi-objcopy -O binary list.elf list.bin
qemu-system-arm -M lm3s6965evb -kernel list.bin -S -gdb tcp::1234
為了簡單, 就不要出動列印功能 (不使用 printf), 直接透過 qemu/gdb 來檢視正確性, gdb 可觀察到 p1, p2 位址, 和使用 List 取出的是一樣的。藉由 template, 可以塞進各種型別, 相信 c++ template 版本看來友善多了。
L13, L15, 是 p1, p2 的位址, L31, L37 是透過 List 取出的位址, 剛好一樣 (不一樣就慘了)。
以下的 gdb 環境為 stm32f4 discovery board, 和模擬器的值一模一樣。
c macro 版本莫測高深, c++ template 版本高深莫測, 相信每個人都有自己喜歡的作法, 神的右手與魔鬼的左手同樣的強大。使用 c++, 你可選神的右手或是魔鬼的左手, 這是我喜歡 c++ 的原因之一。
大概被 llvm 嚇到了, gcc 開始接受 c++ 了。
沒有留言:
張貼留言
使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。
我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。