註冊字元類裝置號
字元裝置號簡介
Linux 的裝置管理是和檔案系統緊密結合的, 各種裝置都以檔案的形式存放在/dev 目錄下, 稱為裝置檔案。 應用程式可以開啟、 關閉和讀寫這些裝置檔案, 完成對裝置的操作, 就像操作普通的資料檔案一樣。
為了管理這些裝置, 系統為裝置編了號, 每個裝置號又分為主裝置號和次裝置號。 主裝置號用來區分不同型別的裝置, 而次裝置號用來區分同一型別的多個裝置。
裝置號的組成
一個字元裝置或者塊裝置都有一個主裝置號和次裝置號。 主裝置號和次裝置號統稱為裝置號。 主裝置號用來表示一個特定的驅動程式。 次裝置號用來表示使用該驅動程式的各個裝置。
1.字元裝置和雜項裝置的區別(複習)
雜項裝置的主裝置號是固定的,固定為10,那麼我們要學習的字元類裝置就需要自己或者系統來給我們分配了。
雜項裝置可以自動生成裝置節點,字元裝置需要我們自己生成裝置節點。
2.註冊字元類裝置號的倆個方法。
標頭檔案 #include <linux/fs.h>
靜態分配裝置號
靜態分配裝置號, 就是驅動程式開發者透過靜態指定一個裝置號。 對於一部分常用的裝置, 核心開發者已經為其分配了裝置號。 這些裝置號可以在核心原始碼 documentation/ devices.txt 檔案中找到。 如果只有開發者自己使用這些裝置驅動程式, 那麼其可以選擇一個尚未使用的裝置號。 在不新增新硬體的時候, 這種方式不會產生裝置號衝突。 但是當新增新硬體時, 則很可能造成裝置號衝突, 影響裝置的使用。 使用“cat /proc/devices”命令即可檢視當前系統中所有已經使用了的主裝置號, 如圖所示:
裝置號的靜態申請函式如下所示:
register_chrdev_region(dev_t, unsigned, const char *);
指定一個裝置號, 需要明確知道我們的系統裡面那些裝置號沒有用。
Linux 提供了一個名為dev_t 的資料型別表示裝置號, dev_t 定義在檔案include/linux/types.h 裡面, 定義如下:
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
dev_t 是個 32 位的變數, 其中 12 位用來表示主裝置號, 20 位用來表示次裝置號。 因此 Linux 系統中主裝置號範圍為 0~4095, 所以大家在選擇主裝置號的時候一定 不要超過這個範圍。 在檔案include/linux/kdev_t.h 中提供了幾個操作裝置號的宏定義, 如下所示:
Linux 提供了幾個宏定義來操作裝置號:
#define MINORBITS20 //次裝置號的位數, 一共是 20 位
#define MINORMASK ((1U << MINORBITS) - 1) //次裝置號的掩碼
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //在 dev_t 裡面獲取我們的主裝置號
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //在 dev_t 裡面獲取我們的次裝置號
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //將我們的主裝置號和次裝置號組成一個 dev_t 型別。第一個引數是主裝置號, 第二個引數是次裝置號
動態分配 裝置號
由於靜態分配裝置號可能存在衝突問題, 因此建議使用動態分配裝置號。 在註冊字元裝置之前先申請一個裝置號, 系統會自動給你一個沒有被使用的裝置號, 這樣就避免了衝突。 解除安裝驅動的時候釋放掉這個裝置號即可, 裝置號的動態申請函式如下:
alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
3.登出裝置號
我們使用的是
unregister_chrdev_region(dev_t, unsigned);
登出字元裝置之後要釋放掉裝置號, 裝置號釋放函式如下:
示例
chrdev.c
#include <linux/init.h> //包含宏定義的標頭檔案
#include <linux/module.h> //包含初始化載入模組的標頭檔案
#include <linux/kdev_t.h>
#include <linux/fs.h>
#define DEVICE_NUMBER 1 // 定義次裝置號的個數
#define DEVICE_SNAME "schrdev" // 定義靜態註冊裝置的名稱
#define DEVICE_ANAME "achrdev" // 定義動態註冊裝置的名稱
#define DEVICE_MINOR_NUMBER 0 // 定義次裝置號的起始地址
static int major_num; // 主裝置號
static int minor_num; // 次裝置號
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);
static int hello_init(void)
{
dev_t dev_num; // 裝置號
int ret;
if (major_num)
{
dev_num = MKDEV(major_num, minor_num); // 主裝置號和次裝置號組成一個 dev_t 裝置號
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); //靜態分配裝置號
if(ret < 0)
{
printk("register_chrdev_region is error\n");
}
printk("register_chrdev_region is ok\n");
printk("major_num = %d\n", major_num);
printk("minor_num = %d\n", minor_num);
}
else
{
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME); //動態分配裝置號
if(ret < 0)
{
printk("alloc_chrdev_region is error\n");
}
printk("alloc_chrdev_region is ok\n");
major_num = MAJOR(dev_num); //在 dev_t 裡面獲取我們的主裝置號
minor_num = MINOR(dev_num); //在 dev_t 裡面獲取我們的次裝置號
printk("major_num = %d\n", major_num);
printk("minor_num = %d\n", minor_num);
}
return 0;
}
static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER); //登出裝置號
printk("byby\n"); // 核心模組解除安裝的時候列印"byb byb
}
module_init(hello_init); // 驅動模組的入口
module_exit(hello_exit); // 驅動模組的出口
MODULE_LICENSE("GPL"); // 宣告模組擁有開源許可證
Makefile
obj-m +=chrdev.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga
PWD?=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules