(3.3)一个按键所能涉及的:异步通知机制

/* AUTHOR: Pinus

* Creat on : 2018-10-24

* 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信号。

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值