嵌入式Linux驅動開發(fā)(四)——字符設備驅動之中斷方式以及中斷方式獲取按鍵值

之前我們完成了關于通過查詢的方式獲取按鍵鍵值的驅動程序惭婿,可以參考:嵌入式Linux開發(fā)——裸板程序之中斷控制器
雖然讀取鍵值沒有什么問題,但是測試程序占用CPU過高进栽,一直在不斷的查詢克胳,資源消耗過大平绩,這個問題非常嚴重,我們必須要來解決一下漠另。但是在解決這個問題之前捏雌,我們先來思考一個問題,除了不斷的這樣 read笆搓,是不是還有其他的方法可以獲取按鍵的鍵值呢性湿?自然是有的,這個方式就是通過終端的方式來獲取鍵值满败。

如果舉一個例子肤频,小明在家等小華給他送東西,有什么方式不會錯過呢算墨?他可以不斷的去門口查看宵荒,其實這就是我們之前的方式,不斷的讓應用程序去read净嘀,這時候自然小明大量的精力都消耗在了不斷的查看過程中报咳,他想做別的事的時間和精力也會被不斷的查看而占用。
為了這個解決這個問題挖藏,方法很多暑刃,我們先來說其中一個普通的,就是小明家安裝一個門鈴熬苍,小華到了稍走,按下門鈴袁翁,這時候小明去開門就可以了柴底。這時候,小明在家做其他事粱胜,也就不會被打擾了柄驻,節(jié)省了大量的資源。

其實上面的故事很平常焙压,但是實際上鸿脓,第一種方式就是不斷查詢的方式,雖然不會錯過小華涯曲,但是消耗了大量資源野哭,一定是不可以采用的。而第二種方式有點類似于今天要說的——中斷模式幻件。

當發(fā)生了某件事拨黔,這件事作為一個中斷,處理完了這個中斷后绰沥,在恢復去執(zhí)行另外一件事篱蝇。

簡單總結一下:

  • 單片機程序處理中斷的過程
    1. 按鍵按下贺待,中斷發(fā)生
    2. 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

那么如何才能將自己的中斷處理加入到終端框架中

  1. 注冊中斷::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->startupdesc->chip->enable:使能中斷
  2. 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í)行結果:

執(zhí)行結果
后臺執(zhí)行琅关,占用資源的情況

以上已經實現(xiàn)了中斷程序的框架結構煮岁,主要用到的了request_irqfree_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í)行鲸阻,那么,這里通過阻塞的方式來獲取按鍵值缨睡,顯然不是我們希望所看到的鸟悴。想要知道如何解決,那么且聽下回分解宏蛉。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末遣臼,一起剝皮案震驚了整個濱河市性置,隨后出現(xiàn)的幾起案子拾并,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗅义,死亡現(xiàn)場離奇詭異屏歹,居然都是意外死亡,警方通過查閱死者的電腦和手機之碗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門蝙眶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人褪那,你說我怎么就攤上這事幽纷。” “怎么了博敬?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵友浸,是天一觀的道長。 經常有香客問我偏窝,道長收恢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任祭往,我火速辦了婚禮伦意,結果婚禮上,老公的妹妹穿的比我還像新娘硼补。我一直安慰自己驮肉,他們只是感情好,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布已骇。 她就那樣靜靜地躺著缆八,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疾捍。 梳的紋絲不亂的頭發(fā)上奈辰,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音乱豆,去河邊找鬼奖恰。 笑死,一個胖子當著我的面吹牛宛裕,可吹牛的內容都是我干的瑟啃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼揩尸,長吁一口氣:“原來是場噩夢啊……” “哼蛹屿!你這毒婦竟也來了?” 一聲冷哼從身側響起岩榆,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤错负,失蹤者是張志新(化名)和其女友劉穎坟瓢,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體犹撒,經...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡折联,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了识颊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诚镰。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖祥款,靈堂內的尸體忽然破棺而出清笨,到底是詐尸還是另有隱情,我是刑警寧澤刃跛,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布函筋,位于F島的核電站,受9級特大地震影響奠伪,放射性物質發(fā)生泄漏跌帐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一绊率、第九天 我趴在偏房一處隱蔽的房頂上張望谨敛。 院中可真熱鬧,春花似錦滤否、人聲如沸脸狸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炊甲。三九已至,卻和暖如春欲芹,著一層夾襖步出監(jiān)牢的瞬間卿啡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工菱父, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留颈娜,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓浙宜,卻偏偏與公主長得像官辽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子粟瞬,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內容