第三章 字符设备驱动程序

            《1》在内核中, dev_t 类型(<linux/types.h> 中定义)用来保存设备编号-----包括主设备号和次设备号。在内核2.6.0版本中,dev_t是一个32位的数,其中的12位用来表示主设备号,而其余20位用来表示次设备号。当然我们的代码不应该对设备编号的组织做任何假定。而应该始终使用<linux/kdev_t.h> 中定义的宏。比如要获得dev_t 的主设备号或次设备号,应使用:  MAJOR(dev_t  dev) ;      MINOR(dev_t  dev) ; 相反, 如果需要将主设备号和次设备号转换成dev_t 类型,则使用: MKDEV(int  major , int minor) ;

            《2》在建立一个字符设备之前,我们首先需要获得一个或者多个设备编号。静态分配设备号需要的函数是: 

                      int  register_chrdev_region(dev_t  first , unsigned int count , char * name);

                     first : 是要分配的设备号范围的起始值。first的次设备号经常被设置为0, 但对该函数来讲并不是必需的。

                     count :所请求的连续设备编号的个数。如果count非常大,则所请求的范围可能和下一个主设备号重叠, 但只要我们所请求的编号范围是可用的,则不会带来任何问题。

                     name : 是和该编号范围关联的设备名称, 它将出现在 /proc/devices 和 sysfs中。

                    成功分配时返回0, 错误情况返回一个错误码,并且不能请求所请求的区域。

                    

                     当我们不知道设备号的时候,我们可以通过动态分配来实现,使用函数是:

                     int   alloc_chrdev_region(dev_t   *dev  , unsigned  int  firstminor , unsigned  int  count , char  *name) ;

                     dev : 是仅用于输出的参数, 在成功完成调用后将保存已分配范围的第一个编号。

                     firstminor 应该是要使用的被请求的第一个次设备号, 它通常是0 。count 和 name 参数与register_chrdev_region 函数是一样的。

             《3》无论采用什么样的方法分配设备号,都应该在不再使用它们时释放这些设备编号。使用下面的函数:

                        void    unregister_chrdev_region(dev_t   first ,  unsigned  int  count ) ;

             《4》在书本中例子主要是讲解scull, 下面这个scull_load 脚本是发布scull的一部分, 使用模块形式发行的驱动程序的用户可以在系统的rc.local 文件中调用这个脚本,或是在需要模块时手工调用。

#!/bin/sh

module = “scull”

device=“scull”

mode=“664”

#使用传入到该脚本的所有参数调用insmod , 同时使用路径名来指定模块位置,

#这是因为新的modutils 默认不会在当前目录中查找模块。

/sbin/insmod  ./$module.ko   $*   ||  exit   1

#删除原有节点

rm   -f   /dev/${device} [0-3]

major  = $(awk   “\$2 =  = \“$module\ ”  {print    \$1 } ”    /proc/devices )

mknod  /dev/${device}0   c   $major   0

mknod  /dev/${device}1   c   $major   1

mknod  /dev/${device}2   c   $major   2

mknod  /dev/${device}3   c   $major   3


#给定适当的组属性及许可,并修改属组。

#并非所有的发行版都具有staff组, 有些有wheel 组。

group =“staff”

grep  -q  '  ^staff :  '  / etc / group    ||   group = “ wheel ”


chgrp   $group   /dev/${device}[ 0 -3 ]

chmod  $mode  /dev/ ${device}[ 0 -3]

这个脚本同样可以适用于其他的驱动程序,只要重新定义变量并调整mknod 那几行语句就可以了。

这个脚本必须由超级用户运行,所以新创建的设备文件自然属于root。默认权限位只属于root对其有写访问权,而其他用户只有读访问权,所以我们需要做相应的修改。

               《5》大部分基本驱动程序操作涉及到三个重要的内核数据结构,分别是file_operations , file 和 inode 。迄今为止,我们已经为自己保留了一些设备编号,但未将任何驱动程序操作连接到这些编号。file_operations 结构就是用来建立这种连接的。这种结构定义在<linux / fs.h>, 其中包含了一组函数指针。每个打开的文件(在内部由一个file 结构表示) 和一组函数关联(通过包含指向一个file_operations 结构的f_op字段) 。

                      这三个结构体主要如下:

struct file {
        union {
             struct list_head fu_list;   文件对象链表指针linux/include/linux/list.h
             struct rcu_head fu_rcuhead;  RCU(Read-Copy Update)是Linux 2.6内核中新的锁机制
        } f_u;
        struct path f_path;    包含dentry和mnt两个成员,用于确定文件路径
        #define f_dentry  f_path.dentry  f_path的成员之一,当前文件的dentry结构
        #define f_vfsmnt  f_path.mnt     表示当前文件所在文件系统的挂载根目录
        const struct file_operations *f_op;    与该文件相关联的操作函数
        atomic_t  f_count;                     文件的引用计数(有多少进程打开该文件)
        unsigned int  f_flags;                 对应于open时指定的flag
        mode_t  f_mode;                        读写模式:open的mod_t mode参数
        off_t  f_pos;                          该文件在当前进程中的文件偏移量
        struct fown_struct f_owner;            该结构的作用是通过信号进行I/O时间通知的数据。
        unsigned int  f_uid, f_gid;            文件所有者id,所有者组id
        struct file_ra_state f_ra;             在linux/include/linux/fs.h中定义,文件预读相关
        unsigned long f_version;
        #ifdef CONFIG_SECURITY
             void  *f_security;
        #endif
        /* needed for tty driver, and maybe others */
        void *private_data;
        #ifdef CONFIG_EPOLL
        /* Used by fs/eventpoll.c to link all the hooks to this file */
        struct list_head f_ep_links;
        spinlock_t f_ep_lock;
       #endif /* #ifdef CONFIG_EPOLL */
       struct address_space *f_mapping;
};


struct inode { 
        struct hlist_node       i_hash; 哈希表 
        struct list_head        i_list;   索引节点链表 
        struct list_head        i_dentry; 目录项链表 
        unsigned long           i_ino;  节点号 
        atomic_t                i_count; 引用记数 
        umode_t                 i_mode; 访问权限控制 
        unsigned int            i_nlink; 硬链接数 
        uid_t                   i_uid;  使用者id 
        gid_t                   i_gid;  使用者id组 
        kdev_t                  i_rdev; 实设备标识符 
        loff_t                  i_size;  以字节为单位的文件大小 
        struct timespec         i_atime; 最后访问时间 
        struct timespec         i_mtime; 最后修改(modify)时间 
        struct timespec         i_ctime; 最后改变(change)时间 
        unsigned int            i_blkbits; 以位为单位的块大小 
        unsigned long           i_blksize; 以字节为单位的块大小 
        unsigned long           i_version; 版本号 
        unsigned long           i_blocks; 文件的块数 
        unsigned short          i_bytes; 使用的字节数 
        spinlock_t              i_lock; 自旋锁 
        struct rw_semaphore     i_alloc_sem; 索引节点信号量 
        struct inode_operations *i_op; 索引节点操作表 
        struct file_operations  *i_fop; 默认的索引节点操作 
        struct super_block      *i_sb; 相关的超级块 
        struct file_lock        *i_flock; 文件锁链表 
        struct address_space    *i_mapping; 相关的地址映射 
        struct address_space    i_data; 设备地址映射 
        struct dquot            *i_dquot[MAXQUOTAS];节点的磁盘限额 
        struct list_head        i_devices; 块设备链表 
        struct pipe_inode_info  *i_pipe; 管道信息 
        struct block_device     *i_bdev; 块设备驱动 
        unsigned long           i_dnotify_mask;目录通知掩码 
        struct dnotify_struct   *i_dnotify; 目录通知 
        unsigned long           i_state; 状态标志 
        unsigned long           dirtied_when;首次修改时间 
        unsigned int            i_flags; 文件系统标志 
        unsigned char           i_sock; 套接字 
        atomic_t                i_writecount; 写者记数 
        void                    *i_security; 安全模块 
        __u32                   i_generation; 索引节点版本号 
        union { 
                void            *generic_ip;文件特殊信息 
        } u; 
};

      inode 译成中文就是索引节点。每个存储设备或存储设备的分区(存储设备是硬盘、软盘、U盘 ... ... )被格式化为文件系统后,应该有两部份,一部份是inode,另一部份是Block,Block是用来存储数据用的。而inode呢,就是用来存储这些数据的信息,这些信息包括文件大小、属主、归属的用户组、读写权限等。inode为每个文件进行信息索引,所以就有了inode的数值。操作系统根据指令,能通过inode值最快的找到相对应的文件。 


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(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);

  ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);

  ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, 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);

  int (*mmap) (struct file *, struct vm_area_struct *);

  int (*open) (struct inode *, struct file *);

  int (*flush) (struct file *);

  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(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);

  ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);

  ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);

  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);

};

            《6》下面对于一些操作的函数给予更为详细的介绍, file_operations结构体中的函数:

                 stuct  module  *owner 

第一个file_operations字段并不是一个操作;相反,它是指向“拥有”该结构的模块的指针。内核使用这个字段以避免在模块的操作正在被使用时卸载该模块。几乎在所有的情况下,该成员都会被初始化为THIS_MODULE, 它是定义在<linux/module.h>中的一个宏。

                 loff_t ( * llseek)(struct file  * , loff_t , int );

方法llseek用来修改文件的当前读写位置,并将新位置作为(正的)返回值返回。参数loff_t一个“长偏移量” , 即使在32位平台上也至少占用64位的数据宽度。出错时返回一个负的返回值。如果这个函数指针是NULL,对seek的调用将会以魔种不可预期的方式修改file结构中的位置计数器。

                  ssize_t   (*read)  (struct file  *  ,char  __user * , size_t  , loff_t * ) ;

用来从设备中读取数据。该函数指针被赋为NULL值时,将导致read系统调用出错并返回 -EINVAL (“invalid  argument ” , 非法参数 “ )。

                 ssize_t  (* aio_read)( struct kiocb * , char __user * , size_t ,loff_t );

初始化一个异步的读取操作----即在函数返回之前可能不会完成读取操作。如果该方法为NULL , 所有的操作将通过read(同步)处理。 

               ssize_t (* write) (struct file *  , const  char __user * ,size_t ,loff_t  *);

向设备发送数据。如果没有这个函数, write 系统调用会向程序返回一个 -EINVAL 。如果返回值非负, 则表示成功写入的字节数。

              ssize_t (* aio_write ) (struct kiocb * , const char __user * , size_t ,loff_t *) ;

初始化设备上的异步写入操作。

             int  (*readdir) (struct  file * , void * , filldir_t );

对于设备文件来说 , 这个字段应该为NULL 。它仅是用于读取目录 , 只对文件系统有用 。  

            unsigned  int  (* poll ) ( struct file * , struct  poll_table_struct  * ) ;

poll  方法是poll , epoll 和 select 这三个系统调用的后端实现。 这三个系统调用可用来查询某个或多个文件描述符上的读取或写入是否会被阻塞。 poll 方法应该返回一个位掩码 , 用来指出非阻塞的读取或写入是否可能 , 并且也会向内核提供将调用进程置于休眠状态直到I/O变为可能时的信息。如果驱动程序将poll的方法定义为NULL, 则设备会被认为即可读也可写, 并且不会被阻塞。

           int (* ioctl ) (struct  inode * , struct  file *  , unsigned int  , unsigned long ) ;

系统调用ioctl提供了一致执行设备特定命令的方法(如格式化软盘的某个磁道,这既不是读操作也不是写操作)。另外, 内核还能识别一部分ioctl 命令, 而不必调用 fops 表中的ioctl 。如果设备不提供ioctl入口点, 则对于任何内核未预先定义的请求, ioctl 系统调用将返回错误(-ENOTTY ,“No such ioctl  for device , 该设备无此ioctl 命令”)。

           int( * mmap) (struct file * , struct  vm_area_struct * ) ;

mmap 用于请求将设备内存隐射到进程地址空间。如果设备没有实现这个方法, 那么mmap 系统调用将返回 -ENODEV。

           int  (*open) (struct  inode *  , struct file  * ) ;

尽管这个始终是对设备文件执行的第一个操作,然后却并不要求驱动程序一定要声明一个相应的方法。如果这个入口为NULL , 设备的打开操作永远成功, 但系统不会通知驱动程序。

         int  (*flush )(struct file *) ;

对flush 操作的调用发生在进程关闭设备文件描述符副本的时候, 它应该执行(并等待) 设备上尚未完结的操作。请不要将它同用户使用的fsync操作相混淆。目前, flush 仅仅用于少数几个驱动程序, 比如, SCSI磁带驱动程序用它来确保设备被关闭之前所有数据都被写到磁带中。如果flush被设置为NULL , 内核将简单地忽略用户应用程序的请求。

       int (* release) (struct  inode * , struct  file *)

当file结构被释放时, 将调用这个操作。与open相仿, 也可以将release 设置为NULL。

       int  (* fsync ) (struct  file *  , struct   dentry *  , int) ;

该方法是fsync系统调用的后端实现, 用户调用它来刷新待处理的数据。如果驱动程序没有实现这一方法fsync 系统调用返回-EINVAL。

      int (* aio_fsync)(struct  kiocb  * , int );

这个是fsync方法的异步版本。

         int (* fasync )(int , struct  file * , int ) ;

这个操作用来通知设备其FASYNC标志发生了变化。异步通知是比较高级的问题, 将在第六章介绍。如果设备不支持异步通知, 该字段可以是NULL 。

        int (* lock) (struct  file * , int , struct  file_lock * );

lock 方法用于实现文件锁定, 锁定是常规文件不可缺少的特性, 但设备驱动几乎从来不会实现这个方法。

      ssize_t (* readv) (struct  file *  , const  struct  iovec * , unsigned long , loff_t  * ) ;

     ssize_t  (* writev) (struct  file *  , const struct  iovec * , unsigned  long , loff_t  * );

这些方法用来实现分散 / 聚集型的读写操作 。应用程序有时需要进行涉及多个内存区域的单次读或写操作, 利用上面这些系统调用可完成这类工作, 而不必强加额外的数据拷贝操作。如果这些函数指针被设置成NULL ,就会调用read 和 write 方法(可能是多次)。

           ssize_t (*sendfile ) (struct file * , loff_t * , size_t , read_actor_t  , void * ) ;

这个方法实现sendfile 系统调用的读取部分。sendfile 系统调用以最小的复制操作将数据从一个文件描述符移动到另一个。例如:Web服务器可以利用这个方法将某个设备的内容发送到网络连接。设备驱动程序通常将sendfile设置为NULL。

            ssize_t (* sendpage) (struct  file * , struct  page  * , int , size_t  , loff_t   *, int );

sendpage  是sendfile 系统调用的另外一半, 它由内核调用以将数据发送到对应的文件, 每次一个数据页。设备驱动程序通常也不需要实现sendpage。

            unsigned  long  (* get_unmapped_area ) (struct  file * , unsigned  long , unsigned  long , unsigned long , unsigned  long ) ;

该方法的目的是在进程的地址空间中找到一个合适的位置,以便将底层设备中的内存段映射到该位置。该任务通常由内存管理代码完成, 但该方法的存在可允许驱动程序强制满足特定设备需要的任何对齐要求。大部分驱动程序可设置该方法为NULL。

           int  (* check_flags )(int )

该方法允许模块检查传递给fcntl(F_SETFL...)调用的标志。

             int  (* dir_notify )(struct file * , unsigned  long ) ;

当应用程序使用fcntl来请求目录改变通知时, 该方法将被调用。该方法仅对文件系统有用,驱动程序不必实现dir_notify。

《7》通常scull设备驱动的file_operations结构被初始化为:

                       struct   file_operations  scull_fops = {

                           .owner  =THIS_MODULE,

                           .llseek = scull_llseek ,

                           .read = scull_read ,

                            .write = scull_write ,

                            .ioctl =  scull_ioctl,

                           .open = scull_open ,

                           .release  =scull_release ,

                     } ;

            《8》struct  file 是一个内核结构, 




               




              

             

                     


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值