字符设备驱动

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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值