本文提供一個完整的字符設備驅動開發範例,涵蓋動態設備號分配、設備節點自動創建、ioctl 命令定義(_IO/_IOR/_IOW/_IOWR)、copy_from_user/copy_to_user 數據傳輸、用戶態測試程序、Makefile 交叉編譯全流程。
#ifndef __MY_CDEV_H__
#define __MY_CDEV_H__
#define LED_MAGIC 'L'
#define LED_ON _IOW(LED_MAGIC, 0, int) // 用戶→內核:寫入參數
#define LED_OFF _IOR(LED_MAGIC, 1, int *) // 內核→用戶:讀取結果
#endif
_IOW 表示寫(用戶到內核),_IOR 表示讀(內核到用戶),宏自動編碼方向、幻數、序號、參數大小,避免 2.4 內核時代的號碼衝突。
#include
#include
#include
#include
#include
#include
#include "my_cdev.h"
struct cdev cdev;
dev_t devno;
struct class *cdev_class;
int param; // 內核側暫存變量
int my_cdev_open(struct inode *node, struct file *filp) {
printk("my_cdev_open success\n");
return 0;
}
long my_cdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
int rc = -1;
switch(cmd) {
case LED_ON:
rc = copy_from_user(¶m, (int __user*)arg, sizeof(int));
if (rc) { printk("copy_from_user failed\n"); break; }
printk("Param = %d, LED_ON set\n", param);
return 0;
case LED_OFF:
printk("LED_OFF set\n");
param = 1000;
rc = copy_to_user((int __user*)arg, ¶m, sizeof(int));
if (rc) printk("copy_to_user failed\n");
param = 0;
return 0;
default: return -EINVAL;
}
return rc;
}
struct file_operations my_cdev_fops = {
.open = my_cdev_open,
.unlocked_ioctl = my_cdev_ioctl,
};
static int __init my_cdev_init(void) {
int ret = alloc_chrdev_region(&devno, 0, 1, "my_chardev");
if (ret) return ret;
cdev_init(&cdev, &my_cdev_fops);
ret = cdev_add(&cdev, devno, 1);
if (ret) { unregister_chrdev_region(devno, 1); return ret; }
cdev_class = class_create(THIS_MODULE, "my_chardev");
if (IS_ERR(cdev_class)) { unregister_chrdev_region(devno, 1); return -1; }
device_create(cdev_class, NULL, devno, NULL, "my_chardev"); // /dev/my_chardev
return 0;
}
static void __exit my_cdev_exit(void) {
device_destroy(cdev_class, devno);
class_destroy(cdev_class);
cdev_del(&cdev);
unregister_chrdev_region(devno, 1);
printk("my_cdev_exit\n");
}
module_init(my_cdev_init);
module_exit(my_cdev_exit);
MODULE_LICENSE("GPL");
#include
#include
#include
#include
#include "my_cdev.h"
int main(int argc, char *argv[]) {
if (argc < 2) { printf("Usage: %s <1|2>\n", argv[0]); return 0; }
int fd = open("/dev/my_chardev", O_RDWR);
if (fd < 0) { perror("open"); return 0; }
unsigned long param = 0;
int cmd = atoi(argv[1]);
switch(cmd) {
case 1: param = 500; ioctl(fd, LED_ON, ¶m); break;
case 2: ioctl(fd, LED_OFF, ¶m); printf("read: %lu\n", param); break;
}
close(fd);
return 0;
}
obj-m := my_char_dev.o
KDIR := /home/win/dn377org/trunk/bcm7252/linux/
all:
make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
clean:
rm -f *.ko *.o *.mod.* *.symvers *.order
# 載入模塊
insmod my_char_dev.ko
# 自動創建 /dev/my_chardev
# 測試寫入 (cmd=1)
./my_char_dev_app 1
# 內核日誌: Param = 500, LED_ON set
# 測試讀取 (cmd=2)
./my_char_dev_app 2
# 用戶態輸出: read: 1000
# 內核日誌: LED_OFF set

alloc_chrdev_region)+ class_create/device_create 自動生成設備節點,無需手動 mknod。copy_from_user/to_user 必須在進程上下文(如 ioctl/open/read/write)中調用,不可在中斷上下文使用。ioctl 命令編碼務必使用 _IO* 宏,保證跨架構兼容。device_destroy → class_destroy → cdev_del → unregister_chrdev_region。