异步通知

生活例子:买奶茶

  • 在旁边等着,眼睛盯着店员,生怕别人插队,他一做好你就知道:你是主动等待他做好,这叫“同步”。
  • 付钱后就去玩手机了,店员做好后他会打电话告诉你:你是被动获得结果,这叫“异步”。
  • 使用流程

    驱动程序怎么通知 APP: 发信号,这只有 3 个字,却可以引发很多问题:

    1. 谁发:驱动程序发。
    2. 发什么:信号。
    3. 发什么信号:SIGIO
    4. 怎么发:内核里提供有函数
    5. 发给谁:APP,APP 要把自己告诉驱动
    6. APP 收到后做什么:执行信号处理函数
    7. 信号处理函数和信号,之间怎么挂钩: APP 注册信号处理函数

    Linux 系统中也有很多信号,在 Linux 内核源文件 include\uapi\asmgeneric\signal.h 中,有很多信号的宏定义

    #define SIGHUP		 1
    #define SIGINT		 2
    #define SIGQUIT		 3
    #define SIGILL		 4
    #define SIGTRAP		 5
    #define SIGABRT		 6
    #define SIGIOT		 6
    #define SIGBUS		 7
    #define SIGFPE		 8
    #define SIGKILL		 9
    #define SIGUSR1		10
    #define SIGSEGV		11
    #define SIGUSR2		12
    #define SIGPIPE		13
    #define SIGALRM		14
    #define SIGTERM		15
    #define SIGSTKFLT	16
    #define SIGCHLD		17
    #define SIGCONT		18
    #define SIGSTOP		19
    #define SIGTSTP		20
    #define SIGTTIN		21
    #define SIGTTOU		22
    #define SIGURG		23
    #define SIGXCPU		24
    #define SIGXFSZ		25
    #define SIGVTALRM	26
    #define SIGPROF		27
    #define SIGWINCH	28
    #define SIGIO		29
    #define SIGPOLL		SIGIO
    /*
    #define SIGLOST		29
    */
    #define SIGPWR		30
    #define SIGSYS		31
    #define	SIGUNUSED	31
     
    /* These should not be considered constants from userland.  */
    #define SIGRTMIN	32
    #ifndef SIGRTMAX
    #define SIGRTMAX	_NSIG
    #endif
    


    就 APP 而言,你想处理 SIGIO 信息,那么需要提供信号处理函数,并且要跟 SIGIO 挂钩。这可以通过一个 signal 函数来“给某个信号注册处理函数”,用法如下:

    #include <signal.h>
     
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);
    


    APP 还要做什么事?想想这几个问题:

  • 内核里有那么多驱动,你想让哪一个驱动给你发 SIGIO 信号?
    APP 要打开驱动程序的设备节点。
  • 驱动程序怎么知道要发信号给你而不是别人?
    APP 要把自己的进程 ID 告诉驱动程序。
  • APP 有时候想收到信号,有时候又不想收到信号:
    应该可以把 APP 的意愿告诉驱动。
  • 驱动程序要做什么?发信号。

  • APP 设置进程 ID 时,驱动程序要记录下进程 ID;
  • APP 还要使能驱动程序的异步通知功能,驱动中有对应的函数:
  • APP 打开驱动程序时,内核会创建对应的 file 结构体, file 中有 f_flags;f_flags 中有一个 FASYNC 位,它被设置为 1 时表示使能异步通知功能。当 f_flags 中的 FASYNC 位发生变化时,驱动程序的 fasync 函数被调用。
  • 发生中断时,有数据时,驱动程序调用内核辅助函数发信号。这个辅助函数名为 kill_fasync。
  • 综上所述,使用异步通知,也就是使用信号的流程如下图所示:

    应用编程

    重点从2开始
    ② 注册信号处理函数:APP 给 SIGIO 这个信号注册信号处理函数 func,以后 APP 收到 SIGIO信号时,这个函数会被自动调用;
    ③ 把APP的PID(进程ID)告诉驱动程序,这个调用不涉及驱动程序,在内核的文件系统层次记录 PID;
    ④ 读取驱动程序文件 Flag;
    ⑤ 设置Flag里面的FASYNC位为1:当FASYNC位发生变化时,会导致驱动程序的fasync被调用;
    ⑥⑦ 调用faync_helper,它会根据FAYSNC的值决定是否设置button_async->fa_file=驱动文件 filp:驱动文件 filp 结构体里面含有之前设置的 PID;
    ⑧ APP 可以做其他事;
    ⑨⑩按下按键,发生中断,驱动程序的中断服务程序被调用,里面调用kill_fasync 发信号;
    ⑪⑫⑬ APP 收到信号后,它的信号处理函数被自动调用,可以在里面调用 read 函数读取按键。

    总结

    应用层需要做的:

    1. 写信号处理函数
    static void sig_func(int sig)
    {
    	in val;
    	read(fd, &val, 4);
    	printf("get button: 0x%x\n", val);
    }
    
    1. 注册信号处理函数
    // 3注册信号处理函数
    signal(SIGIO, sig_func);
    
    1. 打开文件
    fd = open(argv[1], O_RDWR);
    
    1. 把APP的pid告诉驱动程序;
    fcntl(fd ,F_SETOWN, getpid());
    
    1. 读取驱动程序flag并设置FASYNC位为1
    flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | FASYNC);
    

    驱动编程

    驱动层需要做的:

    1. 提供对应的fasync函数,并调用fasync_helper函数;
    struct fasync_struct *button_fasync;
    static int gpio_key_drv_fasync(int fd, struct file *file, int on)
    {
    	if (fasync_helper(fd, file, on, &button_fasync) >= 0)
    	{
    		return 0;
    	}
    	else
    	{
    		return -EIO;
    	}
    }
     
     
    // 定义自己的file_operations结构体
    static struct file_operations gpio_key_drv = {
    	.owner = THIS_MODULE,
    	.read = gpio_key_drv_read,
    	.poll = gpio_key_drv_poll,
    	.fasync = gpio_key_drv_fasync,
    };
    

    fasync_helper 函数会分配、构造一个fasync_struct结构体button_async:
    驱动文件的 flag 被设置为 FAYNC 时:

    button_async->fa_file = filp; // filp 表示驱动程序文件,里面含有之前设置的 PID

    驱动文件被设置为非 FASYNC 时:

    button_async->fa_file = NULL;

    以后想发送信号时,使用 button_async 作为参数就可以,它里面“可能”含有 PID。
    什么时候发信号呢?在本例中,在 GPIO 中断服务程序中发信号。

    1. 中断处理函数中使用kill fasync发送信号。

    kill_fasync(&button_fasync, SIGIO, POLL_IN);

  • 第 1 个参数: button_async->fa_file 非空时,可以从中得到 PID,表示发给哪一个 APP;
  • 第 2 个参数表示发什么信号: SIGIO;
  • 第 3 个参数表示为什么发信号: POLL_IN,有数据可以读了。 (APP 用不到这个参数)
  • 编译

    测试

    把编译出来的设备树文件拷贝到/boot目录下

    reboot重启
    安装驱动,强制安装

    insmod -f gpio_key_drv.ko


    执行测试程序

    ./button_test /dev/100ask_gpio_key

    作者:执念、坚持

    物联沃分享整理
    物联沃-IOTWORD物联网 » 异步通知机制详解

    发表回复