(3.3)一個按鍵所能涉及的:異步通知機制

/* AUTHOR: Pinus

* Creat on : 2018-10-11

* KERNEL : linux-4.4.145

* BOARD : JZ2440(arm9 s3c2440)

* REFS : 韋東山視頻教程第二期

               linux異步通知機制 與 fcntl 函數使用詳解

               Linux驅動技術(四) _異步通知技術

             【Linux 驅動】異步通知機制

*/

概述

已有的方式:

1. 查詢:極度耗費資源

2. 中斷:可以沉睡但app會一直運行

3. poll:可以指定沉睡時間

共同點:都是應用程序主動去調用驅動

        異步通知機制全稱是"信號驅動的異步IO",就是一旦設備就緒,則驅動主動通知應用程序,應用程序根本就不需要輪詢設備,類似於硬件的中斷概念,一個進程收到一個信號與處理器收到一箇中斷請求可以說是一樣的。

        在linux中,異步通知是使用signal(信號)這裏使用的是信號"SIGIO"來實現的,而在linux,大概有30種信號,比如大家熟悉的ctrl+c的SIGINT信號,和常用進程間發信號 kill -9 PID 結束某一個進程。

1.應用層程序將自己註冊爲接收來自設備文件的SIGIO信號的進程

2.驅動實現相應的接口,以期具有向所有註冊接收這個設備驅動SIGIO信號的應用程序發SIGIO信號的能力。

3.驅動在適當的位置調用發送函數,應用程序即可接收到SIGIO信號,運行信號處理函數

實驗

目標: 按鍵按下時驅動程序通知應用程序

1. 應用程序:註冊信號,信號處理函數

在Linux命令行中使用 man signal 查找signal的用法
 

NAME

signal - ANSI C signal handling


SYNOPSIS

#include <signal.h>


typedef void (*sighandler_t)(int);


sighandler_t signal(int signum, sighandler_t handler);

        可見signal()函數指定信號(signum)的處理函數(handler),當程序接收到指定信號時調用處理函數,處理函數(handler)由自己編寫(有些信號系統有默認處理函數)。

man fcntl
 

NAME

fcntl - manipulate file descriptor


SYNOPSIS

#include <unistd.h>

#include <fcntl.h>


int fcntl(int fd, int cmd, ... /* arg */ );  //根據不同的cmd,和不同的參數進行不同的操作

fcntl(fd, F_SETOWN, getpid()); // 告訴內核,發給誰

/*

F_SETOWN

Set the process ID or process group ID that will receive SIGIO

and SIGURG signals for events on file descriptor fd to the ID

given in arg. A process ID is specified as a positive value; a

process group ID is specified as a negative value. Most com‐

monly, the calling process specifies itself as the owner (that

is, arg is specified as getpid(2)). */

Oflags = fcntl(fd, F_GETFL); //獲取文件標記

/*

F_GETFL (void)

Get the file access mode and the file status flags; arg is

ignored.  */

fcntl(fd, F_SETFL, Oflags | FASYNC); // 改變fasync標記,最終會調用到驅動的faync > fasync_helper:每當FASYNC標誌改變時,驅動程序中的fasync()得到執行。驅動中應該事先fasync()函數。

/* F_SETFL (int)

Set the file status flags to the value specified by arg.  還會去調用drv_fasync初始化異步通知鏈表  */

直接上應用程序:

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>

/*
*  fasync
*/
int fd;
void my_signal_func()
{
	unsigned char key_val;
	read(fd, &key_val, 1);
	printf("key_val: 0x%x\n",key_val);
}

int main(int argc, char **argv)
{
	int Oflags;
	
	signal(SIGIO, my_signal_func);
	
	fd = open("/dev/buttons", O_RDWR);
	if (fd < 0){
		printf("can't open dev nod !\n");
		return -1;
	}
		
	fcntl(fd, F_SETOWN, getpid());
	Oflags = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, Oflags | FASYNC);
	
	while(1){
		sleep(1000);
	}

	return 0;
}

2. 誰發: 驅動; 發給誰: app; 怎麼發: IRQ_handler中:

驅動大致仍然遵循前文案,這裏只列出更改部分的代碼

static struct fasync_struct *button_async;

static const struct file_operations jz2440_buttons_fops = {
	.owner 	 = 	THIS_MODULE,
	.read	 =	buttons_drv_read,  
	.open  	 = 	buttons_drv_open,
	.release =  buttons_drv_close,
	.poll	 =  buttons_drv_poll,
	.fasync  =  buttons_drv_fasync,     /* 新增信號函數 */
};

int buttons_drv_fasync (int fd, struct file *file, int on)
{
	printk("driver: button fasync\n");
	return fasync_helper(fd, file, on, &button_async);    /* 真正有用的 */
}
在按鍵中斷處理程序中發送信號
static irqreturn_t button_irq_handle(int irq, void *dev_id)
{
	struct intpin_desc * pindesc = (struct intpin_desc *)dev_id;
	unsigned int pinval;

	pinval = gpio_get_value(pindesc->pin);

	if (pinval){
		/*鬆開*/
		key_val = 0x80 | pindesc->key_val;
	}else{
		/*按下*/
		key_val = pindesc->key_val;
	}

	//ev_press = 1;
	//wake_up_interruptible(&button_waitq);   /*喚醒*/
	kill_fasync(&button_async, SIGIO, POLL_IN);    /* 信號發送程序 */
	
	return IRQ_HANDLED;
}

信號發送出去後,app便會執行信號處理函數,即讀取按鍵值

分析

1.signal函數,。。。布吉島,功能是綁定信號處理函數

2.fcntl

由以前的經驗也能猜出來了,

/fs/fcntl.c

SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)

|

do_fcntl(fd, cmd, arg, f.file);

這個函數好直觀,這裏只看用到的幾個命令

static long do_fcntl(int fd, unsigned int cmd, unsigned long arg, struct file *filp)
{
...
	switch (cmd) {
...
	case F_GETFL:
		err = filp->f_flags;
		break;
	case F_SETFL:
		err = setfl(fd, filp, arg);
		break;
..
	case F_SETOWN:
		f_setown(filp, arg, 1);
		err = 0;
		break;
...
	default:
		break;
	}
	return err;
}

第一個命令比較直觀,跳過

先來分析第三個f_setown()

f_setown(filp, arg, 1)

    __f_setown(filp, pid, type, force);

        f_modown(filp, pid, type, force);

static void f_modown(struct file *filp, struct pid *pid, enum pid_type type, int force)
{
	...
	if (force || !filp->f_owner.pid) {
		put_pid(filp->f_owner.pid);
		filp->f_owner.pid = get_pid(pid);  // 設置了程序進程pid
		filp->f_owner.pid_type = type;

		if (pid) {
			const struct cred *cred = current_cred();
			filp->f_owner.uid = cred->uid;
			filp->f_owner.euid = cred->euid;
		}
	}
...
}

再來第二個setfl(),這個調用了我們定義的驅動fasync函數

static int setfl(int fd, struct file * filp, unsigned long arg)
{
    ...
	/* 如果設置了FASYNC標誌
	 * ->fasync() is responsible for setting the FASYNC bit.
	 */
	if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op->fasync) {
		error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);
                        //前面接觸了這麼多內核驅動接口,看到filp->f_op->fasync,應該知道這是調用我們註冊的自定義fasync函數了,實際上是調用fasync_helper()
                // 感覺這纔是這個函數最大的意義,名字取的不好。。。
          ...
	}
    ...
        //修改flags
	filp->f_flags = (arg & SETFL_MASK) | (filp->f_flags & ~SETFL_MASK);
...
}

我們的底層驅動中幹了什麼?

buttons_drv_fasync

    fasync_helper(fd, file, on, &button_async);  // static struct fasync_struct *button_async;

/*
 * fasync_helper() is used by almost all character device drivers
 * to set up the fasync queue, and for regular files by the file
 * lease code. It returns negative on error, 0 if it did no changes
 * and positive if it added/deleted the entry.

   被絕大多數字符設備調用設置fasync(異步通知)隊列,錯誤返回負數,成功添加|刪除入口返回0

 */
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
	if (!on)
		return fasync_remove_entry(filp, fapp);   
	return fasync_add_entry(fd, filp, fapp);
}
/*
 * Add a fasync entry. Return negative on error, positive if
 * added, and zero if did nothing but change an existing one.
   添加一個異步通知入口,錯誤返回負數,添加返回正數,改變存在項返回0
 */
static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fapp)
{
...
	/*
	 * fasync_insert_entry() returns the old (update) entry if
	 * it existed.  
	 *
	 * So free the (unused) new entry and return 0 to let the
	 * caller know that we didn't add any new fasync entries.
           如果存在要添加的入口項,則釋放新分配的入口,返回0,讓用戶知道
	 */
	if (fasync_insert_entry(fd, filp, fapp, new)) {
		fasync_free(new);
		return 0;
	}
	return 1;
}

        綜上,當應用程序使用F_SETFL命令後,會調用驅動的drv_fasync()->fasync_helper(),經過一系列操作,將當前設備添加進內核異步通知鏈表。

以上所有準備工作都已經完成。

功能實現,是靠:

kill_fasync(&button_async, SIGIO, POLL_IN);

void kill_fasync(struct fasync_struct **fp, int sig, int band)
{
    ...
    kill_fasync_rcu(rcu_dereference(*fp), sig, band);
    ...
}

static void kill_fasync_rcu(struct fasync_struct *fa, int sig, int band)
{
	while (fa) {
		struct fown_struct *fown;
		unsigned long flags;
                ...
		if (fa->fa_file) {
                     /*  
                         struct fown_struct {
                             rwlock_t lock;          // protects pid, uid, euid fields 
                             int pid;        // pid or -pgrp where SIGIO should be sent 
                             uid_t uid, euid;    // uid/euid of process setting the owner 
                             void *security;
                             int signum;     // posix.1b rt signal to be delivered on IO 
                         };
                     */
			fown = &fa->fa_file->f_owner;  // fasync_struct ->fa_file->f_owner
     //通過異步對象的文件指針知道其屬主進程
        ...
				send_sigio(fown, fa->fa_fd, band);
		}
    ...
    	}
}

分析到此爲止send_sigio()發送信號的具體細節就不深究了

總結

        將進程“添加”到異步通知隊列中,實則是通過文件指針關聯對象,將對象添加到隊列中。定位進程則是反過來,根據文件指針查找,然後根據對應文件指針去找關聯的進程,所以有時候會出現一個文件指針可以找到多個進程(多個進程打開了該文件)。這種情況下,應用程序仍然必須藉助於poll/select來確定輸入的來源。

        從用戶程序的角度考慮:爲了啓動文件的異步通知機制,用戶程序必須執行兩個步驟。首先,她們指定一個進程作爲文件的“屬主(owner)”。當進程使用fcntl系統調用執行F_SETOWN命令時,屬主進程的進程ID號就被保存在filp->f_owner中。這一步是必需的,目的是爲了讓內核知道應該通知哪個進程;然後,爲了真正啓動異步通知機制,用戶程序還必須在設備中設置FASYNC標誌,這通過fcntl的F_SETFL命令完成的。 執行完這兩個步驟之後,輸入文件就可以在新數據到達時請求發送一個SIGIO信號。該信號被髮送到存放在filp->f_owner中的進程(如果是負值就是進程組)。

        從驅動程序角度考慮:F_SETOWN被調用時對filp->f_owner賦值,此外什麼也不做;在執行F_SETFL啓用FASYNC時,調用驅動程序的fasync方法。只要filp->f_flags中的FASYNC標識發生了變化,就會調用該方法,以便把這個變化通知驅動程序,使其能正確響應。文件打開時,FASYNC標誌被默認爲是清除的。當數據到達時,所有註冊爲異步通知的進程都會被髮送一個SIGIO信號。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章