Linux字符设备驱动框架

来源:互联网 发布:yy会员签到软件 编辑:程序博客网 时间:2024/05/16 13:56

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

字符设备框架

char_dev_frame.c:

a 驱动程序

#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/device.h>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! \n");    return 0;}static int char_cdev_release(struct inode *inode, struct file *file){    printk(KERN_INFO DEVICE_NAME"closed! \n");    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! \n");    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! \n");    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! \n");    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 \n", 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! \n");        return -1;    }    /* 注册设备节点 */    char_cdev_class = class_create(THIS_MODULE, "char_cdev_class");    if(IS_ERR(char_cdev_class)) {        printk(KERN_INFO"create class error \n");        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.6ifneq ($(KERNELRELEASE),)# kbuild syntax. dependency relationshsip of files and target modules are listed here.obj-m := char_dev_frame.oelsePWD := $(shell pwd)KVER := 2.6.22.6KDIR := /home/eva/Developer/Linux/kernel/linux-2.6.22.6all:    $(MAKE) -C $(KDIR) M=$(PWD) modulesclean:    rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versionsendif

上面的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 <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/ioctl.h>#include <errno.h>#include <fcntl.h>#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\n");        exit(1);    }    ret = read(fd, &data, 1);    if(!ret) {        perror("Read "DEVICE_FILE_NAME" Failed\n");        exit(1);    }    data = 1;    ret = write(fd, &data, 1);    if(!ret) {        perror("Write "DEVICE_FILE_NAME" Failed\n");        exit(1);    }    ret = ioctl(fd, 0, NULL);    if(ret) {        perror("Ioctl "DEVICE_FILE_NAME" Failed\n");        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,具体原因未知

原创粉丝点击