I'll answer my own question after having done more research.
I recommend reading the 2001 USENIX paper "Kqueue: A generic and scalable event notification facility” at http://people.freebsd.org/~jlemon/papers/kqueue.pdf.
When kernel receives a kevent
from user, it creates a knote
to represent the subscription and a filter to deal with the event source. The filter is invoked on event source activity such as packet arrival or signal delivery. The knote
is added to an active list for event delivery to user if the filter indicates so and the knote
is enabled. This is enough to understand the difference between EV_ONESHOT
and EV_DISPATCH
.
EV_ONESHOT
causes the knote
and the filter to be deleted after first event delivery. Thus the filter can no longer be called on event source activity. EV_DISPATCH
causes the knote
to be marked disabled after first event delivery, but the filter will remain to process event source activity. Whether this is a meaningful difference depends on the filter being used. Additionally, enable/disable is a faster operation than add/delete. The USENIX paper has some graphs on the performance.
I wrote a program (tested on macOS) to demonstrate the difference using EVFILT_SIGNAL
. Maybe someone will find this useful in the future.
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
int main()
{
struct timespec timeout = {.tv_sec = 0, .tv_nsec = 0};
sigblock(sigmask(SIGUSR1));
pid_t pid = getpid();
int kq = kqueue();
struct kevent e;
EV_SET(&e, SIGUSR1, EVFILT_SIGNAL, EV_ADD | EV_ENABLE | EV_DISPATCH, 0, 0, NULL);
// EV_SET(&e, SIGUSR1, EVFILT_SIGNAL, EV_ADD | EV_ENABLE | EV_ONESHOT, 0, 0, NULL);
kevent(kq, &e, 1, NULL, 0, NULL);
kill(pid, SIGUSR1);
kevent(kq, NULL, 0, &e, 1, &timeout);
printf("%jd\n", e.data);
// When EV_DISPATCH is used, the filter continues to record
// event source activity. When EV_ONESHOT is used, there is no
// longer a filter to record activity. In either way, kernel
// doesn't deliver events to user.
kill(pid, SIGUSR1);
kill(pid, SIGUSR1);
kill(pid, SIGUSR1);
EV_SET(&e, SIGUSR1, EVFILT_SIGNAL, EV_ADD | EV_ENABLE, 0, 0, NULL);
kevent(kq, &e, 1, &e, 1, &timeout);
// Prints 3 or 0 with EV_DISPATCH or EV_ONESHOT, respectively.
printf("%jd\n", e.data);
close(kq);
}