成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Linux驅動實踐:一起來梳理【中斷】的前世今生(附代碼)

系統 Linux
假如存在這樣一個需求:應用程序需要監控某個硬件GPIO口的電平狀態,當發生變化時,應用程序就做出相應的動作。

[[440009]]

別人的經驗,我們的階梯!

大家好,我是道哥,今天我為大伙兒解說的技術知識點是:【Linux 中斷的注冊和處理】。

在前兩篇文章中,描述的是在應用層如何調用驅動函數來控制GPIO,以及在驅動中如何發送發送信號給應用層。

假如存在這樣一個需求:應用程序需要監控某個硬件GPIO口的電平狀態,當發生變化時,應用程序就做出相應的動作。

利用之前已經介紹的知識,是可以完成這個需求的。

比如:在驅動程序中不停的讀取GPIO口的狀態,一旦發生變化,就把新的電平狀態通過信號發送到應用層。

這樣的方式稱作:輪詢。

輪詢方式的缺點顯而易見:輪詢的時間間隔應該是多少毫秒(or 微秒),才比較合適呢?

輪詢太慢:可能會丟失信號;輪詢太快:消耗 CPU 資源!

因此,在實際的產品中,用中斷觸發的方式才是更切合實際的選擇!

本文所有的描述和測試,都是在 x86 平臺上完成的;

Linux 中斷的知識點梳理

中斷的分類

Linux 的版本在持續更新,對中斷的處理方式也在不停的發生變化。

下面幾張圖,是以前在學習時畫的思維導圖。

這幾張圖比較清晰地描述了在Linux操作系統中,關于中斷的一些基本概念。

這張圖的結構還是比較清晰的,基本上概括了Linux系統中的中斷分類。

另外,在很多關于中斷的書籍中,大部分都是從基礎的 PIC(可編程中斷控制器)開始講解的。

如果您想非常具體、專業、深入的了解關于中斷的相關內容,有一篇文章《Interrupt in Linux.pdf》講得非常好(文章的后面部分我也沒有看懂)。

在文末有下載鏈接,感興趣的小伙伴可以學習一下。

中斷號和中斷向量

這張圖只要記住中斷號與中斷向量的關系就可以了:

  • 中斷號與中斷控制器(PIC/APIC)相關;
  • 中斷向量與 CPU 相關,用來查找中斷處理函數的入口地址;

中斷服務例程 ISR

中斷服務程序,就是針對每一個中斷如何進行處理。

如果您了解Linux中斷的相關內容,一定會看到這樣的描述:中斷處理分為上半部分和下半部分。

上半部分不能消耗太多的時間,主要處理與硬件相關的重要工作;其他不重要的工作,都放在下半部分去做。

從上面這張圖中可以看出,用來完成下半部分工作有好幾種機制可以選擇,每一種方式都是針對不同的需求場景。

在每一種下半部分機制中,Linux都設計了非常方便的接口函數。

作為開發者的我們來說,使用這些下半部分的機制很簡單,只需要幾個函數調用即可。

例如:如果使用工作隊列來實現下半部分的工作,只需要2步動作:

1. 定義處理函數

  1. static struct work_struct mywork; 
  2.  
  3. static void mywork_handler(struct work_struct *work
  4.     printk("This is myword_handler...\n"); 

2. 在中斷處理函數中,注冊注冊函數

  1. NIT_WORK(&mywork, mywork_handler); 
  2.  
  3. schedule_work(&mywork); 

下面幾張圖,是針對每一種“下半部分”處理機制的一些特點,注意:有些機制在新版本中已經廢棄不用了,了解即可。

中斷處理的注冊和注銷 API

所謂的中斷注冊,就是告訴操作系統:我對哪個中斷感興趣。

當這些中斷發生的時候,請通知我。通知的方式就是:調用一個預先注冊好的回調函數。

驅動程序可以通過函數 request_irq(),向操作系統注冊,并且激活指定的中斷線:

  1. int request_irq(unsigned int irq,  
  2.                 irq_handler_t handler, 
  3.                 unsigned long flags,  
  4.                 const char *devname,  
  5.                 void *dev_id); 

參數說明:

irq: 申請的硬件中斷號;

handler: 中斷處理函數。一旦中斷發生,這個函數就被調用;

flags: 中斷的屬性,例如:IRQF_DISABLED,IRQF_TIMER,IRQF_SHARED;

devname: 中斷驅動程序的名稱,在 /proc/interrupts 文件中看到對應的內容;

dev_id: 中斷程序的唯一標識,比如:在共享中斷中,可以用來區分不同的中斷處理程序;

驅動程序通過函數 free_irq(),向操作系統注銷一個中斷處理函數:

  1. void free_irq(unsigned int irq, void *dev_id); 

參數說明:

irq: 硬件中斷號;

dev_id: 中斷程序的唯一標識;

實操:捕獲鍵盤中斷

示例代碼

有了上面的知識鋪墊,下面就來實操一下,實現的功能是:

捕獲鍵盤的中斷,在中斷處理函數中,打印出按鍵的掃描碼,如果是 ESC 鍵被按下,就打印出指定的信息。

與往常一樣,操作的目錄位于:tmp/linux-4.15/drivers 目錄下。

  1. $ mkdir my_driver_interrupt 
  2.  
  3. $ touch driver_interrupt.c 

文件內容:

  1. #include <linux/kernel.h> 
  2. #include <linux/module.h> 
  3. #include <linux/interrupt.h> 
  4.  
  5. // 中斷號 
  6. static int irq;  
  7.  
  8. // 驅動程序名稱  
  9. static char * devname;           
  10.              
  11. // 用來接收加載驅動模塊時傳入的參數 
  12. module_param(irq, int, 0644); 
  13. module_param(devname, charp, 0644); 
  14.  
  15. // 定義驅動程序的 ID,在中斷處理函數中用來判斷是否需要處理             
  16. #define MY_DEV_ID           1211 
  17.  
  18. // 驅動程序數據結構 
  19. struct myirq 
  20.     int devid; 
  21. }; 
  22.  
  23. // 保存驅動程序的所有信息 
  24. struct myirq mydev  ={ MY_DEV_ID }; 
  25.  
  26. // 鍵盤相關的 IO 端口 
  27. #define KBD_DATA_REG        0x60   
  28. #define KBD_STATUS_REG      0x64 
  29. #define KBD_SCANCODE_MASK   0x7f 
  30. #define KBD_STATUS_MASK     0x80 
  31.          
  32. // 中斷處理函數 
  33. static irqreturn_t myirq_handler(int irq, void * dev) 
  34.     struct myirq mydev; 
  35.     unsigned char key_code; 
  36.     mydev = *(struct myirq*)dev;     
  37.      
  38.     // 檢查設備 id,只有當相等的時候才需要處理 
  39.     if (MY_DEV_ID == mydev.devid) 
  40.     { 
  41.         // 讀取鍵盤掃描碼 
  42.         key_code = inb(KBD_DATA_REG); 
  43.  
  44.         /* 這里如果放開,每次按鍵都會打印出很多信息 
  45.         printk("key_code: %x %s\n"
  46.                 key_code & KBD_SCANCODE_MASK, 
  47.                 key_code & KBD_STATUS_MASK ? "released" : "pressed"); 
  48.         */ 
  49.      
  50.         // 判斷:是否為 ESC 鍵 
  51.         if (key_code == 0x01) 
  52.         { 
  53.             printk("EXC key is pressed! \n"); 
  54.         } 
  55.     }    
  56.  
  57.     return IRQ_HANDLED; 
  58.   
  59. // 驅動模塊初始化函數 
  60. static int __init myirq_init(void) 
  61.     printk("myirq_init is called. \n"); 
  62.  
  63.     // 注冊中斷處理函數 
  64.     if(request_irq(irq, myirq_handler, IRQF_SHARED, devname, &mydev)!=0) 
  65.     { 
  66.         printk("register irq[%d] handler failed. \n", irq); 
  67.         return -1; 
  68.     } 
  69.  
  70.     printk("register irq[%d] handler success. \n", irq); 
  71.     return 0; 
  72.   
  73. // 驅動模塊退出函數 
  74. static void __exit myirq_exit(void) 
  75.     printk("myirq_exit is called. \n"); 
  76.  
  77.     // 注銷中斷處理函數 
  78.     free_irq(irq, &mydev); 
  79.   
  80. MODULE_LICENSE("GPL"); 
  81. module_init(myirq_init); 
  82. module_exit(myirq_exit); 

上面的代碼,有兩個小的知識點。

向驅動程序傳參

示例代碼中,在調用 request_irq 時,需要指定中斷號和驅動程序的名稱。

這兩個參數是在加載驅動模塊的時候,從命令行傳入的。

在驅動程序中,通過下面兩行代碼即可實現參數的接收:

  1. module_param(irq, int, 0644); 
  2.  
  3. module_param(devname, charp, 0644); 

module_param 是一個宏定義,定義在 include/linux/moduleparam.h 文件中,具體定義如下:

  1. #define module_param(name, type, perm) 
  2.  
  3. module_param_named(namename, type, perm); 

name: 存儲參數的變量名;

type: 變量的類型;

perm: 訪問參數的權限,表示此參數在sysfs文件系統中所對應的文件節點的屬性;

IO地址:IO端口和IO內存

這是讀取 IO 外設的兩種不同方式。

IO 端口有兩種編址方式:統一編址和獨立編址。

統一編制

把主存單元所在的地址空間,劃出一部分出來,專門用來把IO外設寄存器的地址映射到這部分劃出來的地址空間中。

統一編址的好處是:讀取IO外設的時候,就好像讀取普通的內存地址空間中的數據一樣。

獨立編址

IO 外設的地址空間,與主存單元的地址空間是兩個獨立的地址空間,此時,IO地址一般稱作: IO端口。

我們在讀寫IO外設的時候,從這些 “IO端口” 中讀寫就可以了。不同的外設,被分配了不同的 IO 端口號。

CPU 提供了一些列函數來讀寫 IO 端口,例如:

  1. // 讀寫一個字節 
  2. unsigned inb(unsigned port); 
  3. void outb(unsigned char byte, unsigned port); 
  4.  
  5. // 讀寫一個字 
  6. unsigned inw(unsigned port); 
  7. void outw(unsigned short word, unsigned port); 

編譯、驗證

編譯驅動模塊:

  1. $ make 
  2.  
  3. 輸出文件:driver_interrupt.ko 

因為我們捕獲的是鍵盤中斷(中斷號:1),先看一下在加載驅動模塊之前的中斷驅動程序 head /proc/interrupts:

可以把 demsg 的輸出也清理一下:dmesg -c

執行下面指令來加載驅動模塊(傳遞2個參數):

  1. insmod driver_interrupt.ko irq=1 devname=myirq 

再次執行一下指令 head /proc/interrupts 查看驅動程序:

在中斷號 1 的右側,是不是看到了我們的驅動程序:my_irq?

再來看一下 dmesg 的輸出信息:

成功注冊了中斷號1的處理函數!

此時,按幾次鍵盤左上角的 ESC 鍵,然后再查看 dmesg 的輸出信息:

以上,就是最簡單的中斷注冊和相應的中斷處理函數!

在實際的項目中,如果要把中斷信息通知到應用層,可以通過上一篇文章介紹的發送信號來實現,或者通過其他的回調機制也可以。

下一篇文章,我們在這個示例代碼上進行擴展,看一下:中斷處理中每一個“下半部分”機制應該如何編程。

本文轉載自微信公眾號「IOT物聯網小鎮」,可以通過以下二維碼關注。轉載本文請聯系IOT物聯網小鎮公眾號。

 

責任編輯:武曉燕 來源: IOT物聯網小鎮
相關推薦

2024-06-14 09:32:12

2012-04-14 20:47:45

Android

2012-06-25 09:37:24

Web

2021-04-26 11:18:15

FedoraLinuxBug

2011-09-15 17:03:44

2012-11-08 17:33:53

智慧云

2020-06-11 18:35:23

C++編程語言

2009-10-29 16:32:34

Oracle表空間

2021-06-02 15:30:12

Synchronize并發多線程

2021-06-09 08:15:50

volatileJava開發

2010-05-21 17:32:07

IIS服務器

2012-09-10 13:42:55

PHP項目管理

2011-09-07 22:59:07

聯想一體機

2022-05-20 12:14:50

ZuulSpringClou

2010-05-10 15:31:35

Unix文件

2014-07-30 10:55:27

2015-11-18 14:14:11

OPNFVNFV

2025-02-12 11:25:39

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲国产精品va在线看黑人 | 在线观看a视频 | 欧美韩一区二区三区 | 一级毛片视频在线 | 中文字幕一区二区三区日韩精品 | 日本成年免费网站 | 91xh98hx 在线 国产 | 精品成人av | 日本视频一区二区 | 国产在线1 | 欧美久久一区二区三区 | 91精品国产综合久久久久久丝袜 | 欧美激情亚洲天堂 | 午夜影院普通用户体验区 | 中文字幕高清视频 | 久久久国产一区二区三区 | 久久精品播放 | 午夜久久久 | 国产日韩一区二区三免费高清 | 日本成人久久 | 国产一区不卡在线观看 | 欧美日韩国产一区二区三区 | 中日字幕大片在线播放 | 久久久高清 | 国产在线成人 | 日韩欧美高清 | 国产高清在线精品 | 欧美日韩亚洲国产综合 | 嫩草伊人| 免费黄色特级片 | 国产99久久久国产精品下药 | 97视频精品| 国产精品成人国产乱一区 | 国产精品久久久久久久一区二区 | 日韩快播电影网 | 久久精品国产一区 | 日韩中文字幕第一页 | 久草青青| 高清一区二区 | 综合精品久久久 | 久久在视频 |