struct cdev机构体

cdev 结构体 
内核中使用 cdev 结构体描述字符设备,一个字符设备就有一个cdev.cdev 结构体的定义如代码所示。

  1. 1  struct cdev    
  2. 2  {   
  3. 3   struct kobject kobj; /* 内嵌的 kobject 对象 */   
  4. 4   struct module *owner;    /*所属模块*/    
  5. 5   struct file_operations *ops;  /*文件操作结构体*/   
  6. 6   struct list_head list;   
  7. 7   dev_t dev;           /*设备号*/   
  8. 8   unsigned int count;   /*个数*/
  9. 9  };   
cdev 结构体的 dev_t 成员定义了设备号,为 32 位,其中高 12 位为主设备号,低20 位为次设备号。
主设备号:MAJOR(dev_t dev) 
次设备号:MINOR(dev_t dev) 
 
生成设备号:MKDEV(int major, int minor) 

cdev 结构体的另一个重要成员 file_operations 定义了字符设备驱动提供给虚拟文件系统的接口函数。 
Linux 2.6 内核提供了一组函数用于操作 cdev 结构体,如下所示: 
void cdev_init(struct cdev *, struct file_operations *); //用于初始化 cdev 的成员,并建立 cdev 和 file_operations 之间的连接
struct cdev *cdev_alloc(void);  //用于动态申请一个 cdev 内存
void cdev_put(struct cdev *p); 
int cdev_add(struct cdev *, dev_t, unsigned);  //添加一个 cdev
void cdev_del(struct cdev *);  //删除一个 cdev


cdev_add()函数和 cdev_del()函数完成字符设备的注册和注销。对 cdev_add()的调用通常发生在字符设备驱动模块加载函数中,而对cdev_del()函数的调用则通常发生在字符设备驱动模块卸载函数中。 
分配和释放设备号 
在 调 用 cdev_add() 函数向系统注册字符设备之前,应首先调用register_chrdev_region()或 alloc_chrdev_region()函数向系统申请设备号,这两个函数的原型如下: 
int register_chrdev_region(dev_t from, unsigned count, const char *name); 
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name); 
register_chrdev_region() 函 数 用 于 已 知 起 始 设 备 的 设 备 号 的 情 况 ; 而alloc_chrdev_region()用于设备号未知,向系统动态申请未被占用的设备号的情况。函数调用成功之后,会把得到的设备号放入第一个参数 dev 中。alloc_chrdev_region()与register_chrdev_region()对比的优点在于它会自动避开设备号重复的冲突。 
相反地,在调用 cdev_del() 函数从系统注销字符设备之后 ,unregister_chrdev_region()应该被调用以释放原先申请的设备号,这个函数的原型如下: 
void unregister_chrdev_region(dev_t from, unsigned count); 
file_operations 结构体 
file_operations 结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行 Linux 的 open()、write()、read()、close()等系统调用时最终被调用。file_operations 结构体目前已经比较庞大,它的定义如代码所示。
[html]   view plain   copy
  1. 1  struct file_operations   
  2. 2  {   
  3. 3    struct module *owner;   
  4. 4      // 拥有该结构的模块的指针,一般为 THIS_MODULES   
  5. 5    loff_t(*llseek)(struct file *, loff_t, int);   
  6. 6      // 用来修改文件当前的读写位置    
  7. 7    ssize_t(*read)(struct file *, char _ _user *, size_t, loff_t*);   
  8. 8      // 从设备中同步读取数据   
  9. 9    ssize_t(*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t);   
  10. 10     // 初始化一个异步的读取操作  
  11. 11   ssize_t(*write)(struct  file  *,  const  char  _ _user  *,  size_t, loff_t*);   
  12. 12     // 向设备发送数据   
  13. 13   ssize_t(*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t);   
  14. 14     // 初始化一个异步的写入操作   
  15. 15   int(*readdir)(struct file *, void *, filldir_t);   
  16. 16     // 仅用于读取目录,对于设备文件,该字段为 NULL   
  17. 17   unsigned int(*poll)(struct file *, struct poll_table_struct*);   
  18. 18     // 轮询函数,判断目前是否可以进行非阻塞的读取或写入   
  19. 19   int(*ioctl)(struct inode *, struct file *, unsigned int, unsigned long);   
  20. 20     // 执行设备 I/O 控制命令   
  21. 21   long(*unlocked_ioctl)(struct file *, unsigned int, unsigned long);   
  22. 22     // 不使用 BLK 文件系统,将使用此种函数指针代替 ioctl   
  23. 23   long(*compat_ioctl)(struct file *, unsigned int, unsigned long);   
  24. 24     // 在 64 位系统上,32 位的 ioctl 调用将使用此函数指针代替   
  25. 25   int(*mmap)(struct file *, struct vm_area_struct*);   
  26. 26     // 用于请求将设备内存映射到进程地址空间   
  27. 27   int(*open)(struct inode *, struct file*);   
  28. 28     // 打开   
  29. 29   int(*flush)(struct file*);   
  30. 30   int(*release)(struct inode *, struct file*);   
  31. 31     // 关闭   
  32. 32   int(*synch)(struct file *, struct dentry *, int datasync);   
  33. 33     // 刷新待处理的数据   
  34. 34   int(*aio_fsync)(struct kiocb *, int datasync);   
  35. 35     // 异步 fsync   
  36. 36   int(*fasync)(int, struct file *, int);   
  37. 37     // 通知设备 FASYNC 标志发生变化   
  38. 38   int(*lock)(struct file *, int, struct file_lock*);   
  39. 39   ssize_t(*readv)(struct file *, const struct iovec *, unsigned long, loff_t*);   
  40. 40   ssize_t(*writev)(struct file *, const struct iovec *, unsigned long, loff_t*);   
  41. 41     // readv 和 writev:分散/聚集型的读写操作   
  42. 42   ssize_t(*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void*);   
  43. 43     // 通常为 NULL   
  44. 44   ssize_t(*sendpage)(struct file *, struct page *, int, size_t, loff_t *, int);   
  45. 45     // 通常为 NULL   
  46. 46   unsigned long(*get_unmapped_area)(struct file *,unsigned long, unsigned long,   
  47. 47     unsigned long, unsigned long);   
  48. 48     // 在进程地址空间找到一个将底层设备中的内存段映射的位置   
  49. 49   int(*check_flags)(int);   
  50. 50     // 允许模块检查传递给 fcntl(F_SETEL...)调用的标志   
  51. 51   int(*dir_notify)(struct file *filp, unsigned long arg);   
  52. 52     // 仅对文件系统有效,驱动程序不必实现   
  53. 53   int(*flock)(struct file *, int, struct file_lock*);    
  54. 54 };   
下面对 file_operations 结构体中的主要成员进行讲解。 
llseek()函数用来修改一个文件的当前读写位置,并将新位置返回,在出错时,这个函数返回一个负值。 
read()函数用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值。 
write()函数向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被实现,当用户进行 write()系统调用时,将得到-EINVAL 返回值。 
readdir()函数仅用于目录,设备节点不需要实现它。 
ioctl()提供设备相关控制命令的实现(既不是读操作也不是写操作),当调用成功时,返回给调用程序一个非负值。内核本身识别部分控制命令,而不必调用设备驱动中的ioctl()。如果设备不提供 ioctl()函数,对于内核不能识别的命令,用户进行 ioctl()系统调用时将获得-EINVAL 返回值。  
mmap()函数将设备内存映射到进程内存中,如果设备驱动未实现此函数,用户进行 mmap()系统调用时将获得-ENODEV 返回值。这个函数对于帧缓冲等设备特别有意义。 
当用户空间调用 Linux API 函数 open()打开设备文件时,设备驱动的 open()函数最终被调用。驱动程序可以不实现这个函数,在这种情况下,设备的打开操作永远成功。与 open()函数对应的是 release()函数。 
poll()函数一般用于询问设备是否可被非阻塞地立即读写。当询问的条件未触发时,用户空间进行 select()和 poll()系统调用将引起进程的阻塞。 
aio_read()和 aio_write()函数分别对与文件描述符对应的设备进行异步读、写操作。
设备实现这两个函数后,用户空间可以对该设备文件描述符调用 aio_read()、aio_write()等系统调用进行读写。 
Linux 字符设备驱动的组成 
在 Linux 系统中,字符设备驱动由如下几个部分组成。 
1.字符设备驱动模块加载与卸载函数 
在字符设备驱动模块加载函数中应该实现设备号的申请和 cdev 的注册,而在卸载函数中应实现设备号的释放和 cdev 的注销。 
工程师通常习惯将设备定义为一个设备相关的结构体,其包含该设备所涉及的cdev、私有数据及信号量等信息。 常见的设备结构体、模块加载和卸载函数形式如代码所示。 
[html]   view plain   copy
  1. 1  //设备结构体   
  2. 2  struct xxx_dev_t   
  3. 3  {   
  4. 4    struct cdev cdev;   
  5. 5    ...   
  6. 6  } xxx_dev;   
  7. 7  //设备驱动模块加载函数   
  8. 8  static int _ _init xxx_init(void)   
  9. 9  {   
  10. 10   ...   
  11. 11   cdev_init(&xxx_dev.cdev, &xxx_fops); //初始化 cdev   
  12. 12   xxx_dev.cdev.owner = THIS_MODULE;   
  13. 13   //获取字符设备号   
  14. 14   if (xxx_major)   
  15. 15   {   
  16. 16     register_chrdev_region(xxx_dev_no, 1, DEV_NAME);   
  17. 17   }   
  18. 18   else   
  19. 19   {   
  20. 20     alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);   
  21. 21   }   
  22. 22      
  23. 23   ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); //注册设备   
  24. 24   ...   
  25. 25 }   
  26. 26 /*设备驱动模块卸载函数*/   
  27. 27 static void _ _exit xxx_exit(void)   
  28. 28 {   
  29. 29   unregister_chrdev_region(xxx_dev_no, 1); //释放占用的设备号   
  30. 30   cdev_del(&xxx_dev.cdev); //注销设备   
  31. 31   ...   
  32. 32 }   
设备驱动的读函数中,filp 是文件结构体指针,buf 是用户空间内存的地址,该地址在内核空间不能直接读写,count 是要读的字节数,f_pos 是读的位置相对于文件开头的偏移。 
设备驱动的写函数中,filp 是文件结构体指针,buf 是用户空间内存的地址,该地址在内核空间不能直接读写,count 是要写的字节数,f_pos 是写的位置相对于文件开头的偏移。 
由于内核空间与用户空间的内存不能直接互访,因此借助函数 copy_from_user()完成用户空间到内核空间的复制,函数 copy_to_user()完成内核空间到用户空间的复制。 
copy_from_user()和 copy_to_user()的原型如下所示: 
unsigned long copy_from_user(void *to, const void _ _user *from, unsigned long count); 
unsigned long copy_to_user(void _ _user *to, const void *from, unsigned long count); 
上述函数均返回不能被复制的字节数,因此,如果完全复制成功,返回值为 0。 
如果要复制的内存是简单类型,如 char、int、long 等,则可以使用简单的 put_user()和 get_user(),如下所示: 
int val; //内核空间整型变量 
... 
get_user(val, (int *) arg); //用户空间到内核空间,arg 是用户空间的地址 
... 
put_user(val, (int *) arg); //内核空间到用户空间,arg 是用户空间的地址 
读和写函数中的_ _user 是一个宏,表明其后的指针指向用户空间,这个宏定义如下: 
#ifdef _ _CHECKER_ _ 
# define _ _user _ _attribute_ _((noderef, address_space(1))) 
#else 
# define _ _user 
#endif 
I/O 控制函数的 cmd 参数为事先定义的 I/O 控制命令,而 arg 为对应于该命令的参数。例如对于串行设备,如果 SET_BAUDRATE 是一个设置波特率的命令,那后面的arg 就应该是波特率值。 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值