九鼎创展论坛中文版English
登录 | 立即注册 设为首页收藏本站 切换到宽版
查看: 4384|回复: 0
打印 上一主题 下一主题

x4412&ibox项目实战27-Linux字符设备驱动设备结构

[复制链接]
跳转到指定楼层
楼主
发表于 2014-10-3 07:57:37 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
内核中每个字符设备都对应一个cdev结构的变量,在kernel/include/linux/cdev.h中有它的定义及操作cdev结构体的一些函数:
  1. struct cdev {
  2.          struct kobject kobj; // 每个 cdev 都是一个 kobject
  3.          struct module *owner; // 指向实现驱动的模块
  4.          const struct file_operations *ops; // 操纵这个字符设备文件的方法
  5.          struct list_head list; // 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
  6.          dev_t dev; // 起始设备编号
  7.          unsigned int count;  // 设备范围号大小
  8. };
  9. void cdev_init(struct cdev *, const struct file_operations *);
  10. struct cdev *cdev_alloc(void);
  11. void cdev_put(struct cdev *p);
  12. int cdev_add(struct cdev *, dev_t, unsigned);
  13. void cdev_del(struct cdev *);
复制代码
cdev结构体的结构成员dev_t定义了一个32位的设备号,其中高12位为主设备号,低20位为次设备号。尽管在很多情况下我们无需关心设备号,但是每一个设备文件的设备号都是真实存在的。使用以下宏可以获得主次设备号:
  1. MAJOR(dev_ t dev)
  2. MINOR(dev_ t dev)
复制代码
使用以下宏可以通过主次设备号生成dev_t:
  1. MKDEV(int major, int minor)
复制代码
从cdev.h中的程序清单可以看出,有五个函数用于操作cdev。cdev_init()和*cdev_alloc函数分别用于静态和动态初始化cdev,使用cdev_init()函数静态初始化的示例如下:
  1. struct cdev my_cdev;
  2. cdev_init(&my_cdev, &fops);
  3. my_cdev.owner = THIS_MODULE;
  4. 使用*cdev_alloc()函数动态初始化的示例如下:
  5. struct cdev *my_cdev = cdev_alloc();
  6. my_cdev->ops = &fops;
  7. my_cdev->owner = THIS_MODULE;
复制代码
可见,这二者的区别在于,使用动态初始化时需要手动指定cdev->ops,这是由二者的函数原型决定的。在kernel/fs/char_dev.c中可以看到其函数源码如下:
  1. struct cdev *cdev_alloc(void)
  2. {
  3.          struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
  4.          if (p) {
  5.                    INIT_LIST_HEAD(&p->list);
  6.                    kobject_init(&p->kobj, &ktype_cdev_dynamic);
  7.          }
  8.          return p;
  9. }
  10. void cdev_init(struct cdev *cdev, const struct file_operations *fops)
  11. {
  12.          memset(cdev, 0, sizeof *cdev);
  13.          INIT_LIST_HEAD(&cdev->list);
  14.          kobject_init(&cdev->kobj, &ktype_cdev_default);
  15.          cdev->ops = fops;
  16. }
复制代码
很明显,cdev_init函数相对*cdev_alloc函数,多赋了cdev->ops。这就是动态初始化cdev需要手动指定cdev->ops的真正原因。
cdev_add()函数和cdev_del()函数分别向系统添加和删除一个cdev,完成字符设备的注册和注销。对 cdev_add()的调用通常发生在字符设备驱动模块加载函数中,而对cdev_del()函数的调用则通常发生在字符设备驱动模块卸载函数中。
在调用cdev_add()函数向系统注册字符设备之前,应首先调用register_chrdev_region()或alloc_chrdev_region()函数向系统申请设备号,register_chrdev_region()函数用于已知起始设备的设备号的情况;而alloc_chrdev_region()用于设备号未知,向系统动态申请未被占用的设备号的情况。函数调用成功之后,会把得到的设备号放入第一个参数dev中。alloc_chrdev_region()与register_chrdev_region()对比的优点在于它会自动避开设备号重复的冲突。相反地,在调用cdev_del()函数从系统注销字符设备之 后,unregister_chrdev_region()应该被调用以释放原先申请的设备号。
当设备驱动程序成功调用了cdev_add之后,就意味着一个字符设备对象已经加入到了系统,在需要的时候,系统就可以找到它。对用户态的程序而言,cdev_add调用之后,就已经可以通过文件系统的接口呼叫到我们的驱动程序了。典型的文件系统接口如open(),close(),read(),write()等,他们真正操作的是在驱动程序中封装的file_operations结构体。
在linux/fs.h中可以看到file_operations结构体的原型:
  1. struct file_operations {
  2.          struct module *owner;
  3.          loff_t (*llseek) (struct file *, loff_t, int);
  4.          ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
  5.          ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
  6.          ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
  7.          ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
  8.          int (*readdir) (struct file *, void *, filldir_t);
  9.          unsigned int (*poll) (struct file *, struct poll_table_struct *);
  10.          long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
  11.          long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
  12.          int (*mmap) (struct file *, struct vm_area_struct *);
  13.          int (*open) (struct inode *, struct file *);
  14.          int (*flush) (struct file *, fl_owner_t id);
  15.          int (*release) (struct inode *, struct file *);
  16.          int (*fsync) (struct file *, int datasync);
  17.          int (*aio_fsync) (struct kiocb *, int datasync);
  18.          int (*fasync) (int, struct file *, int);
  19.          int (*lock) (struct file *, int, struct file_lock *);
  20.          ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
  21.          unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
  22.          int (*check_flags)(int);
  23.          int (*flock) (struct file *, int, struct file_lock *);
  24.          ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
  25.          ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
  26.          int (*setlease)(struct file *, long, struct file_lock **);
  27.          long (*fallocate)(struct file *file, int mode, loff_t offset,
  28.                               loff_t len);
  29. };
复制代码
下面对file_operations结构体中的主要成员进行讲解。
*owner指向拥有该结构的模块的指针,避免正在操作时被卸载,一般为初始化为THIS_MODULES。
*llseek函数指针用来修改文件当前的读写位置,返回新位置。在出错时,返回一个负值。
*read函数指针用来从设备中同步读取数据。读取成功返回读取的字节数,失败则返回一个负值。
*write函数指针用来向设备发送数据。成功时该函数返回写入的字节数。如果此函数未被实现,当用户进行write()系统调用时,将得到-EINVAL返回值。
*aio_read函数指针初始化一个异步的读取操作,为NULL时全部通过read处理。
*aio_write函数指针用来初始化一个异步的写入操作。
*readdir函数指针仅用于读取目录,对于设备文件,该字段为 NULL。
*poll函数指针返回一个位掩码,用来指出非阻塞的读取或写入是否可能。把pool设置为NULL,设备会被认为即可读也可写。
*unlocked_ioctl函数指针提供一种执行设备特殊命令的方法。不设置入口点时返回-ENOTTY。注意,自linux3.0以后,ioctl函数将不复存在,使用unlocked_ioctl函数替代不失为一个比较好的方法。
*mmap指针函数用于请求将设备内存映射到进程地址空间。如果没有声明,将访问-ENODEV。
*open指针函数用于打开设备。如果为空,设备的打开操作永远成功,但系统不会通知驱动程序。
*flush指针函数用于在进程关闭设备文件描述符副本时,执行并等待,若设置为NULL,内核将忽略用户应用程序的请求。
*release函数指针在file结构释放时将被调用。
*fsync函数指针用于刷新待处理的数据,如果驱动程序没有实现,fsync调用将返回-EINVAL。
*aio_fsync函数对应异步fsync。
*fasync函数指针用于通知设备FASYNC标志发生变化,如果设备不支持异步通知,该字段可以为NULL。
*lock用于实现文件锁,设备驱动通常不去实现此lock
*sendpage实现sendfile调用的另一部分,内核调用将其数据发送到对应文件,每次一个数据页,设备驱动通常将其设置为NULL。
*get_unmapped_area在进程地址空间找到一个合适的位置,以便将底层设备中的内存段映射到该位置。大部分驱动可将其设置为NULL。
*check_flags允许模块检查传递给fcntl(F_SETEL…)调用的标志。
模块的加载和卸载函数以及file_operations结构体中的成员函数构成了字符设备驱动的主体,驱动需要实现的各种功能都会封装在file_operations结构体的成员函数中,后面我们将通过实例讲述。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|深圳市九鼎创展科技官方论坛 ( 粤ICP备11028681号-2  

GMT+8, 2024-9-25 03:15 , Processed in 0.018697 second(s), 17 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表