zoukankan      html  css  js  c++  java
  • linux kernel 字符设备详解

    有关Linux kernel 字符设备分析:

    参考:http://blog.jobbole.com/86531/

    一.linux kernel 将设备分为3大类,字符设备,块设备,网络设备.

    字符设备是指只能一个字节一个字节读写的设备, 常见的外设基本上都是字符设备.

    块设备:常见的存储设备,硬盘,SD卡都归为块设备,块设备是按一块一块读取的.

    网络设备:linux 将对外通信的一个机制抽象成一个设备, 通过套接字对其进行相关的操作.

    每一个字符设备或块设备都在/dev目录下对应一个设备文件。linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和块设备。

    二、字符设备、字符设备驱动与用户空间访问该设备的程序三者之间的关系。

    三.字符设备的模型

     

    四.下面讲一下字符设备驱动的编写流程,linux 内核为字符设备的创建提供了一套接口.

    首先介绍一下dev_t , 他是主设备号和次设备号的结构体生成,他就代表了一个主次设备号

    通过函数MKDEV (MAJ , MINOR) ; 生成.我们注册一个字符设备可以通过动态注册也可以静态注册 ,  linux kernel 为我们提供了所需要的接口

    首先讲一下静态注册的方法

     1     //分配主设备号为200  次设备号从5开始分配5个  设备名字叫MONEY                
     2     int ret ;                                                                   
     3     DeviceId = MKDEV(MAJ , BASEMINOR);                                          
     4     ret = register_chrdev_region(DeviceId , MINORCNT , "MONEY");                
     5     if(ret < 0)                                                                 
     6     {                                                                           
     7         return ret ;                                                            
     8     }                                                                           
     9                                                                                 
    10     //********************************************                              
    11     //方法一                                                                    
    12     //1> 初始化                                                                 
    13     cdev_init(&device, &fops);                                                  
    14     //2> 添加     domain->probes  HASH表上                                      
    15     cdev_add(&device,DeviceId , MINORCNT);                                      

    通过函数register_chrdev_region() , 我们可以注册主设备号为MAJ , 次设备号BASEMINOR 开始 ,  一共注册MINORCNT 个次设备号 , 名字为MONEY 的字符设备.

    第二种方法是动态申请主次设备号:

     1     //动态分配一个主设备号                                                      
     2     int ret ;                                                                   
     3     ret = alloc_chrdev_region(&DeviceId , BASEMINOR , MINORCNT,"TONY");         
     4     if(ret < 0)                                                                 
     5     {                                                                           
     6         return ret ;                                                            
     7     }                                                                           
     8                                                                                 
     9     printk("major:%d 
    " , MAJOR(DeviceId));                                    
    10                                                                                 
    11     //********************************************                              
    12     //方法二                                                                    
    13     //1> 分配空间                                                               
    14     device = cdev_alloc();                                                      

    我们可以通过linux kernel 提供的alloc_chrdev_region () 的方法 , 申请一个主设备号和基础设备号 , 一共申请 MINORCNT , 名字为 TONY 的一个字符设备.

    这里涉及一个结构体: 

    1 struct cdev {                                                                   
    2     struct kobject kobj;                                                        
    3     struct module *owner;        
    4 const struct file_operations *ops; 5 struct list_head list; 6 dev_t dev; 7 unsigned int count; 8 };

    这里的话还要申请一个cdev 结构体的空间

    通过cdev_alloc() ;

    搞定了主次设备号的问题 , 接下来就是涉及到了初始化和添加到设备列表 .

    linux kernel 为我们提供了以下的方法:

    1     //2> 初始化                                                                 
    2     cdev_init(device, &fops);                                                   
    3     //3> 添加     domain->probes  HASH表上                                      
    4     cdev_add(device,DeviceId , MINORCNT);                                       
    5                                                                                 

    这里面涉及到了一个&fops 的文件操作结构体

    1 static struct file_operations  fops = {
    2     .owner = THIS_MODULE,
    3     .open = myopen,
    4     .read = myread ,
    5     .write = mywrite,
    6     .release = myclose,
    7     .unlocked_ioctl = myioctl,
    8 };

    上层的open read write 等函数经过一系列的转换都会到对应的函数

    相对应的, 释放主次设备号, 删除在设备列表的节点, linux kernel 为我们提供了一下接口:

    1     cdev_del(device);
    2 
    3     unregister_chrdev_region(DeviceId , MINORCNT);

    下面的代码是想深入了解里面的代码的看看就行了 , 如果只是向了解接口的 , 上面的就行了

    从静态申请注册开始跟起吧

    1 #define MKDEV(ma,mi)    ((ma)<<8 | (mi))

    上面这个是制作一个主次设备号的结构体

    1 extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);     
    2 extern int register_chrdev_region(dev_t, unsigned, const char *);     
    3 extern int __register_chrdev(unsigned int major, unsigned int baseminor,     
    4                  unsigned int count, const char *name,       
    5                  const struct file_operations *fops);     
    6 extern void __unregister_chrdev(unsigned int major, unsigned int baseminor,                    unsigned int count, const char *name);
    7 extern void unregister_chrdev_region(dev_t, unsigned);     

    这是几个将要用的函数的函数声明  ,  它在 include/linux/fs.h 文件中

    首先看一下 register_chrdev_region() 函数

    1 /**    
    2  * register_chrdev_region() - register a range of device numbers    
    3  * @from: the first in the desired range of device numbers; must include    
    4  *        the major number.    
    5  * @count: the number of consecutive device numbers required    
    6  * @name: the name of the device or driver.    
    7  *                              
    8  * Return value is zero on success, a negative error code on failure.    
    9  */                     

    注释说明:  注册一个范围的设备号 , 原型如下:

     1 int register_chrdev_region(dev_t from, unsigned count, const char *name)    
     2 {                                                                              
     3     struct char_device_struct *cd;                                    
     4     dev_t to = from + count;                                                 
     5     dev_t n, next;                                           
     6                                                           
     7     for (n = from; n < to; n = next) {                                                 next = MKDEV(MAJOR(n)+1, 0);                  
     8         if (next > to)                                     
     9             next = to;                                     
    10         cd = __register_chrdev_region(MAJOR(n), MINOR(n),    
    11                    next - n, name);                                    
    12         if (IS_ERR(cd))
    13             goto fail;
    14     }
    15     return 0;
    16 fail:
    17     to = n;
    18     for (n = from; n < to; n = next) {
    19         next = MKDEV(MAJOR(n)+1, 0);
    20         kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
    21     }
    22     return PTR_ERR(cd);
    23 }

    它是调用了

          cd = __register_chrdev_region(MAJOR(n), MINOR(n),    

    进里面看看

     1 /*
     2  * Register a single major with a specified minor range.
     3  *
     4  * If major == 0 this functions will dynamically allocate a major and return
     5  * its number.
     6  *
     7  * If major > 0 this function will attempt to reserve the passed range of
     8  * minors and will return zero on success.
     9  *
    10  * Returns a -ve errno on failure.
    11  */

    还是看注释: 注册一个指定的主设备号 和一个指定的次设备号范围

    判断 主设备号是不是为零 , 如果是零的话 就动态申请一个主设备号 , 这就是后面要讲的那个动态申请 , 它也是调用了这个.

    代码如下

     1 static struct char_device_struct *
     2 __register_chrdev_region(unsigned int major, unsigned int baseminor,
     3                int minorct, const char *name)
     4 {
     5     struct char_device_struct *cd, **cp;
     6     int ret = 0;
     7     int i;
     8 
    这里面涉及一个结构体没讲:
    1 static struct char_device_struct {
    2     struct char_device_struct *next;
    3     unsigned int major;                              
    4     unsigned int baseminor;        
    5     int minorct;      
    6     char name[64];
    7     struct cdev *cdev;      /* will die */
    8 } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
     9     cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);     //动态申请了一个char_device_struct 结构体
    10     if (cd == NULL)
    11         return ERR_PTR(-ENOMEM);
    12 
    13     mutex_lock(&chrdevs_lock);      //加一个互斥锁 , 防止其他进程并发
    14 
    15     /* temporary */
    16     if (major == 0) {
    17         for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {       //这里其实就是做了一个动态申请主设备号的功能
    18             if (chrdevs[i] == NULL)
    19                 break;
    20         }
    21 
    22         if (i == 0) {      //没有申请到主设备号, 直接退出
    23             ret = -EBUSY;
    24             goto out;
    25         }
    26         major = i;
    27         ret = major;
    28     }
    29 
    30     cd->major = major;                  //对结构体进行初始化
    31     cd->baseminor = baseminor;
    32     cd->minorct = minorct;
    33     strlcpy(cd->name, name, sizeof(cd->name));
    34     
    35     i = major_to_index(major);      //  哈希表的下标生成
    36 
    37     for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)    //进入chdevs[i] 哈希表快速进入
    38         if ((*cp)->major > major ||
    39             ((*cp)->major == major &&
    40              (((*cp)->baseminor >= baseminor) ||
    41               ((*cp)->baseminor + (*cp)->minorct > baseminor))))
    42             break;
    43 
    44     /* Check for overlapping minor ranges.  */      // 检查次设备号会不会重叠
    45     if (*cp && (*cp)->major == major) {
    46         int old_min = (*cp)->baseminor;
    47         int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
    48         int new_min = baseminor;
    49         int new_max = baseminor + minorct - 1;
    50 
    51         /* New driver overlaps from the left.  */
    52         if (new_max >= old_min && new_max <= old_max) {
    53             ret = -EBUSY;
    54             goto out;
    55         }
    56 
    57         /* New driver overlaps from the right.  */
    58         if (new_min <= old_max && new_min >= old_min) {
    59             ret = -EBUSY;
    60             goto out;
    61         }
    62     }
    63 
    64     cd->next = *cp;
    65     *cp = cd;
    66     mutex_unlock(&chrdevs_lock);      //解锁
    67     return cd;
    68 out:
    69     mutex_unlock(&chrdevs_lock);
    70     kfree(cd);
    71     return ERR_PTR(ret);
    72 }

    到这里一个主次设备号就搞定了 , 并申请一个char_device_struct 结构体, 并对其进行赋值初始化.

    第二步就是对cdev  进行init :

    1 void cdev_init(struct cdev *, const struct file_operations *);    

    这里又涉及到一个struct cdev 的结构体:

    1 struct cdev {                      
    2     struct kobject kobj;                                                  
    3     struct module *owner;                                        
    4     const struct file_operations *ops;                                  
    5     struct list_head list;                            
    6     dev_t dev;                                       
    7     unsigned int count;                                                    
    8 };                                                    

    进初始化代码一看究竟:

     1 /**
     2  * cdev_init() - initialize a cdev structure
     3  * @cdev: the structure to initialize
     4  * @fops: the file_operations for this device
     5  *                
     6  * Initializes @cdev, remembering @fops, making it ready to add to the
     7  * system with cdev_add().         
     8  */                                                                       
     9 void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    10 {                                                                       
    11     memset(cdev, 0, sizeof *cdev);                    
    12     INIT_LIST_HEAD(&cdev->list);                     
    13     kobject_init(&cdev->kobj, &ktype_cdev_default);                        
    14     cdev->ops = fops;                                 
    15 }                                                     

    看一段代码之前我们尽可能的先看注释, 这样会让我们跟代码轻松很多 , 我们可以顺着代码的作者的思路走

    注释: 初始化一个cdev  结构体

    进kobject_init() 看看:

     1 /**                                                                             
     2  * kobject_init - initialize a kobject structure      初始化一个内核项目结构体                            
     3  * @kobj: pointer to the kobject to initialize                                  
     4  * @ktype: pointer to the ktype for this kobject.                               
     5  *                                                                              
     6  * This function will properly initialize a kobject such that it can then       
     7  * be passed to the kobject_add() call.                                         
     8  *                                                                              
     9  * After this function is called, the kobject MUST be cleaned up by a call      
    10  * to kobject_put(), not by a call to kfree directly to ensure that all of      
    11  * the memory is cleaned up properly.                                           
    12  */                                       
     1 void kobject_init(struct kobject *kobj, struct kobj_type *ktype)                
     2 {                                                                               
     3     char *err_str;                                                              
     4                                                                                 
     5     if (!kobj) {                                                                
     6         err_str = "invalid kobject pointer!";                                   
     7         goto error;                                                             
     8     }                                                                           
     9     if (!ktype) {                                                               
    10         err_str = "must have a ktype to be initialized properly!
    ";            
    11         goto error;                                                             
    12     }                                                                           
    13     if (kobj->state_initialized) {                                              
    14         /* do not error out as sometimes we can recover */                      
    15         printk(KERN_ERR "kobject (%p): tried to init an initialized "           
    16                "object, something is seriously wrong.
    ", kobj);                
    17         dump_stack();                                                           
    18     }                                                                           
    19                                                                                 
    20     kobject_init_internal(kobj);                                                
    21     kobj->ktype = ktype;                                                        
    22     return;                                      
    23                                                                                 
    24 error:                                                                          
    25     printk(KERN_ERR "kobject (%p): %s
    ", kobj, err_str);                       
    26     dump_stack();                                                               
    27 }                                                                               
    28 EXPORT_SYMBOL(kobject_init);                                                    
     1 struct kobject {                                                                
     2     const char      *name;                                                      
     3     struct list_head    entry;                                                  
     4     struct kobject      *parent;                                                
     5     struct kset     *kset;                                                      
     6     struct kobj_type    *ktype;                                                 
     7     struct sysfs_dirent *sd;                                                    
     8     struct kref     kref;                                                       
     9     unsigned int state_initialized:1;                                           
    10     unsigned int state_in_sysfs:1;                                              
    11     unsigned int state_add_uevent_sent:1;                                       
    12     unsigned int state_remove_uevent_sent:1;                                    
    13     unsigned int uevent_suppress:1;                                             
    14 };                                           

    下一部就是 cdev_add () ;

    1 int cdev_add(struct cdev *, dev_t, unsigned);                           
  • 相关阅读:
    docker镜像操作
    利用docker搭建lnmp平台
    算法导论笔记
    算法导论笔记
    VMware 安装CentOS 7 NAT模式 配置静态ip 连接外网 xshell连接虚拟机
    spring boot入门笔记(四)
    spring boot入门笔记 (三)
    spring boot入门笔记 (二)
    spring boot入门笔记 (一)
    修改request请求参数
  • 原文地址:https://www.cnblogs.com/chenfulin5/p/5718976.html
Copyright © 2011-2022 走看看