2015年3月11日 星期三

在Raspberry Pi & Pi2 上面學 Linux 驅動程式開發 (一) -- Hello World --

前言


自從開始玩 Raspberry Pi 就發現,對於學習使用Linux系統而言,這實在是一個好東西。
想想以前為了安裝Linux系統,都要準備一台不能太新的電腦,然後還要確定上面的硬體都可以找到驅動程式才行。

不然就得弄成雙系統,還常常要重開機切來切去,搞得很麻煩。更不要說是Driver沒寫好結果把系統搞當掉要花時間重新開機了。

雖然之後有virtual box之類的虛擬系統可以使用,但畢竟隔了一層,特別是在要外接USB或是其他I/O裝置的時候就會血壓上升了。

現在,有了Pi一切都不一樣了。


小小一片系統板配上SD或microSD卡,要備份系統就直接製作SD卡映像檔,要重灌就燒回去。實在是太方便了。

而把系統搞當掉,重新開機也不過一分鐘的事情。

這樣小小一個系統,在上面該有的開發工具也都沒有少,要什麼就直接 apt-get 上網安裝。
網路環境?驅動程式已經預設安裝好了,不用再煩惱有的沒有。


開發環境


就學程式語言來說,一開始要做的第一件事情就是準備開發環境。

一般來說,開發Linux Driver的時候,要先搞清楚等一下寫出來的Driver是要放在什麼地方跑? 再來就是要搞清楚,我們要在那邊程式碼在要哪邊寫,寫完後要在哪裡進行編譯的工作?

但是一開始就搞這麼多有的沒的實在是會讓初學者暈頭轉向。所以在還沒有熟悉整個開發流程的時候,先要把整個事情給簡化。

我們要在Pi的Linux系統裡面,編寫、編譯 Driver 程式碼,並且執行編譯出來的Driver

等到之後熟悉整個事情的來龍去脈之後,就可以開始把編寫和編譯Driver程式碼的工作轉移到效能比較好的桌上型電腦Linux系統上去進行。

要編譯Linux Driver必須要有和當前運行的kernel相同版本的Kernel Source 和 當初編譯此kernel時相同版本的gcc編譯器。

這邊強烈建議 自行編譯一次Linux kernel,並使用該kernel做Linux Driver開發

至於Pi板的核心編譯,網路上已經有一些非常優秀的教學文我就懶得寫了,在下面直接推薦:

    http://www.raspberrypi.com.tw/528/compiling-kernel-for-raspberry-pi/

    http://www.gtwang.org/2014/12/raspberry-pi-compile-linux-kernel.html

或是你手上的是最新的 Raspberry Pi 2 (ARM-7) 的板子,可以參考一下小弟的這篇文章:

    http://blog.ittraining.com.tw/2015/02/raspberry-pi2-kernel.html

這邊特別說明一下,如果成功編譯並安裝好kernel,開機之後。Kernel Source也放好位置了...
如果沒有跑到 Kernel Source 的位置執行過下面這個指令的話,還是不能編譯核心的。

    make modules_prepare

記得編譯安裝完核心之後,記得kernel source的目錄下執行一下這一行指令。

所以,在開始hello world之前,確認一下。先進去Pi的linux 系統裡面,看一下/proc/version這個檔案裡面的內容, 可以知道你現在用的kernel版本和當初是編譯這kernel的gcc版本。

    cat /proc/version

然後看一下現在預設的gcc版本是不是一致:

    gcc --version

筆者在寫這文章的時候編譯核心時用的是gcc 是4.8.3的cross-compiler版本,而Raspbian裡面用apt-get install能裝到最新的gcc版本是4.8.2。 雖然有點小差異,但是目前用起來還沒發現什麼大問題。


來編譯 Hello World 吧


撰寫程式程式碼


先在個人喜歡的地方建一個hello目錄,然後在裡面產生 hello.c 和 Makefile 兩個檔案。
Makefile裡面的內容如下:


說明一下,上面make 所用的參數 "-C" 後面是要接 kernel source tree 的存放路徑。

以我的 Pi2 系統上面而言,上面 "$(shell uname -r)" 的部份會變成 "3.18.9-v7+",所以我自行編譯好的 kernel source tree 可以在這個目錄底下找到 "/lib/modules/3.18.9-v7+/build",然而這個檔其實只是個連結,連結到我放kernel source tree 的地方(我喜歡放在我的家目錄底下 "~/rpi2/linux/",這要去看一些code的內容比較方便)。


hello.c的內容如下:


編譯程式碼


在這個目錄下面直接執行:

    make

然後就會產生一堆檔案,其中有一個hello.ko的檔就是Linux Driver(驅動程式),或是說Linux Module(模組)檔會比較適合。

載入/卸載 驅動程式(模組)

載入


執行insmod來載入驅動程式:

    sudo insmod hello.ko

用dmesg指令看一下系統訊息,是不是在最後一行有顯示出 insmod: Hello World.! 的系統訊息,有的話表示載入成功了。

也可以用 lsmod 指令列出系統中已經載入的驅動程式(Module)有哪些,其中應該會看到 hello 在其中。

    lsmod

卸載


執行rmmod來載入驅動程式:

    sudo rmmod hello.ko

用dmesg指令看一下系統訊息,是不是在最後一行有顯示出 rmmod: Hello World.! 的系統訊息,有的話表示該模組被卸載了。

執行 lsmod 應該會看到 hello 已經不在其中了。

這樣子就成功跑過一次 hello world 的範例了,而其中最麻煩的部份其實就是準備開發環境要自行編譯過一次kernel,這過程很複雜難搞。

為了充份利用Pi板的特性並降低這個過程痛苦的程度,我們之後會事先編譯好完整的開發環境並且整個打包成一個映檔。

初學者只需要準備一個8G的microSD卡,並把映像檔抓下來燒進去,放進去raspberry pi B+的板子上就可以開始學習開發Linux Driver了。

這樣就不用經歷前面那麼痛苦的過程了。(真是功德無量,功德無量阿~~~哈哈哈...)
(或許這件事情應該放在這篇文章的開頭及早說明...)

---------------  一切都不如想像中的美好分隔線  ---------------

後來發現搞出來的 8G 映像檔太大,就算壓縮之後還是有3G大小。要讓人下載這麼大的檔案想想實在是不大實際。

所以之後打算只把整個 compile 完的 kernel source 打包成一個檔(大約200多MB),讓人下載解壓縮之後,再在自已的Pi板上面,建立 compile module 的環境。

之後的文章會開始慢慢的進入程式碼和系統運作的講解...。

所以.....待續...



之後的例子會進行到一些和硬體上的溝通行,會需要一些電路和IC的組合。

艾鍗公司有出一塊他們授課時用的子板,非常適合以用來學習Linux Driver的學習,想買的人可以透過他們的官網去詢問。

http://www.ittraining.com.tw/ittraining/

7 則留言 :

  1. 請教一個問題
    就是如果我用官方網站的NOOBS
    直接裝卡上,安裝執行後
    要如何編譯並執行 MODULE 例如 hello.c
    是不是一定要先自行編譯一次 Linux kernel
    之後才能自己寫 MODULE 例如 hello.c
    並執行(insmod.ko)呢?

    回覆刪除
    回覆
    1. 不一定需要自行編譯一次 Linux Kernel。要編譯 Linux 模組需要配合運行中的 kernel 相同版本的 header 檔和相對應的 gcc 版本。使用官方網站上的 NOOBS 或是其他Linux 發行版的方式所安裝完成的環境,可以試著抓網方維護的 linux-header 檔試看看。如果是要學習 Linux 驅動程式撰寫的話,通常建議去抓 kernel source 下來自行進行跨平台編譯並建置編譯環境。
      可以參考一下這個blog的另一篇有關編譯raspbian kernel source的文章: http://blog.ittraining.com.tw/2015/02/raspberry-pi2-kernel.html
      歡迎多多討論。

      刪除
    2. 我自行編譯了一份kernel source code 並且打包放在網路上了,可以參考下面這篇,試著裝看看。
      http://blog.ittraining.com.tw/2015/03/raspberry-pi-kernel-module.html

      刪除
  2. 作者已經移除這則留言。

    回覆刪除
  3. 想請問一下,M=(shell pwd)代表的意思是什麼呢?我參考過別的文章[1],裡面用的是M=$(PWD)而且後續也沒有加V=1 ARCH=arm。還有就是我的Makefile中把"shell pwd"解譯為
    "/home/pi/svn/kernel/SourceCode/trunk/KernelAPI/driver-example/testdriver"
    但是實際是我的程式碼放置的位置為"/home/pi/code/testdriver",這是為什麼呢?
    請問有推薦的書可以知道嗎?我目正在閱讀"Essential Linux Device Drivers",還有LDD3.pdf
    如果是這兩本書有推薦的章節嗎?抱歉問題有點多。
    [1]http://rswiki.csie.org/dokuwiki/_media/courses:100_2:lab10_doc.pdf

    回覆刪除
  4. M=(shell pwd) 及M=$(PWD)都是Makefile 取得你現在compile driver source code的地方, 如果不一樣那就真得有點太其怪了..還是"/home/pi/code/testdriver 其實是一個symbolic link ?!
    LDD3 應該有寫, 應該在在前兩章.
    另外, 可以參考kernel source tree的文件說明 Documentation/kbuild/modules.txt

    回覆刪除
    回覆
    1. 感激不盡,正在詳細閱讀中。

      刪除