Linux デバイス ドライバー モデル キャラクター デバイス

Linux デバイス ドライバー モデル キャラクター デバイス

Linux デバイス ツリーについては以前紹介しましたが、今回はキャラクター デバイス ドライバーについて紹介します。キャラクタ デバイスは IO 送信中にキャラクタ単位でキャラクタを送信するデバイスであり、キャラクタ デバイス ドライバはキャラクタ デバイス ドライバを駆動できるコードの一部です。現在の Linux におけるキャラクタ デバイス ドライバはどのようなものですか?それについて学びましょう。

基本知識

キャラクターデバイスフレームワーク

キャラクターデバイスのフレームワークをデザインするように頼まれたら、どのようにデザインしますか? 開発者によってニーズは異なりますが、誰もがキャラクター デバイスを登録する必要があり、これらのデバイス ドライバー情報を保存および管理する場所が必要です。コード内に保存する方法は、より柔軟かつ適切になるでしょうか。

キャラクタデバイスを登録する

現在の Linux カーネルは、メジャー デバイス番号とマイナー デバイス番号を通じてドライバーを定義します。メジャー デバイス番号の範囲は 0 ~ CHRDEV_MAJOR_MAX(512) - 1 (合計 512 個のメジャー デバイス番号)、マイナー デバイス番号の範囲は 0 ~ 2 20 - 1 (MINORMASK 定義)。したがって、キャラクター デバイス ドライバーの最初のステップは、デバイス番号をカーネルに登録することです。

#define DAO_NAME        "dao"
static dev_t            dao_devt;

// 注册字符设备函数调用
alloc_chrdev_region(&dao_devt, 0, MINORMASK + 1, DAO_NAME);	//注册字符设备号

/** 注册字符设备函数声明,通过下面我们可以知道,
 * dev 是保存主设备号,
 * baseminor 则是代表可以从该索引开始查找可使用的次设备号
 * count 代表次设备号可搜索的范围
 * name 则是该字符设备的名称
 */
/**
 * alloc_chrdev_region() - register a range of char device numbers
 * @dev: output parameter for first assigned number
 * @baseminor: first of the requested range of minor numbers
 * @count: the number of minor numbers required
 * @name: the name of the associated device or driver
 *
 * Allocates a range of char device numbers.  The major number will be
 * chosen dynamically, and returned (along with the first minor number)
 * in @dev.  Returns zero or a negative error code.
 */
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
                        const char *name);

そして、alloc_chrdev_region() はどのようにしてキャラクターデバイス番号を登録するのでしょうか? その実装を見てみましょう。


/*
 * Register a single major with a specified minor range.
 *
 * If major == 0 this function will dynamically allocate an unused major.
 * If major > 0 this function will attempt to reserve the range of minors
 * with given major.
 *
 */
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
			   int minorct, const char *name)
{
    
    
	struct char_device_struct *cd, *curr, *prev = NULL;
	int ret;
	int i;

    /* 先检查需要申请的主设备号是否在合理范围之内:0 ~ CHRDEV_MAJOR_MAX(512) - 1 */
	if (major >= CHRDEV_MAJOR_MAX) {
    
    
		pr_err("CHRDEV \"%s\" major requested (%u) is greater than the maximum (%u)\n",
		       name, major, CHRDEV_MAJOR_MAX-1);
		return ERR_PTR(-EINVAL);
	}

    /* 再检查次设备号是否在合理范围之内:0 ~ MINORMASK */
	if (minorct > MINORMASK + 1 - baseminor) {
    
    
		pr_err("CHRDEV \"%s\" minor range requested (%u-%u) is out of range of maximum range (%u-%u) for a single major\n",
			name, baseminor, baseminor + minorct - 1, 0, MINORMASK);
		return ERR_PTR(-EINVAL);
	}

    /* 申请一块内存,该内存将是保存字符设备信息的,内核通过结构体(struct char_device_struct)来存储该信息*/
	cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
	if (cd == NULL)
		return ERR_PTR(-ENOMEM);

	mutex_lock(&chrdevs_lock);

    /* 如果调用该函数时传递进来的主设备号为0,则代表内核动态分配主设备号,
     * 此时通过 find_dynamic_major() 搜索可用的主设备号
     */
	if (major == 0) {
    
    
		ret = find_dynamic_major();	/* 动态搜索可用的主设备号 */
		if (ret < 0) {
    
    
			pr_err("CHRDEV \"%s\" dynamic allocation region is full\n",
			       name);
			goto out;
		}
		major = ret;	/* 保存搜索到的主设备号到major */
	}

	ret = -EBUSY;
	i = major_to_index(major);
    /* 确认主、次设备号是否可用 */
	for (curr = chrdevs[i]; curr; prev = curr, curr = curr->next) {
    
    
		if (curr->major < major)
			continue;

		if (curr->major > major)
			break;

		if (curr->baseminor + curr->minorct <= baseminor)
			continue;

		if (curr->baseminor >= baseminor + minorct)
			break;

		goto out;
	}

    /* 保存字符设备的主、次设备号、设备名称 */
	cd->major = major;
	cd->baseminor = baseminor;
	cd->
        
        = minorct;
	strlcpy(cd->name, name, sizeof(cd->name));

	/* 将新注册的字符设备添加到chrdevs */
	if (!prev) {
    
    
		cd->next = curr;
		chrdevs[i] = cd;
	} else {
    
    
		cd->next = prev->next;
		prev->next = cd;
	}

	mutex_unlock(&chrdevs_lock);
    /* 返回配置了设备号的char_device_struct */
	return cd;
out:
	mutex_unlock(&chrdevs_lock);
	kfree(cd);
	return ERR_PTR(ret);
}

alloc_chrdev_region では、利用可能なメジャー デバイス番号の検索と、デバイス情報の chrdev への保存が行われます。

上で質問したのは、このキャラクター デバイス ドライバー フレームワークを設計するとしたら、どのように設計しますか? カーネルはどのように設計しますか?

// fs/char_dev.c

#define CHRDEV_MAJOR_HASH_SIZE 255

static struct char_device_struct {
    
    
        struct char_device_struct *next;
        unsigned int major;
        unsigned int baseminor;
        int minorct;
        char name[64];
        struct cdev *cdev;              /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

カーネルは、キャラクタ デバイス情報を格納するために呼び出されるポインタ配列を定義しており、このポインタは構造体chrdevsを指します。struct char_device_structchrdevs には合計 255 のメンバーがあり、メジャー デバイス番号によって並べ替えられています。ではfind_dynamic_major()、使用可能なメジャー デバイス番号の検索は、chrdevs 配列の末尾から CHRDEV_MAJOR_DYN_END (234) まで始まります。要素が空の場合は、有効なメジャー デバイス番号を見つけるためにインデックスが直接返されます。簡単に言うと、234から254までを後ろから前に検索するというもので、配列要素が空の場合は配列のインデックスをメジャーデバイス番号として返します。動的割り当て (234 254) が見つからない場合は、314 から 511 まで検索を開始します。chrdevs には 255 要素しかないことに注意してください。そのため、カーネルはメジャーmajor_to_indexデバイス番号を、実際には chrdevs の要素である cherdev のインデックスに変換します。 2 つのキャラクター デバイスによって共有されます。たとえば、メジャー デバイス番号 59 のデバイスとメジャー デバイス番号 314 のデバイスは、chrdevs 要素 chrdevs[59] を共有します。このとき、chrdevs要素が空かどうかを確認し、空の場合はそのまま戻り、要素内のメインデバイスが埋まっている場合は次のインデックスのchrdevs要素を検索します。

static int find_dynamic_major(void)
{
    
    
        int i;
        struct char_device_struct *cd;

    	/* 234~254是否为空闲的,为空闲则返回 */
        for (i = ARRAY_SIZE(chrdevs)-1; i >= CHRDEV_MAJOR_DYN_END; i--) {
    
    
                if (chrdevs[i] == NULL)
                        return i;
        }

    	/*234~254已经被占用了,从314~511开始查找 */
        for (i = CHRDEV_MAJOR_DYN_EXT_START;
             i >= CHRDEV_MAJOR_DYN_EXT_END; i--) {
    
    
                for (cd = chrdevs[major_to_index(i)]; cd; cd = cd->next)
                    	/* 确认chrdevs元素对应的大于255的主设备号已经被占用了,
                    	 * 则从再下一个主设备开始查找,该设备号已经被占用
                    	 */
                        if (cd->major == i)	
                                break;

                if (cd == NULL)
                        return i;
        }

        return -EBUSY;
}

つまり、各 chrdevs 要素は 2 つのメジャー デバイス番号を表すことができ、1 つは要素のインデックス i で、もう 1 つは i+255 です。両方を使用した場合、検索は次の要素からのみ続行できます。

概要:

カーネル キャラクタ デバイスは、グローバル静的ポインタ配列 chrdevs を通じてキャラクタ デバイス情報を保存します。配列には合計 255 個の要素があります。各要素は 2 つの異なるメジャー デバイス番号のキャラクタ デバイス情報を保存できます。したがって、1 つのカーネルは 512 個の異なるメジャー デバイスに適用できます数字の文字と装備。

ドライバーがalloc_chrdev_region()デバイス番号を登録するために呼び出した後、端末は次の情報を確認できるようになります。

root@root:/# cat /proc/devices
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  7 vcs
 10 misc
 13 input
 89 i2c
 90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
247 ubi0
248 ttyS
249 hidraw
250 rpmb
251 dao		// 这里就是我们注册的字符设备,可以看到它的主设备号是251,名字叫dao
252 watchdog
253 rtc
254 gpiochip

Block devices:
  8 sd
 31 mtdblock
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
254 ubiblock
259 blkext

ただし、現時点ではこのキャラクター デバイスには操作を追加していません。操作を追加する方法を引き続き見てみましょう。

キャラクターデバイスの初期化

上記では、カーネルにキャラクタデバイス番号を登録するように要求しましたが、まだデバイス番号との関係が確立されていません。

static dev_t            dao_devt;	// 上面注册得到的主设备号
static struct cdev      dao_cdev;	// 字符设备结构体
static const struct file_operations dao_fops = {
    
    	// 字符设备操作集
}

cdev_init(&dao_cdev, &dao_fops);	// 初始化字符设备结构体,并更新其文件字符操作集
cdev_add(&dao_cdev, dao_devt, MINORMASK + 1);	// 将主设备号devt与字符设备 cdev 建立关系,实际上就是更新cdev中的主设备号成员信息。同时在cdev_add中,cdev信息保存到指针数组cdev_map中。

構造体ファイル操作

file_operations は何をするのでしょうか? Linux ではすべてのデバイスがファイルです。ファイルには、開く、読み取る、書き込む、閉じるなどのさまざまな操作があります。上記の操作はデバイスによって異なるため、デバイス ドライバーをカスタマイズする必要があります。デバイスの操作を増やすことで、アプリケーション層がデバイスを正しく使用できるようになります。

上記の操作の後、キャラクタ デバイスは基本的な初期化を完了しました。/proc/devices登録されたキャラクタ デバイス番号が に表示され、デバイス操作用の file_operations も設定されています。この時点で、ユーザー空間はすでにデバイス上で操作を実行できます。操作する。

上記の操作を完了すると、/dev ディレクトリに該当する文字ノードが存在しないことがわかります。現時点では、このようなコマンドを通じてのみノードmknod /dev/dao c 251 0を作成できます。実行すると、 open、read、 file_operations の関数と release 関数が順番に呼び出されます。/dev/daocat /dev/dao

では、/dev ディレクトリへのノードの登録を完了するには何をする必要があるのでしょうか?

デバイスノードの作成

ノードの作成を紹介する前に、まずクラスについて理解しましょう。カーネルでは、xxx_class という文字がよく見られますが、カーネルはデバイスをキャラクター デバイス、ブロック デバイス、ネットワーク デバイス、さらにクラスに分割します。同じ特性を持つデバイスは同じクラスに属し、自分でクラスを作成すると、デバイスは特定のクラスに属します。さて、上記でキャラクターデバイスの初期化は完了しましたが、クラスへのバインドは完了していません。したがって、デバイス ノードを作成するには、まずクラスを作成します。

#define DAO_CLASS_NAME  "dao"
static struct class     *dao_class;

/* 创建一个class,此时在/sys/class目录下看到一个叫dao的文件夹,这个就是我们注册的class */
dao_class = class_create(THIS_MODULE, DAO_CLASS_NAME); 

次に、デバイスを作成します。

static struct device    *dao_dev;

dao_dev = device_create(dao_class, NULL, dao_devt, NULL, DAO_NAME);

函数声明:
struct device *device_create(struct class *class, struct device *parent,
                             dev_t devt, void *drvdata, const char *fmt, ...);

device_create を通じて、キャラクターデバイス番号 dao_devt を dao_class にバインドし、デバイスを作成します。device_create 関数の宣言により、デバイスの作成時に、クラス、親デバイス、デバイス番号、デバイスのプライベート データ、およびデバイス名を渡すことができることがわかります。

device_create は主に次の操作を実行します。

  • struct device構造体の初期化を完了します。
  • 新しいデバイスがシステムに追加されたことをプラットフォームの他のバスに通知します。
  • デバイスイベントノードを作成します。
  • デバイスとクラス間のリンクを確立します。
  • 他のデバイスノード情報を追加します。
  • バスの場合はバスを追加します。
  • デバイス attr_dev を作成します。
  • sys ディレクトリにデバイス ノードを作成します。
  • dev ディレクトリにデバイス ノードを作成します。
  • バスのプローブをトリガーします。

上記の操作が完了すると、デバイスは対応する登録を完了し、ユーザー空間はデバイスを正常に動作させることができます。

フレームワークアーキテクチャ図

ここに画像の説明を挿入します

ルーティーン

// SPDX-License-Identifier: GPL-2.0+
/*
 * dao char device test code
 *
 * Copyright (c) 2022, dao. All rights reserved.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/cdev.h>
#include <linux/sysfs.h>
#include <linux/fs.h>


#define DAO_NAME	"dao"
#define DAO_CLASS_NAME	"dao"

static dev_t		dao_devt;
static struct class	*dao_class;
static struct cdev	dao_cdev;
static struct device	*dao_dev;

static int dao_dev_open(struct inode *inode, struct file *file)
{
    
    
	pr_info("%s\n", __func__);

	return 0;
}

static int dao_dev_release(struct inode *inode, struct file *file)
{
    
    
	pr_info("%s\n", __func__);

	return 0;
}

static ssize_t dao_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ptr)
{
    
    
	pr_info("%s\n", __func__);

	return 0;
}

static long dao_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    
    
        pr_info("%s\n", __func__);

        return 0;
}

static const struct file_operations dao_fops = {
    
    
        .owner          = THIS_MODULE,
        .open           = dao_dev_open,
        .release        = dao_dev_release,
        .read           = dao_dev_read,
        .unlocked_ioctl = dao_dev_ioctl,
};

static int __init dao_dev_init(void)
{
    
    
	int ret = 0;

	ret = alloc_chrdev_region(&dao_devt, 0, MINORMASK + 1, DAO_NAME);
	if (ret < 0) {
    
    
		pr_err("Error: failed to register dao_dev, err: %d\n", ret);
		return ret;
	}
	cdev_init(&dao_cdev, &dao_fops);
	cdev_add(&dao_cdev, dao_devt, MINORMASK + 1);
	pr_info("%s: major %d\n", __func__, MAJOR(dao_devt));

	dao_class = class_create(THIS_MODULE, DAO_CLASS_NAME);
	if (IS_ERR(dao_class)) {
    
    
		pr_err("Error: failed to register dao_dev class\n");
		ret = PTR_ERR(dao_class);
		goto failed1;
	}

	dao_dev = device_create(dao_class, NULL, dao_devt, NULL, DAO_NAME);
	if (!dao_dev)
		goto failed2;

	return 0;

failed2:
	class_destroy(dao_class);
failed1:
	cdev_del(&dao_cdev);
	unregister_chrdev_region(dao_devt, MINORMASK + 1);

	return ret;
}

static void __exit dao_dev_exit(void)
{
    
    
	device_destroy(dao_class, dao_devt);
	class_destroy(dao_class);
	cdev_del(&dao_cdev);
	unregister_chrdev_region(dao_devt, MINORMASK + 1);
}

module_init(dao_dev_init)
module_exit(dao_dev_exit)

ドライバー登録

デバイスの登録については上で紹介しましたが、ドライバーの登録はどのようなプロセスになるのでしょうか。ドライバーの登録は、driver_register() 関数によって完了します。この関数は主に次のことを行います。

  1. バスにはドライバーが全員乗車しており、ドライバーも名前で区別されているため、バスに同じ名前のドライバーがいないことを確認する必要があります。
  2. ドライバーをバスにマウントし、ドライバーを klist_drivers リンク リストに追加して、デバイスとの照合操作を実行します。
  3. ドライバー属性構成ノードを作成します。

具体的なデバイスとドライバーのマッチング操作については、次の章で紹介します。

おすすめ

転載: blog.csdn.net/weixin_41944449/article/details/132922704