如何在 Linux 中的文件描述符上生成信号中断?

如何在 Linux 中的文件描述符上生成信号中断?

如何在 Linux 中的文件描述符上生成信号中断?

动机是在用户区生成中断,就像我们在微控制器中一样。我将拥有 I/O 文件描述符,并希望在其状态发生变化时生成中断?

有人可以告诉我吗?如果可能的话请给我例子。

答案1

Linux提供了两种监控文件系统事件的机制;dnotifyinotify

两者中较旧的一个dnotify是在内核版本 2.4.0 中引入的。它允许应用程序注册以通过以下方式接收有关目录更改的通知fcntl()界面。通知本身是通过信号传递的。该dnotify机制仅限于监视目录中的更改,它不允许监视单个文件。此外,它需要维护正在监视的目录的打开文件描述符。该dnotify机制在 2.6.13inotify引入时已被弃用。

新程序应该使用该inotify机制,它支持对目录和单个文件的监视。然而,它并不是基于信号。一个inotify实例与一个文件描述符相关联。可以从此文件描述符读取事件通知。

这两种机制的局限性在于没有选项可以递归地监视目录。这意味着必须为要监视的子树中的每个目录单独建立监视。


例子 (dnotify):

#define _GNU_SOURCE
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

/* For error handling */
#include <stdlib.h>
#include <errno.h>
#include <error.h>

static volatile int event_fd;

static void handler(int sig, siginfo_t *si, void *data)
{
    event_fd = si->si_fd;
}

int main(int argc, char *argv[])
{
    struct sigaction sa;
    int fd;

if(argc < 2)
    error(EXIT_FAILURE, 0, "missing argument");

    sa.sa_sigaction = handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGRTMIN + 1, &sa, NULL);

    if((fd = open(argv[1], O_RDONLY)) < 0)
        error(EXIT_FAILURE, errno, "failed to open '%s'", argv[1]);

    if(fcntl(fd, F_SETSIG, SIGRTMIN + 1) < 0)
         error(EXIT_FAILURE, errno, "failed to set dnotify signal");

    if(fcntl(fd, F_NOTIFY, DN_MODIFY|DN_CREATE|DN_DELETE|DN_MULTISHOT))
    error(EXIT_FAILURE, errno, 
              "failed to register notification for '%s'", argv[1]);

    while (1) {
        pause();
        printf("event occured for fd=%d\n", event_fd);
    }
}

解释:

fcntl(fd, F_SETSIG, SIGRTMIN + 1)

设置通知事件发生时发送的信号。值为零表示SIGIO(默认)已发送。任何其他值(包括SIGIO)都被解释为要发送的信号。在后一种情况下,信号处理程序接收一个siginfo_t结构作为其第二个参数,并且si_fd该结构的字段将包含生成事件的文件描述符。

如果使用实时信号 ( >= SIGRTMIN) 进行通知,则可以使用相同的信号编号(取决于可用内存)对多个 I/O 事件进行排队。应使用实时信号,尤其是在使用时DN_MULTISHOT

fcntl(fd, F_NOTIFY, DN_MODIFY|DN_CREATE|DN_DELETE|DN_MULTISHOT)

设置当 fd 引用的目录或其包含的任何文件发生更改时将引起通知的事件。可用的事件类型有:

  • DN_ACCESS 访问一个文件。
  • DN_MODIFY 文件被修改。
  • DN_CREATE 创建一个文件。
  • DN_DELETE 文件已取消链接。
  • DN_RENAME 文件在目录中被重命名。
  • DN_ATTRIB 文件的属性被更改。

通知通常是一次性的,即应用程序必须重新注册才能接收进一步的通知。如果DN_MULTISHOT指定,则通知将保持有效,直到明确删除为止。


例子 (inotify):

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/inotify.h>

/* For PATH_MAX */
#include <limits.h>

/* For error handling */
#include <errno.h>
#include <error.h>

int main(int argc, char *argv[]) {
    int fd, wd, len, i;
    char buf[sizeof(struct inotify_event) + PATH_MAX];

    if (argc < 2)
        error(EXIT_FAILURE, 0, "missing argument");

    if ((fd = inotify_init()) < 0)
        error(EXIT_FAILURE, errno, "failed to initialize inotify instance");

    for (i = 1; i < argc; i++) {
         if ((wd = inotify_add_watch (fd, argv[i], 
                                      IN_MODIFY | IN_CREATE | IN_DELETE)) < 0)
             error(EXIT_FAILURE, errno,
                   "failed to add inotify watch for '%s'", argv[i]);
    }

     while ((len = read(fd, buf, sizeof(buf))) > 0) {
         i = 0;
             while (i < len) {
                 struct inotify_event *ie = (struct inotify_event*) &buf[i];

                 printf("event occured for '%s': ", argv[ie->wd]);
                 if (ie->mask & IN_MODIFY)
                     printf("%s was modified\n", ie->len ? ie->name : "file");
                 else if (ie->mask & IN_CREATE)
                     printf("%s was created\n",  ie->name);
                 else if (ie->mask & IN_DELETE)
                     printf("%s was deleted\n",  ie->name);
                 else
                     printf("unexpected event\n");

                 i += sizeof(struct inotify_event) + ie->len;
             }
     }

    error(EXIT_FAILURE, len == 0 ? 0 : errno, "failed to read inotify event");
}

解释:

fd = inotify_init()

初始化一个新inotify实例。返回值是与新创建的事件队列关联的文件描述符inotify。默认情况下,文件描述符是阻塞的。

wd = inotify_add_watch (fd, argv[i], IN_MODIFY | IN_CREATE | IN_DELETE)

观察列表中的新项目(又名观察)添加有inotify_add_watch()。第三个参数是一个位掩码,用于指示inotify要监视的事件。可用的事件类型有:

  • IN_ACCESS 文件被访问。
  • IN_ATTRIB 文件的属性被更改。
  • IN_CLOSE_WRITE 为写入而打开的文件已关闭。
  • IN_CLOSE_NOWRITE 以只读方式打开的文件已关闭。
  • IN_CREATE 文件或目录在监视目录中创建。
  • IN_DELETE 文件或目录在监视目录中被删除。
  • IN_DELETE_SELF 正在监视的文件或目录被删除。
  • IN_MODIFY 文件已修改。
  • IN_MOVE_SELF 正在监视的文件或目录被移动。
  • IN_MOVED_FROM 文件已移出监视目录。
  • IN_MOVED_TO 文件被移动到监视目录中。
  • IN_OPEN 文件已打开。
  • IN_ALL_EVENT 上述所有的。
  • IN_MOVE 相当于IN_MOVED_TO|IN_MOVED_FROM
  • IN_CLOSE 相当于IN_CLOSE_WRITE|IN_CLOSE_NOWRITE

还可以在 的 mask 参数中设置以下选项inotify_add_watch()

  • IN_DONT_FOLLOW 不要遵循符号链接。
  • IN_EXCL_UNLINK 不要为曾经位于监视目录中的未链接文件生成事件。
  • IN_MASK_ADD 如果监视已存在,则累积添加监视事件。
  • IN_ONESHOT 发生一项事件后自动将手表从手表中移除。
  • IN_ONLYDIR 仅查看路径名(如果是目录)。

返回的值是与 指示的实例inotify_add_watch()中正在监视的文件系统对象关联的监视描述符。如果指定的对象已被监视,则返回现有监视的描述符。inotifyfd

while ((len = read(fd, buf, sizeof(buf))) > 0) {
    i = 0;
    while (i < len) {
        struct inotify_event *ie = (struct inotify_event*) &buf[i];
        /* ... */
        i += sizeof(struct inotify_event) + ie->len;
    }
}

read()与实例关联的文件描述符中的每个成功inotify都会返回一个或多个inotify_event具有以下字段的结构。

  • int wd 触发的监视的监视描述符。
  • uint32_t mask 触发手表的事件的掩码。
  • uint32_t cookie 关联相关事件的唯一 cookie。
  • uint32_t len 名称字段的大小。
  • char name[] 在监视目录中触发事件的文件的可选空终止名称。

除了与传递给 的事件类型相对应的位之外inotify_add_watch(),掩码字段还可以设置以下状态位:

  • IN_IGNORED 监视已被删除(通过inotify_rm_watch()、路径名取消链接等)。
  • IN_ISDIR 由目录触发的事件。
  • IN_Q_OVERFLOW事件队列溢出。另外wd设置为-1。
  • IN_UNMOUNT 包含监视路径名的文件系统已卸载。

inotify_event每个结构体的长度sizeof(inotify_event) + len取决于可变长度name字段。

在内核版本 2.6.21 之前,如果传递给的缓冲区read()太小而无法容纳下一个事件,则 read() 将返回 0。自 2.6.21 起,read()失败并errno设置为EINVAL

答案2

我认为不可能收到文件更改的信号。然而inotify,如图所示它的维基百科页面,应该允许您编写一个在文件更改时接收事件的程序。

这个网站有一篇关于 的好文章inotify。它还具有监视当前目录中事件的示例。

相关内容