学习总要时时进行一下总结,这样才会使自己对于知识的理解更上一层楼的。
今天讲一下在linux2.6内核中字符设备驱动大概框架。在该kernel版本中,使用cdev结构体描述一个字符设备。为了更好的说明,这里以实例代码进行讲解。
完整的源码粘贴在文章的最后,一方面便于大家学习使用,更可以作为自己开发学习参考之用,希望可以把csdn作为自己学习经验总结的平台。
1. 首先从一些操作字符设备的结构体开始。
a. 研究字符设备驱动,我们首先谈谈struct cdev结构体:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
kobj为内嵌的kobject对象,struct kobject数据结构使所有设备在底层都具有统一的接口,kobject提供基本的对象管理,是构成Linux2.6设备模型的核心结构,
owner指针表示该cdev设备所属模块,我的代码这里使用THIS_MODULE表示cdev的设备属于该驱动模块。
ops文件操作结构体,在linux中有一句经典的话:“一切都是文件”,最终设备可以以操作文件的方式进行操作,进行open,read,write等操作,而ops中的成员便是cdev结构
体中为我们提供的对应用户空间的内核中的操作设备的函数接口的结构。
dev为对应该设备驱动的设备号,设备有主设备号和次设备号之分。
b. 上面谈到struct file_operations结构体为操作设备文件提供底层实现接口,现在就来看看file_operation结构体的成员:
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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (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 *, struct dentry *, 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 (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
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 (*dir_notify)(struct file *filp, unsigned long arg);
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 (*open) (struct inode *, struct file *):与用户空间的open函数相对应,用于打开设备;
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *):与用户空间的read函数相对应,用于从设备中读取数据,成功时返回读取的设备数,出错时返回一个负值;
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *):与用户空间的write函数相对应,用于向设备写入数据,成功时该函数返回写入的字节数;
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long):与用户空间的ioctl函数相对应,提供设备相关控制命令的实现,调用成功返回一个非负值;
int (*release) (struct inode *, struct file *):与用户空间的close函数相对应,实现对已打开设备的关闭功能。
上面简述了一下主要的两个结构体的大概组成,下面从模块的加载函数和卸载函数开始说起基本的驱动程序如何去写。
2. 设备驱动模块加载和卸载函数
模块加载函数是加载驱动时首先执行的程序代码;模块卸载函数是模块卸载的时候需要首先执行的程序代码。
struct cdd_dev
{
struct cdev device;
char globalmem[GLOBAL_SIZE];
};
static struct cdd_dev *cdd_devp = NULL;
上面定义了设备驱动的重要结构体,cdev成员变量和在内核空间分配的可以让我们控制一段内存。
static int __init cdd_init(void)
{
int ret;
int i = 0;
/* static malloc devno */
if(cdd_major)
{
dev = MKDEV(CDD_MAJOR, CDD_MINOR);
ret = register_chrdev_region(dev, CDD_COUNT, "cdd");
}
else
ret = alloc_chrdev_region(&dev, CDD_MINOR, CDD_COUNT, "cdd");
if(ret < 0)
goto failure_chrdev_region;
上面这段为模块的加载函数,首先根据自定义的主设备号决定产生设备号的方式:1.定义主设备号不为0,静态方式产生,2 定义主设备号为0,使用动态方式产生,这里推荐大家使用动态的方式产生方式产生设备号,这样可以自动避免和原有的设备节点文件主设备号的冲突, 如使用手动创建,命令如:mknod /dev/cdd c 248 0(c为设备类型,248为自定义的主设备号,0为次设备号)。
cdd_devp = kmalloc(sizeof(struct cdd_dev)*CDD_COUNT, GFP_KERNEL);
if(cdd_devp == NULL)
{
ret = -ENOMEM;
goto failure_kmalloc;
}
使用kmalloc函数为cdd_devp分配sizeof(struct cdd_dev)*CDD_COUNT的空间,上面使用的为GFP_KERNEL的方式分配,关于kmalloc的第二个参数:
GFP_KERNEL:分配内存,分配过程中可能导致睡眠
GFP_ATOMIC:分配内存,不会导致睡眠
GFP_DMA:申请到的内存位于物理地址的0~16M
_GFP_HIGHMEM:申请高端内存(896M以上的物理地址内存)
cdd_classp = class_create(THIS_MODULE, "cdd_class");
if(IS_ERR(cdd_classp))
{
ret = PTR_ERR(cdd_classp);
goto failure_class_create;
}
class_create为THIS_MODULE分配一个设备类名为"cdd_class",下面的代码将在此设备类下面建立对应的节点文件。
for(; i<CDD_COUNT; i++)
{
cdev_init(&cdd_devp[i].device, &cdd_fops);
cdev_add(&cdd_devp[i].device, dev, CDD_COUNT);
device_create(cdd_classp, NULL, dev, "cdd%d", i);
}
cdev_init函数实现设备与文件操作函数cdd_fops的相关联,cdev_add实现cdev的添加功能;而最终device_create将实现将cdd_classp下面新建设备的功能,关于创建设备类与设备最终会在/sys/class文件夹下体现,如上在/sys/class/文件夹下会产生cdd_test/cdd0 ...的设备目录。
return 0;
failure_class_create:
kfree(cdd_devp);
failure_kmalloc:
unregister_chrdev_region(dev, CDD_COUNT);
failure_chrdev_region:
return ret;
}
模块卸载函数的功能与模块加载函数功能相反,例:
static void __exit cdd_exit(void)
{
int i = 0;
for(; i<CDD_COUNT; i++)
cdev_del(&cdd_devp[i].device);
删除cdev
class_destroy(cdd_classp);
销毁设备类
kfree(cdd_devp);
释放cdd_devp内存
unregister_chrdev_region(dev, CDD_COUNT);
取消已注册的设备号为dev的注册,释放。
}
3. 具体的文件操作函数
看一下file_operations结构体的定义:
static struct file_operations cdd_fops =
{
.owner = THIS_MODULE,
.open = cdd_open,
.read = cdd_read,
.write = cdd_write,
.ioctl = cdd_ioctl,
.release = cdd_release,
};
首先从open函数看起
static int cdd_open (struct inode *inode, struct file *filp)
{
struct cdd_dev *devp;
devp = container_of(inode->i_cdev, struct cdd_dev, device);
filp->private_data = devp;
return 0;
}
open函数实现设备打开的功能,此open使用container_of函数实现返回对应open的设备cdev结构体,然后将其加入到file结构体的私有变量中,这种方式可以实现同一主设备号的同时包含两个或两个以上的设备驱动。优化:这里的open我们其实可以使用上锁的方式使设备只能被打开一次。
接着为read和write函数:
ssize_t cdd_read (struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
unsigned int count = size;
unsigned long pos = *offset;
struct cdd_dev *devp = filp->private_data;
printk("enter cdd_read\n");
if(pos > GLOBAL_SIZE)
return 0;
printk("pos > GLOBAL_SIZE\n");
if(count >= GLOBAL_SIZE-pos)
count = GLOBAL_SIZE-pos;
ret = copy_to_user(buf, (void *)(devp->globalmem+pos), count);
if(ret)
ret = EFAULT;
else
{
printk("copy_to_user else: %d, offset : %ld, mem: %s\n", count, pos, buf);
*offset += count;
ret = count;
}
return 0;
}
ssize_t cdd_write (struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
unsigned int count = size;
unsigned long pos = *offset;
struct cdd_dev *devp = filp->private_data;
printk("enter cdd_write\n");
if(pos > GLOBAL_SIZE)
return 0;
printk("pos > GLOBAL_SIZE\n");
if(count >= GLOBAL_SIZE-pos)
count = GLOBAL_SIZE-pos;
ret = copy_from_user((void *)(devp->globalmem+pos), buf, count);
if(ret)
ret = -EFAULT;
else
{
printk("copy_from_user else: %d, offset : %lu, mem: %s\n", count, pos, devp->globalmem);
offset += count;
ret = count;
}
return ret;
}
read和write函数主要实现对cdd_devp中的globalmem的数据读写功能。主要就是读偏移位置和写入和读取数量的考虑,没有什么好说的。
主要说下两个函数:copy_from_user和copy_to_user;因为用户空间和内核空间是相互分开的,要是在两者之间的内存操作,需要借助这两个函数,我们可以看到在write和read函数的参数中__user表示这段存储区域位于用户空间中,所以需要借助copy_to_user和copy_from_user实现内存的copy。
关于ioctl函数:
int cdd_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long args)
{
struct cdd_dev *devp = filp->private_data;
switch(cmd)
{
case MEM_CLEAR:
memset(devp->globalmem, 0, GLOBAL_SIZE);
break;
default:
break;
}
return 0;
}
ioctl对应用户空间的ioctl函数,用于向设备发送命令,这里只定义了一个命令MEM_CLEAR,实现内存的清除。
关于release函数没有什么好说的。
综上所述,为linux字符设备驱动框架的自我总结,现将整体源码粘贴。
cdd.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#define CDD_MAJOR 0
#define CDD_MINOR 0
#define CDD_COUNT 1
#define GLOBAL_SIZE 9
#define MEM_CLEAR 0x01
static int cdd_major = CDD_MAJOR;
static dev_t dev = 0;
struct cdd_dev
{
struct cdev device;
char globalmem[GLOBAL_SIZE];
};
static struct cdd_dev *cdd_devp = NULL;
static struct class *cdd_classp = NULL;
static int cdd_open (struct inode *inode, struct file *filp)
{
struct cdd_dev *devp;
devp = container_of(inode->i_cdev, struct cdd_dev, device);
filp->private_data = devp;
return 0;
}
ssize_t cdd_read (struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
unsigned int count = size;
unsigned long pos = *offset;
struct cdd_dev *devp = filp->private_data;
printk("enter cdd_read\n");
if(pos > GLOBAL_SIZE)
return 0;
printk("pos > GLOBAL_SIZE\n");
if(count >= GLOBAL_SIZE-pos)
count = GLOBAL_SIZE-pos;
ret = copy_to_user(buf, (void *)(devp->globalmem+pos), count);
if(ret)
ret = EFAULT;
else
{
printk("copy_to_user else: %d, offset : %ld, mem: %s\n", count, pos, buf);
*offset += count;
ret = count;
}
return 0;
}
ssize_t cdd_write (struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
unsigned int count = size;
unsigned long pos = *offset;
struct cdd_dev *devp = filp->private_data;
printk("enter cdd_write\n");
if(pos > GLOBAL_SIZE)
return 0;
printk("pos > GLOBAL_SIZE\n");
if(count >= GLOBAL_SIZE-pos)
count = GLOBAL_SIZE-pos;
ret = copy_from_user((void *)(devp->globalmem+pos), buf, count);
if(ret)
ret = -EFAULT;
else
{
printk("copy_from_user else: %d, offset : %lu, mem: %s\n", count, pos, devp->globalmem);
offset += count;
ret = count;
}
return ret;
}
int cdd_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long args)
{
struct cdd_dev *devp = filp->private_data;
switch(cmd)
{
case MEM_CLEAR:
memset(devp->globalmem, 0, GLOBAL_SIZE);
break;
default:
break;
}
return 0;
}
int cdd_release (struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations cdd_fops =
{
.owner = THIS_MODULE,
.open = cdd_open,
.read = cdd_read,
.write = cdd_write,
.ioctl = cdd_ioctl,
.release = cdd_release,
};
static int __init cdd_init(void)
{
int ret;
int i = 0;
/* static malloc devno */
if(cdd_major)
{
dev = MKDEV(CDD_MAJOR, CDD_MINOR);
ret = register_chrdev_region(dev, CDD_COUNT, "cdd");
}
else
ret = alloc_chrdev_region(&dev, CDD_MINOR, CDD_COUNT, "cdd");
if(ret < 0)
goto failure_chrdev_region;
cdd_devp = kmalloc(sizeof(struct cdd_dev)*CDD_COUNT, GFP_KERNEL);
if(cdd_devp == NULL)
{
ret = -ENOMEM;
goto failure_kmalloc;
}
cdd_classp = class_create(THIS_MODULE, "cdd_class");
if(IS_ERR(cdd_classp))
{
ret = PTR_ERR(cdd_classp);
goto failure_class_create;
}
for(; i<CDD_COUNT; i++)
{
cdev_init(&cdd_devp[i].device, &cdd_fops);
cdev_add(&cdd_devp[i].device, dev, CDD_COUNT);
device_create(cdd_classp, NULL, dev, "cdd%d", i);
}
return 0;
failure_class_create:
kfree(cdd_devp);
failure_kmalloc:
unregister_chrdev_region(dev, CDD_COUNT);
failure_chrdev_region:
return ret;
}
static void __exit cdd_exit(void)
{
int i = 0;
for(; i<CDD_COUNT; i++)
cdev_del(&cdd_devp[i].device);
class_destroy(cdd_classp);
kfree(cdd_devp);
unregister_chrdev_region(dev, CDD_COUNT);
}
module_init(cdd_init);
module_exit(cdd_exit);
MODULE_LICENSE("GPL v2");
测试文件:cdd_test.c
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#define MEM_CLEAR 0x01
void print_usage(char *cmd)
{
printf("usage: %s [option]\n", cmd);
printf(" -w content\n");
printf(" -r num\n");
printf(" -c\n");
}
int main(int argc, char **argv)
{
int fd = -1;
int ret = 0;
char buf[32] = {0};
if(argc < 2)
{
print_usage(argv[0]);
return -1;
}
fd = open("/dev/cdd0", O_RDWR);
if(fd < 0)
{
printf("open error!\n");
return -1;
}
switch(argv[1][0])
{
case 'r': case 'R':
if(argc < 3)
{
print_usage(argv[0]);
return -1;
}
ret = read(fd, buf, atoi(argv[2]));
if(ret < 0)
{
printf("read error!\n");
return -1;
}
printf("read: %s\n", buf);
break;
case 'w': case 'W':
if(argc < 3)
{
print_usage(argv[0]);
return -1;
}
ret = write(fd, argv[2], strlen(argv[2]));
if(ret < 0)
{
printf("read error!\n");
return -1;
}
break;
case 'c': case 'C':
ret = ioctl(fd, MEM_CLEAR);
if(ret < 0)
{
printf("ioctl error!\n");
return -1;
}
break;
default:
print_usage(argv[0]);
return -1;
break;
}
return 0;
}