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 的操作函数等。