如何在 Linux 中的文件描述符上生成信号中断?
动机是在用户区生成中断,就像我们在微控制器中一样。我将拥有 I/O 文件描述符,并希望在其状态发生变化时生成中断?
有人可以告诉我吗?如果可能的话请给我例子。
答案1
Linux提供了两种监控文件系统事件的机制;dnotify
和inotify
。
两者中较旧的一个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()
中正在监视的文件系统对象关联的监视描述符。如果指定的对象已被监视,则返回现有监视的描述符。inotify
fd
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
。