• 字符驱动之二操作方法(struct file_operations)【转】


    从上一篇我们看到了字符驱动的三个重要结构,那我现在跟大家详细的说说 struct file_operations
     
    这个文件操作方法的数据结构。其实这结构中包含了用户空间所需要的大部分的系统调用函数指针,因此如何
     
    我们应该如何去实现这些函数的策略呢?这就应该跟用户空间函数所实现的函数功能相对应,去实现这些函数
     
    策略。本博客重点描述几个重要的比如 open、read、write、ioctl、lseek ... 至于这个结构里成员,大家
     
    自己去看看内核源代码,我也贴出来了。
     
    头文件:
    #include
    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 (*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 **);
    };
     
       那我就废话少说,直接进入主题了:
      
       1、open 方法提供文件被操作之前状态(struct inode 结构描述)到文件被打开之后要操作状态(struct 
     
    file结构描述)的转换,同时我们在系统调用时也指定了文件模式(可读可写等)、读写位置(fopen)等。因此
     
    open 方法大部分应做如下操作:
      
       (1)确定打开哪个设备,如第一次打开,应做初始化工作;
      
       (2)open 系统调用以阻塞/非阻塞/可读可写...方式打开的;
     
       (3)涉及到硬件时,应考虑是否检查和初始化;
     
       (4)大部分我们要分配并填充要放进 filp->private_data 的任何数据结构;
     
       (5)打开同一设备文件记数(因为同一时间,不仅仅是单个进程或线程在操作)  MOD_INC_USE_COUNT;
     
    【note】
    (A)由于内核只知道 struct cdev 而不知道我们自己创建的数据结构,那我们如何去获取我们的数据结构
     
        呢?很幸运的是内核应该帮我们做,用container_of(pointer, container_type, 
     
         container_field);这个宏即可实现。
     
    (B)为何要放在 struct file 中的(void *)private_data 中呢?
     
         如何不采用该方法,就应该定义一个全局变量,这样函数的可重入性就没了,更别说其他方面了。 
     
       2、open 方法相对应的应该就是 release,因此我们容易看出 release 应该要干嘛哦。
     
         (1)释放 open 分配在 filp->private_data 中的任何东西;
     
         (2)设备文件减数 MOD_DEC_USE_COUNT
     
         (3)在最后的 close 关闭设备。
     
       3、read/write 应用程序中 read(fd, buf,count)
                               write(fd, buf, count);
         ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);
         ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);
     
        对于 2 个方法, filp 是文件指针,对应打开的文件描述符 fd, count 是请求的传输数据大小. buff
     
    参数指向持有被写入数据的缓存, 或者放入新数据的空缓存,它是用户指针,不能直接被内核解引用,这涉
     
    及到内核安全问题. 最后, offp 是一个指针指向一个"longoffset type"对象, 它指出用户正在存取的文件
     
    位置. 应用程序没有,是系统调用函数帮忙做好的。返回值是一个"signed size type";用 read 参数传递图
     
    来讨论吧。
     
      
       图来自linux设备驱动程序,本博客中大部分参考来自该书本
      
     read 返回值 ret 解释:
     
       (1)ret = count,这种情况是最好不过的啦,说明所需数据全部被传送;
     
       (2)0 < ret < count,说明只有部分数据被传送出去;
     
       (3)ret = 0,说明达到文件末尾了;
     
       (4)ret < 0,表示文件出错,返回值会提示文件出了哪种类型错误;
     
         出错的典型返回值包括 -EINTR( 被打断的系统调用) 或者 -EFAULT( 坏地址 ).
     
      (5)刚开始没有数据可读,可后来会有,这种情况 就会出现阻塞现象,而系统调用在缺省情况下就是有阻
     
    塞现象发生
     
       write 返回值 ret 解释:
     
       (1)ret = count,这种情况是最好不过的啦,说明所需数据全部传送完毕;
     
       (2)0 < ret < count,说明只有部分数据被传送,一般应用程序会重新写入;
     
       (3)ret = 0,没有数据可写了,可按具体情况定义;
     
       (4)ret < 0,表示文件出错,返回值会提示文件出了哪种类型错误;
     
         出错的典型返回值包括 -EINTR( 被打断的系统调用) 或者 -EFAULT( 坏地址 ).
     
       (5)刚开始没有内存可写,可后来会有,这种情况也会出现阻塞现象,而系统调用在缺省情况下就是有阻
     
    塞现象发生
     
       4、ioctl
       int ioctl(int fd,unsigned long cmd,...)
     
      原型中的点表示这是一个可选的参数,存在与否依赖于控制命令(第2 个参数)是否涉及到与设备的数据交
     
    互。
     
       ioctl 驱动方法有和用户空间版本不同的原型:
       int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
     
     cmd参数从用户空间传下来,可选的参数arg 以一个unsigned long 的形式传递,不管它是一个整数或一个指
     
    针 。如果cmd命令不涉及数据传输,则第 3 个参数arg的值无任何意义。
     
       我想大家关注的是如何去实现ioctl方法吧。
     
    【步骤】
     
    (1)定义命令==>类型(魔数、数)、序号、传送方向、参数的大小
     
    (2)实现命令==>返回值、参数使用、命令操作
     
    【解析】
     
    A)定义ioctl 命令的正确方法是使用4 个位段, 这个列表中介绍的符号定义在中:
     
    _IO(type,nr)              //没有参数的命令
    _IOR(type,nr,datatype)  //从驱动中读数据
    _IOW(type,nr,datatype)  //写数据到驱动
    _IOWR(type,nr,datatype) //双向传送,type 和number 成员作为参数被传递。 
     
    /**type 魔数 一般采用宏定义,表明了哪个设备的命令,具体查看/Docementation/ioctl-number.txt**/
     
    /**number 序号 8位宽,可定义255个命令,(nr)不一定从 0 开始定义**/
     
    /**Direction 方向,决定了往设备读还是写,甚至可读可写,作用在于具有一定的保护性**/
     
    /**size 参数大小(datatype)**/
     
    举个例子,让大家更容易理解一些:
     
    #define XXX_IOC_MAGIC ‘m’ //定义魔数==>单引号要小心,不要随便使用不确定的魔数
    #define XXX_IOCSET   _IOW(XXX_IOC_MAGIC, 0, int)
    #define XXX_IOCGQSET _IOR(XXX_IOC_MAGIC, 1, int)
     
    B)定义好了命令,下一步就是要实现Ioctl函数了,看个具体例子清楚吧:关键在于命令如何操作、参数要检
     
    查、返回值要准确。

    1. /**命令有效性的检查**/
    2.   if (_IOC_TYPE(cmd) != XXX_IOC_MAGIC)
    3.    {
    4.      return -ENOTTY;
    5.    }
    6.    if (_IOC_NR(cmd) > XXX_IOC_MAXNR)
    7.    {
    8.      return -ENOTTY;
    9.   }
    10.   
    11.   /**参数有效性检查**/
    12.   if (_IOC_DIR(cmd) & _IOC_READ) 
    13.    {                                    /*?为何_IOC_READ对应_IOC_WRITE*/
    14.      err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
    15.    }
    16.    else if (_IOC_DIR(cmd) & _IOC_WRITE)
    17.    { 
    18.      err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
    19.    }
    20.    if (err)
    21.    { 
    22.      return -EFAULT;
    23.   }
    24.   
    25.   /**命令的实现**/
    26.   switch(cmd)
    27.   {
    28.     case XXX_IOCSET:
    29.          ... 
    30.          break;
    31.     case XXX_IOCGQSET:
    32.          ...
    33.          break; 
    34.     default: 
    35.          break;
    36.   }
  • 相关阅读:
    PHP 文件包含之文件路径截断(转)
    如何使用Linux通用后门(转zafe)
    利用sqlmap和burpsuite绕过csrf token进行SQL注入 (转)
    正则表达式30分钟入门教程<转载>
    php empty()和isset()的区别<转载>
    $_SERVER详细资料整理(转)
    [C语言(VC)] 打造自己的键盘记录器 (zaroty)
    metasploit(MSF)终端命令大全
    linux提权总结(外文)
    kettle菜鸟学习笔记1----相关准备知识
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/4812387.html
Copyright © 2020-2023  润新知