linux驱动学习笔记(一)字符设备驱动

cdev结构体

在include/linux/cdev.h

struct cdev {
	struct kobject kobj; /*内嵌的kobject对象*/
	struct module *owner; /*所属模块*/
	const struct file_operations *ops; /*文件操作结构体*/
	struct list_head list;
	dev_t dev;/*设备号*/
	unsigned int count;
};

cdev操作函数

//用于初始化cdev成员,并建立文件操作和cdev之间的联系
void cdev_init(struct cdev *cdev, struct file_operations *fops)
//用于动态申请一个cdev内存
struct cdev *cdev_alloc(void)
//减少使用计数
void cdev_put(struct cdev *p);
//向系统添加一个cdev
int cdev_add(struct cdev *, dev_t, unsigned);
//向系统删除一个cdev
void cdev_del(struct cdev *);

分配和释放设备号

在调用cdev_add之前应该先申请设备号

//用于已知设备号情况
int register_chrdev_region(dev_t from, unsigned count, const char *name);
//用于未知设备号情况(可以自动避开重复的设备号)
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
//释放掉已经申请的设备号
void unregister_chrdev_region(dev_t from, unsigned count);

自动创建设备节点

1.创建类和删除类

struct class *class_create (struct module *owner, const char *name);
void class_destroy(struct class *cls);

2.创建设备和删除设备

/*
	 class 就是设备要创建哪个类下面;
	 parent 是父设备,一般为 NULL,也就是没有父设备;
	 devt 是设备号;
	 drvdata 是设备可能会使用的一些数据,一般为 NULL;
	 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx这个设备文件。
	 返回值就是创建好的设备。
*/
struct device *device_create(struct class *class,struct device *parent,dev_t devt,void *drvdata,const char *fmt, ...);
void device_destroy(struct class *class, dev_t devt);

file_operations结构体

struct file_operations {
	struct module *owner;
	//用来修该文件当前读写位置,返回新的为止
	loff_t (*llseek) (struct file *, loff_t, int);
	//用来从设备中读取数据, 成功时函数返回读取的字节数, 出错时返回一个负值。
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	//向设备发送数据, 成功时该函数返回写入的字节数。
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	//异步读
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	//异步写
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iterate) (struct file *, struct dir_context *);
	//函数一般用于询问设备是否可被非阻塞地立即读写。
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	/*	提供设备相关控制命令的实现( 既不是读操作, 也不是写操作) , 当调用成功时,
		返回给调用程序一个非负值。 它与用户空间应用程序调用的int fcntl 和int
		ioctl( int d, int request, ...) 对应。
	*/
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	/*	函数将设备内存映射到进程的虚拟地址空间中, 如果设备驱动未实现此函数, 用户进行
		mmap( ) 系统调用时将获得-ENODEV返回值。 
	*/
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*mremap)(struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **, void **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
	void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
	unsigned (*mmap_capabilities)(struct file *);
#endif
};

字符设备驱动模板

/* 设备结构体*/
struct xxx_dev_t {
struct cdev cdev;
...
} xxx_dev;
/* 设备驱动模块加载函数*/
int xxx_release(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase release!\r\n");
	return 0;
}
int xxx_open(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase open!\r\n");
	return 0;
}
/* 写设备
	filp是文件结构体指针
	buf是用户空间内存的地址
	该地址在内核空间不宜直接读
	count是要写的字节数
	f_pos是写的位置相对于文件开头的偏移
*/
ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos){
	...
	copy_from_user(..., buf, ...);//用户空间缓冲区到内核空间的复制
	...
}
/* 读设备*/
ssize_t xxx_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	copy_to_user(buf,...,...);
}
/* ioctl函数 */
long xxx_ioctl(struct file *filp, unsigned int cmd,unsigned long arg){
	...
	switch (cmd) {
		case XXX_CMD1:
		...
		break;
		case XXX_CMD2:
		...
		break;
		default:
		/* 不能支持的命令 */
		return - ENOTTY;
	}
	return 0;
}
//字符设备驱动文件操作结构体模板
struct file_operations xxx_fops = {
	.owner = THIS_MODULE,
	.read = xxx_read,
	.write = xxx_write,
	.unlocked_ioctl= xxx_ioctl,
	.open = xxx_open;
	.release = xxx_release;
	...
};
static int __init xxx_init(void)
{
	...
	cdev_init(&xxx_dev.cdev, &xxx_fops); /* 初始化cdev */
	xxx_dev.cdev.owner = THIS_MODULE;
	/* 获取字符设备号*/
	if (xxx_major) {
		register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
	} else {
		alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);
	}
	ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); /* 注册设备*/
	...
}
/* 设备驱动模块卸载函数*/
static void __exit xxx_exit(void)
{
unregister_chrdev_region(xxx_dev_no, 1); /* 释放占用的设备号*/
cdev_del(&xxx_dev.cdev); /* 注销设备*/
...
}

实例

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/sysfs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define MEM_CLEAR (_IO(0XEF, 0x1)) 
#define MEM_READ (_IOR(0XEF, 0x2)) 
#define MEM_WRITE (_IOW(0XEF, 0x3)) 
#define MEM_READ_WRITE (_IOWR(0XEF, 0x4)) 
#define DEMO_CNT			1		  	/* 设备号个数 */
#define DEMO_NAME			"demo"	/* 名字 */
#define MEM_BUFFER_SIZE 1000
/* demo设备结构体 */
struct demo_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct class val_class;
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	unsigned char mem[MEM_BUFFER_SIZE];
};
struct demo_dev demo;	/* demo设备 */

static ssize_t demo_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offset)
{
	unsigned long p = *offset;
	unsigned int count = cnt;
	int ret = 0;
	struct demo_dev *dev = filp->private_data;
	if (p >= MEM_BUFFER_SIZE)
		return 0;
	if (count > MEM_BUFFER_SIZE - p)
		count = MEM_BUFFER_SIZE - p;
	if (copy_to_user(buf, dev->mem + p, count)) {
		ret = -EFAULT;
	} else {
		*offset += count;
		ret = count;
		printk("read %u bytes(s) from %lu\n",cnt,p);
	}
	return ret;
}
static ssize_t demo_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offset)
{
	unsigned long p = *offset;
	unsigned int count = cnt;
	int ret = 0;
	struct demo_dev *dev = filp->private_data;
	if (p >= MEM_BUFFER_SIZE)
		return 0;
	if (count > MEM_BUFFER_SIZE - p)
		count = MEM_BUFFER_SIZE - p;
	if (copy_from_user(dev->mem + p, buf, count))
		ret = -EFAULT;
	else {
		*offset += count;
		ret = count;
		printk("written %u bytes(s) from %lu\n", count, p);
	}
	return ret;
}
static loff_t demo_llseek(struct file *filp, loff_t offset, int orig)
{
	loff_t ret = 0;
	switch (orig) {
		case 0: /* 从文件开头位置seek */
			if (offset< 0) {
				ret = -EINVAL;
				break;
			}
			if ((unsigned int)offset > MEM_BUFFER_SIZE) {
				ret = -EINVAL;
				break;
			}
			filp->f_pos = (unsigned int)offset;
			ret = filp->f_pos;
			break;
		case 1: /* 从文件当前位置开始seek */
			if ((filp->f_pos + offset) > MEM_BUFFER_SIZE) {
				ret = -EINVAL;
				break;
			}
			if ((filp->f_pos + offset) < 0) {
				ret = -EINVAL;
				break;
			}
			filp->f_pos += offset;
			ret = filp->f_pos;
		break;
		default:
			ret = -EINVAL;
			break;
	}
	return ret;
}
static long demo_ioctl(struct file *filp, unsigned int cmd,unsigned long arg)
{
	struct demo_dev *dev = filp->private_data;
	switch (cmd) {
		case MEM_CLEAR:
			memset(dev->mem, 0, MEM_BUFFER_SIZE);
			printk(KERN_INFO "demo is set to zero\n");
			break;
		default:
			return -EINVAL;
	}
	return 0;
}
static int demo_open(struct inode *inode, struct file *filp)
{
	struct demo_dev *dev = container_of(inode->i_cdev,struct demo_dev, cdev);
	filp->private_data = dev;/* 设置私有数据 */
	return 0;
}
static int demo_release(struct inode *inode, struct file *filp)
{
	return 0;
}
static struct file_operations demo_fops = {
	.owner = THIS_MODULE,
	.llseek = demo_llseek,
	.read = demo_read,
	.write = demo_write,
	.unlocked_ioctl = demo_ioctl,
	.open = demo_open,
	.release = demo_release,
};
char* strcpy(char* dest, const char* src) {
	char* tmp = dest;
	while ((*dest++ = *src++) != '0');
	return tmp;
}
static ssize_t std_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "%s\n",demo.mem);
}
static ssize_t std_store(struct device *dev,  
	struct device_attribute *attr, const char *buf, size_t len)
{
	strcpy(demo.mem,buf);
	return len;
}
static DEVICE_ATTR_RW(std);


static struct attribute *led_class_attrs[] = {
	&dev_attr_std.attr,
	NULL,
};

static const struct attribute_group led_group = {
	.attrs = led_class_attrs,
};

static const struct attribute_group *led_groups[] = {
	&led_group,
	NULL,
};
static int __init demo_init(void)
{
	/* 1、创建设备号 */
	if (demo.major) {		/*  定义了设备号 */
		demo.devid = MKDEV(demo.major, 0);
		register_chrdev_region(demo.devid, DEMO_CNT, DEMO_NAME);
	} else {						/* 没有定义设备号 */
		alloc_chrdev_region(&demo.devid, 0, DEMO_CNT, DEMO_NAME);	/* 申请设备号 */
		demo.major = MAJOR(demo.devid);	/* 获取分配号的主设备号 */
		demo.minor = MINOR(demo.devid);	/* 获取分配号的次设备号 */
	}
	printk("major=%d,minor=%d\r\n",demo.major, demo.minor);	
	
	/* 2、初始化cdev */
	demo.cdev.owner = THIS_MODULE;
	cdev_init(&demo.cdev, &demo_fops);
	
	/* 3、添加一个cdev */
	cdev_add(&demo.cdev, demo.devid, DEMO_CNT);

	/* 4、创建类 */
	demo.val_class.owner = THIS_MODULE;
	demo.val_class.name = DEMO_NAME;
	demo.val_class.class_groups = led_groups;
	class_register(&demo.val_class);
	/* 5、创建设备 */
	demo.device = device_create(&demo.val_class, NULL, demo.devid, NULL, DEMO_NAME);
	if (IS_ERR(demo.device)) {
		return PTR_ERR(demo.device);
	}
	
	return 0;
}
static void __exit demo_exit(void)
{
	/* 注销字符设备驱动 */
	cdev_del(&demo.cdev);/*  删除cdev */
	unregister_chrdev_region(demo.devid, DEMO_CNT); /* 注销设备号 */

	device_destroy(&demo.val_class, demo.devid);
	class_unregister(&demo.val_class);
	//class_destroy(demo.class);
}
//将上面两个函数指定为驱动的入口和出口函数 
module_init(demo_init);
module_exit(demo_exit);

//LICENSE和作者信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("bin");

container_of() 的作用是通过结构体成员的指针找到对应结构体的指针,这个技巧在Linux内核编程中十分常用。
在struct demo_dev *dev = container_of(inode->i_cdev,struct demo_dev, cdev); 语句
中, 传给container_of() 的第1个参数是结构体成员的指针, 第2个参数为整个结构体的类型, 第3个参数
为传入的第1个参数即结构体成员的类型, container_of() 返回值为整个结构体的指针。

实例说明

本实例以两方式进行和应用层通信 分别是file_operations , sysfs
编译加载到内核中后,可以在/dev节点下找到demo这个文件,或者在/sys/class/demo中找到std文件 可对应进行读写操作

总结

字符设备驱动主要工作就是初始化,添加删除结构体,申请释放设备号,编写file_operations 的操作函数等。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值