首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux 字元裝置驅動核心:cdev_init() 與 cdev 結構體深度解析

     編輯:關於C語言

cdev 結構體定義

每個字元裝置在核心中對應一個 struct cdev(include/linux/cdev.h):


struct cdev {
    struct kobject kobj;           // 核心物件,用於 refcount、sysfs 整合
    struct module *owner;          // 擁有模組(THIS_MODULE)
    const struct file_operations *ops; // 檔案操作函數表
    struct list_head list;         // 鏈結同一主裝置號的多個次裝置
    dev_t dev;                     // 起始裝置號
    unsigned int count;            // 次裝置號範圍數量
};

兩種初始化方式

1. 靜態分配(模組全域變數)


static struct cdev my_cdev;
static const struct file_operations my_fops = { ... };

static int __init my_init(void) {
    cdev_init(&my_cdev, &my_fops);
    my_cdev.owner = THIS_MODULE;
    return cdev_add(&my_cdev, MKDEV(major, 0), 1);
}

2. 動態分配(執行期 kmalloc)


struct cdev *my_cdev = cdev_alloc();
if (!my_cdev) return -ENOMEM;
my_cdev->ops = &my_fops;
my_cdev->owner = THIS_MODULE;
cdev_add(my_cdev, dev_num, count);

核心函數實現剖析

cdev_alloc()


struct cdev *cdev_alloc(void) {
    struct cdev *p = kzalloc(sizeof(*p), GFP_KERNEL);
    if (p) {
        INIT_LIST_HEAD(&p->list);
        kobject_init(&p->kobj, &ktype_cdev_dynamic);  // 動態類型
    }
    return p;
}

零初始化 + 初始化 list_head + 核心物件綁定動態類型。

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_alloc 類似,但額外設定 ops 指標。靜態分配時需先 cdev_init 再手動設 owner。

註冊與註銷流程

cdev_add():加入核心裝置號雜湊表


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);
}

cdev_map 是全域 struct kobj_map,內含雜湊表,將 dev_t 映射到 struct cdev*。使用者空間 open() 時,VFS 透過 kobj_lookup(cdev_map, dev) 找到 cdev,進而取得 ops。

cdev_del():移除並釋放


void cdev_del(struct cdev *p) {
    cdev_unmap(p->dev, p->count);   // 從 cdev_map 移除
    kobject_put(&p->kobj);         // 釋放 kobject 參考計數
}

模組 exit 時必須呼叫,否則裝置號殘留導致重複註冊失敗。

完整驅動骨架範例


#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>

static dev_t dev_num;
static struct cdev *my_cdev;

static int my_open(struct inode *inode, struct file *filp) { return 0; }
static ssize_t my_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { return 0; }
static ssize_t my_write(struct file *filp, const char __user *buf, size_t len, loff_t *off) { return 0; }
static int my_release(struct inode *inode, struct file *filp) { return 0; }

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = my_open,
    .read = my_read,
    .write = my_write,
    .release = my_release,
};

static int __init char_drv_init(void) {
    alloc_chrdev_region(&dev_num, 0, 1, "my_char_dev");
    my_cdev = cdev_alloc();
    my_cdev->ops = &fops;
    my_cdev->owner = THIS_MODULE;
    cdev_add(my_cdev, dev_num, 1);
    return 0;
}

static void __exit char_drv_exit(void) {
    cdev_del(my_cdev);
    unregister_chrdev_region(dev_num, 1);
}

module_init(char_drv_init);
module_exit(char_drv_exit);
MODULE_LICENSE("GPL");

常見坑點提醒

  • 忘記設定 cdev.owner = THIS_MODULE → 模組卸載時核心拒絕,裝置檔案打不開。
  • cdev_add 成功後裝置即進入活躍狀態,file_operations 必須已完整實現。
  • 動態分配的 cdev 必須配對 cdev_del + kfree(或依賴 kobject_put 釋放)。
  • 同一主裝置號多個次裝置時,count 需設為次裝置數量,list 會自動串聯。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved