Linux Device Drivers

ldd3筆記基礎知識 http://zzzppp.cublog.cn/ 去年(2005)9月2號就買了Linux Device Drivers第三版,但一直沒仔細拜讀.最近決定仔細研讀它以學習Linux設備驅動程序. 在這裏歸納學習筆記. 不知道自己有沒有恆心把它看完,總之better later than never. 就讓這一系列的筆記伴隨我學習ldd3的漫漫長路吧 ldd3介紹的是2.6.10版的內核 An Introduction to Devices Drivers 驅動扮演的角色 Driver is a software layer that lies between the applications and the actual device. Mechanism & Policy " the role of a device driver is providing mechanism, not policy". mechanism和policy也是隱藏在unix設計背後的經典思想. mechanism: What capabilities are provided. policy : How these capabilities can be used. e.g1: KDE or GNOME: 它們都的基礎都是X server. KDE和GNOME屬於policy. 它們所關注的是使用什麼樣的圖形界面, 用戶面板. 卻無須考慮底層硬件. X server屬於mechanism, 它與硬件交互, 併爲用戶程序提供接口. 所以不同的圖形界面可以存在於同一臺主機上. e.g2: TCP/IP suite OS提供socket接口, 應用程序則可以使用各種協議. socket就是mechanism, 應用程序則關注policy. 經典的OSI七層模型或TCP/IP四層模型也是這個思想. Device Drivers: policy free, only provide mechanism! but, sometimes, u may lay some policies on them. 和驅動一起發佈的可能還會有一些庫, 應用程序. 庫和應用程序就要涉及到policies了. 開發者提供的庫或應用程序中的policy即默認的policy. Kernel可劃分爲下列功能單元 1, 進程管理: 進程調度, 資源分配, 進程間通信. 2, 內存管理: 其實也算是資源分配的一部分. 3, 文件系統: 管理, 組織物理媒介上數據的方法. 4, 設備控制: 設備驅動(ldd3所關注的) 5, 網絡: 實質上是進程間通信. 但它不侷限於一個特定的進程. 它關注收/發packets, 路由, 地址解析... 可加載模塊(lodable modules) module: 可實時加載到內核中的代碼, 它可動態連接到內核(insmod, rmmod). 設備驅動就是module的代表, 但module還包括文件系統等等. 當然, 你也可以在開機後關閉模塊的功能. 2.2版之後的內核支持在開機後關閉對加載module的支持. 設備, 模塊的分類 針對不同的設備類型(實際上也就是文件類型, 可參考APUEv2, p88), 模塊分爲這些類型: character module, block module, network interface. 每種類型的模塊驅動對應類型的設備. 字符設備: 以字節流的形式被訪問的設備. e.g: /dev/console : 文本控制檯. /dev/ttyS0 : 串口 它通過文件系統節點被訪問. e.g: /dev/tty1, /dev/lp0 字符設備與一般文件(regular file)的區別: 可以在一般文件中前後移動(lseek), 但只能順序訪問字符設備. 當然, 也有特例: frame grabbers. 塊設備: 能支持文件系統的設備. 傳統的UNIX: 只能以block(512B)爲單位訪問塊設備. Linux: 能以訪問字符設備的方式訪問塊設備, 即以字節文單位訪問塊設備. Linux中字符設備與塊設備的區別: 1, 內核內部對數據的組織和管理不同, 但這對驅動開發者來說是透明的. 2, 它們與內核之間的接口不同: 使用兩套不同的interface. 網絡接口: 能與其他主機通信的設備. 它可以是硬件設備, 也可以是軟件設備, 比如lo. (參考TCP/IP詳解p26) 網絡接口只管收發數據包, 而不管這些數據包被什麼協議所使用. 不同於字符設備和塊設備, 網絡接口沒有對應的文件系統節點. 雖然可以通過類似eth0這樣的"文件名"來訪問網絡接口, 但文件系統節點中卻沒有針對網絡接口的節點. 內核與網絡接口之間的通信也不同於內核與字符/塊設備之間的通信(read, write), 它們之間使用特定的傳輸數據包的函數調用. 另, 也有一些module不能嚴格地劃分到上面的類型. 比如 USB module: 它工作在內核的USB子系統之上, 而實際的USB設備可以是字符設備, 塊設備, 也可以是網絡接口. 模塊是如何加載到內核的 要支持模塊的動態加載,卸載, 在編譯內核時要注意: “Lodable Module Support" 中的相應選項要選上. 比如要支持加載模塊, 應該選上Enable loadable module support; 要支持卸載模塊, 要選上Module unloading ; 要支持強制卸載模塊, 要選上Forced module unloading! 1, When the kernel needs a feature that is not resident in the kernel, the kernel module daemon kmod (In earlier versions of linux, this was known as kerneld) execs modprobe to load the module in. modprobe is passed a string in one of two forms: · A module name like softdog or ppp. · A more generic identifier like char-major-10-30 傳遞給mdoprobe的參數爲模塊名時, 不需要加.ko的擴展名. 若傳遞給modprobe的是通用標誌符, 那麼modprobe通過查看etc/modprobe.conf知道通用標誌符對應的模塊名. modprobe.conf文件是特定於發行版的, 比如我的Ubuntu中, modprobe查看/etc/modprobe.d/aliases來將通用標誌符轉化爲模塊名. 2, 注意模塊也存在依賴性問題: 比如你要加載msdos.ko, 需要先加載fat.ko. modprobe查看/lib/module/version/modules.dep得知模塊的依賴關係. (version = uname -r). moules.dep由$ depmod -a 命令創建. 依賴其他模塊的模塊稱爲: "stacking modules" 3, 知道了依賴關係之後, mprobe先加載prerequisites模塊, 再加載模塊自身. 實際上, modprobe是通過調用insmod來加載這些模塊的! 有兩種加載模塊的方法, 以剛纔的msdos.ko爲例子: $ insmod /lib/modules/version/kernel/fs/fat/fat.ko $ insmod /lib/modules/version/kernel/fs/msdos/msdos.ko $ modprobe msdos modprobe和insmod的區別: 1, modprobe知道內核模塊默認的存在目錄(/lib/modules/version/), 而insmod不知道. 2, 調用modprobe時, 只需給出模塊名(不代,ko擴展), 而insmod需要給出完整路徑和模塊名. 3, modprobe自動解決依賴性. 而insmod需要指定加載內核的先後順序. 4, 由於modprobe假設要加載的模塊在默認目錄, 那麼若要加載在默認目錄之外的模塊, 就要調用insmod了. 發行版將modprobe, insmod, depmod打包到一起, 稱之爲Linux內核模塊管理工具, 針對2.4或以前的內核, 該工具名爲modutils, 2.6的爲module-init-tools. 通過$ lsmod 可以看到加載到內核中的模塊信息 也可以查看/proc/modules文件的內容. 實際上,lsmod讀命令就是通過查看/proc/modules的內容來顯示模塊信息的. 卸載模塊 使用 $ sudo rmmod mod_name 可以卸載模塊. 但內核有時候認爲卸載該模塊是不安全的, 此時可以使用 $ sudo rmmod -f mod_name來強制卸載模塊. 這裏都是介紹的加載,卸載模塊的命令, 至於模塊加載,卸載的原理. 參考" 模塊運行環境" 準備工作 模塊可以加載到當前運行的內核中, 也可以加載到另一個未運行的內核. 這裏暫時只考慮將模塊加載到啓動的內核中. 學習模塊編程, 先要重新編譯內核, 爲什麼要編譯內核的? 原因有二: 1, 我們使用的Linux發行版中的內核針對kernel.org的官方內核添加了許多補丁, 提供的內核頭文件並不完整, 內核API也可能被修改了, 要學習模塊編程, 最好使用官方內核編譯. 2, 發行版的內核中, 一般默認的CONFIG_MODVERSIONS被設置爲y. 這樣你在加載模塊時會由於版本問題失敗, 所以應該不設置CONFIG_MODVERSIONS. 參考 內核模塊編程之_初窺門徑 模塊程序組成 模塊程序設計有點類似於應用程序設計: 起碼模塊程序中有entry point和exit point. 並且模塊程序代碼位於獨立的文件中. 下面看看模塊程序組成. 模塊程序中至少要有兩個函數: 一個初始化函數, 它在模塊被加載到內核中的時候被調用, 一個退出函數(clean_up), 它在模塊被卸載的時候被調用. 有兩種方法定義上述的兩個函數, 推薦用後面的定義方法! int init_module(void) { ... } void cleanup_module(void) { ... } 在2.3.13版之後的內核, 可以用下面的方法來定義它們: static int hello_start(void) { ... } static void hello_end(void) { ... } module_init(hello_start); module_exit(hello_end); 1, 初始化函數和退出函數的定義. 初始化函數和退出函數應該是這樣的形式: static int funname_init(void); /* 模塊被加載時被調用 */ static void funname_exit(void); /* 模塊被卸載時被調用 */ 由於不向外輸出這些函數, 一般給它們加上static限定符號. 2, module_init和module_exit宏 funname_init()通過module_init()被註冊爲模塊的entry point. 同樣地, funname_exit()通過module_exit()宏被註冊爲模塊的exit point. 注意: 如果文件被編譯到靜態內核映像中, 退出函數不會被執行. 這些宏擴展必須位於相應函數定義之後! 3, 版權信息 從2.4版的內核起, 可以使用一些宏來聲明模塊的版權信息: MODULE_LICENSE()宏: 括號中的內容可以是下面的幾種形式: "GPL" [GNU Public License v2 or later] "GPL v2" [GNU Public License v2] "GPL and additional rights" [GNU Public License v2 rights and more] "Dual BSD/GPL" [GNU Public License v2 or BSD license choice] "Dual MPL/GPL" [GNU Public License v2 or Mozilla license choice] "Proprietary" [Non free products] 如果是MODULE_LICENSE("Proprietary"), 那麼你所編寫的模塊不是免費的, 內核社區將其視爲"污染"了內核, 不會理會相關的bug report, 而且, 不遵循GPL的模塊無法調用只針對GPL的符號(參考"輸出符號"的內容). 如果聲明雙重版權, 那麼在Linux上, 它與聲明爲GPL的效果是一樣的, 即Linux只在意GPL版權. 另外, MODULE_DESCRIPTION() 描述模塊的功能; MODULE_AUTHOR()描述模塊的作者; and MODULE_SUPPORTED_DEVICE() 聲明模塊所支持設備的類型. 在內核源碼樹的/include/linux/module.h中定義版權宏. 這些宏一般位於文件結尾. 4, 包含頭文件. 在模塊程序開頭需要包含頭文件: #include /* 所有的模塊文件都要包含 */ #include /* 若使用了優先級標誌,需包含 */ #include /* 若使用了宏, 需包含 */ 編譯內核模塊的方法與編譯一般應用程序的方法略有不同. 我們會發現在內核源碼樹的層層目錄中, 都存在有Makefile. 即這些Makefile是分層次組織的. 以往的內核版本中, 編譯模塊比較麻煩, 需要我們對這些Makefile做出許多更改. 2.6的內核採用了"kbuild"編譯系統, 簡化了這些問題. 關於kbuild, 可參考內核源碼樹中的 /Documentation/kbuild/modules.txt. 編譯之前, 肯定是需要源文件的. 這些源文件可以放在內核源碼樹中, 也可以放在內核源碼樹之外的任何地方. 根據源文件存在的目錄, 存在兩種編譯方法: 在源碼樹之中和在源碼樹之外. 在源碼樹中編譯模塊 官方內核模塊的源代碼都是按模塊(驅動)類型組織的, 我們到內核源碼樹的drivers目錄可以看到char, usb, block之類的子目錄. 那麼我們在內核源碼樹中添加文件時, 最好也遵循這些分類. 分類的規則自己靈活把握. 下面以前面的"hello, world"這個簡單的模塊爲例, 來看看如何在內核源碼樹中編譯模塊. 1, 不新建子目錄 (1) 先在內核源碼樹中的drivers目錄編輯一個c源程序, 名爲hello.c. (2) 修改drivers目錄的Makefile文件, 添加: obj-m += hello.o (3) 重新編譯內核(回到源碼樹根目錄, 運行 $ sudo make). 這樣, 在drivers目錄多出了這樣幾個文件: hello.mod.c, hello.mod.o, hello.o, hello.ko. hello.ko就是編譯出來的模塊了. 2, 新建子目錄 如果源文件比較多, 可以在drivers目錄中新建子目錄. 還是以hello, world爲例: (1) 在內核源碼樹的drivers目錄中新建一個hello子目錄, 並將hello.c放在hello目錄中. (2) 修改drivers目錄的Makefile文件, 添加: obj-m += hello/ (3) 在hello目錄中新建一個Makefile文件, 內容爲: obj-m += hello.o (4) 重新編譯內核(回到源碼樹根目錄, 運行 $ sudo make). 這樣, 新生成的模塊文件就位於hello目錄中. 若在內核源碼樹中編譯模塊, 如果不新建子目錄, 那麼只需修改當前目錄的Makefile, 否則應該在當前新建的子目錄中新建Makefile指定編譯選項, 並修改上層目錄的Makefile以讓kbuild能夠進入新建的子目錄. 在源碼樹之外編譯模塊 還是以上面的hello, world爲例. 在當前目錄有個hello.c: (1)首先在模塊代碼所在的目錄新建一個Makefile, 內容爲: obj-m := hello.o (2)這樣調用make命令: $ sudo make -C /usr/local/src/kernel/linux-2.6.16.20 SUBDIRS=$PWD modules 這裏/usr/local/src/kernel/linux-2.6.16.20是內核源碼樹所在的目錄. -C表示要求make先切換到-C指定的目錄. SUBDIRS(也可以用M代替SUBDIRS)使make在編譯模塊之前回到當前目錄. 整個編譯過程實際上是執行-C指定的內核源碼樹的Makefile, 並通過SUBDIR指定你要編譯的內核源文件的目錄. 簡化命令行輸入 每次調用make的時候輸入這些參數比較比較麻煩, 可以這樣來改寫Makefile以簡化: obj-m += hello.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 這樣, 只需在當前目錄調用 $ sudo make 就可以完成上面的工作. 調用 $ sudo make clean 將刪除所有新生成的文件. 上面的Makefile是這樣確定內核源碼樹所在的目錄的: 我們先到/lib/modules目錄, 會看到一些以內核版本爲名的目錄, 目錄中有一個build文件, 它是一個符號連接, 指向內核源碼樹. 那麼如何確定進入哪個內核版本的目錄呢? 這就可以通過 $ uname -r 來確定, 它指出了當前運行內核的版本. 還可以進一步簡化這個Makefile: obj-m := hello.o KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean 這樣不用在Makefile中一次又一次地指定內核代碼樹的目錄. 上面的例子中只討論了所有的代碼在一個文件中的情況. 若代碼分佈在多個源文件中, 比如file1.c, file2.c, 生成hello.ko. 應該這樣寫Makefile: obj-m := hello.o hello-objs := file1.o file2.o 注意, 雖然我們的目的是生成.ko文件, 但在Makefile中寫爲.o! 爲預編譯的內核編譯模塊 前面都討論的是針對當前運行的內核編譯模塊, 實際上, 也可以針對非當前運行的內核編譯模塊.比如, 我當前運行的內核版本是2.6.16, 但系統中還有2.6.12版的內核. 在不想重啓來運行2.6.12版內核的情況下, 如何針對2.6.12編譯模塊呢? 內核模塊 VS 應用程序 起始與結束 應用程序一般從main()開始, 它執行一些指令, 再結束(返回值). Every module must have an entry function and an exit function. 不同於應用程序, 模塊從初始化函數開始, 這個初始化函數名可以是init_module(), 也可以是你通過module_init宏註冊的其他函數名. 該初始化函數被稱爲模塊的入口函數. 入口函數的作用: 它告訴內核, 該模塊提供了哪些功能, 並設置內核以便在需要這些功能的時候調用模塊的函數. 這一過程完畢之後, 入口函數馬上返回, 直到內核需要調用模塊提供的函數時, 模塊纔開始被加載, 執行. 有入口, 就自然有出口. 出口函數可以是cleanup_module(), 也可以是你通過module_exit宏註冊的其他函數名. 出口函數"undo"入口函數執行的所有工作: 清除入口函數註冊的模塊功能. 可調用的函數 在應用程序或模塊程序中都可調用程序本身爲定義的函數. 不同的是: · 應用程序可調用C庫所提供的函數(比如libc提供的printf()). · 模塊程序只能調用內核所提供的函數(比如內核提供的printk()). 由於內核無法訪問C庫, 所以模塊程序中無法調用C庫中的函數. 好在內核本身提供了一些C庫中的函數或者替代函數. 查看 /proc/kallsyms , 可直到內核輸出了那些函數. 由於調用了函數本身未調用的外部函數, 所以應用程序和模塊程序都需要解析這些外部函數, 它們也互不相同: · 應用程序在linking階段, linker解析外部函數, 將C庫所提供的函數添加到對應位置. · 模塊程序在insmod時, insmod解析內核所提供的外部函數. 實際上, 上述的應用程序和模塊程序的函數調用還是有聯繫的. 讓我們來回憶一下庫函數和系統調用的關係. 庫函數可能最終調用一個系統調用(比如printf(), 會調用write()), 而系統調用實際上就是內核所輸出的函數! 寫一個hello, world的應用程序, 編譯它, 並運行 $ strace ./hello 看看它調用了那些系統函數! 用戶空間 VS 內核空間 用戶程序運行於用戶空間, 模塊運行於內核空間. 內核的功能之一就是實現資源資源分配, 資源包括CPU, 內存, 硬件資源... 在執行一個程序時, 內核爲程序分配資源, 爲了便於管理資源分配, 內核劃分了兩個運行級別: 管理員模式和用戶模式, 分別對應內核空間和用戶空間. 這兩個運行級別由CPU來實現, 比如: X86的CPU劃分了4個運行級別, 這些級別以ring命名. Linux只使用其中的兩個級別: 最高的爲管理員模式(ring0), 最低的爲用戶模式(ring3). 用戶模式與管理員模式的區別在於它們具有不同的內存映射: 用戶模式只能運行於受保護的用戶空間中,它所能訪問的資源是受限的. 而管理員模式運行於內核空間, 它能訪問所有資源. 以最終調用系統調用的庫函數爲例, 當庫函數調用系統調用時, 運行級別切換到管理員模式, 即程序運行於內核空間. 此時"內核代應用程序執行某些功能". 當系統調用返回時, 切換回用戶模式. 此時的進程上下文未變, 只是運行級別發生了改變. 實際上, 在Linux中, 處理器總是處於下列三個狀態之一: 1, 在內核空間中, 處於進程上下文, 內核代進程執行. 2, 在內核空間中, 處於中斷上下文, 運行中斷處理程序, 此時沒有對應的進程. 3, 在用戶空間中, 執行進程中的用戶代碼. 命名空間 ( Name Space) 不管是寫用戶程序還是些模塊程序, 都要注意變量的命名. 用戶程序: 寫比較小的C程序時, 你可以選擇你覺得方便的變量命名. 如果程序的規模比較大, 命名就要遵循一定的標準, 如果定義的全局變量中有重名的, 就會造成"命名空間污染 (namespace pollution). 所以在開發用戶程序時要注意變量的命名問題: 最好遵循一定的標準, 避免C語言的保留字. 模塊程序: 開發模塊程序的時候更要注意變量命名: 因爲所有的模塊都要和整個內核連接. 最好的方法是給你模塊代碼中所有的變量加上static限定符, 並給你的符號(symbol)添加合適的前綴. (所有的內核前綴應該是小寫!) 如果你不想聲明static, 你也可以聲明一個符號表, 並想內核註冊它. 符號表後面再討論. /proc/kallsysm列出了內核知道的符號, 而且模塊於內核共享代碼空間, 所以你可以在模塊程序中訪問它們. 代碼空間 ( Code Space) 要理解代碼空間就要理解Linux的內存管理. 理解MM("memeory management", 或者"美妹" ) 難度比較大, 這裏只涉及皮毛. 應用程序 當進程被創建時, 內核爲該進程分配物理內存. 這些內存被進程用以存放執行代碼, 變量, 堆棧... 該內存從0x00000000開始, 擴展到某個需要的地方. 每個進程只能訪問一個內存空間, 任何兩個進程的內存空間不能重疊. (這裏不討論訪問另一個進程的地址空間的情況). 注意, 進程可見的地址和內存實際的物理地址是不相同的! 比如, 某個進程訪問地址爲0xbffff978的內存時, 它所訪問的實際物理內存地址並不是該地址. 這都是內核的內存映射造成的. 它所訪問的實際內存地址可能是以0xbffff978爲名的一個索引, 它指向內存物理地址的某處. 內核 內核具有自己的內存空間. 由於內核能動態地加載到內核中或從內核中卸載, 模塊與內核共享代碼空間. 所以, 當模塊程序段錯誤時, 即引發整個內核的段錯誤, 所以編寫模塊程序的時候要格外小心溢出引發段錯誤! 設備名 字符設備通過文件系統中的文件名(設備名)來訪問. 這些設備名, 文件名有許多不同的, 可以互換的叫法, 比如: 設備節點, 節點文件, 設備文件... 在Linux中, 所有的設備都被視爲特殊文件. 它們儲存在文件系統中, 但是僅佔用文件目錄, 而不涉及存儲數據. 實際上, 它們只記錄了設備所屬的設備類別, 主設備號和從設備號等相關信息. 上述的設備名位於/dev目錄中, 可以通過ls -l 命令來查看它們: $ ls -l sda[1-3] 顯示下列信息: brw-rw---- 1 root disk 8, 1 2006-06-14 20:26 /dev/sda1 brw-rw---- 1 root disk 8, 2 2006-06-14 20:26 /dev/sda2 brw-rw---- 1 root disk 8, 3 2006-06-14 12:30 /dev/sda3 上述的命令列出了系統中SCSI硬盤的信息. 注意這些內容與列出一般文件的區別: (1) 開頭是b, 表示是塊文件(本章討論的是char設備, 自然以c開頭) (2) 對於設備文件, 在最終修改時間之前列出的是以逗號分隔的兩個數字, 而不是一般文件的文件大小. 主, 從設備號 Linux利用設備號來標識設備文件, 設備號分爲主設備號和從設備號. 上述的逗號分隔的兩個數字就分別是主設備號和從設備號. 主設備號: 標識設備對應的驅動程序. 每個在內核中的設備驅動程序(包括編譯進內核的和動態加載的)都有一個唯一的主設備號對應, 內核根據主設備號將設備和相應的設備驅動程序對應起來. 從設備號: 內核使用從設備號來區分具體的物理設備. 實際上, 次設備號只有設備驅動程序使用, 內核的其它部分對它一無所知: 從設備號只是用在驅動程序中來指示某個物理設備. 不同類型的設備文件, 其設備號是獨立的. 關於設備號的分配, 可參考內核源碼樹的Documentation/devices.txt. 設備號的內部表徵 2.6以前的內核將主, 從設備號限定在255以內, 2.6的內核沒有了這一限制. 在2.6.0及以後版的內核中, 主, 從設備號位於一個dev_t類型的32位數中. (dev_t位於/usr/include/linux/types.h中). 前12位用於主設備號, 後20位用於從設備號. 但這一約定可能會隨着版本的更新而改變, 所以在獲取主,從設備號時, 不要對dev_t類型的組織作任何假設, 而應使用這兩個宏: MAJOR(dev_t dev); MINOR(dev_t dev); 同樣, 使用下面的宏來將主, 從設備號轉化爲一個設備: MKDEV(int major, int minor); 上述宏位於 /usr/include/linux/kdev_t.h中. 分配, 釋放設備號 向系統增加一個驅動程序時, 首先要賦予它設備號. 一旦設備註冊到內核表中, 無論何時操作與設備驅動程序的主設備號匹配的設備文件, 內核都會調用驅動程序中的函數. 分配設備號: int register_chrdev_region(dev_t first, unsigned int count, char *name); 返回: 0 if 成功分配. 負數 if 失敗. first : 你想要分配的第一個設備號, 一般它的從設備號爲0. count: 分配設備號(分配的設備號爲連續的)個數. name: 用來與設備號對應的設備名, 它位於/proc/devices和sysfs中. register_chrdev_region適用於你預先知道需要分配哪個設備號的情況. 然而一般來說, 你無法提前知道設備將使用那個主設備號. 這種情況下就應該使用下面的函數: int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); 返回: 0 if 成功分配. 負數 if 失敗. 在該函數中, count 和 name與前面的函數相同. *dev : 輸出成功分配的設備號中的第一個設備號. firstminor : 分配的第一個從設備號, 一般爲0. 分配設備號有兩種選擇, 但釋放設備號只能調用下面的函數: void unregister_chrdev_region(dev_t first, unsigned int count); count於前兩個函數中的count都是相同的, first與dev_t first相同, 但和dev_t *dev是如何對應的呢? 一般在模塊的清理函數中調用它. 動態分配主設備號 有兩個選擇來分配主設備號: 靜態地, 即直接使用內核源碼數的Documentation/devices.txt中定義的未使用的主設備號. 它對應於register_chrdev_region函數. 動態地: 即動態分配一個主設備號.它對應於alloc_chrdev_region函數. 推薦使用動態分配的方法, 這樣你就能在加載甚至編譯模塊的時候設定主設備號. 動態分配有個缺點: 你無法在分配設備號之前創造設備節點. 但一旦設備號被分配之後, 你可以通過/proc/devices來讀取它. 你還可以通過sysfs來獲取更多的設備信息. 2.6版內核的sysfs被掛載到/sys目錄. awk資料: http://www.linuxjournal.com/article/8913 本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u2/60434/showart_471552.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章