robot
最新文章(10)
Mqskit 和其它相關工具
CPython 的 GC 二、三事
寫 Mecurial Extension 是件快樂的事!
Mozilla 台灣辨公室徵人啟事
關於 Apple 的兩項專利
core dump 之前的 frame
怎麼發出 beep 聲?
先承認你要找的是奴才吧!
程式碼要清的多乾淨?
FreeBSD 的 Thread-Local Storage 實作
首頁
新編
最新留言
Entries RSS
重要關鍵字(10)
coding (122)
Python (93)
FreeBSD (71)
WEB (61)
URL (48)
hardware (46)
javascript (36)
Linux (34)
blog (30)
C++ (16)
所有關鍵字
新增 URL
FreeBSD Kernel Module 實例
by thinker
2 Columns
關鍵字:
FreeBSD
之前談過一些 $FreeBSD$ kernel 的相關設計,但是,若無實際的$應用$,總是會感覺飄飄浮浮。剛好最近幫 Vortex86 6071 這顆 SoC 寫了 $hardware$ watchdog timer 的 driver ,非常適合當作$範例$。於是將程式碼整理一下,留為記錄。 == Kernel Module == 自從引入 kernel module 之後,許多 $FreeBSD$ 的子系統和 driver 改成以 module 的型式存在,能依需要動態載入,而不需要重新 compile kernel 。因此,了解 kernel module 的寫法對實作 kernel space 的程式有很大的幫助。 $FreeBSD$ kernel module 其實是一個有實作 module event handler 的 partial linked object file,將所有的 object file 進行局部的 link ,留下 unresolved 的部分。 Module event handler 是一個 function ,當 module 被載入、移除、和系統 shutdown 時會被呼叫。 handler 負責實作 module 的功能,是 module 的進入點。 handler 必需透過 $FreeBSD$ 提供的 macro 向 kernel 登記, 方法如下: {{{#!cpp static int foo_mod_event_handler(module_t mod, int what, void *arg) { switch(what) { case MOD_LOAD: case MOD_UNLOAD: case MOD_SHUTDOWN: default: } return 0; } static moduledata_t foo_mod = { "foo", foo_mod_event_handler, priv_data }; DECLARE_MODULE(foo, foo_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); }}} `foo_mod_event_handler' 是 `foo' 這個 kernel module 的 event handler ,透過 `DECLARE_MODULE' 這個 macro 向 kernel 登記。登記的運作方式,和 compiler 提供 section attribute 有關,先案下不談。 macro 的第一個參數為 module 的名稱,第二個參數是 module data ,提供 event handler 和額外的私有參數。第三個參數是 module 所屬的 sub-system 的 ID,例如 drivers 。sub-system 決定了 module 被執行的順序,也就是 event handler 被執行的順序。所有的 sub-system 的 ID 都定義在 `sys/kernel.h' 裡。第四個參數,設定 module 在同 sub-system 的 module 間的次序。同樣定義在 `sys/kernel.h' 。這些次數只有 module 和 kernel static linked 在一起,或 boot 就載入才有意義。開機之後才載入時,次序已沒有作用。 === Event Handler === Event Handler 在分別在載入、解除載入和系統 shutdown 時,被 kernel 呼叫。呼叫時,第一個參數為代表 kernel module 本身的 module_t 物件,第二個參數傳入呼叫 handler 的事件、原因。第三個參數是設定在 moduledata_t 裡的第三個 member ,讓 module 本身可以傳遞一些資訊。 呼叫 event handler 的事件有 * MOD_LOAD * MOD_UNLOAD * MOD_SHUTDOWN * MOD_QUIESCE 前三者望文生義,唯最後者 MOD_QUIESCE 和 MOD_UNLOAD 接近,卻不盡相同。MOD_QUIESCE 使 module 有機會檢查目前 module 是否在使用中。若真, event handler 傳回非零,將使的 kernel 停止 unload module。MOD_UNLOAD 同樣在傳回非零時,中止 unload module 的程序,但 MOD_UNLOAD 的非零,代表因為某些系統原因, module 無法被 unload ,因此要求程序中止。例如: 別的 module 正在使用本 module ,卻又無法解除這個關係。當使用者在 unload module 時,使用了 force (-f) flag 時, MOD_QUIESCE 的傳回值將失效,但 MOD_UNLOAD 的傳回非零卻依然發揮作用。 MOD_LOAD 若傳回非零時,會導至 load (載入)失敗,因此, event handler 可在 MOD_LOAD 發生時,initialize module 所需要的環境,若能順利執行,則回傳零。 === 完整的 kernel module === 稱為 `foo.c' ,如下: {{{#!cpp #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/types.h> #include <sys/systm.h> static int foo_mod_event_handler(module_t mod, int what, void *arg) { int err = 0; switch(what) { case MOD_LOAD: printf("MOD_LOAD\n"); break; case MOD_UNLOAD: printf("MOD_UNLOAD\n"); break; case MOD_SHUTDOWN: printf("MOD_SHUTDOWN\n"); break; case MOD_QUIESCE: printf("MOD_QUIESCE\n"); break; default: err = 1; } return err; } static moduledata_t foo_mod = { "foo", foo_mod_event_handler, NULL }; DECLARE_MODULE(foo, foo_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); }}} 開頭的 header files ,可查 man page DECLARE_MODULE(9) 、 printf(9)。 另外需要一個 Makefile: {{{ KMOD= foo SRCS= foo.c .include <bsd.kmod.mk> }}} 完成之後,執行 * make * make install 就會產生一個 `foo.ko' 的檔案,並安裝至 `/boot/kernel' 目錄下。安裝時,必需要有 `root' 權限。安裝之後, user 可執行 * kldload foo * kldunload foo 進行 load 和 unload ,並用 `kldstat' 和 'dmesg' 觀察載入情形和 `printf' 出來的訊息。 {{{ foo_kmod# kldload foo foo_kmod# dmesg | tail -3 nfe0: promiscuous mode enabled nfe0: promiscuous mode disabled MOD_LOAD foo_kmod# kldstat Id Refs Address Size Name 1 32 0xc0400000 406a1c kernel (/boot/kernel/kernel) 2 1 0xc0807000 756b34 nvidia.ko (/boot/modules/nvidia.ko) 3 1 0xc0f5e000 6b6e0 acpi.ko (/boot/kernel/acpi.ko) 4 1 0xc469a000 1f000 nfsserver.ko (/boot/kernel/nfsserver.ko) 5 1 0xc54b0000 2000 foo.ko (/boot/kernel/foo.ko) foo_kmod# kldunload foo foo_kmod# dmesg | tail -3 MOD_LOAD MOD_QUIESCE MOD_UNLOAD foo_kmod# kldstat Id Refs Address Size Name 1 30 0xc0400000 406a1c kernel (/boot/kernel/kernel) 2 1 0xc0807000 756b34 nvidia.ko (/boot/modules/nvidia.ko) 3 1 0xc0f5e000 6b6e0 acpi.ko (/boot/kernel/acpi.ko) 4 1 0xc469a000 1f000 nfsserver.ko (/boot/kernel/nfsserver.ko) foo_kmod# }}} == Driver == 多年前,為應付越來越多樣化、複雜的 bus 種類,$FreeBSD$ 引進了 new bus 系統,依硬體的聯接關係,在系統建立對應的結構,以有效的管理這些 driver 和 device 或 bus 的對應。關於 new bus 請參考前文 linkname:[$FreeBSD$ Bus Sub-system 概念] http://heaven.branda.to/~thinker/GinGin_CGI.py/show_id_doc/276 。 為 $FreeBSD$ 寫 device driver 前,必需先知道 driver 所驅動的 device 會安裝在哪一種 bus ? PCI? ISA? 甚至是 SCSI? USB?! $FreeBSD$ 透過 devclass 將 driver 依 bus 分類,device 會裝在 PCI bus 者,driver 則需登記為 `pci' 這個 device class 。當 PCI bus 上發現一個新 device ,bus 的 driver 就能透過 device class 找到相關的 driver ,進行 probe ,並選擇最合適的 driver 。(見 linkname:[$FreeBSD$ Bus Sub-system 概念] http://heaven.branda.to/~thinker/GinGin_CGI.py/show_id_doc/276 一文) 對許多 embedded system 而言,可能大部分 device 直接聯接在 system bus 上,直接和 CPU 溝通。這時,將 driver 直接掛在 legacy 或 nexus 上面最為合適。這裡將以 nexus 為例,示範 device driver 的寫法。 === Driver Module === driver 的 module 和前面介紹的 module 是一樣的,只是會在 `MOD_LOAD' 這事件發生時,向 kernel 登記一個新的 driver 。 driver 的宣告如下 {{{#!cpp static device_method_t foo_methods[] = { DEVMETHOD(device_probe, foo_probe), DEVMETHOD(device_attach, foo_attach), DEVMETHOD(device_detach, foo_detach), {0, 0} }; static driver_t foo_driver = { "foo", foo_methods, sizeof(struct foo_softc) } }}} `foo_driver' 宣告了 driver 的名稱 `foo' , driver 的 methods 和 driver 私用資料的大小。 `struct foo_softc' 是 driver 自行宣告的資料型態,用以存放 device 的狀態和相關資訊。 driver 必需告知 kernel `struct foo_softc' 的大小, 當 kernel 決定使用該 driver 驅動某一 device 時,將據指定之大小 allocate 一區記憶體,用以儲放 `struct foo_softc' 的內容。該記憶體可用 `device_get_softc()' 取得。 `foo_methods' 則指定 method 和實作之間的對應。和 device 有關的 method ,定義在 `/usr/src/sys/kern/device_if.m' 這個檔案裡,並透過 `makeobjops.awk' 轉成 header file ,容後詳述。 driver 必需在 `MOD_LOAD' 時向 kernel 登記為 device class `nexus' 或合適的 device class (bus) ,如此 bus driver 才知道使用該 driver 驅動 device 。 driver 可透過 `devclass_add_driver()' 登記 driver 。 {{{#!cpp static int foo_mod_event_handler(module_t mod, int what, void *arg) { devclass_t nexus_dc; int err = 0; switch(what) { case MOD_LOAD: nexus_dc = devclass_find("nexus"); devclass_add_driver(parent, foo_driver); break; ...... } return err; } }}} === Identify Method === 當 bus driver 發覺一個新的 device 時,要 probe 每一個 driver ,以決定使用哪一個 driver 。但,有些 $hardware$ 並沒有自動發現 device 的能力。這時,除了使用者的介入之外,只能靠 driver 主身去發掘。 `identify' 即是用於發掘 device ,當新 driver 被登記時, driver 的 `identify' method 會被呼叫、執行。這時 driver 可透過 `BUS_ADD_CHILD' ,將發現的 device 加入 parent bus 。 {{{#!cpp static void foo_identify(driver_t *driver, device_t parent) { device_t foo_dev; foo_dev = device_find_child(parent, "foo", 0); if(foo_dev != NULL) return; foo_dev = BUS_ADD_CHILD(parent, 0, "foo", 0); } }}} `BUS_ADD_CHILD' 的第三個參數為 device class 的名稱,若為 NULL ,則 bus driver 會試遍 bus 對應 device class 下所有的 driver 。這裡我們指定為 `foo' ,則 bus driver 只使用 driver name 為 `foo' 的 driver 。 device class name 和 device 、 driver 間的關係,請參考 linkname:[$FreeBSD$ Bus Sub-system 概念] http://heaven.branda.to/~thinker/GinGin_CGI.py/show_id_doc/276 一文。 第後一個參數為 unit 字數,若 unit 為 0,則該 device 命名為 `foo0'。若 unit 為 -1 ,則 kernel 會代為選抽一個最小而唯一的數字。 在新增一個 device 前,我們必需先檢查是否之前已經加入相同的 device 。否則,會造成系統的 panic 。對於這一點,我有點不解。在 man page 裡有提到,在使用 `BUS_ADD_CHILD()' 之前,必需先檢查。然而,在明知 driver 可能沒做檢查就直接加一個新 device 的情況下,為何 kernel 任由這種狀況造成 panic ,也不願在 kernel 加一行程式,檢查某 function 的傳回值是否為 NULL 。 === Driver 解除登記 === 在 unload module 時,必需記得將 driver 解除登錄。一旦 unload module ,原本 driver 的 binary 所佔空間,就可能被挪作他用。若不解除登錄而被持續使用,可能會造成嚴重的錯誤。因此,在 `MOD_UNLOAD' 事件發生時,應該解除 driver 的登錄。以下透過 `devclass_delete_driver()' ,能將 foo driver 從 `nexus' device class 解除登錄。 {{{#!cpp static int foo_mod_event_handler(...) { ...... switch(what) { ...... case MOD_UNLOAD: nexus_dc = devclass_find("nexus"); devclass_delete_driver(nexus_dc, &foo_driver); break; ...... } ...... } }}} === 完整的 driver === {{{#!cpp #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/types.h> #include <sys/systm.h> #include <sys/bus.h> static void foo_identify(driver_t *driver, device_t parent) { device_t foo_dev; printf("foo_identify\n"); foo_dev = device_find_child(parent, "foo", 0); if(foo_dev != NULL) return; printf("BUS_ADD_CHILD\n"); foo_dev = BUS_ADD_CHILD(parent, 0, "foo", 0); } static int foo_probe(device_t dev) { printf("foo_probe\n"); return 0; } static int foo_attach(device_t dev) { printf("foo_attach\n"); return 0; } static int foo_detach(device_t dev) { printf("foo_detach\n"); return 0; } static device_method_t foo_methods[] = { DEVMETHOD(device_identify, foo_identify), DEVMETHOD(device_probe, foo_probe), DEVMETHOD(device_attach, foo_attach), DEVMETHOD(device_detach, foo_detach), {0, 0} }; static driver_t foo_driver = { "foo", foo_methods, 0 }; static int foo_mod_event_handler(module_t mod, int what, void *arg) { devclass_t nexus_dc; #if 0 device_t nexus_dev, dev; #endif int err = 0; switch(what) { case MOD_LOAD: printf("MOD_LOAD\n"); nexus_dc = devclass_find("nexus"); devclass_add_driver(nexus_dc, &foo_driver); break; case MOD_UNLOAD: printf("MOD_UNLOAD\n"); nexus_dc = devclass_find("nexus"); #if 0 nexus_dev = devclass_get_device(nexus_dc, 0); dev = device_find_child(nexus_dev, "foo", 0); device_delete_child(nexus_dev, dev); #endif devclass_delete_driver(nexus_dc, &foo_driver); break; case MOD_SHUTDOWN: printf("MOD_SHUTDOWN\n"); break; case MOD_QUIESCE: printf("MOD_QUIESCE\n"); break; default: err = 1; } return err; } static moduledata_t foo_mod = { "foo", foo_mod_event_handler, NULL }; DECLARE_MODULE(foo, foo_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); }}} 在 `#if 0' 裡面的 code ,會在 unload 時,連 device 一起移除,而不止於解除 driver 的登錄。在 driver 移除之後, kernel 並不會連同 device 一起移除。看官可以試試,將 `0' 改成 `1' 。 下面是 Makefile,需要加入 device_if.h 和 bus_if.h ,以將 `kern/device_if.m' 和 `kern/bus_if.m' 轉成 header file。 {{{#!cpp KMOD= foo SRCS= foo.c device_if.h bus_if.h .include <bsd.kmod.mk> }}} 以 root 身份, `make; make install' 之後,就能試著將 driver 載入。在載入之後,可以用 `sysctl' 得到下面資訊。 {{{ driver_kmod# sysctl dev.foo dev.foo.0.%driver: foo dev.foo.0.%parent: nexus0 foo_kmod# }}} 透過 `demsg' 也會得到相關訊息 {{{ foo_kmod# kldload foo foo_kmod# dmesg | tail -5 MOD_LOAD foo_identify foo_probe foo0 on motherboard foo_attach foo_kmod# }}} 第三行表示在 system bus 加入一個新的 device ,稱為 `foo0' 。 === DRIVER_MODULE === 事實上,前面登錄 driver 和解除登錄的動作,可以透過以 `DRIVER_MODULE' 取代 `DECLARE_MODULE' 的方式進行。 $FreeBSD$ 體認到 device driver 是多個常見的型態,於是透過 `DRIVER_MODULE' ,減化我們的工作。 {{{#!cpp #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/types.h> #include <sys/systm.h> #include <sys/bus.h> static void foo_identify(driver_t *driver, device_t parent) { device_t foo_dev; printf("foo_identify\n"); foo_dev = device_find_child(parent, "foo", 0); if(foo_dev != NULL) return; printf("BUS_ADD_CHILD\n"); foo_dev = BUS_ADD_CHILD(parent, 0, "foo", 0); } static int foo_probe(device_t dev) { printf("foo_probe\n"); return 0; } static int foo_attach(device_t dev) { printf("foo_attach\n"); return 0; } static int foo_detach(device_t dev) { printf("foo_detach\n"); return 0; } static device_method_t foo_methods[] = { DEVMETHOD(device_identify, foo_identify), DEVMETHOD(device_probe, foo_probe), DEVMETHOD(device_attach, foo_attach), DEVMETHOD(device_detach, foo_detach), {0, 0} }; static driver_t foo_driver = { "foo", foo_methods, 0 }; static int foo_mod_event_handler(module_t mod, int what, void *arg) { #if 0 devclass_t nexus_dc; device_t nexus_dev, dev; #endif int err = 0; switch(what) { case MOD_LOAD: printf("MOD_LOAD\n"); break; case MOD_UNLOAD: printf("MOD_UNLOAD\n"); #if 0 nexus_dev = devclass_get_device(nexus_dc, 0); dev = device_find_child(nexus_dev, "foo", 0); device_delete_child(nexus_dev, dev); #endif break; case MOD_SHUTDOWN: printf("MOD_SHUTDOWN\n"); break; case MOD_QUIESCE: printf("MOD_QUIESCE\n"); break; default: err = 1; } return err; } static devclass_t foo_devclass; DRIVER_MODULE(foo, nexus, foo_driver, foo_devclass, foo_mod_event_handler, NULL); }}} 不需要再寫程式去處理 driver 的登錄問題了。 == Driver 的空瞉 == 本文呈現的是一個 driver 的空瞉,實際的 driver ,還要和 $hardware$ 互動,當然更加複雜。但總體而言,寫 driver 話穿了,就是跑跑固定的劇本,再加上一些硬體的互動。其實和寫 user space 的程式沒兩樣,只是不小心就能 kernel panic ,必需重開機。較難 debug ,如此而已。
最後更新時間: 2008-07-11 18:06:04 CST |
引用
查詢:
COMMENTS: