linux中斷號是系統分配給每個中斷源的代號,以便識別和處理;在采用向量中斷方式的中斷系統中,CPU必須通過它才可以找到中斷服務程序的入口地址,實現程序的轉移。
本教程操作環境:linux5.9.8系統、Dell G3電腦。
linux 中斷號是什么意思?
中斷號與中斷編程:
1、中斷號
中斷號是系統分配給每個中斷源的代號,以便識別和處理。在采用向量中斷方式的中斷系統中,CPU必須通過它才可以找到中斷服務程序的入口地址,實現程序的轉移。
在ARM裸機中實現中斷需要配置:
I/O口為中斷模式,觸發方式,I/O口中斷使能 設置GIC中斷使能,分發配置,分發總使能,CPU外部中斷接口使能,中斷優先級
在linux內核中實現中斷,只需要知道:
中斷號是什么,怎么得到中斷號 中斷處理方法
2、獲取中斷號的方法:
/arm/boot/dts/exynos4412-fs4412.dts
1)看原理圖,芯片手冊找到中斷源對應的中斷號SPI Port? No
?
? 2)進入設備樹,在arch/arm/boot/dts/exynos4x12-pinctrl.dtsi中
gpx1:?gpx1?{ ????????????????????gpio-controller; ????????????????????#gpio-cells?=?; ????????????????????interrupt-controller;??//中斷控制器 ????????????????????interrupt-parent?=?;??//繼承于gic ????????????????????interrupts?=?,?,?,?, ?????????????????????????????????,?,?,?; ????????????????????#interrupt-cells?=?;?//子繼承的interrupts的長度 ????????????};
括號中的24、 25等對應于SPI Port No,以上是系統中已經定義好的節點
在編程中,需要定義自己的節點,用來描述按鍵,打開可編輯的設備樹文件:
arch/arm/boot/dts/exynos4412-fs4412.dts,進入文件。
3)定義節點,描述當前設備用的中斷號
1 key_int_node{ 2 compatible = "test_key"; 3 interrupt-parent = <&gpx1>; //繼承于gpx1 4 interrupts = <2 4>; //2表示第幾個中斷號,4表示觸發方式為下降沿5 }; //interrupts里長度由父母的-cell決定
再舉個栗子,設置k4 — GPX3_2(XEINT26) 的節點,中斷號
1?key_int_node{ 2??????????????compatible?=?"test_key"; 3??????????????interrupt-parent?=?;??//繼承于gpx3 4??????????????interrupts?=?;??????//2表示第2個中斷號,4表示觸發方式為下降沿 5??????????};
中斷號的定位方法:
看I/O引腳,GPX1_2,中斷號就是GPX1里面的第2個
4)編譯設備樹:make dtbs
更新設備樹文件: cp? -raf arch/arm/boot/dts/exynos4412-fs4412.dtb? ?/tftpboot/
查看定義的節點:在根目錄的 proc/device-tree/目錄下
?
?3、實現中斷處理方法
在驅動中通過代碼獲取到中斷號,并且申請中斷
先看一下中斷相關的函數:
1?a,獲取到中斷號碼: ?2?????int?get_irqno_from_node(void) ?3?????{ ?4?????????//?獲取到設備樹中的節點 ?5?????????struct?device_node?*np?=?of_find_node_by_path("/key_int_node"); ?6?????????if(np){ ?7?????????????printk("find?node?okn"); ?8?????????}else{ ?9?????????????printk("find?node?failedn"); 10?????????} 11? 12?????????//?通過節點去獲取到中斷號碼 13?????????int?irqno?=?irq_of_parse_and_map(np,?0); 14?????????printk("irqno?=?%dn",?irqno); 15????????? 16?????????return?irqno; 17?????} 18?b,申請中斷 19?int?request_irq(unsigned?int?irq,?irq_handler_t?handler,?unsigned?long?flags,?const?char?*?name,?void?*?dev) 20?????參數1:?irq?????設備對應的中斷號 21?????參數2:?handler?????中斷的處理函數 22?????????????typedef?irqreturn_t?(*irq_handler_t)(int,?void?*); 23?????參數3:flags?????觸發方式 24?????????????#define?IRQF_TRIGGER_NONE????0x00000000??//內部控制器觸發中斷的時候的標志 25?????????????#define?IRQF_TRIGGER_RISING????0x00000001?//上升沿 26?????????????#define?IRQF_TRIGGER_FALLING????0x00000002?//下降沿 27?????????????#define?IRQF_TRIGGER_HIGH????0x00000004??//?高點平 28?????????????#define?IRQF_TRIGGER_LOW????0x00000008?//低電平觸發 29?????參數4:name?????中斷的描述,自定義,主要是給用戶查看的 30?????????????/proc/interrupts 31?????參數5:dev?????傳遞給參數2中函數指針的值 32?????返回值:?正確為0,錯誤非0 33? 34? 35?????參數2的賦值:即中斷處理函數 36?????irqreturn_t?key_irq_handler(int?irqno,?void?*devid) 37?????{ 38?????????return?IRQ_HANDLED; 39?????} 43????? 44?c,?釋放中斷: 45?????????void?free_irq(unsigned?int?irq,?void?*dev_id) 46?????????參數1:?設備對應的中斷號 47?????????參數2:與request_irq中第5個參數保持一致
代碼實現獲取中斷號,并注冊中斷,按下按鍵引發中斷,打印信息
1?#include?<linux> ?2?#include?<linux> ?3?#include?<linux> ?4?#include?<linux> ?5?#include?<asm> ?6?#include?<asm> ?7?#include?<linux> ?8?#include?<linux> ?9?#include?<linux> 10?#include?<linux> 11? 12?int?irqno;????//中斷號 13? 14? 15?irqreturn_t?key_irq_handler(int?irqno,?void?*devid) 16?{ 17?????printk("----------%s---------",__FUNCTION__); 18?????return?IRQ_HANDLED; 19?} 20? 21? 22?//獲取中斷號 23?int?get_irqno_from_node(void) 24?{ 25?????//獲取設備樹中的節點 26?????struct?device_node?*np?=?of_find_node_by_path("/key_int_node"); 27?????if(np){ 28?????????printk("find?node?successn"); 29?????}else{ 30?????????printk("find?node?failedn"); 31?????} 32? 33?????//通過節點去獲取中斷號 34?????int?irqno?=?irq_of_parse_and_map(np,?0); 35?????printk("iqrno?=?%d",irqno); 36? 37?????return?irqno; 38?} 39? 40? 41? 42?static?int?__init?key_drv_init(void) 43?{ 44?????//演示如何獲取到中斷號 45?????int?ret; 46????? 47?????irqno?=?get_irqno_from_node(); 48? 49?????ret?=?request_irq(irqno,?key_irq_handler,?IRQF_TRIGGER_FALLING?|?IRQF_TRIGGER_RISING,? 50?????????"key3_eint10",?NULL); 51?????if(ret?!=?0) 52?????{ 53?????????printk("request_irq?errorn"); 54?????????return?ret; 55?????} 56????? 57?????return?0; 58?} 59? 60?static?void?__exit?key_drv_exit(void) 61?{ 62?????free_irq(irqno,?NULL);??//free_irq與request_irq的最后一個參數一致 63?} 64? 65? 66? 67?module_init(key_drv_init); 68?module_exit(key_drv_exit); 69? 70?MODULE_LICENSE("GPL"); key_drv.c</linux></linux></linux></linux></asm></asm></linux></linux></linux></linux>
key_drv.c
測試效果:
按鍵按下,打印信息,但出現了按鍵抖動?
cat /proc/interrupt
?
4、?中斷編程 — 字符設備驅動框架
//?1,設定一個全局的設備對象 key_dev?=?kzalloc(sizeof(struct?key_desc),??GFP_KERNEL); //?2,申請主設備號 key_dev->dev_major?=?register_chrdev(0,?"key_drv",?&key_fops); //?3,創建設備節點文件 key_dev->cls?=?class_create(THIS_MODULE,?"key_cls"); key_dev->dev?=?device_create(key_dev->cls,?NULL,?MKDEV(key_dev->dev_major,0),?NULL,?"key0"); //?4,硬件初始化: ????????a.地址映射 ????????b.中斷申請
5、驅動實現將硬件所產生的數據傳遞給用戶
1)硬件如何獲取數據
key:?按下和抬起:?1/0讀取key對應的gpio的狀態,可以判斷按下還是抬起 ???? 讀取key對應gpio的寄存器--數據寄存器? //讀取數據寄存器int?value?=?readl(key_dev->reg_base?+?4)?&?(1<p> ? 2)驅動傳遞數據給用戶</p><div class="cnblogs_code"><pre class="brush:php;toolbar:false">在中斷處理中填充數據: key_dev->event.code = KEY_ENTER; key_dev->event.value = 0; 在xxx_read中獎數據傳遞給用戶 ret = copy_to_user(buf, &key_dev->event, count);
3)用戶獲取數據
while(1) { read(fd, &event, sizeof(struct key_event)); if(event.code == KEY_ENTER) { if(event.value) { printf("APP__ key enter pressedn"); }else{ printf("APP__ key enter upn"); } } }
6、示例:
1?#include?<linux> ??2?#include?<linux> ??3?#include?<linux> ??4?#include?<linux> ??5?#include?<linux> ??6?#include?<linux> ??7?#include?<linux> ??8?#include?<linux> ??9?#include?<linux> ?10?#include?<linux> ?11?#include?<linux> ?12?#include?<asm> ?13?#include?<asm> ?14? ?15? ?16?#define?GPXCON_REG?0X11000C20???//不可以從數據寄存器開始映射,要配置寄存器 ?17?#define?KEY_ENTER??28 ?18? ?19?//0、設計一個描述按鍵的數據的對象 ?20?struct?key_event{ ?21?????int?code;????//按鍵類型:home,esc,enter ?22?????int?value;???//表狀態,按下,松開 ?23?}; ?24? ?25?//1、設計一個全局對象———?描述key的信息 ?26?struct?key_desc{ ?27?????unsigned?int?dev_major; ?28?????int?irqno;??//中斷號 ?29?????struct?class??*cls; ?30?????struct?device?*dev; ?31?????void?*reg_base; ?32?????struct?key_event?event; ?33?}; ?34? ?35?struct?key_desc?*key_dev; ?36? ?37? ?38?irqreturn_t?key_irq_handler(int?irqno,?void?*devid) ?39?{ ?40?????printk("----------%s---------",__FUNCTION__); ?41? ?42?????int?value; ?43?????//讀取按鍵狀態 ?44?????value?=?readl(key_dev->reg_base?+?4)?&?(0x01event.code??=?KEY_ENTER; ?49?????????key_dev->event.value?=?0; ?50?????}else{ ?51?????????printk("key3?downn"); ?52?????????key_dev->event.code??=?KEY_ENTER; ?53?????????key_dev->event.value?=?1; ?54?????} ?55?????return?IRQ_HANDLED; ?56?} ?57? ?58? ?59?//獲取中斷號 ?60?int?get_irqno_from_node(void) ?61?{ ?62?????int?irqno; ?63?????//獲取設備樹中的節點 ?64?????struct?device_node?*np?=?of_find_node_by_path("/key_int_node"); ?65?????if(np){ ?66?????????printk("find?node?successn"); ?67?????}else{ ?68?????????printk("find?node?failedn"); ?69?????} ?70? ?71?????//通過節點去獲取中斷號 ?72?????irqno?=?irq_of_parse_and_map(np,?0); ?73?????printk("iqrno?=?%d",key_dev->irqno); ?74? ?75?????return?irqno; ?76?} ?77? ?78?ssize_t?key_drv_read?(struct?file?*?filp,?char?__user?*?buf,?size_t?count,?loff_t?*?fops) ?79?{ ?80?????//printk("----------%s---------",__FUNCTION__); ?81?????int?ret; ?82?????ret?=?copy_to_user(buf,?&key_dev->event,?count); ?83?????if(ret?>?0) ?84?????{ ?85?????????printk("copy_to_user?errorn"); ?86?????????return?-EFAULT; ?87?????} ?88? ?89?????//傳遞給用戶數據后,將數據清除,否則APP每次讀都是第一次的數據 ?90?????memset(&key_dev->event,?0,?sizeof(key_dev->event)); ?91?????return?count; ?92?} ?93? ?94?ssize_t?key_drv_write?(struct?file?*filp,?const?char?__user?*?buf,?size_t?count,?loff_t?*?fops) ?95?{ ?96?????printk("----------%s---------",__FUNCTION__); ?97?????return?0; ?98?} ?99? 100?int?key_drv_open?(struct?inode?*?inode,?struct?file?*filp) 101?{ 102?????printk("----------%s---------",__FUNCTION__); 103?????return?0; 104?} 105? 106?int?key_drv_close?(struct?inode?*inode,?struct?file?*filp) 107?{ 108?????printk("----------%s---------",__FUNCTION__); 109?????return?0; 110?} 111? 112? 113?const?struct?file_operations?key_fops?=?{ 114?????.open????=?key_drv_open, 115?????.read????=?key_drv_read, 116?????.write???=?key_drv_write, 117?????.release?=?key_drv_close, 118? 119?}; 120? 121? 122? 123?static?int?__init?key_drv_init(void) 124?{ 125?????//演示如何獲取到中斷號 126?????int?ret; 127????? 128?????//1、設定全局設備對象并分配空間 129?????key_dev?=?kzalloc(sizeof(struct?key_desc),?GFP_KERNEL);??//GFP_KERNEL表正常分配內存 130???????????????????????????//kzalloc相比于kmalloc,不僅分配連續空間,還會將內存初始化清零 131? 132?????//2、動態申請設備號 133?????key_dev->dev_major?=?register_chrdev(0,?"key_drv",?&key_fops); 134? 135?????//3、創建設備節點文件 136?????key_dev->cls?=?class_create(THIS_MODULE,?"key_cls"); 137?????key_dev->dev?=?device_create(key_dev->cls,?NULL,?MKDEV(key_dev->dev_major,?0),?NULL,?"key0"); 138? 139?????//4、硬件初始化?--?地址映射或中斷申請???? 140????? 141?????key_dev->reg_base?=?ioremap(GPXCON_REG,8); 142? 143?????key_dev->irqno?=?get_irqno_from_node(); 144????? 145?????ret?=?request_irq(key_dev->irqno,?key_irq_handler,?IRQF_TRIGGER_FALLING?|?IRQF_TRIGGER_RISING,? 146?????????"key3_eint10",?NULL); 147?????if(ret?!=?0) 148?????{ 149?????????printk("request_irq?errorn"); 150?????????return?ret; 151?????} 152? 153?????//a.?硬件如何獲取數據 154????? 155????? 156????? 157?????return?0; 158?} 159? 160?static?void?__exit?key_drv_exit(void) 161?{ 162?????iounmap(GPXCON_REG); 163?????free_irq(key_dev->irqno,?NULL);??//free_irq與request_irq的最后一個參數一致 164?????device_destroy(key_dev->cls,?MKDEV(key_dev->dev_major,?0)); 165?????class_destroy(key_dev->cls); 166?????unregister_chrdev(key_dev->dev_major,?"key_drv"); 167?????kfree(key_dev); 168?} 169? 170? 171? 172?module_init(key_drv_init); 173?module_exit(key_drv_exit); 174? 175?MODULE_LICENSE("GPL"); key_drv.c</asm></asm></linux></linux></linux></linux></linux></linux></linux></linux></linux></linux></linux>
key_drv.c
1?#include?<stdio.h> ?2?#include?<string.h> ?3?#include?<stdlib.h> ?4?#include?<unistd.h> ?5?#include?<sys> ?6?#include?<sys> ?7?#include?<fcntl.h> ?8? ?9? 10?#define?KEY_ENTER??28 11? 12?//0、設計一個描述按鍵的數據的對象 13?struct?key_event{ 14?????int?code;????//按鍵類型:home,esc,enter 15?????int?value;???//表狀態,按下,松開 16?}; 17? 18? 19?int?main(int?argc,?char?*argv[]) 20?{ 21?????struct?key_event?event; 22?????int?fd; 23?????fd?=?open("/dev/key0",?O_RDWR); 24?????if(fd?<span class="cnblogs_code_collapse">key_test.c</span></fcntl.h></sys></sys></unistd.h></stdlib.h></string.h></stdio.h>
1?ROOTFS_DIR?=?/home/linux/source/rootfs#根文件系統路徑 ?2? ?3?APP_NAME?=?key_test ?4?MODULE_NAME?=?key_drv ?5? ?6?CROSS_COMPILE?=?/home/linux/toolchains/gcc-4.6.4/bin/arm-none-linux-gnueabi- ?7?CC?=?$(CROSS_COMPILE)gcc ?8? ?9?ifeq?($(KERNELRELEASE),) 10? 11?KERNEL_DIR?=?/home/linux/kernel/linux-3.14-fs4412??????????#編譯過的內核源碼的路徑 12?CUR_DIR?=?$(shell?pwd)?????#當前路徑 13? 14?all: 15?????make?-C?$(KERNEL_DIR)?M=$(CUR_DIR)?modules??#把當前路徑編成modules 16?????$(CC)?$(APP_NAME).c?-o?$(APP_NAME) 17?????@#make?-C?進入到內核路徑 18?????@#M?指定當前路徑(模塊位置) 19? 20?clean: 21?????make?-C?$(KERNEL_DIR)?M=$(CUR_DIR)?clean 22? 23?install: 24?????sudo?cp?-raf?*.ko?$(APP_NAME)?$(ROOTFS_DIR)/drv_module?????#把當前的所有.ko文件考到根文件系統的drv_module目錄 25? 26?else 27? 28?obj-m?+=?$(MODULE_NAME).o????#指定內核要把哪個文件編譯成ko 29? 30?endif Makefile
Makefile
執行用戶程序,按下按鍵可以看到信息
? ? 退出用戶程序,按下按鍵,也會打印相應信息。
? 查看設備與中斷節點信息:
? 再看下CPU情況:
? 可以看到key_test應用程序占了很高的CPU,什么原因呢?
在應用程序中,是通過while循環,一直read內核的信息,當有按鍵中斷發生的時候,就會對key_event賦值,在while循環里判斷,進而打印出來,這樣在用戶空間與內核空間一直來回切換,一直read會十分消耗CPU資源。
解決思路:當有中斷發生時,才來調用read,沒有數據產生,跳出進程調度,進程休眠。
推薦學習:《linux視頻教程》