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 \n" , 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!\n";            
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.\n", 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\n", 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);                           

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM