驱动字符设备的基础

来源:互联网 发布:php 清空cookie 编辑:程序博客网 时间:2024/04/29 16:20
在linux2.6内核中使用cdev结构体描述一个字符设备,cdev结构体定义如下:
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结构体的dev_t成员定义了设备号,为32位,其中12为主设备号,20为次设备号,使用下列宏可以获得主设备号和次设备号
MAJOR(dev_t dev)
MINOR(dev_t dev)
而使用下列宏可以通过主设备号和次设备号生成dev_t
MKDEV(inf major,int minor)
linux2.6内核还提供了一组函数用以操作cdev结构体
void cdev_init(struct cdev *, const struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
void cd_forget(struct inode *);
cdev_init()函数用以初始化cdev并建立cdev成员和file_operations连接,源代码如下
void cdev_init(struct cdev *cdev, const struct file_operations *fops)//注意传进的cdev结构体指针
{
 memset(cdev, 0, sizeof *cdev);
 INIT_LIST_HEAD(&cdev->list);
 kobject_init(&cdev->kobj, &ktype_cdev_default);
 cdev->ops = fops;//将传进的文件操作结构体指针赋给cdev的ops
}
cdev_alloc()动态申请一个cdev内存,源代码如下
struct cdev *cdev_alloc(void)
{
 struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);//动态分配cdev内存
 if (p) {
  INIT_LIST_HEAD(&p->list);
  kobject_init(&p->kobj, &ktype_cdev_dynamic);
 }
 return p;//返回内存所在地址
}
cdev_add()用于注册设备cdev;cdev_dely()用于释放设备
 
下面要说一下的是分配和释放设备号函数
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)
register_chrdev_region()函数用于已知起始设备号的情况,而alloc_chrdev_region()用于设备号未知,是用来向系统动态分配未分配的设备号,函数成功后就会得到一个设备号存在第一个参数dev中
相反释放申请设备号函数如下
void unregister_chrdev_region(dev_t from, unsigned count)
 
下面的是file_operations结构体
file_operations结构体中的成员函数式字符设备驱动的主体内容,结构体如下:
struct file_operations {
 struct module *owner;//拥有该结构体模块的指针,通常为THIS_MODULES
 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);//执行I/O控制命令
 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 (*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 **);
llseek()函数用于修该一个文件当前的读写位置;
read()函数用来从设备中读取数据,成功时返回读取字节数
write()函数用来向设备发送数据,成功时返回写入字节数
ioctl()提供设备相关的控制命令
....
linux字符设备驱动组成
在字符设备模块加载函数中应该实现设备号申请和cdev的注册,而在卸载函数应实现设备号释放和cdev注销
工程师通常习惯为设备定于一个设备相关的结构体,其中包含所涉及的cdev,私有数据及信号量等信息,常见的设备结构体,模块加载和卸载函数形式如下
//设备结构体
struct xxx_dev{


struct cdev cdev;
        ......
}; 
struct xxx_dev xxx_dev;
//设备驱动模块加载函数
statrc int __init xxx_init(void)
{
.....
int err,devno = MKDEV(globalmem_major,index);
cdev_init(&xxx_dev.cdev,&gxxx_fops);//初始化cdev并建立cdev和file_operations的链接
xxx->cdev.owner = THIS_MODULE;
if(globalmem_major)
 result = register_chrdev_region(devno, 1, "globalmem");//申请设备号
else{
 result = alloc_chrdev_region(&devno,0,1,"globalmem");//动态申请设备号
 globalmem_major = MAJOR(devno);//获得int的主设备号
ret = cdev_add(&xxx_dev.cdev,xxx_dev_no,1);//注册设备xxx_dev_no是dev_t的设备号
...
}
//设备驱动模块卸载函数
static void __exit xxx_exit(void)
{
unregister_chrdev_region(xxx_dev_no,1);//释放设备号
cdev_del(&xxx_dev,cdev);//注销设备(cdev)
}
字符设备中的file_operations结构体中的成员函数
file_operations结构体中成员函数式字符设备驱动与内核的接口,是用户空间对linux进行系统调用的最终落实者。大多数驱动read(),write(),ioctl()函数如下所示
static ssize_t xxx_read(struct file *filp,char __user *buf,size_t size,loff_t *ppos)
{//filp表示文件结构体指针,buf指针指向用户空间,size是要读的字节数,ppos指针是读的位置相对文件开头
...
copy_to_user(buf,...,...);//内核空间到用户空间
...
}
 //写函数用户空间到内核空间
static ssize_t xxx_write(struct file *filp,const char __user *buf,size_t size,loff_t *ppos)
{//filp表示文件结构体指针,buf指针指向用户空间,size是要读的字节数,ppos指针是写的位置相对文件开头
...
copy_from_user(...,buf,...);//用户空间到内核空间
...
}
static int xxx_ioctl(struct inode *inodeep,struct file *filp,unsigned int cmd,unsigned long arg)
{
...
switch (cmd){
case xxx_CMD1;
...
break;
case xxx_CMD2;
...
break;
default:
...
return -ENOTTY;
}
return 0;
0 0