天天看點

94 從源碼看如何管理(配置設定)裝置号

文章目錄

    • 一、chrdevs哈希表
    • 二、、 __register_chrdev_region 相關函數分析

一、chrdevs哈希表

用于管理裝置号。

定義在

ebf-buster-linux/fs/char_dev.c

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];
// #define CHRDEV_MAJOR_HASH_SIZE	255 		fs.h
           

示意圖:

94 從源碼看如何管理(配置設定)裝置号

注意上圖中的一個節點可以表示

一個主裝置号,一段次裝置号

二、、 __register_chrdev_region 相關函數分析

此函數用于向 linux kernel 注冊一個主裝置号,一個或多個次裝置号。

定義在

char_dev.c

/* 參數含義依次為:
 * 主裝置号,次裝置号起始,次裝置号數量,裝置名字
 */
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
			   int minorct, const char *name)
{
	struct char_device_struct *cd, **cp;
	int ret = 0;
	int i;
	/* 動态申請記憶體 */
	cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
	if (cd == NULL)
		return ERR_PTR(-ENOMEM);
	/* 互斥鎖,保護資源 */
	mutex_lock(&chrdevs_lock);

	/* 若主裝置号為0,由kernel配置設定一個裝置号 */
	if (major == 0) {
		/* 從哈希表中找一個空閑位置 */
		for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
			if (chrdevs[i] == NULL)
				break;
		}

		if (i == 0) {
			ret = -EBUSY;
			goto out;
		}
		major = i;
	}
	// 儲存傳進來的四個參數
	cd->major = major; /* 主裝置号 */
	cd->baseminor = baseminor; /* 次裝置号起始 */
	cd->minorct = minorct; /* 次裝置号數量 */
	strlcpy(cd->name, name, sizeof(cd->name));
	/* 計算在哈希表中的位置,就是對 255 進行取餘 */
	i = major_to_index(major);
	/* 
	 * 周遊此位置所指向的連結清單,知道放在連結清單中的哪個位置。
	 * 一個連結清單内按主裝置号遞增,次裝置号遞增排序。一個
	 * 節點可能包含一個主裝置号,一段連續的次裝置号
	 */
	for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
		if ((*cp)->major > major ||
		    ((*cp)->major == major &&
		     (((*cp)->baseminor >= baseminor) ||
		      ((*cp)->baseminor + (*cp)->minorct > baseminor))))
			break;

	/* 
	 * 主裝置号相同的情況下需要判斷次裝置号是否沖突  
	 * 裡面兩個 if 判斷
	 */
	if (*cp && (*cp)->major == major) {
		int old_min = (*cp)->baseminor;
		int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
		int new_min = baseminor;
		int new_max = baseminor + minorct - 1;

		/* New driver overlaps from the left.  */
		if (new_max >= old_min && new_max <= old_max) {
			ret = -EBUSY;
			goto out;
		}

		/* New driver overlaps from the right.  */
		if (new_min <= old_max && new_min >= old_min) {
			ret = -EBUSY;
			goto out;
		}
	}
	/* 連結清單的節點插入操作 */
	cd->next = *cp;
	*cp = cd;
	mutex_unlock(&chrdevs_lock);
	return cd;
out:
	mutex_unlock(&chrdevs_lock);
	kfree(cd);
	return ERR_PTR(ret);
}
           

儲存

新注冊的裝置号

chrdevs哈希表

中,防止裝置号沖突

分析結論:

哈希表中每個元素都是一個連結清單。

連結清單中的每一個元素似乎可以表示一個主裝置号,一段次裝置号(多個裝置)

相關的其他函數

int register_chrdev_region(dev_t from, unsigned count, const char *name);
	此函數内部會調用 __register_chrdev_region。
	申請自己指定的一段裝置号。這段裝置号可能會包含多個主裝置号。
	而 __register_chrdev_region 每次調用隻能申請一個主裝置号,一個或多個次裝置号。
	是以在此函數内部要進行判斷,分次調用 __register_chrdev_region。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
							const char *name);
	由系統配置設定一個裝置号,并調用 __register_chrdev_region 進行注冊。

static struct char_device_struct * 
	__unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct);
	此函數用于登出一段裝置号
	在哈希表 chrdevs 中找到對應的節點,删除之。
	由于之前是kzalloc動态申請,此時不需要手動free了。