linux之异步通知机制(二)

1  概念:

      异步通知机制:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,是一种“信号驱动的异步I/O”。

      信号是在软件层次上对中断机制的一种模拟,进程收到信号与处理器收到中断请求在原理上可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候会到达。

2   异步通知机制无怪乎四个方面:谁发,怎么发,发什么,发给谁。满足这四个方面也就完成了信号的异步通知。

以按键产生信号为例:驱动程序处理按键操作,用户端相应对应按键值。

谁发:异步通知一般是由驱动软件或者是硬件检测到事件发生,将事件以信号的方式发送出去,所以可认为由驱动发送信号

怎么发:1.用fcnt的F_SETOWN命令来确定用户端作为按键信号处理的“属主(owner)”,属主进程号通过getpid()获取并保存到filp->f_owner中,并被按键驱动获取到,2.还要设置 FASYNC标志,通过fcnt的 F_SETFL设置。通过这两步,驱动端检测到按键信号后发送一个SIGIO信号,通过filp->f_owner存储到id号,主动发送到用户端,用户端去响应。

发什么:发送的是SIGIO信号。

发给谁:驱动发送信号出去是为了通知用户进行相应,所以发给用户端

3   具体过程

用户端:

3.1  执行怎么发两步设置,将用户端进程ID号告诉驱动。注册  对应驱动端的fasync_helper

fcntl(fd, F_SETOWN, getpid());         //设置用户进程为fd文件的拥有者,没有这一步,内核不会知道应该将信号发给哪个进程

oflags = fcntl(fd, F_GETFL);           //获取设备文件的f_flags

fcntl(fd, F_SETFL, oflags | FASYNC);   //为了启用异步通知机制,还需对设备设置FASYNC标志

 

3.2  客户端声明一个信号signal(SIGIO, &handler);,其中handle对应信号处理函数,保存接收到信号后的处理方式。可以默认打印输出。

驱动端:

3.3  在驱动端的file_operations中添加fasync     

static const struct file_operations f_ops =

{

    .owner =    THIS_MODULE,

    ......

    .fasync =    my_fasync,

};

3.4  my_fasync中放入函数fasync_helper

//当一个用户端的FASYNC标志被修改时,调用驱动程序的fasync方法间接调用fasync_helper函数以便将当前进程加入到驱动程序的异步通知等待队列中。

static int  my_fasync(int fd, struct file * file, int on)

{

    err = fasync_helper(fd, file, on, &button);  //根据传过来的ID

    return 0;

}

//当设备可访问时,可使用kill_fasync函数发信号所有的相关进程。进程进而调用绑定的消息处理函数。

kill_fasync(&ctl->fasync, SIGIO, POLL_IN);

//分析fasync_helper的内部实现

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)

{

    struct fasync_struct *fa, **fp;

    struct fasync_struct *new = NULL;

    int result = 0;

 

 

    if (on) {

        new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);

        if (!new)

            return -ENOMEM;

    }

    write_lock_irq(&fasync_lock);

    //遍历整个异步通知队列,看是否存在对应的文件指针

    for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {

        if (fa->fa_file == filp) {//已存在

            if(on) {

                fa->fa_fd = fd;//文件描述符赋值 //注:不明白为什么这里只需要更新文件描述符,而不需要更新文件指针

                kmem_cache_free(fasync_cache, new);//销毁刚创建的对象

            } else {

                *fp = fa->fa_next;

                kmem_cache_free(fasync_cache, fa);//删除非目标对象 此用于应用程序屏蔽异步通知.

                result = 1;

            }

            goto out;

        }

    }

    //看到下面可以得知,所谓的把进程添加到异步通知队列中

    //实则是将文件指针关联到异步结构体对象,然后将该对象挂载在异步通知队列中(等待队列也是这个原理)

    //那么最后发送信号又是怎么知道是哪个进程的呢?我们看后面的kill_fasync函数。

    if (on) {//不存在

        new->magic = FASYNC_MAGIC;

        new->fa_file = filp;//指定文件指针

        new->fa_fd = fd;//指定文件描述符

        new->fa_next = *fapp;//挂载在异步通知队列中

        *fapp = new;//挂载

        result = 1;

    }

out:

    write_unlock_irq(&fasync_lock);

    return result;

}

//看看kill_fasync函数是怎么将信号通知指定进程的:

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;

    }

}

 

void kill_fasync(struct fasync_struct **fp, int sig, int band)

{

    /* First a quick test without locking: usually

     * the list is empty.

     */

    if (*fp) {

        read_lock(&fasync_lock);

        /* reread *fp after obtaining the lock */

        __kill_fasync(*fp, sig, band); 

        read_unlock(&fasync_lock);

    }

}

总结:用户端使用fcntl获取当前PID,并将PID和FANSYNC发送到驱动端(注册),驱动端收到后将其存入fasync_struct的结构链条中,按键触发后kill_fasync,通过其中的函数__kill_fasync遍历整个fasync_struct结构链条,找出对应PID,通过send_sigio将信号发送给用户端进程,用户端获取到主动上传的信号后跳转到与信号绑定的信号处理函数中,完成一次信号触发。

 

这里是上面64种信号的说明:

信号

起源

默认行为

含义

SIGHUP

POSIX

Term

控制终端挂起

SIGINT

ANSI

Term

键盘输入以终端进程(ctrl + C)

SIGQUIT

POSIX

Core

键盘输入使进程退出(Ctrl + \)

SIGILL

ANSI

Core

非法指令

SIGTRAP

POSIX

Core

断点陷阱,用于调试

SIGABRT

ANSI

Core

进程调用abort函数时生成该信号

SIGIOT

4.2BSD

Core

和SIGABRT相同

SIGBUS

4.2BSD

Core

总线错误,错误内存访问

SIGFPE

ANSI

Core

浮点异常

SIGKILL

POSIX

Term

终止一个进程。该信号不可被捕获或被忽略

SIGUSR1

POSIX

Term

用户自定义信号之一

SIGSEGV

ANSI

Core

非法内存段使用

SIGUSR2

POSIX

Term

用户自定义信号二

SIGPIPE

POSIX

Term

往读端关闭的管道或socket链接中写数据

SIGALRM

POSIX

Term

由alarm或settimer设置的实时闹钟超时引起

SIGTERM

ANSI

Term

终止进程。kill命令默认发生的信号就是SIGTERM

SIGSTKFLT

Linux

Term

早期的Linux使用该信号来报告数学协处理器栈错误

SIGCLD

System V

Ign

和SIGCHLD相同

SIGCHLD

POSIX

Ign

子进程状态发生变化(退出或暂停)

SIGCONT

POSIX

Cont

启动被暂停的进程(Ctrl+Q)。如果目标进程未处于暂停状态,则信号被忽略

SIGSTOP

POSIX

Stop

暂停进程(Ctrl+S)。该信号不可被捕捉或被忽略

SIGTSTP

POSIX

Stop

挂起进程(Ctrl+Z)

SIGTTIN

POSIX

Stop

后台进程试图从终端读取输入

SIGTTOU

POSIX

Stop

后台进程试图往终端输出内容

SIGURG

4.3 BSD

Ign

socket连接上接收到紧急数据

SIGXCPU

4.2 BSD

Core

进程的CPU使用时间超过其软限制

SIGXFSZ

4.2 BSD

Core

文件尺寸超过其软限制

SIGVTALRM

4.2 BSD

Term

与SIGALRM类似,不过它只统计本进程用户空间代码的运行时间

SIGPROF

4.2 BSD

Term

与SIGALRM 类似,它同时统计用户代码和内核的运行时间

SIGWINCH

4.3 BSD

Ign

终端窗口大小发生变化

SIGPOLL

System V

Term

与SIGIO类似

SIGIO

4.2 BSD

Term

IO就绪,比如socket上发生可读、可写事件。因为TCP服务器可触发SIGIO的条件很多,故而SIGIO无法在TCP服务器中用。SIGIO信号可用在UDP服务器中,但也很少见

SIGPWR

System V

Term

对于UPS的系统,当电池电量过低时,SIGPWR信号被触发

SIGSYS

POSIX

Core

非法系统调用

SIGUNUSED

 

Core

保留,通常和SIGSYS效果相同

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

怀想天空2011

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值