字符设备驱动

来源:互联网 发布:视频剪刀手软件 编辑:程序博客网 时间:2024/06/08 11:27

1:本文实现简单的字符设备的驱动,并且提供两种访问设备资源的两种方法:一是传统的利用文件接口来访问,二是利用devfs文件系统来访问;

2:首先来看hello.h文件,其中实现了用户自定义的数据结构体

#include <linux/cdev.h>#include <linux/semaphore.h>struct hello_dev{int val;               /*共用的设备资源,4字节数据*/struct semaphore sem;  /*同步和互斥信号量*/struct cdev cdev;      /*字符设备结构体*/};
其中val变量我们模拟它是设备的资源,可以理解为用户访问设备的一个4字节大小的寄存器,sem实现对val变量访问的同步,cdev是代表一个字符设备;

3:模块加载和卸载函数

/*注册字符设备和初始化信号量*/static int hello_setup_chardev(void){int err;dev_t devno = 0;devno = MKDEV(hello_major,hello_minor);memset(hello_dev,0,sizeof(struct hello_dev));cdev_init(&(hello_dev->cdev),&hello_device_fops);hello_dev->cdev.owner = THIS_MODULE;hello_dev->cdev.ops = &hello_device_fops;err = cdev_add(&(hello_dev->cdev),devno,1); if(err){return err;}sema_init(&(hello_dev->sem),1);hello_dev->val = 41;return 0;}static int __init hello_init(void){int result = 0;dev_t devno = 0;struct device *dev = NULL;/*申请字符设备号*/if(hello_major){devno = MKDEV(hello_major,hello_minor);result = register_chrdev_region(devno,1,"hello_device");  /*hello_device是字符设备名字,在proc/devices下可见*/}else{result = alloc_chrdev_region(&devno,0,1,"hello_device"); /*动态申请一个字符设备,从设备号为0*/}if(result < 0){printk(KERN_ALERT "Failed register char dev region\n");goto fail;}hello_major = MAJOR(devno);hello_minor = MINOR(devno);/*动态分配hello_dev结构体*/hello_dev = kmalloc(sizeof(struct hello_dev),GFP_KERNEL);if(hello_dev == NULL){result = -ENOMEM;printk(KERN_ALERT "Failed alloc hello_dev\n");goto unregister;}/*注册字符设备,初始化信号量*/result = hello_setup_chardev();if(result){printk(KERN_ALERT "Failed setup char dev\n");goto free;}/*在sys/class/目录下创建hello目录*/hello_class = class_create(THIS_MODULE,"hello");if(IS_ERR(hello_class)){result = PTR_ERR(hello_class);printk(KERN_ALERT "Failed to create hello class.\n");goto cdev_destroy;}/*在/dev/目录和/sys/class/hello目录下分别创建设备文件hello*/dev = device_create(hello_class,NULL,devno,NULL,"hello");if(IS_ERR(dev)){result = PTR_ERR(dev);printk(KERN_ALERT "Failed to create hello device.\n");goto class_destroy;}/*在sys/class/hello/hello目录下创建属性文件*/result = device_create_file(dev,&dev_attr_val);if(result < 0){printk(KERN_ALERT "Failed to create arrribute val.\n");goto device_destroy;}printk(KERN_ALERT "hello char device init ok\n");return 0;device_destroy:device_destroy(hello_class,devno);class_destroy:class_destroy(hello_class);cdev_destroy:cdev_del(&(hello_dev->cdev));free:kfree(hello_dev);unregister:unregister_chrdev_region(devno,1);fail:return result;}static void __exit hello_exit(void){dev_t devno = 0;printk(KERN_ALERT "hello char device exit\n");devno = MKDEV(hello_major,hello_minor);device_destroy(hello_class,devno);class_destroy(hello_class);cdev_del(&(hello_dev->cdev));kfree(hello_dev);unregister_chrdev_region(devno,1);}module_init(hello_init);module_exit(hello_exit);MODULE_LICENSE("Dual BSD/GPL");MODULE_AUTHOR("GOLF/FXB,<1029930509@qq.com>");MODULE_DESCRIPTION("A SIMPLE CHAR DEVICE DRIVER");
模块加载函数主要完成:字符设备设备好申请,自定义设备结构体hello_dev动态申请内存空间,注册字符设备,创建sys节点;

4:文件操作函数的实现

static int hello_open(struct inode *inode, struct file *filp){struct hello_dev *dev;/*将文件节点的私有数据指针指向用户自定义的数据结构体,方便read和write方法中使用*/dev = container_of(inode->i_cdev, struct hello_dev, cdev);filp->private_data = dev;return 0;}static ssize_t hello_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos){ssize_t result = 0;int cnt = count;/*取出用户自定义设备结构体*/struct hello_dev *dev = filp->private_data;/*信号量使用,用于多个写进程之间互斥以及读进程同步*/if(down_interruptible(&(dev->sem))){return -ERESTARTSYS;}/*写界限控制*/if(*f_pos >= sizeof(dev->val))goto out;/*写数据字节数控制*/if(count > sizeof(dev->val) - *f_pos){count = sizeof(dev->val)- *f_pos;}/*从用户空间读取count个字节的数据给val变量*/if(copy_from_user(&(dev->val),buf,count)){result = -EFAULT;goto out;}/*每次跟新pos指针*/*f_pos += count;out:up(&(dev->sem));return cnt;   /*使用echo写设备节点的时候,必须返回cnt,否则进程会死循环*/}static ssize_t hello_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos){ssize_t result = 0;struct hello_dev *dev = filp->private_data; if(down_interruptible(&(dev->sem))) { return -ERESTARTSYS; } if(*f_pos > sizeof(dev->val))  goto out; if(count > sizeof(dev->val) - *f_pos) {count = sizeof(dev->val) - *f_pos; } if(copy_to_user(buf,&(dev->val),count))     { result = -EFAULT; goto out; } *f_pos += count; result = count;out:up(&(dev->sem));return result;}/*设备文件释放时调用*/int hello_release(struct inode *inode, struct file *filp){return 0;}/*字符设备文件操作结构体*/static struct file_operations hello_device_fops ={.owner = THIS_MODULE,.open = hello_open,.release = hello_release,.read = hello_read,.write = hello_write,};
分别实现文件的open,read,write方法,注意:在使用cat指令读取文件节点的时候,read方法中一定要有判断文件读结束的语句(也就是read函数返回0字节数据),这样cat进程才会终止,否则,进程死循环;在使用echo写文件节点的时候,write函数一定要返回echo进程调用write函数时参数count的值,否则echo进程不会终止,具体见上面代码;

5:文件节点属性和方法的实现

/*val属性的读方法,读取val的值到缓冲区buf中,内核态使用*/static ssize_t hello_val_show(struct device *dev,struct device_attribute *attr, char *buf){int val = 0;if(down_interruptible(&(hello_dev->sem))){return -ERESTARTSYS;}val = hello_dev->val;up(&(hello_dev->sem));return sprintf(buf,"%x\n",val);   /*必须返回转换的字节数*/}/*var属性的写方法,将缓冲区buf中的值写到val中,内核态使用*/static ssize_t hello_val_store(struct device *dev,struct device_attribute *attr,const char *buf, size_t size){if(down_interruptible(&(hello_dev->sem)))  /*同步和互斥*/{return -ERESTARTSYS;}hello_dev->val = simple_strtol(buf,NULL,16);  /*将buf缓冲中的字符转换成10进制数*/up(&(hello_dev->sem));  /*释放信号量*/return size;   /*必须返回size*/}/*dev_attr_val结构体定义*/static DEVICE_ATTR(val, S_IRUGO | S_IWUSR,hello_val_show, hello_val_store);
模块初始化函数中创建了文件节点val,上面代码实现了val属性对应的方法,面向对象的思想,show和store函数分别实现val属性的读和写的方法;


0 0
原创粉丝点击