之前我們完成了關于通過查詢的方式獲取按鍵鍵值的驅動程序惭婿,可以參考:嵌入式Linux開發(fā)——裸板程序之中斷控制器。
雖然讀取鍵值沒有什么問題,但是測試程序占用CPU過高进栽,一直在不斷的查詢克胳,資源消耗過大平绩,這個問題非常嚴重,我們必須要來解決一下漠另。但是在解決這個問題之前捏雌,我們先來思考一個問題,除了不斷的這樣 read笆搓,是不是還有其他的方法可以獲取按鍵的鍵值呢性湿?自然是有的,這個方式就是通過終端的方式來獲取鍵值满败。
如果舉一個例子肤频,小明在家等小華給他送東西,有什么方式不會錯過呢算墨?他可以不斷的去門口查看宵荒,其實這就是我們之前的方式,不斷的讓應用程序去read净嘀,這時候自然小明大量的精力都消耗在了不斷的查看過程中报咳,他想做別的事的時間和精力也會被不斷的查看而占用。
為了這個解決這個問題挖藏,方法很多暑刃,我們先來說其中一個普通的,就是小明家安裝一個門鈴熬苍,小華到了稍走,按下門鈴袁翁,這時候小明去開門就可以了柴底。這時候,小明在家做其他事粱胜,也就不會被打擾了柄驻,節(jié)省了大量的資源。
其實上面的故事很平常焙压,但是實際上鸿脓,第一種方式就是不斷查詢的方式,雖然不會錯過小華涯曲,但是消耗了大量資源野哭,一定是不可以采用的。而第二種方式有點類似于今天要說的——中斷模式幻件。
當發(fā)生了某件事拨黔,這件事作為一個中斷,處理完了這個中斷后绰沥,在恢復去執(zhí)行另外一件事篱蝇。
簡單總結一下:
- 單片機程序處理中斷的過程
- 按鍵按下贺待,中斷發(fā)生
- CPU發(fā)生中斷,跳轉到異常向量入口進行執(zhí)
a. 保存被中斷的現(xiàn)場(寄存器的值)
b. 判斷中斷的來源零截,執(zhí)行中斷處理函數麸塞,清中斷
c. 恢復被中斷的現(xiàn)場,繼續(xù)執(zhí)行
那么對于Linux嵌入式來說涧衙,處理中斷是不是相同呢哪工?
對于Linux來說,處理流程也是相同的绍撞,肯定需要先設置異常向量正勒,不然都不知道該調用誰來處理。同樣也需要分辨 異常的來源傻铣,畢竟按下每個鍵都應該執(zhí)行不同的任務章贞。同樣,不能因為按下按鍵非洲,恢復以后之前的程序就不繼續(xù)執(zhí)行了(reset除外)鸭限。所以,裸板程序中處理異常事件的流程在Linux下都是成立的两踏,只是這些代碼都是由系統(tǒng)幫我們實現(xiàn)的败京,不需要我們自己來實現(xiàn)。那么我們就需要使用好梦染。那么赡麦,我們可以來思考一下,對于以上流程帕识,哪一個對于系統(tǒng)來說沒辦法幫我們代勞泛粹,必須我們自己來完成呢?那么這個過程對我們來說肯定是需要關注的了肮疗。
其實晶姊,不難想到,以上的處理流程里面伪货,分析中斷源们衙、中斷處理函數這些部分一定是系統(tǒng)沒辦法幫我們代勞的。不但這些內容和硬件相關性比較高碱呼,同時蒙挑,我們想要如何處理,也都不一定相同愚臀。
ARM架構下的Linux的異常處理原理如下圖:
- 按下按鍵
- CPU進入異常模式
b vector_irq + stubs_offset
忆蚀,vector_irq是使用宏來定義的。在這個宏中調用__irq_xxxx
的列表,其中會做對現(xiàn)場進行相應的保存的工作 - 最終會調用到
asm_do_IRQ
- 其中調用
desc_handle_irq(irq, desc)
- 調用
desc->handle_irq(irq, desc)
以中斷號為下標取出handle_irq蜓谋,在其中調用irq_desc[irq] -> handle_irq
梦皮,irq_desc
實際是一個結構體數組
- 其中調用
-
handle_irq 就等于
handle_edge_irq`- 其中主要做了
desc0>chip->ack(irq)
清中斷 - 調用
handle_IRQ_event
來處理中斷,- 取出action鏈表中的成員
- 執(zhí)行
action->handler
- 其中主要做了
那么如何才能將自己的中斷處理加入到終端框架中
- 注冊中斷::
request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char * devname, void * dev_id)
:
irq為中斷號桃焕,handler為處理函數剑肯,irqflags為上升沿觸發(fā)、下降沿觸發(fā)观堂、邊緣觸發(fā)等標志让网,devname為設備名,dev_id為設備號
實際背后做了以下工作:- 分配
irq_action
結構师痕,以上成員都指向傳入的參數 - 調用
setut_irq(irq, action)
溃睹,傳入中斷號和irqaction結構- 找到
irq_desc[irq]
,并在irq_desc[irq] -> action
鏈表中胰坟,加入之前構造的action
結構體 -
desc->chip->set_type(irq, new->flags & IRQF_TRIGGER_MASK)
:將對應的引腳設置為中斷引腳 -
desc->chip->startup
或desc->chip->enable
:使能中斷
- 找到
- 分配
-
free_irq(irq, dev_id)
:來解除中斷(出鏈因篇,禁止中斷)
說了這么多,還是老規(guī)矩笔横,先來看看一個按鍵的中斷框架是如何竞滓,然后我們再來考慮該如區(qū)分不同的中斷
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/irq.h>
static const char* dev_name = "first_eint";
static unsigned int major = 0;
static struct class* first_class;
static struct class_device* first_class_device;
//中斷的處理函數
static irqreturn_t irq_handler(int irq, void *dev_id)
{
printk("Interrupted!\n");
return 0;
}
static int first_open (struct inode *inode, struct file *file)
{
//int request_irq(unsigned int irq, irq_handler_t handler,
// unsigned long irqflags, const char *devname, void *dev_id)
//IRQ_EINT0:中斷號,定義在\include\asm-arm\arch\irqs.h中
// IRQ_TYPE_EDGE_BOTH:雙邊緣觸發(fā)吹缔,定義在\include\linux\irq.h中
request_irq(IRQ_EINT0, irq_handler, IRQ_TYPE_EDGE_BOTH, dev_name, major);
return 0;
}
static int first_release (struct inode *inode, struct file *file)
{
printk("release\n");
//void free_irq(unsigned int irq, void *dev_id)
free_irq(IRQ_EINT0, major);
return 0;
}
static struct file_operations first_fops =
{
.owner = THIS_MODULE,
.open = first_open,
.release= first_release,
};
static int __init first_init(void)
{
major = register_chrdev(major, dev_name, &first_fops);
first_class = class_create(THIS_MODULE, dev_name);
first_class_device = class_device_create(first_class, NULL, MKDEV(major, 0), NULL, dev_name);
return 0;
}
static void __exit first_exit(void)
{
unregister_chrdev(major, dev_name);
class_device_unregister(first_class_device);
class_destroy(first_class);
}
module_init(first_init);
module_exit(first_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ethan Lee <4128127@qq.com>");
這個驅動還是很簡單的商佑,主要用到了我們之前講的驅動框架,在open的時候會注冊中斷厢塘,使用了request_irq
茶没,在release中,調用了free_irq
來釋放了注冊的中斷晚碾。
Makefile
KERN_DIR=/code/LinuxDev/Lab/KernelOfLinux/linux-2.6.22.6 #內核目錄
all:
make -C $(KERN_DIR) M=`pwd` modules #M=`pwd`表示抓半,生成的目標放在pwd命令的目錄下 # -C代表使用目錄中的Makefile來進行編譯
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -f modules.order
obj-m += first.o #加載到module的編譯鏈中,內核會編譯生成出來ko文件迄薄,作為一個模塊
最后就是測試程序了
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
int main()
{
int cnt = 15;
int fd = open("/dev/first_eint", O_RDWR);
if(fd < 0)
{
printf("open error\n");
return -1;
}
while(cnt--){ //十五秒后如果自動結束程序
if(!(cnt % 5)) printf("count = %d\n", cnt);
sleep(1);
}
printf("time out\n");
close(fd);
return 0;
}
執(zhí)行結果:
以上已經實現(xiàn)了中斷程序的框架結構煮岁,主要用到的了request_irq
和free_irq
兩個函數讥蔽。只要設備文件被打開,當按鈕被按下画机,就會觸發(fā)中斷函數的調用冶伞。當時對于上面的代碼來說,還不夠完善步氏,因為它只能夠處理一個按鍵的信息响禽,我們希望開發(fā)板上的按鍵都可以注冊在驅動程序中統(tǒng)一管理。并且前端可以通過read函數來獲取鍵值。現(xiàn)在我們的程序來說芋类,雖然資源占用降低了隆嗅,但是實際測試程序并沒有進行read操作,僅僅是sleep侯繁。如果測試程序不斷的去讀取鍵值胖喳,那么,占用資源的情況依然很難得到緩解贮竟。
為了解決以上的問題丽焊,可以讓前端的程序不斷的read的驅動程序所提供的鍵值,那么咕别,這時候可以有這樣一個思路來解決技健。
read函數的大致調用流程如下:
當測試程序調用了read函數->通過sys_read等系統(tǒng)調用->調用驅動函數中的read函數。
我們采用一種阻塞式的方法來講驅動函數中的read函數進行阻塞處理惰拱,如果沒有發(fā)生中斷就讓程序睡眠雌贱。如果中斷發(fā)生,那么偿短,一定會調用中斷處理函數帽芽,在中斷處理函數中講程序喚醒。以此來解決測試程序read的時候占用資源較多的情況翔冀。既然測試程序已經可以read數據导街,那么,多個鍵值的問題其實也就引刃而解了纤子。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/irq.h>
#include <linux/poll.h>
static const char* dev_name = "third_eint";
static volatile unsigned int major = 0;
static struct class* third_class;
static struct class_device* third_class_device;
//定義一個結構體搬瑰,用來描述引腳的狀態(tài),方便講引腳的值管理起來
struct pin_desc
{
unsigned int pin; //引腳名稱
unsigned int value; //鍵值
};
static struct pin_desc btn_pins[4] =
{
{S3C2410_GPF0, 0x1},
//引腳名和預設的鍵值控硼,未被按下為1泽论,按下位0x81
//引腳名定義在:\include\asm\arch-s3c2410\regs-gpio.h
{S3C2410_GPF2, 0x2},
{S3C2410_GPG3, 0x3},
{S3C2410_GPG11, 0x4},
};
static int eints[4] = {IRQ_EINT0, IRQ_EINT3, IRQ_EINT9, IRQ_EINT11,};
unsigned int status = 0;//用于存放引腳值信息
unsigned int condition = 0; //用于描述是否需要睡眠,如果非零表示可以睡眠了卡乾,0表示不需要睡眠
unsigned char value = 0; //記錄鍵值的內容翼悴,以便判斷是否被按下
static DECLARE_WAIT_QUEUE_HEAD(wait_queue);
/*
這是一個宏,用來生成存放睡眠信息的結構體幔妨。定義在:\include\linux\wait.h頭文件中鹦赎。傳入的參數就是結構體的名稱,可以直接使用傳入的信息來獲取結構體误堡。
睡眠的原理:在內核中維護一個睡眠的數組古话,需要睡眠的程序就掛在這個數組中;如果需要喚醒锁施,內核會從這個數組里面將對應的結構體取出來陪踩,并喚醒
*/
/*
在中斷函數的框架中杖们,有一個非常重要的參數,就是void* dev_id
這個實際上是由request_irq時傳入的變量肩狂,最終傳遞給注冊的中斷處理函數
方便傳遞任何類型的信息摘完。
在這里我們傳遞的是關于引腳描述的信息
*/
static irqreturn_t irq_handler(int irq, void *dev_id)
{
struct pin_desc* desc = (struct pin_desc*) dev_id; //強制類型轉換
status = s3c2410_gpio_getpin(desc->pin); //根據我們發(fā)送的引腳名稱,從中獲取到引腳值傻谁,和我們之前按位獲取沒有什么區(qū)別描焰,只是一個系統(tǒng)封裝好的函數
if(status) //如果被按下了,那么將value變量改為0x8x栅螟,比如第一個按鍵未被按下就是0x81荆秦,被按下則是0x01。以此來判斷到底是哪個按鍵被按下了力图。
value = desc->value | 0x80;
else
value = desc->value;
wake_up_interruptible(&wait_queue); //從睡眠的隊列中喚醒
condition = 1; //中斷處理完畢步绸,表示可以進入睡眠狀態(tài),再次吃媒,read的時候瓤介,會將此變量傳遞給系統(tǒng)
return 0;
}
static ssize_t third_read (struct file *file, char __user *buff, size_t size, loff_t *ppos)
{
printk(".........read\n");
wait_event_interruptible(wait_queue, condition);//進入睡眠狀態(tài),阻塞于此
condition = 0;//此時值為0赘那,表示不需要再進入睡眠了
printk("read.........\n");
copy_to_user(buff, &value, 1);//將按鍵數據發(fā)送給用戶空間
return 0;
}
static int third_open (struct inode *inode, struct file *file)
{
int i = 0;//循環(huán)將所有的按鍵引腳都請求設置為中斷
for(; i < EINT_PIN_COUNT; ++i){
request_irq(eints[i], irq_handler, IRQT_BOTHEDGE, dev_name, &btn_pins[i]);
}
return 0;
}
static int third_release (struct inode *inode, struct file *file)
{
//void free_irq(unsigned int irq, void *dev_id)
int i = 0;
for(;i < EINT_PIN_COUNT; ++i){
free_irq(eints[i], &btn_pins[i]);
}
printk("button released\n");
return 0;
}
struct file_operations third_fops =
{
.owner = THIS_MODULE,
.open = third_open,
.read = third_read,
.poll = third_poll,
.release = third_release,
};
static int __init third_init(void)
{
major = register_chrdev(major, dev_name, &third_fops);
third_class = class_create(THIS_MODULE, dev_name);
third_class_device = class_device_create(third_class, NULL, MKDEV(major, 0), NULL, dev_name);
printk("init\n");
return 0;
}
static void __exit third_exit(void)
{
unregister_chrdev(major, dev_name);
class_device_unregister(third_class_device);
class_destroy(third_class);
printk("exit\n");
}
module_init(third_init);
module_exit(third_exit);
MODULE_AUTHOR("Ethan Lee <4128127@qq.com>");
MODULE_LICENSE("GPL");
測試代碼:
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
int main()
{
int fd = open("/dev/third_eint", O_RDWR);
if(fd < 0)
{
printf("open error\n");
return -1;
}
while(1){
unsigned char value = 0;
read(fd, &value, 1);
printf("0x%02x\n", value);
}
return 0;
}
測試結果:
根據以上的測試結果可以看出,可以通過中斷來正確的識別不同按鍵的鍵值募舟。同時genuine打印信息可以看出wait_event_interruptible(wait_queue, condition);
實際的阻塞點位于此處祠斧。
目前,測試程序為死循環(huán)read拱礁,但實際資源占用率并不高琢锋,解決了我們之前所提出的問題。
但是是不是就完美解決了呢呢灶?實際并沒有吴超,比如,測試程序不希望被阻塞在這里鸯乃,而有其他的邏輯需要執(zhí)行鲸阻,那么,這里通過阻塞的方式來獲取按鍵值缨睡,顯然不是我們希望所看到的鸟悴。想要知道如何解決,那么且聽下回分解宏蛉。