Linux驱动编程 step-by-step (二) 简单字符设备驱动

时间:2021-05-02 16:35:19
简单字符设备驱动 1、主次设备号
主设备号标识设备连接的的驱动,此设备好由内核使用,标识在相应驱动下得对应的设备
在linux中设备号是一个32位的dev_t类型
typedef __u32    __kernel_dev_t;
typedef __kernel_dev_t    dev_t;


crw------- 1 root  root  101 Apr 11  2011 psaux 
crw------- 1 root  root  4,  1 Oct 2803:04 tty1 
crw-rw-rw- 1 root  tty   464 Apr 11  2011 ttys0 
crw-rw---- 1 root  uucp  465 Apr 11  2011 ttyS
上图是再/dev目录下用$ls -l 命令显示的部分结果可以看到tty driver的主设备号都为4(各个系统版本有差别),次设备号不同

前12位标识主设备号 MAJOR(dev_t dev) 获得主设备号
后20位标识此设备号 MINOR(dev_t dev) 获得此设备号

由主次设备号生成设备号
可以使用宏MKDEV
dev_t  dev_num = MKDEV(dev_t major, dev_t minor);

2、分配与释放设备号

在linux2.6的字符设备中(kernel3.0也是)首先做的事就是申请一个或者多个设备号

  1. /*  静态分配设备号    
  2.  *  parameter:        
  3.  *      first : 分配的第一个设备号  
  4.  *      count: 分配的设备个数  
  5.  *      name : 设备名  
  6.  *  return value:  
  7.  *      0: success  
  8.  *      负值:出现错误,错误码  
  9. */  
  10. int register_chrdev_region(dev_t first, unsigned int count, char *name);   
  11.   
  12. /*  动态分配设备号    
  13.  *  parameter:        
  14.  *      dev : 用来存储分配的设备号值  
  15.  *      firstminor: 次设备号(一般填0)  
  16.  *      count: 分配的设备个数  
  17.  *      name : 设备名  
  18.  *  return value:  
  19.  *      0: success  
  20.  *      负值:出现错误,错误码  
  21. */  
  22. int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);   
  23.   
  24. /*  释放设备号  
  25.  *  parameter:        
  26.  *      first: 设备号  
  27.  *      count: 分配的设备个数  
  28. */  
  29. void unregister_chrdev_region(dev_t first, unsigned int count);  

静态分配设备号,是在已经知道一个可用设备号的时候使用,而程序员在编写程序之前大多并知道设备号是否可用,或者现在可用,不能确保在系统升级时候次设备还是可用的
所以linux社区极力推荐使用动态分配,它会去寻找可用的设备号,而不会产生冲突。在次设备卸载的时候需要释放次设备号。

3、一个没有作用的字符设备驱动

  1. #include <linux/init.h>   
  2. #include <linux/module.h>   
  3. #include <linux/types.h>   
  4. #include <linux/fs.h>   
  5.   
  6. #define SIMPLE_DEBUG 1   
  7. #define DEV_COUNT 2   
  8. #define SIMPLE_NAME "simple_char"   
  9.   
  10. static int simple_major = 108;   
  11. static int simple_minor = 0;   
  12.   
  13. static __init int simple_init(void)   
  14. {   
  15.     dev_t dev;   
  16.     int err;   
  17. #if SIMPLE_DEBUG   
  18.     printk(KERN_INFO "In %s\n", __func__);   
  19. #endif   
  20.     dev = MKDEV(simple_major, simple_minor); //求取设备号  
  21.     if(dev > 0)//设备号有效  
  22.     {   
  23. #if SIMPLE_DEBUG   
  24.         printk(KERN_INFO "try to register static char dev  %d \n", dev);   
  25. #endif   
  26.         err = register_chrdev_region(dev,DEV_COUNT, SIMPLE_NAME); //静态分配设备号  
  27.         if(err < 0) //静态分配出错 尝试使用动态分配  
  28.         {   
  29.             printk(KERN_WARNING "register static char dev error\n");   
  30.             err = alloc_chrdev_region(&dev, 0, DEV_COUNT, SIMPLE_NAME); //动态分配设备号  
  31.             if(err < 0)   
  32.             {   
  33.                 printk(KERN_ERR "register char dev error in line %d\n",__LINE__);   
  34.                 goto error;   
  35.             }   
  36.             else  
  37.             {   
  38.                 simple_major = MAJOR(dev);//重新计算主设备号  
  39.                 simple_minor = MINOR(dev);//重新计算此设备号  
  40.             }   
  41.                
  42.         }   
  43.         else{   
  44.            
  45.         }   
  46.     }   
  47.     else //设备号无效使用动态分配  
  48.     {    
  49. #if SIMPLE_DEBUG   
  50.         printk(KERN_INFO "try to register alloc char dev  \n");   
  51. #endif   
  52.         err = alloc_chrdev_region(&dev, 0, DEV_COUNT, SIMPLE_NAME);   
  53.         if(err < 0)   
  54.         {   
  55.             printk(KERN_ERR "register char dev error in line %d\n\n",__LINE__);   
  56.             goto error;   
  57.         }   
  58.         else  
  59.         {   
  60.             simple_major = MAJOR(dev);   
  61.             simple_minor = MINOR(dev);   
  62.         }   
  63.   
  64.     }   
  65.   
  66. #if SIMPLE_DEBUG   
  67.     printk(KERN_INFO "register char dev success major = %d minor = %d \n", simple_major, simple_minor);   
  68. #endif   
  69.   
  70. error:   
  71.     return err;   
  72. }   
  73.   
  74. static __exit void simple_exit(void)   
  75. {   
  76.     dev_t dev;   
  77. #if SIMPLE_DEBUG   
  78.     printk(KERN_INFO "In %s\n", __func__);   
  79. #endif   
  80.     dev = MKDEV(simple_major, simple_minor);   
  81.     unregister_chrdev_region(dev, DEV_COUNT); //释放设备号  
  82. }   
  83.   
  84. module_init(simple_init);   
  85. module_exit(simple_exit);   
  86.   
  87. MODULE_LICENSE("GPL");   
  88. MODULE_AUTHOR("kai_zhang(jsha.zk@163.com)");   
  89. MODULE_DESCRIPTION("simple char driver!");  

这里只在模块初始化的时候去分配设备号,在模块注销的时候去释放次驱动拥有的设备号
在函数里边我们看到用到了在应用编程里边声名狼藉的goto函数,在linux驱动编程时 goto 函数可以让我们的编程更加有条理性,在出现错误时候能更快的去处理。
如果在调用函数检查返回者都去做错误处理则模块函数就显得臃肿,庞大。所以还是建议合理使用goto函数的。
加载次模块后 
运行 $cat /proc/devices可以看到 simple_char 的设备以及主设备号。

Linux驱动编程 step-by-step (二) 简单字符设备驱动

这里我们看到原来假设的主设备号是不可用的,所以使用的动态分配设备号,由此我们申请到主设备号为249,我们可以在上边添加我们的设备,具体操作下一节会讲到。呵呵留点悬念先。