內核模塊-實現一個簡單的設備

上一篇文章講了如何實現基於內核模塊的“helloworld”,相信大家通過這個例子對於內核模塊有了一個基本的瞭解。當然,內核模塊絕不僅僅只能實現這點功能,其最大的應用就是實現硬件的驅動程序。其實,linux內核中很大一部代碼都是硬件處理相關的,比如,設備-總線-驅動框架,USB框架、spi框架、i2c框架等等,對應於各種不同的硬件設備,相應的就會有設備驅動程序,從最簡單的按鍵、LED驅動,到十分複雜的USB子系統驅動,可以好不誇張的說,Linux內核可以適配絕大多數的硬件設備。

那這些驅動框架和驅動程序,一般都是通過內核模塊的方式設計和使用的。在構建內核時,一般將設備驅動程序編譯成內核模塊,內核可以根據系統接入的硬件情況,動態的加載、卸載相應的驅動模塊。

那具體到一個硬件驅動程序,是如何與內核模塊結合在一起的呢?下面通過一個簡單的字符設備驅動例子說明一下。

設備存在方式–設備文件

“一切皆文件”是Linux的十分重要的設計哲學。內核爲應用程序提供的所有服務都是通過“文件”的形式。設備也不例外,任何硬件最終都會在文件系統中創建一個對應的文件,可以通過命令ls /dev看到系統中所有的設備文件。例如,大家熟悉的鼠標,其設備文件的主要信息如下所示:

ls input/mouse0 -l
crw-rw---- 1 root input 13, 32 5月  16 22:42 input/mouse0

其中,c代表設備類型爲字符設備,rw-rw----表示用戶對於文件的操作權限,13,52表示設備的主、次設備號,這個下一節會着重點講解。

用戶空間的應用程序,通過這些設備文件,就可以完成與硬件設備的交互。操作設備文件的方式與操作普通文件沒有任何區別,都是通過標準的文件操作接口完成。

  • 打開設備:open
  • 關閉設備:close
  • 寫設備參數:write
  • 讀設備參數:read
  • 控制設備:ioctl
  • … …

在“一切皆文件”這種哲學的指導下,Linux系統的設備管理十分的和諧、統一,只要你學會了操作普通文件,那麼操作任何設備都沒有太大的問題,至少你可以不需要太多學習,就可以完成對一個設備的使用。

設備標識–設備號

Linux系統會使用很多的硬件設備,去/dev目錄下看一下就知道了。那這麼多的設備文件,內核是如何進行區分的呢?其實很簡單,就是通過一個數字名字進行區分的,這個數字就是設備號。不同的設備文件擁有系統唯一的設備號,內核通過這個設備號完成設備的識別。

設備號,分爲兩部分:主設備號和次設備號。主設備號用來定義設備的類型,次設備用來定義同屬於某一類型的設備編號。這就好比,在學校裏,會給每個班級編號,具體每個班級的裏學生,又會通過學號進行編號,對應一下,主設備號就是班級編號,次設備號就是班內學生編號。

通常而言,一個驅動程序會對應唯一的一個主設備號,而每個被其驅動的硬件設備,對應一個次設備號。

內核通過dev_t表示設備號,其包括主、次設備號兩部分。一般不會通過dev_t直接解析主次設備號,而是通過下面的宏進行操作:

- MAJOR(dev_t dev);獲取主設備號
- MINOR(dev_t dev);獲取次設備號
- MKDEV(int major, int minor);根據主次設備號合成設dev_t類型

申請和釋放設備號

不同類型的設備,管理設備號的方式是不同的,由於本文舉的驅動例子是字符類型的,所以只介紹一下,字符類設備的設備號的申請和釋放方式。

設備編號的申請有兩種方式:

  • 已經主設備號:

如果事先,已經知道了主設備,那麼可以使用接口申請設備號。

int register_chdev_region(dev_t first, unsigned int count, char*name);

- first:主設備號
- count:連續的次設備的個數,次設備號一般從0開始
- name:設備名稱
  • 未知主設備號:

如果事先,不知道主設備號,那麼使用下面的接口,內核會動態的分配一個可用的主設備供該設備使用。

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
- dev:保存內核分配的設備號
- firstminor:起始次設備號
- count:連續的次設備號個數
- name:設備名稱

不論使用哪種方式,設備號申請成功,返回0,失敗返回錯誤碼。

設備號是系統資源,不使用時應該主動的將其釋放,該操作一般發生在驅動卸載時,使用的接口定義如下。

void unregister_chdev_region(dev_t first, unsigned int count);

可以通過查看/proc/devices,來看系統中硬件設備文件的設備號。

 cat devices 
 Character devices:
  1 mem
  4 /dev/vc/0
 ... ... 
 Block devices:
  7 loop
  8 sd
  9 md
... ...

簡單字符設備

好了,有了設備號的概念之後, 就可以在內核模塊中添加申請設備號的操作,而後就可以在/dev目錄下,看到設備文件。這一個真正意義的字符設備文件,下面我們就來實現一個這個設備。

上一小節說過,設備號可以靜態指定,也可以動態生成,我們採用兩種方式來申請設備編號。通過定義一個全局的變量:global_major,來保存設備號,如果global_major爲0,那麼採用動態設備號申請方式,否則,採用global_major來申請設備號。

  //scdev.c
  #include <linux/fs.h>                                                                                                                                                                                                                                                                   
  #include <linux/init.h>
  #include <linux/module.h>
   
  static int global_major = 0;
  static int global_minor = 1;
  static int global_nr_devs = 2;
   
  static int __init module_init_func(void)
  {
      int ret;
      dev_t dev;
   
      printk("register simple cdev.\n");
   
      if(global_major) {
          dev = MKDEV(global_major, global_minor);
          ret = register_chrdev_region(dev, global_nr_devs, "scdev");
      } else {
          ret = alloc_chrdev_region(&dev, global_major, global_nr_devs, "scdev");
          global_major = MAJOR(dev);
          global_minor = MINOR(dev);
      }   
   
      if(ret < 0) {
          printk(KERN_WARNING "scdev:can't get major %d.\n", global_major);
          return ret;
      }   
   
      printk(KERN_INFO "scdev:major:%d, minor:%d.\n", global_major, global_minor);
   
      return 0;
  }
   
  static void __exit module_exit_func(void)
  {
      return;
  }
   
  MODULE_LICENSE("GPL v2");
  MODULE_VERSION("v0.1");
  MODULE_AUTHOR("lhl");
  MODULE_DESCRIPTION("LKM, scdev.");
   
  module_init(module_init_func);
  module_exit(module_exit_func);

Makefile文件

obj-m:=scdev.o

KERS :=/lib/modules/$(shell uname -r)/build

 all:
  	make -C $(KERS) M=$(shell pwd) modules

 clean:
  	make -C $(KERS) M=$(shell pwd) clean

編譯成功後, 通過sudo install scdev.ko,安裝模塊,通過dmesg可以看到動態申請的設備號:

[10169.420211] register simple cdev.
[10169.420212] scdev:major:239, minor:0.

動態申請的次設備號從0開始,可是,這個設備還沒有生成設備文件,通過mknod命令,就可以創建設備文件。

sudo mknod /dev/scdev c 239 0

ls /dev/scdev -l

crw-r--r-- 1 root root 239, 0 5月  18 21:26 /dev/scdev

不過,由於沒有實現文件相關的操作,所以,如果試圖往讀取或者寫入數據到scdev時,系統會提示:

cat /dev/scdev 
cat: /dev/scdev: 沒有那個設備或地址
或
echo 0 > /dev/scdev 
bash: /dev/scdev: 沒有那個設備或地址

後續實現文件相關操作之後,就可以實現設備文件的讀取和寫入。

總結

本文主要介紹了,如何基於設備模塊實現一個簡單的字符設備scdev,並且創建了相應的設備文件。但是,這個scdev除了申請了設備號之外,沒有其他的功能。不過,我們已經有了設備文件,待後續增加文件相關的操作之後,就可以實現更復雜的功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章