1. 程式人生 > >字元裝置驅動------非同步通知 按鍵之使用非同步通知(詳解)

字元裝置驅動------非同步通知 按鍵之使用非同步通知(詳解)

引入:

按鍵驅動方式對比

  1. 查詢:一直讀,耗資源
  2. 中斷: 沒有超時機制,當沒有中斷,read函式一直休眠
  3. poll機制,加入超時機制

以上3種,都是讓應用程式主動去讀,本節我們學習非同步通知,它的作用就是當驅動層有資料時,主動告訴應用程式,然後應用程式再來讀, 這樣,應用程式就可以幹其它的事情,不必一直讀

比如:kill -9 pid ,其實就是通過發訊號殺死程序,kill發資料9給指定id號程序

 

程序間發訊號

使用 man signal檢視需要標頭檔案 #include <signal.h>測試程式如下如果用在gcc下編譯需要為sleep

函式新增標頭檔案#include <unistd.h>

例項函式如下arm-linux-gcc -o test test.c

 1 #include <stdio.h>
 2 #include <signal.h>
 3 #include <unistd.h>
 4 
 5  //typedef void (*sighandler_t)(int); 
 6 void my_signal_fun(int signum)
 7 {
 8     static int cnt=0;
 9     printf("signum=%d ,%d times\n
",signum,++cnt ); 10 11 } 12 13 int main(int argc, char const *argv[]) 14 { 15 signal(SIGUSR1,my_signal_fun); 16 while(1) 17 { 18 19 sleep(100); 20 } 21 return 0; 22 }

測試使用kill -USR1 pid後會列印訊號

 

目標

實現非同步通知:驅動程式使用訊號通知應用程式去讀取按鍵

要求:

  • 1.應用程式要實現,有:註冊訊號處理函式,使用signal函式
  • 2.誰來發?驅動來發
  • 3.發給誰?驅動發給應用程式,但應用程式必須告訴驅動PID,
  • 4.怎麼發?驅動程式呼叫kill_fasync函式

 

看下別人怎麼寫的:

搜尋下發送訊號的函式kill_fasync,尋找一個字元裝置是怎麼使用的,在drivers/char/rtc.c中有如下呼叫

 1 kill_fasync (&rtc_async_queue, SIGIO, POLL_IN);
 2 
 3 //結構體定義如下:
 4 static struct fasync_struct *rtc_async_queue;
 5 struct fasync_struct {
 6     int    magic;
 7     int    fa_fd;
 8     struct    fasync_struct    *fa_next; /* singly linked list */
 9     struct    file         *fa_file;
10 };

 

訊號的接受者被定義在fasync_struct中,搜尋下其初始化函式,發現函式rtc_fasync被呼叫如下形式,也就是類似於open、close的形式了

 1 static const struct file_operations rtc_fops = {
 2     .owner        = THIS_MODULE,
 3     .llseek        = no_llseek,
 4     .read        = rtc_read,
 5 #ifdef RTC_IRQ
 6     .poll        = rtc_poll,
 7 #endif
 8     .ioctl        = rtc_ioctl,
 9     .open        = rtc_open,
10     .release    = rtc_release,
11     .fasync        = rtc_fasync,
12 };

函式的原型如下,這個函式用來初始化結構體,應用程式會最終呼叫他告知驅動應該訊號給哪個pid

1 static int rtc_fasync (int fd, struct file *filp, int on)
2 {
3     return fasync_helper (fd, filp, on, &rtc_async_queue);
4 }

也就是是說應用程式通過fasync呼叫驅動具體的rtc_fasync,這個函式會設定fasync_struct這個結構體,這個結構體會被當做驅動傳送訊號函式的引數,也就是說應用程式告訴驅動程式其目標。

 

步驟:

1.先寫驅動程式,在之前的中斷程式上修改 

1.1定義 非同步訊號結構體 變數:
1 static struct fasync_struct * button_async;

 1.2在file_operations結構體新增成員.fasync函式,並寫函式

 1 static int fourth_fasync (int fd, struct file *file, int on)
 2 {
 3   return fasync_helper(fd, file, on, & button_async); //初始化button_async結構體,就能使用kill_fasync()了
 4 }
 5 
 6 static struct file_operations third_drv_fops={
 7          .owner = THIS_MODULE,
 8          .open = fourth_drv_open,
 9          .read = fourth _drv_read,
10        .release= fourth _drv_class,   
11       .poll = fourth _poll,
12       .fasync = fourth_fasync             //新增初始化非同步訊號函式
13 };

 

 3.3在buttons_irq中斷服務函式裡傳送訊號:

 1 static irqreturn_t buttons_irq(int irq, void *dev_id)
 2 {
 3 
 4     ......
 5     ev_press = 1;    /*表示中斷髮生了*/
 6     wake_up_interruptible(&button_waitq);    /*去佇列button_waitq裡面喚醒休眠的程序*/
 7 
 8     /*kill_fasync: 傳送訊號,發給誰?-->在&button_async中定義*/
 9     /*定義之後,要在fasync_helper中初始化,才可使用*/
10     kill_fasync (&button_async, SIGIO, POLL_IN);        /*SIGIO表有資料可供讀寫*/
11     return IRQ_HANDLED;
12 }

 驅動程式程式碼如下:

/*所需標頭檔案,參考其他類似的驅動*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <linux/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h> 


static struct class *forthdrv_class;
static struct class_device    *forthdrv_class_dev;

volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
volatile unsigned long *gpgcon = NULL;
volatile unsigned long *gpgdat = NULL;

// 定義一個名為`button_waitq`的佇列
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
static struct fasync_struct *button_async;    /*由結構fasync_stuct定義, */


/*中斷事件標誌,中斷服務程式將它置 1 ,forth_drv_read 將它清0 */
static volatile int ev_press = 0;
struct pin_desc{
    unsigned int pin;
    unsigned int key_val;
};

static unsigned char key_val;

 /*鍵值: 按下時,0x01, 0x02, 0x03, 0x04*/
 /*鍵值: 鬆開時,0x81, 0x82, 0x83, 0x84*/
struct pin_desc pins_desc[4] = {
   /*   pin      ,key_val */
    {s3c2410_GPF0, 0x01},
    {s3c2410_GPF2, 0x02},
    {s3c2410_GPG3, 0x03},
    {s3c2410_GPG11,0x04},
};



/*確定按鍵值*/
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
    /*在中斷處理函式中如何使用pins_desc[4] */
    struct pin_desc *pin = (struct pin_desc *)dev_id;
    unsigned int pinval;
    
    /*系統函式,可讀出單個引腳狀態值,高低電平*/
    pinval = s3c2410_gpio_getpin(pin_desc->pin);

    /*確定按鍵值*/
    if (pinval)
    {
        /* 為1--鬆開 */
        key_val = 0x80 | pin_desc->key_val;
        
    }
    else
    {
        /* 0--按下 */
        key_val = pin_desc->key_val;
    }
    ev_press = 1;    /*表示中斷髮生了*/
    wake_up_interruptible(&button_waitq);    /*去佇列button_waitq裡面喚醒休眠的程序*/

    /*kill_fasync: 傳送訊號,發給誰?-->在&button_async中定義*/
    /*定義之後,要在fasync_helper中初始化,才可使用*/
    kill_fasync (&button_async, SIGIO, POLL_IN);        /*SIGIO表有資料可供讀寫*/
    return IRQ_HANDLED;
}


static int forth_drv_open(struct inode *inode, struct file *file)
{
    //GPF0 GPF2  GPG3 GPG11,使用虛擬地址
    /*設定GPF0,2  GPG3,11為中斷引腳*/
    request_irq(IRQ_EINT0,  buttons_irq, IRQT_BOTHEDGE, "s2",  &pins_desc[0]);
    request_irq(IRQ_EINT2,  buttons_irq, IRQT_BOTHEDGE, "s3",  &pins_desc[1]);
    request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "s4",  &pins_desc[2]);
    request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "s5",  &pins_desc[3]);
    return 0; 
}

ssize_t forth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{    

    if(size != 1)
    return -EINVAL;

    /*如果沒有按鍵動作,ev_press = 0, 將程序掛在佇列裡面等待,休眠, 不會執行下面的函式*/
    wait_event_interruptible(button_waitq, ev_press);
    //誰來喚醒程序?-> 中斷髮生的時候,就喚醒程序
    
    /*/*如果有按鍵動作,ev_press = 1,會執行到此函式,並返回鍵值,將變數清零*/*/
    copy_to_user(buf, &key_val, 1);
    ev_press = 0return 1;
}

int forth_drv_close(struct inode *inode, struct file *file)
{
    free_irq(IRQ_EINT0,  &pins_desc[0]);
    free_irq(IRQ_EINT2,  &pins_desc[1]);
    free_irq(IRQ_EINT11, &pins_desc[2]);
    free_irq(IRQ_EINT19, &pins_desc[3]);
    return 0;
}

/*這個函式用來初始化結構體&button_async,應用程式會最終呼叫他告知驅動應該訊號給哪個pid*/
static int forth_drv_fasync (int fd, struct file *filp, int on)

{
    return fasync_helper (fd, filp, on, &button_async); /**/
}


static struct file_operations forth_drv_fops = {
    .owner   = THIS_MODULE,
    .open    = forth_drv_open,
    .read    = forth_drv_read,
    .release = forth_drv_close,
    .fasync  = forth_drv_fasync,
};

int major; /*定義全域性變數,儲存主裝置號*/

static int forth_drv_init(void)
{
    major = register_chrdev(0, "forth_dev", &forth_drv_fops);
    
    forthdrv_class = class_create(THIS_MODULE, "forthdrv");    /*先建立一個類class,再在class下面建立一個裝置(如下)*/
    forthdrv_class_dev = class_device_create(forthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/xyz */

    /*建立地址對映*/
    gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
    gpfdat = gpfcon + 1; 
    gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
    gpgdat = gpgcon + 1; 
    
    return 0;
}

static int forth_drv_exit(void)
{
    unregister_chrdev(major, "forth_dev");

    class_device_unregister(forthdrv_class_dev);    /*解除安裝裝置*/
    class_destroy(forthdrv_class);        /*銷燬建立的類*/
    /*解除地址對映*/
    iounmap(gpfcon);
    iounmap(gpfcon);
    
    return 0;
}

module_init(forth_drv_init);
module_exit(forth_drv_init);

MODULE_LICENSE("GPL");
forth_drv.c

 

2.寫應用測試程式

步驟如下:

1) signal(SIGIO, my_signal_fun);  

  呼叫signal函式,當接收到SIGIO訊號就進入my_signal_fun函式,讀取驅動層的資料

2) fcntl(fd,F_SETOWN,getpid());  

  指定程序做為fd檔案的”屬主”,核心收到F_SETOWN命令,就會設定pid(驅動無需處理),這樣fd驅動程式就知道發給哪個程序

3) oflags=fcntl(fd,F_GETFL);

  獲取fd的檔案狀態標誌

4) fcntl(fd,F_SETFL, oflags| FASYNC );

  新增FASYNC狀態標誌,會呼叫驅動中成員.fasync函式,執行fasync_helper()來初始化非同步訊號結構體

  這4個步驟執行後,一旦有驅動層有SIGIO訊號時,程序就會收到

應用層程式碼如下:

 1 #include <sys/types.h>    
 2 #include <sys/stat.h>    
 3 #include <stdio.h>
 4 #include <string.h>
 5 #include <poll.h>               
 6 #include <signal.h>
 7 #include <unistd.h>
 8 #include <fcntl.h>
 9 
10 int fd,ret;
11 void my_signal_fun(int signame)      //有訊號來了
12 {
13    read( fd, &ret, 1);              //讀取驅動層資料
14    printf("key_vale=0X%x\r\n",ret); 
15 
16 }
17  
18 /*useg:    fourthtext   */
19 int main(int argc,char **argv)
20 {
21   int oflag;
22   unsigned int val=0;      
23   fd=open("/dev/buttons",O_RDWR); 
24   if(fd<0)
25         {printf("can't open!!!\n");
26        return -1;}
27 
28    signal(SIGIO,my_signal_fun); //指定的訊號SIGIO與處理函式my_signal_run對應
29    fcntl( fd, F_SETOWN, getip());  //指定程序作為fd 的屬主,傳送pid給驅動
30    oflag=fcntl( fd, F_GETFL);   //獲取fd的檔案標誌狀態
31    fcntl( fd, F_SETFL, oflag|FASYNC);  //新增FASYNC狀態標誌,呼叫驅動層.fasync成員函式 
32 
33    while(1)
34    {
35          sleep(1000);                  //做其它的事情
36    }
37    return 0;
38 
39 }
forth_test.c

 

3.測試

可以發現開啟檔案的時候會有提示driver: drv_fasync,說明App會主動呼叫.fasync

 1 # insmod dri.ko
 2 # 後臺執行
 3 # ./test &
 4 # driver: drv_fasync
 5 #實際休眠狀態的
 6 
 7 # ps
 8   PID  Uid        VSZ Stat Command
 9   798 0          1308 S   ./test
10 ....
11 
12 # 按鍵按下正常列印
13 # irq55
14 key_val: 0x3
15 irq55
16 key_val: 0x83
17 irq18

 

參考:

字元裝置驅動(七)按鍵非同步通知

按鍵之使用非同步通知(詳解)