1. 程式人生 > >Linux 字元裝置驅動結構(一)—— cdev 結構體、裝置號相關知識解析

Linux 字元裝置驅動結構(一)—— cdev 結構體、裝置號相關知識解析

一、字元裝置基礎知識

1、裝置驅動分類

      linux系統將裝置分為3類:字元裝置、塊裝置、網路裝置。使用驅動程式:



字元裝置:是指只能一個位元組一個位元組讀寫的裝置,不能隨機讀取裝置記憶體中的某一資料,讀取資料需要按照先後資料。字元裝置是面向流的裝置,常見的字元裝置有滑鼠、鍵盤、串列埠、控制檯和LED裝置等。

塊裝置:是指可以從裝置的任意位置讀取一定長度資料的裝置。塊裝置包括硬碟、磁碟、U盤和SD卡等。

每一個字元裝置或塊裝置都在/dev目錄下對應一個裝置檔案linux使用者程式通過裝置檔案(或稱裝置節點)來使用驅動程式操作字元裝置和塊裝置



2、字元裝置、字元裝置驅動與使用者空間訪問該裝置的程式三者之間的關係


     如圖,在Linux核心中:

a -- 使用cdev結構體來描述字元裝置;

b -- 通過其成員dev_t來定義裝置號(分為主、次裝置號)以確定字元裝置的唯一性;

c -- 通過其成員file_operations來定義字元裝置驅動提供給VFS的介面函式,如常見的open()、read()、write()等;


     在Linux字元裝置驅動中:

a -- 模組載入函式通過 register_chrdev_region( ) 或 alloc_chrdev_region( )來靜態或者動態獲取裝置號;

b -- 通過 cdev_init( ) 建立cdev與 file_operations之間的連線,通過 cdev_add( ) 向系統新增一個cdev以完成註冊;

c -- 模組解除安裝函式通過cdev_del( )來登出cdev,通過 unregister_chrdev_region( )來釋放裝置號;


     使用者空間訪問該裝置的程式:

a -- 通過Linux系統呼叫,如open( )、read( )、write( ),來“呼叫”file_operations來定義字元裝置驅動提供給VFS的介面函式;


3、字元裝置驅動模型



二、cdev 結構體解析

      在Linux核心中,使用cdev結構體來描述一個字元裝置,cdev結構體的定義如下:


  
  1. <include/linux/cdev.h>
  2. struct cdev {
  3. struct kobject kobj; //內嵌的核心物件.
  4. struct module *owner; //該字元裝置所在的核心模組的物件指標.
  5. const struct file_operations *ops; //該結構描述了字元裝置所能實現的方法,是極為關鍵的一個結構體.
  6. struct list_head list; //用來將已經向核心註冊的所有字元裝置形成連結串列.
  7. dev_t dev; //字元裝置的裝置號,由主裝置號和次裝置號構成.
  8. unsigned int count; //隸屬於同一主裝置號的次裝置號的個數.
  9. };

核心給出的操作struct cdev結構的介面主要有以下幾個:

a -- void cdev_init(struct cdev *, const struct file_operations *);

其原始碼如程式碼清單如下:


  
  1. void cdev_init(struct cdev *cdev, const struct file_operations *fops)
  2. {
  3. memset(cdev, 0, sizeof *cdev);
  4. INIT_LIST_HEAD(&cdev-> list);
  5. kobject_init(&cdev->kobj, &ktype_cdev_default);
  6. cdev->ops = fops;
  7. }
      該函式主要對struct cdev結構體做初始化最重要的就是建立cdev 和 file_operations之間的連線:

(1) 將整個結構體清零;

(2) 初始化list成員使其指向自身;

(3) 初始化kobj成員;

(4) 初始化ops成員;


 b --struct cdev *cdev_alloc(void);

     該函式主要分配一個struct cdev結構動態申請一個cdev記憶體,並做了cdev_init中所做的前面3步初始化工作(第四步初始化工作需要在呼叫cdev_alloc後,顯式的做初始化即: .ops=xxx_ops).

其原始碼清單如下:


  
  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. }

     在上面的兩個初始化的函式中,我們沒有看到關於owner成員、dev成員、count成員的初始化;其實,owner成員的存在體現了驅動程式與核心模組間的親密關係,struct module是核心對於一個模組的抽象,該成員在字元裝置中可以體現該裝置隸屬於哪個模組,在驅動程式的編寫中一般由使用者顯式的初始化 .owner = THIS_MODULE, 該成員可以防止裝置的方法正在被使用時,裝置所在模組被解除安裝。而dev成員和count成員則在cdev_add中才會賦上有效的值。

 
c -- int cdev_add(struct cdev *p, dev_t dev, unsigned count);

       該函式向核心註冊一個struct cdev結構,即正式通知核心由struct cdev *p代表的字元裝置已經可以使用了。

當然這裡還需提供兩個引數:

(1)第一個裝置號 dev,

(2)和該裝置關聯的裝置編號的數量。

這兩個引數直接賦值給struct cdev 的dev成員和count成員。


d -- void cdev_del(struct cdev *p);

     該函式向核心登出一個struct cdev結構,即正式通知核心由struct cdev *p代表的字元裝置已經不可以使用了。

     從上述的介面討論中,我們發現對於struct cdev的初始化和註冊的過程中,我們需要提供幾個東西

(1) struct file_operations結構指標;

(2) dev裝置號;

(3) count次裝置號個數。

但是我們依舊不明白這幾個值到底代表著什麼,而我們又該如何去構造這些值!



三、裝置號相應操作

1 -- 主裝置號和次裝置號(二者一起為裝置號):

      一個字元裝置或塊裝置都有一個主裝置號和一個次裝置號。主裝置號用來標識與裝置檔案相連的驅動程式,用來反映裝置型別。次裝置號被驅動程式用來辨別操作的是哪個裝置,用來區分同類型的裝置。

  linux核心中,裝置號用dev_t來描述,2.6.28中定義如下:

  typedef u_long dev_t;

  在32位機中是4個位元組,高12位表示主裝置號,低20位表示次裝置號。

核心也為我們提供了幾個方便操作的巨集實現dev_t:

1) -- 從裝置號中提取major和minor

MAJOR(dev_t dev);                              

MINOR(dev_t dev);

2) -- 通過major和minor構建裝置號

MKDEV(int major,int minor);

注:這只是構建裝置號。並未註冊,需要呼叫 register_chrdev_region 靜態申請;


  
  1. //巨集定義:
  2. #define MINORBITS 20
  3. #define MINORMASK ((1U << MINORBITS) - 1)
  4. #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
  5. #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
  6. #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))</span>


2、分配裝置號(兩種方法):

a -- 靜態申請

int register_chrdev_region(dev_t from, unsigned count, const char *name);

其原始碼清單如下:


  
  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. for (n = from; n < to; n = next) {
  7. 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. }

b -- 動態分配:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

其原始碼清單如下:


  
  1. int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
  2. const char *name)
  3. {
  4. struct char_device_struct *cd;
  5. cd = __register_chrdev_region( 0, baseminor, count, name);
  6. if (IS_ERR(cd))
  7. return PTR_ERR(cd);
  8. *dev = MKDEV(cd->major, cd->baseminor);
  9. return 0;
  10. }

可以看到二者都是呼叫了__register_chrdev_region 函式,其原始碼如下:


  
  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. cd = kzalloc( sizeof(struct char_device_struct), GFP_KERNEL);
  9. if (cd == NULL)
  10. return ERR_PTR(-ENOMEM);
  11. mutex_lock(&chrdevs_lock);
  12. /* temporary */
  13. if (major == 0) {
  14. for (i = ARRAY_SIZE(chrdevs) -1; i > 0; i--) {
  15. if (chrdevs[i] == NULL)
  16. break;
  17. }
  18. if (i == 0) {
  19. ret = -EBUSY;
  20. goto out;
  21. }
  22. major = i;
  23. ret = major;
  24. }
  25. cd->major = major;
  26. cd->baseminor = baseminor;
  27. cd->minorct = minorct;
  28. strlcpy(cd->name, name, sizeof(cd->name));
  29. i = major_to_index(major);
  30. for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
  31. if ((*cp)->major > major ||
  32. ((*cp)->major == major &&
  33. (((*cp)->baseminor >= baseminor) ||
  34. ((*cp)->baseminor + (*cp)->minorct > baseminor))))
  35. break;
  36. /* Check for overlapping minor ranges. */
  37. if (*cp && (*cp)->major == major) {
  38. int old_min = (*cp)->baseminor;
  39. int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
  40. int new_min = baseminor;
  41. int new_max = baseminor + minorct - 1;
  42. /* New driver overlaps from the left. */
  43. if (new_max >= old_min && new_max <= old_max) {
  44. ret = -EBUSY;
  45. goto out;
  46. }
  47. /* New driver overlaps from the right. */
  48. if (new_min <= old_max && new_min >= old_min) {
  49. ret = -EBUSY;
  50. goto out;
  51. }
  52. }
  53. cd->next = *cp;
  54. *cp = cd;
  55. mutex_unlock(&chrdevs_lock);
  56. return cd;
  57. out:
  58. mutex_unlock(&chrdevs_lock);
  59. kfree(cd);
  60. return ERR_PTR(ret);
  61. }
 通過這個函式可以看出  register_chrdev_region  alloc_chrdev_region 的區別,register_chrdev_region直接將Major 註冊進入,而 alloc_chrdev_region從Major = 0 開始,逐個查詢裝置號,直到找到一個閒置的裝置號,並將其註冊進去;

二者應用可以簡單總結如下:

                                     register_chrdev_region                                                alloc_chrdev_region 

    devno = MKDEV(major,minor);     ret = register_chrdev_region(devno, 1, "hello");      cdev_init(&cdev,&hello_ops);     ret = cdev_add(&cdev,devno,1);      alloc_chrdev_region(&devno, minor, 1, "hello");     major = MAJOR(devno);     cdev_init(&cdev,&hello_ops);     ret = cdev_add(&cdev,devno,1) register_chrdev(major,"hello",&hello

     可以看到,除了前面兩個函式,還加了一個register_chrdev 函式,可以發現這個函式的應用非常簡單,只要一句就可以搞定前面函式所做之事;

下面分析一下register_chrdev 函式,其原始碼定義如下:


  
  1. static inline int register_chrdev(unsigned int major, const char *name,
  2. const struct file_operations *fops)
  3. {
  4. return __register_chrdev(major, 0, 256, name, fops);
  5. }
呼叫了 __register_chrdev(major, 0, 256, name, fops) 函式:

  
  1. int __register_chrdev( unsigned int major, unsigned int baseminor,
  2. unsigned int count, const char *name,
  3. const struct file_operations *fops)
  4. {
  5. struct char_device_struct *cd;
  6. struct cdev *cdev;
  7. int err = -ENOMEM;
  8. cd = __register_chrdev_region(major, baseminor, count, name);
  9. if (IS_ERR(cd))
  10. return PTR_ERR(cd);
  11. cdev = cdev_alloc();
  12. if (!cdev)
  13. goto out2;
  14. cdev->owner = fops->owner;
  15. cdev->ops = fops;
  16. kobject_set_name(&cdev->kobj, "%s", name);
  17. err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
  18. if (err)
  19. goto out;
  20. cd->cdev = cdev;
  21. return major ? 0 : cd->major;
  22. out:
  23. kobject_put(&cdev->kobj);
  24. out2:
  25. kfree(__unregister_chrdev_region(cd->major, baseminor, count));
  26. return err;
  27. }
可以看到這個函式不只幫我們註冊了裝置號,還幫我們做了cdev 的初始化以及cdev 的註冊;

3、登出裝置號:

void unregister_chrdev_region(dev_t from, unsigned count);


4、建立裝置檔案:

     利用cat /proc/devices檢視申請到的裝置名,裝置號。

1)使用mknod手工建立:mknod filename type major minor

2)自動建立裝置節點:

    利用udev(mdev)來實現裝置檔案的自動建立,首先應保證支援udev(mdev),由busybox配置。在驅動初始化程式碼裡呼叫class_create為該裝置建立一個class,再為每個裝置呼叫device_create建立對應的裝置。

    詳細解析見:Linux 字元裝置驅動開發 (二)—— 自動建立裝置節點


下面看一個例項,練習一下上面的操作:

hello.c


  
  1. #include <linux/module.h>
  2. #include <linux/fs.h>
  3. #include <linux/cdev.h>
  4. static int major = 250;
  5. static int minor = 0;
  6. static dev_t devno;
  7. static struct cdev cdev;
  8. static int hello_open (struct inode *inode, struct file *filep)
  9. {
  10. printk( "hello_open \n");
  11. return 0;
  12. }
  13. static struct file_operations hello_ops=
  14. {
  15. .open = hello_open,
  16. 相關推薦

    Linux 字元裝置驅動結構—— cdev 結構裝置相關知識解析

    一、字元裝置基礎知識 1、裝置驅動分類       linux系統將裝置分為3類:字元裝置、塊裝置、網路裝置。使用驅動程式: 字元裝置:是指只能一個位元組一個位元組讀寫的裝置,不能隨機讀取裝置記憶體中的某一資料,讀取資料需要按照先後資料。

    Linux 字元裝置驅動結構—— cdev 結構裝置相關知識解析[轉載]

    一、字元裝置基礎知識1、裝置驅動分類      linux系統將裝置分為3類:字元裝置、塊裝置、網路裝置。使用驅動程式:字元裝置:是指只能一個位元組一個位元組讀寫的裝置,不能隨機讀取裝置記憶體中的某一資料,讀取資料需要按照先後資料。字元裝置是面向流的裝置,常見的字元裝置有滑鼠

    Linux I2C裝置驅動編寫

    在Linux驅動中I2C系統中主要包含以下幾個成員: I2C adapter 即I2C介面卡 I2C driver 某個I2C裝置的裝置驅動,可以以driver理解。 I2C client 某個I2C裝置的裝置宣告,可以以device理解。 I2C adapter 是

    嵌入式Linux裝置驅動開發

    裝置驅動開發是Linux開發領域一個非常重要的部分,在Linux原始碼的85%都是驅動程式的程式碼。裝置驅動開發不僅需要了解硬體底層的知識,還需要擁有作業系統的背景。驅動程式追求的是高效,穩定,驅動程式發生的問題有可能直接導致整個系統的崩潰。 驅動程式不主動執

    I/O體系結構裝置驅動程式

    1、I/O體系結構 為確保計算機能夠正常工作,必須提供資料通路,讓資訊在連線到計算機的CPU、RAM、和I/O裝置之間流動,這些資料通路總稱為匯流排,擔當計算機內部主通訊通道的作用。 所有計算機都擁有一條系統匯流排,它連線大部分內部硬體裝置,一種典型的系統匯流排是PCI(

    Linux設備驅動程序設備驅動程序簡介

    包括 收集 字符設備 調度器 計算機 啟動 驅動程序 str 單個 機制or策略: 驅動提供機制(what),而不是提供策略(how); 內核功能劃分: 根據內核完成任務的不同,可分為如下幾個部分: 1. 進程管理 負責進程的的創建和銷毀,並

    如何寫DOS下的裝置驅動程式

    基本上我寫的文章中的程式例項都是32位的,需要執行在保護模式下,但是不要祈求在DOS下可以寫32位的裝置驅動程式,因為DOS本身是16位真實模式下的作業系統,當然其驅動程式的機制也只能是真實模式下的,儘管在DOS下可以編防寫模式的程式,但這些程式亦可以通過DPMI呼叫真實模式

    Linux 下wifi 驅動開發—— WiFi基礎知識解析

     一、WiFi相關基礎概念 1、什麼是wifi        我們看一下百度百科是如何定義的:       Wi-Fi是一種可以將個人電腦、手持裝置(如pad、手機)等終端以無線方式互相連線的技術,事實上它是一個高頻無線電訊號。[1]  無線保真是一個無線網路通訊技術的品牌

    Linux下I2C驅動分析

            最近在做一個基於全志A33晶片的android移植時發現嵌入式裝置很多都用到了I2C匯流排通訊,比如說攝像頭,G-sensor,觸控式螢幕等,為此我覺得很好的理解I2C裝置驅動在今後的嵌入式開發中是非常有好處的,而目前我也是處於學習階段,便將這些學習的過程給

    嵌入式Linux——nand flash 驅動開發:硬體介紹

    本文章講nand flash的驅動開發,而在介紹驅動程式之前我想先介紹一下我所用的硬體。這樣對程式的開發更為方便。本文所使用的nand flash晶片為K9F2G08U0C,下面是他的一些必要的特性:

    數據結構線性表循環鏈表相關補充

    width hide cli 機器 都是 實時 思路 在外 for循環 (一)合並兩個循環鏈表 p = rearA->next; //A的頭結點,一會還要使用 rearA->next = rearB->next->next

    資料結構——資料結構初識及演算法複雜度介紹

        資料結構,毋庸置疑,在程式設計中是極其重要的存在,在電腦科學中,不只是一般程式設計的基礎,而且是設計和實現編譯程式、作業系統、資料庫系統及其他系統程式和大型應用程式的重要基礎。     因此,CSDN的第一篇文章就選擇了資料結構(也是打算再次補習可

    資料結構:資料結構的基本概念和演算法的時間和空間複雜度

    資料結構討論的範疇 計算機技術的兩大支柱:1是資料結構,2是演算法。在某種程度上講,程式設計等同於資料結構+演算法。 程式設計是為計算機設計一組指令集,演算法是解決問題的策略,資料結構是模型。 問

    PE檔案結構 基本結構

    PE檔案結構(一) 參考 書:《加密與解密》 視訊:小甲魚 解密系列 視訊         exe,dll都是PE(Portable Execute)檔案結構。PE檔案使用的是一個平面地址空間,所有程式碼和資料都被合併在一起,組成一個很大的結構。先看2張圖,來大

    Linux 字元裝置驅動結構—— file_operations 結構知識解析

            前面在 Linux 字元裝置驅動開發基礎 (三)—— 字元裝置驅動結構(中) ,我們已經介紹了兩種重要的資料結構 struct inode{...}與 struct file{...} ,下面來介紹另一個比較重要資料結構 struct _file_oper

    Linux USB 驅動開發—— USB裝置基礎概念

    Linux USB 驅動開發(一)—— USB裝置基礎概念           在終端使用者看來,USB裝置為主機提供了多種多樣的附加功能,如檔案傳輸,聲音播放等,但對USB主機來說,它與所有USB裝置的介面都是一致的。一個USB裝置由3個功

    linux裝置驅動之USB主機控制器驅動分析

    一:前言 Usb是一個很複雜的系統.在usb2.0規範中,將其定義成了一個分層模型.linux中的程式碼也是按照這個分層模型來設計的.具體的分為 usb裝置,hub和主機控制器三部份.在閱讀程式碼的時候,必須要參考相應的規範.最基本的就是USB2.0的spec.

    Linux I2C設備驅動編寫

    ive AC ner 解決 args nali smb man lin http://blog.csdn.net/airk000/article/details/21345457 在Linux驅動中I2C系統中主要包含以下幾個成員: I2C adapter 即I2C適配

    Linux驅動開發3——以module方式註冊裝置

    通過 s3c_device_leds_ctl->*smdk4x12_devices[]->platform_add_devices()->platform_device_register() 可以直接使用“platform_device_register()”來註冊裝置

    嵌入式Linux裝置驅動開發

    上一篇中介紹到裝置驅動如何匹配裝置以及繫結裝置的,在Linux系統下進行註冊,這裡將繼續介紹probe函式的功能。 5、probe函式 Probe()函式必須驗證指定裝置的硬體是否真的存在,probe()可以使用裝置的資源,包括時鐘,platform_dat