1、在Linux2.6內核中一個字符設備用cdev結構來描述,其定義如下:
struct cdev {
struct kobject kobj;
struct module *owner; //所屬模塊
const struct file_operations *ops;
//文件操作結構,在寫驅動時,其結構體內的大部分函數要被實現
struct list_head list;
dev_t dev; //設備號,int 類型,高12位爲主設備號,低20位爲次設備號
unsigned int count;
};
可以使用如下宏調用來獲得主、次設備號:
MAJOR(dev_t dev)
MINOR(dev_t dev)
MKDEV(int major,int minor) //通過主次設備號來生成dev_t
以上宏調用在內核源碼中如此定義:
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
//(1<<20 -1) 此操作後,MINORMASK宏的低20位爲1,高12位爲0
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
2、下面一組函數用來對cdev結構體進行操作:
void cdev_init(struct cdev *, const struct file_operations *);//初始化,建立cdev和file_operation 之間的連接
struct cdev *cdev_alloc(void); //動態申請一個cdev內存
void cdev_put(struct cdev *p); //釋放
int cdev_add(struct cdev *, dev_t, unsigned); //註冊設備,通常發生在驅動模塊的加載函數中
void cdev_del(struct cdev *);//註銷設備,通常發生在驅動模塊的卸載函數中
3、在註冊時應該先調用:int register_chrdev_region(dev_t from,unsigned count,const char *name)函數爲其分配設備號,此函數可用:int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name)函數代替,他們之間的區別在於:register_chrdev_region()用於已知設備號時,另一個用於動態申請,其優點在於不會造成設備號重複的衝突。
在註銷之後,應調用:void unregister_chrdev_region(dev_t from,unsigned count)函數釋放原先申請的設備號。
他們之間的順序關係如下:
register_chrdev_region()-->cdev_add() //此過程在加載模塊中
cdev_del()-->unregister_chrdev_region() //此過程在卸載模塊中
轉載自:http://blog.chinaunix.net/u2/74234/showart_1095078.html
在 linux 2.6內核中,使用 cdev結構體描述字符設備,cdev 的定義在 <linux/cdev.h> 中可找到,其定義如下:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
cdev 結構體中的 dev_t 成員定義了設備號,爲 32 位,其中高 12 位爲主設備號,低 20 位爲次設備號。
其中,struct kobject 是內嵌的 kobject 對象;
struct module 是所屬模塊;
struct file_operations 爲文件操作結構體。
使用以下宏可以從 dev_t 獲得主設備號和次設備號:
MAJOR (dev_t dev);
MINOR (dev_t dev);
而使用下面宏可以通過主設備號和次設備號生成 dev_t :
MKDEV (int major, int minor);
有兩個方法可以分配並初始化 cedv 結構。如果希望在運行時動態的獲得一個獨立的 cdev 結構,可以如下這麼做:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
cdev_alloc(void) 函數的代碼爲(對 cdev 結構體操作的系列函數可在 fs/char_dev.c 中找到):
struct cdev *cdev_alloc(void)
{
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
if (p) {
INIT_LIST_HEAD(&p->list);
kobject_init(&p->kobj, &ktype_cdev_dynamic);
}
return p;
}
cdev_alloc() 的源代碼可能由於內核版本號的不同而有差別(上面的代碼爲 2.6.30)
有時可能希望就把 cdev 結構內嵌在自己的特定設備結構裏,那麼在分配好 cdev 結構後,就用 cdev_init() 函數對其初始化:
void cdev_init (struct cdev *cdev, struct file_operations *fops)
cdev_init() 函數代碼爲:
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->ops = fops;
}
另外,像 cdev 中的 owner 要設置爲 THIS_MOULE 。
一旦 cdev 結構體設置完畢,最後一步就是要把這事告訴給內核,使用下面的函數:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
cdev_add() 對應的代碼爲:
/**
* cdev_add() - add a char device to the system
* @p: the cdev structure for the device
* @dev: the first device number for which this device is responsible
* @count: the number of consecutive minor numbers corresponding to this
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure.
*/
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);
}
參數 p 是 cdev 結構體的指針;
參數 dev 是設備響應的第一個設備號;
參數 count 和設備相關聯的設備號的數目。
一般的,count 的值爲 1,但是有些情形也可能是大於 1 的數。比如 SCSI 磁帶機,它通過給每個物理設備安排多個此設備號來允許用戶在應用程序裏選擇操作模式(比如密度)。
cdev_add 如果失敗了,那麼返回一個負值,表明驅動無法加載到系統中。然而它一般情況下都會成功,一旦 cdev_add 返回,設備也就 “活” 了起來,於是所對應的操作方法(file_operations 結構裏所定義的各種函數)也就能爲內核所調用。
從系統中移除一個字符設備,可以調用:
void cdev_del(struct cdev *p)
老版本的字符設備註冊與註銷
在許多驅動程序代碼裏,會看到許多字符設備驅動並沒有用 cdev 這個接口。這是一種老式的方法,但新寫的代碼應該使用 cdev 接口。
用於註冊字符設備驅動程序的老式函數 register_chrdev() 函數定義如下:
int register_chardev (unsigned int major, const char *name, struct file_operations *fops)
利用該函數註冊時,應先定義好主設備號、設備驅動程序的名稱、file_operations 結構體的變量。
應用程序中利用設備文件搜索設備驅動程序的時候使用主設備號 (major) 。
在內核中表示 proc 文件系統或錯誤代碼時,使用設備驅動程序名稱。
另外,利用 unregister_chrdev() 函數註銷字符設備驅動程序時,可以作爲區分標誌。註冊函數中關鍵的地方是定義 file_operations 結構體變量的地址。
所謂註冊字符設備驅動程序,應理解爲在內核中註冊與主設備號相關的 file_operations 結構體。
register_chrdev() 函數註冊完設備驅動程序,把定義主設備號的 major 設置爲 0,返回註冊的主設備號(動態分配),把已知的主設備號設爲 major 值時,返回 0 (人工指定)。註冊失敗時,返回負值
從內核中註銷字符設備驅動程序的 unregister_chrdev() 函數形式如下:
int unregister_chrdev (unsigned int major, const char *name)
該函數中使用主設備號(major) 和設備驅動程序名稱 (name) 與 register_chrdev 函數中使用的值相同,因爲內核會把這些參數作爲註銷字符設備驅動程序的基準對比兩個設定內容。從內核成功註銷了字符設備驅動程序時,返回 0 ,失敗則返回負值。