2015年(14)
分类: LINUX
2015-06-12 15:07:11
原文地址:scull字符设备注释版 作者:hunaiquan
下面注释可能有不对的地方,还请指正.
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>//printk()
#include <linux/slab.h>//kmalloc()
#include <linux/fs.h>//everything...
#include <linux/errno.h>//error codes
#include <linux/types.h>//size_t
#include <linux/fcntl.h>//O_ACCMODE
#include <linux/cdev.h>//struct cdev{}
#include <asm/system.h>//cli(), *_flags
#include <asm/uaccess.h>//copy_*_user
#include "scull.h"//local definitions
//参数可以使用insmod加载
int scull_major = SCULL_MAJOR;//主设备号
int scull_minor = 0;//次设备号
int scull_nr_devs = SCULL_NR_DEVS;//设备数量
int scull_quantum = SCULL_QUANTUM;//每个量子分配的内存大小
int scull_qset = SCULL_QSET;//量子集的个数
module_param(scull_major,int,S_IRUGO);
module_param(scull_minor,int,S_IRUGO);
module_param(scull_nr_devs,int,S_IRUGO);
module_param(scull_quantum,int,S_IRUGO);
module_param(scull_qset,int,S_IRUGO);
struct scull_dev *scull_devices;//全局持久的scull设备变量
//释放整个数据区域的函数,类似于清零
//scull_dev结构中*data指向一个有scull_qset的链表,链表中每个scull_qset结构中的**data指向一个指针数组,数组中每个指针指向一片内存区域,这些内存区域用来 存储数据。而scull_trim函数则使用循环的方式释放这些内存区域和scull_qset结构
int scull_trim(struct scull_dev *dev)
{
struct scull_qset *next,*dptr;
int qset=dev->qset;//量子集中量子的个数,也就是一个量子集的大小
int i;
for(dptr=dev->data;dptr;dptr=next)
{
if(dptr->data)
{
for(i=0;i<qset;i++)//每次循环都释放一个量子的空间,一共有qset个量子
kfree(dptr->data[i]);
kfree(dptr->data);//释放一个scull_qset链表项指针的空间
dptr->data = NULL;//赋值为空
}
next = dptr->next;//准备下一个链表项
kfree(dptr);//释放除第一个节点(scull_dev dev)以外的所有节点!也就是释放所有的scull_qset结构体
}
dev->size = 0;
dev->quantum = scull_quantum;//量子的大小,量子的定义:每次申请分配内存的最小单位.
dev->qset = scull_qset;//量子集的大小
dev->data = NULL;
return 0;
}
//Open & Close
//Open函数关键是完成:分配并填写被置于filp->private_data里的数据结构!!
int scull_open(struct inode *inode,struct file *filp)
{
struct scull_dev *dev;
dev = container_of(inode->i_cdev,struct scull_dev,cdev);//通过指针struct cdev*来获得struct scull_dev*这个新指针!注意cdev数据是scull_dev结构体的成员变量名,inode->i_cdev是指向cdev变量类型的指针.
filp->private_data = dev;//for other methods
if((filp->f_flags & O_ACCMODE) == O_WRONLY)//只写的方式打开设备
{
if(down_interruptible(&dev->sem))//获取信号函数!返回值为0表示成功,非0表示失败
return -ERESTARTSYS;//
up(&dev->sem);//释放信号函数
}
return 0;
}
int scull_release(struct inode *inode,struct file *filp)//释放设备
{
return 0;
}
/*
*以下是scull模块中的一个沿链表前行得到正确scull_set指针的函数,将在read和write方法中被调用
*这个函数的实质是:如果已经存在这个scull_set,就返回这个scull_set的指针。如果不存在这个scull_set,一边沿链表为scull_set分配空间一边沿链表前行,直到所需要的scull_set 被分配到空间并初始化为止,就返回这个scull_set 的指针。
*/
struct scull_qset *scull_follow(struct scull_dev *dev,int n)
{
struct scull_qset *qs = dev->data;//指向第一个量子集
if(!qs)//如果在该索引之前的scull_qset结构不存在的话,为之分配内存
{
qs = dev->data = kmalloc(sizeof(struct scull_qset),GFP_KERNEL);
if(qs == NULL)
return NULL;//创建失败
memset(qs,0,sizeof(struct scull_qset));
}
while(n--)//接下来继续创建节点
{
if(!qs->next)//如果在该索引之前的scull_qset结构不存在的话,为之分配内存
{
qs->next = kmalloc(sizeof(struct scull_qset),GFP_KERNEL);
if(qs->next == NULL)
return NULL;//创建失败
memset(qs->next,0,sizeof(struct scull_qset));
}
qs = qs->next;
continue;
}
return qs;//正好返回第item个scull_qset链表项指针.
}
//read and write
//注意:scull设备的读和写函数仅限在1个量子集中进行!!!
ssize_t scull_read(struct file *filp,char __user *buf,size_t count,loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data;//将设备数据赋值给变量dev,为提取设备数据做准备!
struct scull_qset *dptr;//the first listitem 第一个链表项
int quantum = dev->quantum,qset = dev->qset;
int itemsize = quantum * qset;//该链表项中有多少字节
int item,s_pos,q_pos,rest;
ssize_t retval = 0;//实际读取的字节数,初始化为0
//获取互斥信号
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
if(*f_pos >= dev->size)//超过了设备数据量,文件指针的位置已经不在设备中了!!
goto out;
if(*f_pos + count > dev->size)//确保提取设备数据的长度是有效的!!
count = dev->size - *f_pos;
//find listitem,qset index,and offset in the quantum
//在量子集中寻找链表项、qset索引以及quantum偏移量
//计算当前偏移对应第几个scull_qset、在scull_qset中data的下标、以及在*data指向的内存区域中的偏移;
item = (long)*f_pos / itemsize;//当前偏移对应第item个scull_qset
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;//qset索引,scull_qset中data的下标
q_pos = rest % quantum;//量子偏移量的位置,在*data指向的内存区域中的偏移
//遍历链表(如果没有链表则建立链表),直至到达正确的节点位置
//根据当前要操作的sucll_qset的索引item,返回其指针,如果在该索引之前的scull_qset结构不存在的话,为之分配内存
dptr = scull_follow(dev,item);//返回scull_qset*指针
if(dptr == NULL || !dptr->data || !dptr->data[s_pos])
goto out;
if(count > quantum - q_pos)//仅读至当前量子的末尾
count = quantum - q_pos;
//ulong copy_to_user(void __user *to,const void *from,ulong count);
//拷贝数据
if(copy_to_user(buf,dptr->data[s_pos]+q_pos,count))//返回0表示成功,非0表示失败
{
retval = -EFAULT;//无效的地址
goto out;
}
//重置偏移量
*f_pos += count;
retval = count;//返回读取的字节数count
out:
up(&dev->sem);//释放信号量
return retval;
}
//write 将用户空间的数据写入到设备!
ssize_t scull_write(struct file *filp,const char __user *buf,size_t count,loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data;//scull设备的信息,在scull_open函数中赋值给了filp->private_data
struct scull_qset *dptr;//准备保存写入的信息
int quantum = dev->quantum,qset = dev->qset;
int itemsize = quantum * qset;
int item,s_pos,q_pos,rest;
ssize_t retval = -ENOMEM;//写入数据的报错
if(down_interruptible(&dev->sem))//获取信号量函数
return -ERESTARTSYS;
//计算当前偏移对应第几个scull_qset、在scull_qset中data的下标、以及在*data指向的内存区域中的偏移;
//find listitem,qset index,offset in the quantum
item = (long)*f_pos / itemsize;//listitem 得到当前偏移对应第item个scull_qset
rest = (long)*f_pos % itemsize;//得到当前链表项的偏移量
s_pos = rest / quantum;//scull_qset中data的下标
q_pos = rest % quantum;//在*data指向的内存区域中的偏移
//follow the list up to the right position
dptr = scull_follow(dev,item);//根据当前要操作的sucll_qset的索引item,返回其指针,如果在该索引之前的scull_qset结构不存在的话,为之分配内存
if(dptr == NULL)//查找的链表项不存在,或者其他错误
goto out;
if(!dptr->data)//分配内存(地址空间),用作数据的存储!!
{
dptr->data = kmalloc(qset * sizeof(char *),GFP_KERNEL);//分配qset字节的空间,sizeof(char *)表示单位是字节
if(dptr->data == NULL)
goto out;
memset(dptr->data,0,qset * sizeof(char *));
}
if(!dptr->data[s_pos])//给qset分配空间,用作数据的存储.
{
dptr->data[s_pos] = kmalloc(quantum,GFP_KERNEL);
if(dptr->data[s_pos] == NULL)
goto out;
}
if(count > quantum - q_pos)//仅写至当前量子的末尾
count = quantum - q_pos;
//ulong copy_from_user(void *to,const void __user *from,ulong count);
//拷贝数据
if(copy_from_user(dptr->data[s_pos]+q_pos,buf,count))//返回0表示成功,非0表示失败
{
retval = -EFAULT;
goto out;
}
//重置偏移量
*f_pos += count;
retval = count;
/* update the size */
if (dev->size < *f_pos)
dev->size = *f_pos;
out:
up(&dev->sem);//释放信号量
return retval;
}
//文件操作结构
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.read = scull_read,
.write = scull_write,
.open = scull_open,
.release = scull_release,
};
//为scull设备建立字符设备结构体
static void scull_setup_cdev(struct scull_dev *dev,int index)
{
int err,devno = MKDEV(scull_major,scull_minor + index);
cdev_init(&dev->cdev,&scull_fops);//初始化建立cdev和file_operations之间的连接,struct file_operations scull_fops
dev->cdev.owner = THIS_MODULE;//所属模块
err = cdev_add(&dev->cdev,devno,1);//注册设备,返回0表示成功,非0表示失败
if(err)
printk(KERN_NOTICE "Error %d adding scull%d",err,index);
}
//卸载模块的函数!
//scull_dev结构中*data指向一个有scull_qset的链表,链表中每个scull_qset结构中的**data指向一个指针数组,数组中每个指针指向一片内存区域,这些内存区域用来 存储数据。而scull_trim函数则使用循环的方式释放这些内存区域和scull_qset结构
void scull_cleanup_module(void)
{
int i;
dev_t devno = MKDEV(scull_major,scull_minor);
//卸载设备,清零处理
if(scull_devices)
{
for(i=0;i<scull_nr_devs;i++)
{
scull_trim(scull_devices+i);//调用scull_trim()函数释放每个设备中存储数据的内存区域
cdev_del(&scull_devices[i].cdev);//调用cde_del()函数移除该设备对应的字符设备结构
}
kfree(scull_devices);//释放设备scull_dev结构
}
unregister_chrdev_region(devno,scull_nr_devs);//释放设备号
}
//初始化模块的函数
int scull_init_module(void)
{
int result,i;
dev_t dev = 0;
//注册设备号
if(scull_major)//根据已知的主设备号,静态注册设备
{
dev = MKDEV(scull_major,scull_minor);//调用MKDEV宏定义,利用主次设备号得到dev_t
result = register_chrdev_region(dev,scull_nr_devs,"scull");
}
else //动态注册设备
{
result = alloc_chrdev_region(&dev,scull_minor,scull_nr_devs,"scull");
scull_major = MAJOR(dev);
}
if(result < 0)//注册失败
{
printk(KERN_WARNING "scull:can't get major %d\n",scull_major);
return result;
}
//全局scull设备变量,struct scull_dev *scull_devices;
//为scull_nr_devs个设备分配scull_dev的内存空间,并清空
scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev),GFP_KERNEL);
if(!scull_devices)//返回0,表示分配内存失败
{
result = -ENOMEM;
goto fail;
}
//分配的内存空间初始化为0
memset(scull_devices,0,scull_nr_devs * sizeof(struct scull_dev));
//Initialize each device
//在循环中初始化每个scull_dev设备:初始化quantnum、qset等变量,初始化信号量、调用函数scull_setup_cdev()
for(i=0;i<scull_nr_devs;i++)
{
scull_devices[i].quantum = scull_quantum;
scull_devices[i].qset = scull_qset;
init_MUTEX(&scull_devices[i].sem);//互斥信号量,scull设备用不上.
scull_setup_cdev(&scull_devices[i],i);
}
return 0;
fail:
scull_cleanup_module();
return result;
}
module_init(scull_init_module);
module_exit(scull_cleanup_module);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("beyond2010-6-4");