聲明:內容搬自阿三哥網站,只是翻譯了一下。侵刪。https://embetronicx.com/tutorials/linux/device-drivers/
正文如下:
這是“linux設備驅動系列”教程的續集,並且接着討論字符驅動程序及其實現。本系列的目的是提供簡單實用的示例,使每個人都能以簡單的方式理解這些概念。現在讓我們即將學習“linux設備驅動第四部分:字符設備驅動major號 & minor號”。
在之前的教程中,我們討論了如何在加載時向Linux設備驅動程序傳遞參數。所以現在讓我們來學習有關major號和minor號的知識。
內容速覽
·1 簡介
·2 應用程序如何與硬件設備通信?
·4.1 major號
·5.1 靜態分配
·1 簡介
我們已經知道了什么是驅動程序,以及為什么我們需要驅動程序。字符驅動程序有什么特殊的嗎?如果我們編寫面向字節操作的驅動程序,我們就將其視為字符驅動程序。由於大多數設備都是面向字節的,大多數設備驅動程序都是字符設備驅動程序。例如,串行驅動程序,音頻驅動程序,視頻驅動程序,攝像頭驅動程序,以及基本I/O驅動程序。事實上,
設備驅動程序中,既不是存儲設備驅動程序,也不是網絡設備驅動程序的話,就屬於字符驅動程序啦。
·2 應用程序如何與硬件設備通信?
下圖展示了通信的完整路徑。
·首先,應用程序會打開設備文件。這個設備文件是設備驅動程序創建的。
·然后這個設備文件會根據major號和minor號來找到對應的設備驅動程序。
·然后找到的這個設備驅動程序就可以跟硬件設備交流。
Linux內核的基本特性之一就是抽象對設備的處理。所有的硬件設備看起來都像是普通文件;可以使用相同的,標准的,用來操作文件的系統調用(system call)來打開(open),關閉(close),讀(read),寫(write)他們。對於Linux來說,一切皆文件。往硬盤寫入數據時,其實是往文件寫入。從鍵盤讀取數據時,其實是向文件讀取。保存磁帶設備的備份時,其實是往文件里寫入。甚至讀取內存數據時也是從文件中讀取。假如你想讀或寫的文件是一個普通文件,過程非常好理解:文件被打開,然后你讀取或者寫入數據。設備驅動程序與普通文件也差不多。驅動程序會為每個硬件設備創建一個特殊文件(與普通文件相對)。我們通過這些特殊文件(設備文件)與硬件溝通。
如果你想創建一個特殊文件,我們得了解設備驅動程序的major號與minor號相關的知識。在此節教程中我們會了解major和minor號。
·4 major號與minor號
Linux內核用一對數字來表示字符和塊設備 <major> : <minor>。
·4.1 major號
傳統來說,major號用來標識與設備相關聯的驅動程序。一個major可以被許多設備驅動程序共享。在 /proc/devices 中我們可以看到,在一個運行中的Linux實例是怎么分配major號的。
這些數字是major號。
·4.2 minor號
major號用來標識相應的驅動程序。許多設備使用相同的major號。所以我們需要為使用相同major號的設備單獨再分配一個數字。這就是minor號啦。換句話說,設備驅動程序使用minor號<minor>來辨別單個物理或邏輯設備。
·5 分配major號與minor號
我們有兩種方法分配major號和minor號。
·靜態分配
·動態分配
·5.1 靜態分配
假如你想為你的驅動程序設置一個特別的major號,你可以使用這種方法。如果major號可用的話,這種方法會將major號分配給你。假如不可用,就分配失敗。
int register_chardev_refion(dev_t first, unsigned int count, char *name);
first 是你想要分配設備號范圍的起始數字。
count 是你請求的連續設備號的總數。請注意,如果count過大,你請求的范圍會溢出至下一個major號;但是只要你請求的數字范圍可用,一切都可以正常工作。
name 是與請求的設備號范圍([first, first+count])相關聯的設備名稱。它將會在 /proc/devices 和 sysfs 目錄中出現。
申請成功的話,函數 register_chrdev_region 的返回值為零。出錯的話,會返回一個負的錯誤碼,並且不能訪問所申請的設備號范圍。
dev_t 數據類型(在<linux/types.h>中定義)用來存儲設備號的major和minor部分。dev_t 是32位的數據,12bits用於表示major號,20bits用來表示minor號。
假如你想創建dev_t結構體變量表示你的major號和minor號,請使用下面這個函數。
MKDEV(int major, int minor);
假如你想從dev_t變量中獲取major號和minor號,請使用以下方法。
MAJOR(dev_t dev);
MINOR(dev_t dev);
如果你將dev_t變量傳入MAJOR 或者MINOR函數,他會返回驅動程序的major號/minor號。
舉個栗子,
dev_t dev = MKDEV(235, 0); register_chrdev_region(dev, 1, "Embetronicx_Dev");
·5.2 動態分配
假如我們不想要固定的major號和minor號請使用這種方法。這種方法會為驅動程序動態分配可用的major號。
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
dev 是一個輸出參數,成功執行時會返回分配范圍的第一個值。
firstminor 應為請求使用的第一個minor號,通常為0。
count 為您請求的連續設備號總數。
name 時與分配的設備號相關聯的設備名稱。他會在 /proc/devices 和 sysfs 中出現。
·5.3 靜態和動態方法的區別
靜態方法只有在你提前知道你想從哪一個major號開始時真的有用。使用靜態方法時,你告訴內核你想要的設備號(起始major/minor號以及count)然后內核分配給你或不分配(取決於這些設備號是否可用)。
使用動態方法時,你告訴內核你需要多少設備號(起始minor號以及count)然后他會為你找到一個起始major號,如果他是可用的。
有些人為了避免與其他設備驅動程序產生沖突,會優先選擇使用動態方法函數,它會為你動態分配設備號。
動態分配的缺點就是你不能提前創建設備節點,因為分配給你的major號可能會變化。對於驅動程序的正常使用來說,這不算是個問題。因為一旦分配了設備號,你可以從 /proc/devices 中讀取。
·6 注銷major號與minor號
不論你是怎樣分配你的設備號的,當你不再使用他們的時候應該釋放掉。設備號使用下面函數釋放:
void unregister_chrdev_region(dev_t first, unsigned int count);
通常將unregister_chrdev_region放在模塊的退出函數中。
·7 靜態分配major號程序示例
從GitHub中獲取源代碼
在此程序中,我指定235作為major號。
/***************************************************************************//** * \file driver.c * * \details Simple linux driver (Statically allocating the Major and Minor number) * * \author EmbeTronicX * * *******************************************************************************/ #include<linux/kernel.h> #include<linux/init.h> #include<linux/module.h> #include <linux/fs.h> //creating the dev with our custom major and minor number dev_t dev = MKDEV(235, 0); /* ** Module Init function */ static int __init hello_world_init(void) { register_chrdev_region(dev, 1, "Embetronicx_Dev"); printk(KERN_INFO "Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev)); printk(KERN_INFO "Kernel Module Inserted Successfully...\n"); return 0; } /* ** Module exit function */ static void __exit hello_world_exit(void) { unregister_chrdev_region(dev, 1); printk(KERN_INFO "Kernel Module Removed Successfully...\n"); } module_init(hello_world_init); module_exit(hello_world_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("EmbeTronicX <embetronicx@gmail.com>"); MODULE_DESCRIPTION("Simple linux driver (Statically allocating the Major and Minor number)"); MODULE_VERSION("1.0");
·使用makefile生成驅動程序(sudo make)
·使用 sudo insmode 加載驅動程序
·使用 cat /proc/devices 檢查major號
linux@embetronicx-VirtualBox::/home/driver/driver$ cat /proc/devices | grep "Embetronicx_Dev" 235 Embetronicx_Dev
·8 動態分配major號程序示例
從GitHub中獲取源代碼
此程序會動態分配一個major號。
/***************************************************************************//** * \file driver.c * * \details Simple linux driver (Dynamically allocating the Major and Minor number) * * \author EmbeTronicX * * *******************************************************************************/ #include<linux/kernel.h> #include<linux/init.h> #include<linux/module.h> #include<linux/kdev_t.h> #include<linux/fs.h> dev_t dev = 0; /* ** Module Init function */ static int __init hello_world_init(void) { /*Allocating Major number*/ if((alloc_chrdev_region(&dev, 0, 1, "Embetronicx_Dev")) <0){ printk(KERN_INFO "Cannot allocate major number for device 1\n"); return -1; } printk(KERN_INFO "Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev)); printk(KERN_INFO "Kernel Module Inserted Successfully...\n"); return 0; } /* ** Module exit function */ static void __exit hello_world_exit(void) { unregister_chrdev_region(dev, 1); printk(KERN_INFO "Kernel Module Removed Successfully...\n"); } module_init(hello_world_init); module_exit(hello_world_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("EmbeTronicX <embetronicx@gmail.com>"); MODULE_DESCRIPTION("Simple linux driver (Dynamically allocating the Major and Minor number)"); MODULE_VERSION("1.1");
·使用makefile生成驅動程序(sudo make)
·使用 sudo insmode 加載驅動程序
·使用 cat /proc/devices 檢查major號
linux@embetronicx-VirtualBox::/home/driver/driver$ cat /proc/devices | grep "Embetronicx_Dev" 243 Embetronicx_Dev
函數分配的major號243給這個驅動程序。
在卸載驅動程序之前使用 ls /dev/ 檢查下/dev下的文件。你找不到我們的驅動程序文件。因為我們現在還沒有創建它呢。在下一小節的教程里,我們會看到設備文件。
---------------------分割線-------------------