1. 程式人生 > >cdev結構體及其相關函式 【轉】

cdev結構體及其相關函式 【轉】

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()     //此過程在解除安裝模組中

在 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 ,失敗則返回負值。