Chinaunix首页 | 论坛 | 博客
  • 博客访问: 37622
  • 博文数量: 14
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2015-05-12 14:56
文章分类
文章存档

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;
//
       scull_trim(dev);
//ignore errors 清零处理!
       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");

阅读(1096) | 评论(0) | 转发(0) |
0

上一篇:C 语言经典面试题

下一篇:I2C接口

给主人留下些什么吧!~~