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

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

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

背景著作 Unix Network Programming [9] I/O Models一节非常清晰地指出了应用中各种可能的 I/O 模型:
  • blocking I/O
  • non-blocking I/O
  • I/O multiplexing(select 和 poll)
  • signal driven I/O(SIGIO)
  • asynchronous I/O(POSIX aio_函数)
其中 signal driven 中的 signal 是指 Unix 信号,它有两个限制:
  • 大部分信号有专门用途,用户可定制使用的个数极少,主要是 SIGIO;
  • 信号没有附加信息,如果一个信号源有多种产生信号的原因,信号接收者无法确定究竟发生了什么。例如:对于 socket,I/O 完成意味着多种可能,对于 UDP socket 有两种可能,而 TCP socket 则有七种之多,这样应用程序收到 SIGIO,根本无从区分处理,甚至收到数据还是数据发出都不知道;
所以只能在特定情况下个别地应用这种机制。Unix Network Programming第三版 于 2003 年底出版,但 signal 驱动的机制并未被更多地讨论。实际上Linux 从 2.3 的内核起,已经引入了 POSIX RT-Signal(Real-Time Signal) 机制。 解决了传统信号的局限,不但数量足够多,而且每个信号还可以携带相应的必要信息(Payload),这样,基于 RT-signal 的 I/O 框架可以处理来自不同设备的不同事件。关于使用 RT-Signal 的详细描述似乎并不多见,其中developerWorks 中国网站有作者曾撰文上下两篇 介绍信号, 非常深入地介绍了应用程序中的信号编程,主要是从进程间通信角度出发,但并未涉及设备和 I/O。
I/O 与实时信号根据 I/O 模型,当一个设备的 I/O 完成,应用进程可以通过 RT-Signal 获得异步通知,进行处理,Windows 中称为 I/O Completion Port 。如果程序处理的信号根源来自于设备,驱动程序一定扮演了重要的角色,而内核的在应用和驱动之间的联系机制也十分关键,那究竟是什么呢?在经典著作 Linux Device Driver 中有一节"异步通知"描述了相关内容,但未提及 RT-Signal。而 2005 年 2 月出的 LDD 第三版 [4],该节内容未做改动,第二版的相关错误也就未能改正:
There is one remaining problem with input notification. When a process       receives a SIGIO, it doesn't know which input file has new input to       offer. If more than one file is enabled to asynchronously notify the       process of pending input, the application must still resort to poll       or select to find out what happened.
这种理解是错误的,在应用中相应的使用指导也就不是完整的,这恐怕与作者未引入 RT-Signal 有关,也导致应用中的使用很有局限性,下面将会详细讨论原因。本文的讨论也助于理解 AIO 机制,signal 也是其基础。
实时信号与应用框架 在应用程序中使用实时信号(Real-Time Signal),应该如何进行呢?假设,相关的设备支持这个机制,那么它们的初始设置应有以下几步:
1.        与使用其它设备一样,应首先打开设备,比如键盘是 /dev/tty,视频采集卡是 /dev/video0, 而对网络略有不同,打开一个 socket 作为设备文件句柄;
2.        设置异步通知的相关属性;
3.        很重要的是,为了实现多设备异步事件到达时的差异性,应为不同设备的事件设置对应的不同实时信号值,这个数字从 SIGRTMIN(32) 到SIGRTMAX(63),传统的信号 0-31 可以被认为非实时信号;
4.        注意,因为使用 F_SETSIG,在该程序文件一开始,必须做如下声明。
1
2
/* We need F_SETSIG */
  #define _GNU_SOURCE 1




对于不同设备,这个过程都为如下处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
      /* Open Deveice. socket() replaces open() if networking is involved */
if(-1 == (fd = open(devicename, O_RDONLY))){
  perror("device open");
  return -1;
}
/* Tell the OS that we should get a signal for a particular process'
   device handler */
if (-1 == fcntl(fd, F_SETOWN, getpid())) {
  perror("fcntl F_SETOWN");
  return -1;
}
/* Set the FD nonblocking */
flags = fcntl(fd, F_GETFL, 0);
if (-1 == fcntl(fd, F_SETFL, flags | O_NONBLOCK | FASYNC)) {
  perror("fcntl F_SETFL");
  return -1;
}
/* FD should raise signum from SIGRTMIN when an event happens */
if (-1 == fcntl(fd, F_SETSIG, signum)){
  perror("fcntl F_SETSIG");
  return -1;
}




一旦这些打开的设备启动后,如果有数据到达,比如视频采集卡获得一帧图像, 本应用程序进程将捕捉到相应的信号,之前如果使用 signal() 和 sigaction()注册了回调函数,则会被调用,介绍这方面使用的资料较多,请查阅。这里将利用信号支持队列的属性,程序框架使用 sigwaitinfo() 系统调用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
      /* The idea here is to eliminate the cost and complexity of
a jump into a signal handler. Instead we just dequeue signals
as they become available */
while (1) {
  /* This is a blocking call, we could use sigtimedinfo
  which takes a timeout value and then do other work
  in this loop, but we only have one FD. */
  if (sigwaitinfo(&blocked_sigs, &info) < 0) {
    if (errno != EINTR) {
      perror("sigwaitinfo");
      return 1;
    }
  }
  switch(handle_siginfo(&info))
  {
    case -1:
    return 1;
    case 0:
    return 0;
    default:
    break;
  }
}




其中,变量blocked_sigs 就是定义了一个信号集合,下面的代码就是保证这些信号不使用回调机制,而是呆在队列里,由应用程序调用 sigwaitinfo() 提取。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
      sigset_t blocked_sigs;
/* SIGIO shouldn't be queued since it represents a queue overflow */
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = sigio_handler;
if (sigaction(SIGIO, &sa, 0) == -1) {
  perror("sigaction SIGIO");
  return 1;
}        
/* Queue these signals */
sigemptyset(&blocked_sigs);
sigaddset(&blocked_sigs, INTSIG); //就是传统信号SIGINT
sigaddset(&blocked_sigs, TIMSIG); //定时器信号,SIGRTMIN + 1
sigaddset(&blocked_sigs, KEYSIG); //键盘,      SIGRTMIN + 2
sigaddset(&blocked_sigs, VIDSIG); //图像,      SIGRTMIN + 3
sigaddset(&blocked_sigs, UDPSIG); //socket,    SIGRTMIN + 4
sigprocmask(SIG_BLOCK, &blocked_sigs, &sa.sa_mask);




这里,将键盘、视频采集和 UDP 通讯对应的信号都放入该集合,还加入了中断信号(CTRL-C 产生的非实时信号)和定时器信号,所有该集合的信号统一使用上面的框架处理,进程的信号队列是一个优先级队列,信号值越小,将会插在队列的前面。如果队列溢出,会产生一个 SIGIO 信号,所以不要将它纳入到框架中,保证可以进行合适的异常处理。从进程信号队列中提取的信息,结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
      struct siginfo {
  int si_signo; //信号值
  int si_errno;
  int si_code;
  union {
    /* other members elided */
    struct {
      int _band; //
      int _fd;   //文件句柄
    } _sigpoll;
  } _sifields;
} siginfo_t;
struct pollfd {
  int fd;
  short events;
  short revents;
};




它不仅含有发生的信号值,还有其它信息,所以这些值可以用来区分不同的设备发生的不同的事件,据此做出相应的处理,显然这彻底解决了传统信号的根本问题。对于一帧新到达的图像,应用进程得到通知后就可以去访问相应的缓冲区,进行视频处理。对于图像这样的大量数据,应用进程常常通过 mmap() 映射共享核态驱动程序的缓冲区,实际这正是 AIO 的效果。
返回列表