每個字元裝置在核心中對應一個 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; // 次裝置號範圍數量
};
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);
}
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);
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 + 核心物件綁定動態類型。
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。
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。
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 會自動串聯。