Linux內核修煉之字符設備分析二(源碼分析)

====本文系本站原創,歡迎轉載! 轉載請註明出處:http://blog.csdn.net/yyplc==== 

繼上篇,本篇結合源碼分析

cdev數據結構:

struct cdev {
	struct kobject kobj;  //kobject實體
	struct module *owner;
	const struct file_operations *ops;  //大家熟悉的file_operations結構
	struct list_head list;              //list用於設備管理的鏈表
	dev_t dev;                          //設備號
	unsigned int count;                 //設備的連續次設備號的數量(範圍)
};

與cdev相關的函數如下(源碼在fs/char_dev.c實現):

void cdev_init(struct cdev *, const struct file_operations *); //
struct cdev *cdev_alloc(void);   //返回cdev實例
void cdev_put(struct cdev *p);   //減少cdev實例中kobject的引用計數,也就是kref的值
int cdev_add(struct cdev *, dev_t, unsigned); //
void cdev_del(struct cdev *);    //刪除一個cdev實例
static int chrdev_open(struct inode *inode, struct file *filp); //打開一個設備時調用,這個函數只是內部使用(static)
下面我們選一些對我們有用的函數來了解,什麼叫有用?所謂有用就是寫驅動時有用的。
//作用:初始化一個cdev結構,包括kobj,關聯fops函數。
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	memset(cdev, 0, sizeof *cdev);
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default); //初始化cdev->kobj
	cdev->ops = fops; //關聯cdev的fops
}
//作用:爲系統添加一個cdev實例。ldd3上說"常常 count 是 1, 但是有多個設備號對應於一個特定的設備的情形.
//例如, 設想 SCSI 磁帶驅動, 它允許用戶空間來選擇操作模式(例如密度), 通過安排多個次編號給每一個物理設備."
//但我們用register_chr_dev時,看到的是count = 256
先看看struct kobj_map cdev_map的結構:
struct kobj_map {
	struct probe {
		struct probe *next;
		dev_t dev;
		unsigned long range;
		struct module *owner;
		kobj_probe_t *get;
		int (*lock)(dev_t, void *);
		void *data;
	} *probes[255];
	struct mutex *lock;
};
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	p->dev = dev;
	p->count = count;
	return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
kobj_map()會創建一個probe對象,然後將其插入cdev_map中的某一項中,並關聯probe->data指向cdev,probe->range = count;
//打開char_dev時,都打開chrdev_open,因爲在進入驅動的自定義的file_operations前已定義下面:
const struct file_operations def_chr_fops = {
	.open = chrdev_open,
};
//作用:根據設備號(cdev_map ,inode->i_rdev),通過kobj_lookup()來獲取kobject,再通過獲取cdev,最後獲取關聯的fops操作函數
static int chrdev_open(struct inode *inode, struct file *filp); 
除以上cdev操作函數以外,下面我們看看字符設備相關函數:可以分成兩類,註冊函數和註銷函數
註冊函數:
//通過主設備號,靜態註冊一個char設備,其中包括cdev_alloc(),cdev_add()過程
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops);
//根據設備號,註冊一段設備號,from是設備號,當爲0時就是動態註冊,count是“多少個來連續次設備號”
int register_chrdev_region(dev_t from, unsigned count, const char *name);
//動態分配設備號,dev是輸出的設備號
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
//其實上面三個函數的實現都是調用這個函數的
static struct char_device_struct *__register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)
註銷函數:
void unregister_chrdev(unsigned int major, const char *name);
static struct char_device_struct * __unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct);
看看它們的調用過程:
register_chrdev(major, name, fops)--> __register_chrdev_region(major, 0, 256, name)-->cdev = cdev_alloc()
-->cdev_add(cdev, MKDEV(cd->major, 0), 256);
alloc_chrdev_region(dev, baseminor, count, name)-->__register_chrdev_region(0, baseminor, count, name)-->
*dev = MKDEV(cd->major, cd->baseminor);
register_chrdev_region(from, count, name)--> __register_chrdev_region(MAJOR(from), MINOR(from)), next-from, name);
由於以上3個函數都通過調用
__register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)
先看字符設備的數據結構:
內核中所有已分配的字符設備編號都記錄在一個名爲 chrdevs 散列表裏。該散列表中的每一個元素是一個 char_device_struct 結構,它的定義如下:
static struct char_device_struct {
	struct char_device_struct *next;    // 指向散列衝突鏈表中的下一個元素的指針
       unsigned int major;                 // 主設備號
       unsigned int baseminor;             // 起始次設備號
       int minorct;                        // 設備編號的範圍大小
       char name[64];                      // 處理該設備編號範圍內的設備驅動的名稱
       struct cdev *cdev;                  // 指向字符設備驅動程序描述符的指針
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
定義一個指針chardevs數組,大小爲CHRDEV_MAJOR_HASH_SIZE,注意,數組中的元素是一個地址,其中
#define CHRDEV_MAJOR_HASH_SIZE 255
指針數組常適用於指向若干字符串,這樣使字符串處理更加靈活方便。
注意,內核並不是爲每一個字符設備號定義一個 char_device_struct 結構,而是爲一組對應同一個字符設備驅動的設備編號範圍定義一個 
char_device_struct 結構。chrdevs 散列表的大小是 255,散列算法是把每組字符設備編號範圍的主設備號以 255 取模插入相應的散列桶中。
同一個散列桶中的字符設備編號範圍是按起始次設備號遞增排序的。
函數 __register_chrdev_region() 主要執行以下步驟:
1. 分配一個新的 char_device_struct 結構,並用 0 填充。
2. 如果申請的設備編號範圍的主設備號爲 0,那麼表示設備驅動程序請求動態分配一個主設備號。動態分配主設備號的原則是從散列表的最後一個桶向前尋找,那個桶是空的,主設備號就是相應散列桶的序 號。所以動態分配的主設備號總是小於 255,如果每個桶都有字符設備編號了,那動態分配就會失敗。
3. 根據參數設置 char_device_struct 結構中的初始設備號,範圍大小及設備驅動名稱。
4. 計算出主設備號所對應的散列桶,爲新的 char_device_struct 結構尋找正確的位置。同時,如果設備編號範圍有重複的話,則出錯返回。
5. 將新的 char_device_struct 結構插入散列表中,並返回 char_device_struct 結構的地址。

現在我們通過分析這個函數,來知道內核是怎麼註冊設備號的。
註冊代碼:
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,一個char_device_struct內存空間,並初始化爲0
	cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
	if (cd == NULL)
		return ERR_PTR(-ENOMEM);


	mutex_lock(&chrdevs_lock);


	/* temporary */
	if (major == 0) {  //動態分配
		for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {//從254開始,也就是第255個開始
			if (chrdevs[i] == NULL)  
				break;
		}


		if (i == 0) { //因爲主設備號爲0,內核定義爲UNNAMED_MAJOR,棄之不用
			ret = -EBUSY;
			goto out;
		}
		major = i;  //找到一個沒用過的主設備號來用
		ret = major;
	}
//找到major後,給cd賦值
	cd->major = major;
	cd->baseminor = baseminor;
	cd->minorct = minorct;
	strlcpy(cd->name, name, sizeof(cd->name));


	i = major_to_index(major);//求餘:i= major % CHRDEV_MAJOR_HASH_SIZE


	for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) //根據主設備號,確定正確的次設備號的範圍(位置),注意有可能‘迴轉‘的情況,所以後面會檢測
		if ((*cp)->major > major ||             //如果是一個沒用過的主設備號,(*cp)->next =NULL
		    ((*cp)->major == major &&
		     (((*cp)->baseminor >= baseminor) ||
		      ((*cp)->baseminor + (*cp)->minorct > baseminor))))
			break;


	/* Check for overlapping minor ranges.  */
	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; //指向下一個設備的char設備結構
	*cp = cd;       //把當前cd的地址保存到chrdevs[i]中
	mutex_unlock(&chrdevs_lock);
	return cd;   //返回cd
out:
	mutex_unlock(&chrdevs_lock);
	kfree(cd);
	return ERR_PTR(ret);
}
註銷代碼:
static struct char_device_struct *
__unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct)
{
	struct char_device_struct *cd = NULL, **cp;
	int i = major_to_index(major);


	mutex_lock(&chrdevs_lock);
	for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)//在chardevs[]中找到已註冊的cd結構位置
		if ((*cp)->major == major &&
		    (*cp)->baseminor == baseminor &&
		    (*cp)->minorct == minorct)
			break;
	if (*cp) {//從chardevs[]中剔除cd結構,並將當前的chardevs[]指向cd的下一個char結構
		cd = *cp;
		*cp = cd->next; 
	}
	mutex_unlock(&chrdevs_lock);
	return cd;  //返回找到的cd
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章