LINUX驱动之异步通信
文章目录
好久没有用CSDN来写笔记了,之前都用有道云笔记,但是markdown支持没那么好,所以试试CSDN写博客效果如何
简介
对于之前按键驱动程序,之前有三种处理方式
-
查询方式:
这种方式CPU使用效率非常高,显然是不可取的方法 -
中断方式:
这种方式还要结合让进程休眠、唤醒的处理方法,可以得到一个很高的处理效率 -
poll方式:
这种方式也要结合中断方式,它可以监测多个设备的某个事件的发生与否,如果发生被监测的事件,或者超过一定时间,进程都会被唤醒,被返回特定的值给用户程序,让用户程序自己选择处理方法。
这三种方式的用户程序都得不停的去读驱动程序的接口,是一种主动的方式,用户进程不能做其他的事情了,那现在的问题是是否存在一种方法,使得用户进程可以执行其他的工作,直到有事件(中断)发生的时候,才通知用户程序去处理呢?
有的,这种方法是存在的就是异步通知
简单的应用程序的例子
以下例子参考韦东山视频,
#include <stdio.h>
#include <signal.h>
void my_signal_fun( int signum ){
static int cnt;
printf("signal = %d, %d times\n",signum, ++cnt);/*signums信号编好,cnt记录进如该信号处理函数的次数*/
}
int main( int argc , char **argv ){
signal( SIGUSR1, my_signal_fun );
while(1){
/*可以做其他的工作,这里先让他休眠*/
sleep(1000);
}
return 0
}
解释:
signal( SIGUSER1, my_signal_fun );可以在shell 里面输入 man signal看到这个函数的一些
基本用法,
SIGNAL(2) Linux Programmer's Manual SIGNAL(2)
NAME
signal - ANSI C signal handling
SYNOPSIS
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
DESCRIPTION
The behavior of signal() varies across UNIX versions, and has also var‐
ied historically across different versions of Linux. Avoid its use:
use sigaction(2) instead. See Portability below.
signal() sets the disposition of the signal signum to handler, which is
either SIG_IGN, SIG_DFL, or the address of a programmer-defined func‐
tion (a "signal handler").
If the signal signum is delivered to the process, then one of the fol‐
lowing happens:
* If the disposition is set to SIG_IGN, then the signal is ignored.
* If the disposition is set to SIG_DFL, then the default action asso‐
ciated with the signal (see signal(7)) occurs.
* If the disposition is set to a function, then first either the dis‐
position is reset to SIG_DFL, or the signal is blocked (see Porta‐
bility below), and then handler is called with argument signum. If
invocation of the handler caused the signal to be blocked, then the
signal is unblocked upon return from the handler.
The signals SIGKILL and SIGSTOP cannot be caught or ignored.
RETURN VALUE
signal() returns the previous value of the signal handler, or SIG_ERR
on error.
这个函数的作用是设置当前进程接受到某个信号号所对应的信号的时候,对应的处理函数。
typedef void (*sighandler_t)(int); 这个语句是定义了一个返回值为void ,参数为int的
一个函数指针,
sighandler_t signal(int signum, sighandler_t handler);
这个是signal函数的原型,第一个参数是信号编号,第二个参数是对应的处理函数,
返回值的话正常执行是返回改处理函数的指针,错误情况会返回SIG_ERR。
编译这个程序并拷贝到开发板上,
在控制台上,ps查询进程号,然后
输入命令
kill -USR1 837
将会返回程序中的打印语句
我们从控制台向程序发送了一个SIGUSR1信号之后,程序会放下手动的主程序的流程,来执行预定义的那个信号处理函数中的程序。
这个机制好处在于在等待一个事件发生的时候,不需要阻塞式的等待,而是可以在程序执行主任务的时候,由另一个进程发出一个信号,让程序跳转到对应信号处理流程,处理完以后,再来执行主流程。这么一解释,感觉跟中断 的机制完全吻合。
按键程序异步通信流程的简单分析
我们要在驱动程序中,发送信号来让用户程序来进行处理。
整个流程很简单,如下:
- 按下按键的时候,驱动发出一个信号,通知应用程序
- 应用程序调入到注册的信号处理函数中,进行处理。
用户端要完成的事情:
- 定义一个信号处理函数
- 注册信号处理函数,让它与某个信号挂钩
- 告诉驱动,自己的进程号是多少,驱动 会往这个进程里发送信息
== 驱动端==要完成的事情:
- 获取用户程序的进程号
- 当中断发生的时候,向用户程序发送信号
用户层代码流程
具体是用哪些函数来实现的呢?首先聚焦到用户程序,前两点已经解决了,现在就是第三点需要解决,用户程序到底如何获取自己的进程号,并发送给驱动程序呢?先上代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
/* fifthdrvtest
*/
int fd;
void my_signal_fun(int signum)
{
unsigned char key_val;
read(fd, &key_val, 1);
printf("key_val: 0x%x\n", key_val);
}
int main(int argc, char **argv)
{
unsigned char key_val;
int ret;
int Oflags;
signal(SIGIO, my_signal_fun);
fd = open("/dev/buttons", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
fcntl(fd, F_SETOWN, getpid());
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC);
while (1)
{
sleep(1000);
}
return 0;
}
获取自己的PID,并发送给驱动程序的关键代码是
fcntl(fd, F_SETOWN, getpid());
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC);
在控制台输入man fcntl 可以看到fcntl函数的具体用法,这个函数的功能简单的说是处理文件描述符,功能比较多,现在就针对使用到的几个功能进行学习
那么现在就是要理解F_SETOWN, F_GETFL,以及FASYNC这几个标识的意义,
小发现:在man里面可以类似vi的输入查找命令/name,可以很快找到这几个标识符
-F_SETOWN(第三个参数为long):
这个标识将设置进程或者进程组号,用来接收由文件描述符fd里面某些事件发出的SIGIO或者SIGURG信号,进程号赋给arg参数上。进程id被指定为一个正值,进程组id被指定为一个负值。大部分情况下,指定自己就是owner
arg设置为getpid(2).那么可以知道
fcntl(fd, F_SETOWN, getpid());
这句话的意思是将当前的进程号,设置到已经打开的这个文件描述符里面去,为了当前进程能接受到SIGIO或者SIGURG信号。
-F_GETFL F_SETFL
fcntl函数可以改变已经打开的文件的性质,如果是F_GETFL,返回值就是一个文件的状态标志,
F_SETFL可以用来改变文件的状态标识,
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC);
这两句话是先读取一个文件状态,返回到Oflags上,然后Oflags加上一个FASYNC属性,再设置到文件中去,相当于是给文件添加了一个FASYNC的标识,FASYNC这个属性很特殊,这个应该是异步通知属性,一旦设置了这个属性,就会驱动程序中就会调用fsync这个函数,我的理解是用fcntl设置了FASYNC标识以后会发送一个异步通知给驱动层,让驱动层调用fsync这个函数。
驱动层的代码
主要分两块:
- fsync函数,上层向驱动发送了一个异步通知以后,最终会会调用,驱动层的fasync函数
static struct fasync_struct *button_async;
static int fifth_drv_fasync (int fd, struct file *filp, int on)
{
printk("driver: fifth_drv_fasync\n");
return fasync_helper (fd, filp, on, &button_async);
}
解释:先定义一个静态的fasync_struct结构体指针,实际上应该是一个队列头,然后调用fasync_helper函数,这个函数,在内核中定义,我简单看了看应该是首先初始化一个fasync_struct的结构,然后添加到队列里面去。
if (on) {
new->magic = FASYNC_MAGIC;
new->fa_file = filp;
new->fa_fd = fd;
new->fa_next = *fapp;
*fapp = new;
result = 1;
}
添加的队列项里面包含了进程ID信息,以及文件指针信息显然,发送信号的时候,要用到这个队列头。
fasync_struct结构体,然后
- 在中断发生的时候发送一个信号,发送一个SIGIO信息给用户进程
kill_fasync (&button_async, SIGIO, POLL_IN);
第一个参数是button_async,就是在fasync函数里面初始化的一个异步通知队列,里面包含了用户进程PID,
SIGIO,就是要发送的信号编号。可以看看内核里面的源码
void __kill_fasync(struct fasync_struct *fa, int sig, int band)
{
while (fa) {
struct fown_struct * fown;
if (fa->magic != FASYNC_MAGIC) {
printk(KERN_ERR "kill_fasync: bad magic number in "
"fasync_struct!\n");
return;
}
fown = &fa->fa_file->f_owner;
/* Don't send SIGURG to processes which have not set a
queued signum: SIGURG has its own default signalling
mechanism. */
if (!(sig == SIGURG && fown->signum == 0))
send_sigio(fown, fa->fa_fd, band);
fa = fa->fa_next;
}
}
可以看到kill_fasync会调用send_sigio来发送信号,发送的信号会遍历fasync队列的每一项,都进行发送,
我认为甚至跟很多个进程同时发送消息。
- 最后必须考虑关闭文件的时候,release函数里面的要释放资源,比如这个fasync队列,学习了一下,源码
fasync_helper函数就能够用来释放资源,只要on那个参数设置为0就行。