Linux字符设备驱动框架

2019-07-12 20:39发布

推荐周立功先生的书籍《嵌入式Linux开发教程(下册)》,该书籍用于学习开发是不错的参考资料。

字符设备框架

char_dev_frame.c:

a 驱动程序

#include #include #include #include #include static int major = 0; /* 主设备号默认值 */ static int minor = 0; /* 次设备号默认值 */ module_param(major, int, S_IRUGO); module_param(minor, int ,S_IRUGO); struct cdev *char_cdev; /* c_dev数据结构 */ static dev_t dev_no; /* 设备编号 */ static struct class *char_cdev_class; static struct class_device *char_cdev_class_device; #define DEVICE_NAME "char_cdev" #define DEVICE_FILE_NAME "/dev/char_cdev" static int char_cdev_open(struct inode *inode, struct file *file) { try_module_get(THIS_MODULE); printk(KERN_INFO DEVICE_NAME"opened! "); return 0; } static int char_cdev_release(struct inode *inode, struct file *file) { printk(KERN_INFO DEVICE_NAME"closed! "); module_put(THIS_MODULE); return 0; } static ssize_t char_cdev_read(struct file *file, char *buf, size_t count, loff_t *f_ops) { printk(KERN_INFO DEVICE_NAME"read method! "); return count; } static ssize_t char_cdev_write(struct file *file, const char *buf, size_t count, loff_t *f_ops) { printk(KERN_INFO DEVICE_NAME"write method! "); return count; } static int char_cdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { printk(KERN_INFO DEVICE_NAME"ioctl method! "); return 0; } struct file_operations char_cdev_fops = { .owner = THIS_MODULE, .read = char_cdev_read, .write = char_cdev_write, .ioctl = char_cdev_ioctl, .open = char_cdev_open, .release = char_cdev_release }; static int __init char_cdev_init(void) { int ret; /* 分配设备号 */ if(major > 0) { /* 静态分配 */ dev_no = MKDEV(major, minor); ret = register_chrdev_region(dev_no, 1, DEVICE_NAME); } else { ret = alloc_chrdev_region(&dev_no, minor, 1, DEVICE_NAME); major = MAJOR(dev_no); } if(ret < 0) { printk(KERN_ERR"can't get major %d ", major); return -1; } /* 注册设备 */ char_cdev = cdev_alloc(); /* 分配cdev_设备结构 */ if(char_cdev != NULL) { cdev_init(char_cdev, &char_cdev_fops); /* 初始化char_cdev结构 */ char_cdev->owner = THIS_MODULE; if(cdev_add(char_cdev, dev_no, 1) != 0) { printk(KERN_ERR"add cdev error!"); goto error; } } else { printk(KERN_ERR"cdev_alloc_error! "); return -1; } /* 注册设备节点 */ char_cdev_class = class_create(THIS_MODULE, "char_cdev_class"); if(IS_ERR(char_cdev_class)) { printk(KERN_INFO"create class error "); return -1; } char_cdev_class_device = class_device_create(char_cdev_class, NULL, dev_no, NULL, "char_cdev"); return 0; error: unregister_chrdev_region(dev_no, 1); return ret; } static void __exit char_cdev_exit(void) { cdev_del(char_cdev); unregister_chrdev_region(dev_no, 1); class_device_unregister(char_cdev_class_device); class_destroy(char_cdev_class); } module_init(char_cdev_init); module_exit(char_cdev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("huchunrong"); 添加设备节点时,因为kernel版本为2.6.22.6,应使用class_device_create,否则一直提示警告。后期的某个版本开始,class_device_createdevice_create替代。关于这两个函数的分析,网上分析很多。

b 驱动程序Makefile

# Makefile2.6 ifneq ($(KERNELRELEASE),) # kbuild syntax. dependency relationshsip of files and target modules are listed here. obj-m := char_dev_frame.o else PWD := $(shell pwd) KVER := 2.6.22.6 KDIR := /home/eva/Developer/Linux/kernel/linux-2.6.22.6 all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions endif 上面的Makefile为Linux2.6内核下一段比较通用的Makefile。
aKERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义,所以make将读取执行else之后的内容;
b 当make的目标为all时,-C $(KDIR)跳转到内核源码目录下读取Makefile;M=$(PWD) 然后返回到当前目录继续读入、执行当前的Makefile。

c 测试程序

char_dev_test.c: #include #include #include #include #include #include #define DEVICE_FILE_NAME "/dev/char_cdev" int main(int argc, char *argv[]) { int fd, ret; char data; fd = open(DEVICE_FILE_NAME, O_RDWR); if(fd < 0) { perror("Open "DEVICE_FILE_NAME" Failed "); exit(1); } ret = read(fd, &data, 1); if(!ret) { perror("Read "DEVICE_FILE_NAME" Failed "); exit(1); } data = 1; ret = write(fd, &data, 1); if(!ret) { perror("Write "DEVICE_FILE_NAME" Failed "); exit(1); } ret = ioctl(fd, 0, NULL); if(ret) { perror("Ioctl "DEVICE_FILE_NAME" Failed "); exit(1); } close(fd); return 0; }

编译

a 编译驱动

因为事先已经写好了Makefile,直接执行make命令即可 make

b 编译测试程序

arm-linux-gcc char_dev_test.c -o char_dev_test.o

加载和运行

拷贝可执行文件到nfs共享目录:cp char_dev_frame.ko char_dev_test.o ../../../bin/ 在嵌入式设备上挂载nfs共享目录:mount -t nfs -o intr,nolock,rsize=1024,wsize=1024 192.168.1.12:/home/eva/Developer/Linux/bin /mnt

当文件比较大时,使用该指令成功率比较高 安装驱动程序:insmod char_dev_frame.ko
执行测试程序:./char_dev_test.o 如果驱动程序里没有进行创建设备文件,即没有执行class_device_create,也可以根据主设备号和次设备号,手动创建字符设备节点mknod /dev/char_cdev c 252 0 查看当前系统支持的设备cat /proc/devices
查看设备节点ls /dev

警告及错误

现象描述:
编译时在readwrite函数附近,提示warning: initialization from incompatible pointer type,一般原因为数据类型不匹配
解决措施:
定义read函数时,第三个参数类型为size_t,而不是ssize_t,改正后如下:static ssize_t char_cdev_read(struct file *file, char *buf, ssize_t count, loff_t *f_ops)write函数如此 现象描述:
编译时device_create函数提示警告warning: too many arguments for format,原因为jz2440的内核版本linux2.6.22.6,还不支持device_create函数 解决措施:
改用class_device_create函数,完整示例如下char_cdev_class_device = class_device_create(char_cdev_class, NULL, dev_no, NULL, "char_cdev"); 现象描述:
insmod安装驱动后,无法在/dev/路径下找到设备节点,只能手动创建设备文件节点,但驱动程序里已经正确调用class_device_create函数 解决措施:
和烧写的文件系统有关,烧写fs_mini_mdev.yaffs2文件系统,而不是 fs_mini.yaffs2,具体原因未知