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属性的读和写的方法;