首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

RT-Signal 及其应用要点(2)

RT-Signal 及其应用要点(2)

驱动程序当设备有 I/O 事件发生,应有机制保证向登记的应用进程发送信号,显然设备驱动程序扮演了重要角色,实际终端 tty、网络 socket 等的驱动层标准实现已经包括了对实时信号驱动的支持,所以,在 Linux 应用程序中可以如上框架直接使用fcntl()配置。但有些设备的驱动程序还并没有支持此特征,对此,LDD [3] [4]都有描述(两个版本是一致的),能够提供一些重要的信息。以下两个 API 应该可以屏蔽所有相似操作(如send_sig()) 的标准接口:
1
2
3
      int fasync_helper(int fd, struct file *filp, int mode,
                  struct fasync_struct **fa);
void kill_fasync(struct fasync_struct **fa, int sig, int band);




为了支持异步通知机制,设备结构中需要有异步事件通知队列,它应该与睡眠队列类似,并且需要实现 fasync 方法(method)。当一个打开的文件 FASYNC 标志变化时,如fcntl(fd, F_SETFL, flags | FASYNC)操作,它将被调用将相应的进程登记到 async_queue 上去。
1
2
3
4
5
6
7
8
9
10
      struct my_dev{
  wait_queue_head_t in, out;
  ...
  struct fasync_struct *async_queue;
};
static int my_f_fasync(int fd, struct file *filp, int mode)
{
  struct my_dev *dev = filp->private_data;
  return fasync_helper(fd, filp, mode, &dev->async_queue);
}




但做异步通知登记的进程又是怎么能知道事件的发生呢?应该是在确切知道事件发生的地方,那本源应该就是中断服务程序,或相应的软中断中发出信号通知。
1
2
    if (dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);




如果是写操作,就是 POLL_OUT。不过,LDD 提醒大家在实现release方法(设备文件关闭)时,注意执行 fasync(),使得本文件的操作从上述的设备异步事件等待链表中剥离。
1
2
      /* remove this filp from the asynchronously notified filp's */
my_f_fasync(-1, filp, 0);




至此,一个支持 RT-Signal 的驱动框架也就具备了。不过,LDD 的读者请注意以下几点:
  • kill_async() 并没有发出用户指定的信号值,而是发出 SIGIO, 这是一个兼容传统的做法,没有问题,但并不是说,用户进程将得到 SIGIO,如果用户设置了希望的信号值,用户进程将得到它,如 SIGRTMIN + 2 而不是 SIGIO,下一节将解释为什么;
  • 上一点是至关重要的,因为不能理解这一点,用户进程就没能使用 fcntl(fd, F_SETSIG, signum)指定信号值,那缺省值,的确就是 SIGIO 了;
  • 此外, LDD 中的例子是一个管道设备,管道读的信号通知时机由写决定,这不具有一般性,而中断服务程序或相关软中断中显然是最常见的适用时机;
以上问题,实际从 LDD第二版  就存在, 因为 2.4 的内核已经完全具有以上机制, 但在第三版 [4] 中仍未更正,希望注意。
相关内幕 为了进一步弄清信号通知机制,首先,让我们看一下 fs/fcntl.c, fcntl(fd, F_SETSIG, signum)到底做了什么:
1
2
3
4
5
6
7
8
      case F_SETSIG:
/* arg == 0 restores default behaviour. */
if (arg < 0 || arg > _NSIG) {
  break;
}
err = 0;
filp->f_owner.signum = arg;
break;




显然,在文件描述属性中有专门的成员变量存贮设定的信号值。当执行 kill_fasync() 时,将向该设备 async_queue 链表中的所有使用 fasync_help() 登记的进程发送信号,注意这里的从入口得到的 sig(一般为SIGIO)只作为不是 SIGURG 的指示,真正向进程发送的信号将为f_owner.signum。
1
2
3
4
5
        /* 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);




send_sigio() 不仅只向进程发送一个信号, 还有相关的更多的信息(payload,siginfo_t),它的实现主如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    switch (fown->signum) {
  siginfo_t si;
  default:
    /* Queue a rt signal with the appropriate fd as its
    value.  We use SI_SIGIO as the source, not
    SI_KERNEL, since kernel signals always get
    delivered even if we can't queue.  Failure to
    queue in this case _should_ be reported; we fall
    back to SIGIO in that case. --sct */
    si.si_signo = fown->signum;
    si.si_errno = 0;
    si.si_code  = reason;
    /* Make sure we are called with one of the POLL_*
    reasons, otherwise we could leak kernel stack into
    userspace.  */
    if ((reason & __SI_MASK) != __SI_POLL)
      BUG();
    if (reason - POLL_IN >= NSIGPOLL)
      si.si_band  = ~0L;
    else
      si.si_band = band_table[reason - POLL_IN];
    si.si_fd    = fd;
    if (!send_sig_info(fown->signum, &si, p))
      break;
    /* fall-through: fall back on the old plain SIGIO signal */
  case 0:
    send_sig(SIGIO, p, 1);
}




如果发送(向信号队列插入)失败,将发出一个传统的信号 SIGIO 给进程(上面的应用进程对此异常登记了回调函数予以处理:)。 至于 send_sig_info() 如何将信号加到进程的信号队列中,很容易理解,可以去阅读相关的代码,实际 [7] [8]有很好的描述,其中关于信号的可靠/不可靠讨论令人印象深刻。
结论本文自顶而下又自底而上给出了用户程序和设备驱动支持 POSIX RT-Signal的要点,讨论了相关的系统内部,并纠正了一些现有的认识误区,希望彻底扫清理解和使用实时信号上的障碍。
返回列表