10_註冊字元類裝置號

爱吃冰激凌的黄某某發表於2024-04-22

註冊字元類裝置號

字元裝置號簡介

​ Linux 的裝置管理是和檔案系統緊密結合的, 各種裝置都以檔案的形式存放在/dev 目錄下, 稱為裝置檔案。 應用程式可以開啟、 關閉和讀寫這些裝置檔案, 完成對裝置的操作, 就像操作普通的資料檔案一樣。

​ 為了管理這些裝置, 系統為裝置編了號, 每個裝置號又分為主裝置號和次裝置號。 主裝置號用來區分不同型別的裝置, 而次裝置號用來區分同一型別的多個裝置。

裝置號的組成

一個字元裝置或者塊裝置都有一個主裝置號和次裝置號。 主裝置號和次裝置號統稱為裝置號。 主裝置號用來表示一個特定的驅動程式。 次裝置號用來表示使用該驅動程式的各個裝置。

1.字元裝置和雜項裝置的區別(複習)

雜項裝置的主裝置號是固定的,固定為10,那麼我們要學習的字元類裝置就需要自己或者系統來給我們分配了。

雜項裝置可以自動生成裝置節點,字元裝置需要我們自己生成裝置節點。

2.註冊字元類裝置號的倆個方法。

標頭檔案 #include <linux/fs.h>

靜態分配裝置號

​ 靜態分配裝置號, 就是驅動程式開發者透過靜態指定一個裝置號。 對於一部分常用的裝置, 核心開發者已經為其分配了裝置號。 這些裝置號可以在核心原始碼 documentation/ devices.txt 檔案中找到。 如果只有開發者自己使用這些裝置驅動程式, 那麼其可以選擇一個尚未使用的裝置號。 在不新增新硬體的時候, 這種方式不會產生裝置號衝突。 但是當新增新硬體時, 則很可能造成裝置號衝突, 影響裝置的使用。 使用“cat /proc/devices”命令即可檢視當前系統中所有已經使用了的主裝置號, 如圖所示:

image-20240422173733707

裝置號的靜態申請函式如下所示:

register_chrdev_region(dev_t, unsigned, const char *);

指定一個裝置號, 需要明確知道我們的系統裡面那些裝置號沒有用。

image-20240422235502074

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 *);image-20240422205517129

image-20240422205536129

3.登出裝置號

我們使用的是

unregister_chrdev_region(dev_t, unsigned);

登出字元裝置之後要釋放掉裝置號, 裝置號釋放函式如下:

image-20240422205643006

示例

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

相關文章