KQUEUE(2)
π KQUEUE(2) π β the kqueue, kevent situation, explained by your fave Unix bestie
okay so this is a page from the System Calls Manual, and it's literally the whole entire lore of the kernel event notification mechanism. grab a snack, bestie, because this one is a bop. πβ¨
πΈ NAME πΈ
kqueue, kevent β the kernel event notification mechanism. it's giving "the kernel tells you when stuff happens". no cap. π
π LIBRARY π
Standard C Library (libc, -lc) β yes she lives in libc, as she should. π
β¨ SYNOPSIS β¨
#include <sys/event.h>
int
kqueue(void);
int
kevent(int kq, const struct kevent *changelist, int nchanges,
struct kevent *eventlist, int nevents,
const struct timespec *timeout);
EV_SET(kev, ident, filter, flags, fflags, data, udata);
look at her. three whole entry points. iconic behaviour fr fr. π
π¦ DESCRIPTION π¦
so kqueue() is a generic way of telling your program "hey bestie, an event happened, or a condition is holding rn", and it does this based on the results of tiny little pieces of kernel code called filters. a kevent is identified by the (ident, filter) pair β and lowkey important: there may only be one unique kevent per kqueue. one. that's the rule. π
The filter gets executed the moment you initially register a kevent, specifically to detect whether a preexisting condition is present, and it also runs whenever an event gets passed to the filter for evaluation. if the filter decides the condition should be reported, then boom β the kevent gets placed on the kqueue for the user to retrieve. slay. π«Ά
The filter also runs when the user tries to retrieve the kevent from the kqueue. and if the filter says "nah, the condition that triggered this event no longer holds", then the kevent gets removed from the kqueue and is not returned. it's giving self-cleaning. β¨
Multiple events that trigger the filter do not result in multiple kevents being placed on the kqueue β instead, the filter aggregates them into a single struct kevent. (efficiency queen π
) Also: calling close() on a file descriptor will remove any kevents that reference that descriptor. automatic. we love a tidy system.
The kqueue() system call creates a brand new kernel event queue and returns a descriptor. Important lore: the queue is not inherited by a child created with fork(2). However, if rfork(2) is called without the RFFDG flag, then the descriptor table is shared, which will allow sharing of the kqueue between two processes. sharing is caring ig. π
The kevent() system call is what you use to register events with the queue, and to return any pending events back to the user. here's the breakdown, bestie:
changelistis a pointer to an array of kevent structures, as defined in<sys/event.h>. All changes contained in thechangelistare applied before any pending events are read from the queue.nchangesgives the size ofchangelist.eventlistis a pointer to an array of kevent structures.neventsdetermines the size ofeventlist.
π¨ Whenneventsis zero,kevent()will return immediately even if there is a timeout specified β unlikeselect(2). noted. different vibe entirely.
If timeout is a non-NULL pointer, it specifies a maximum interval to wait for an event, which will be interpreted as a struct timespec. If timeout is a NULL pointer, kevent() waits indefinitely (patient king π). To effect a poll, the timeout argument should be non-NULL, pointing to a zero-valued timespec structure. and yes β the same array may be used for both the changelist and the eventlist. recycle era. β»οΈ
The EV_SET() macro is provided for ease of initializing a kevent structure. thank you for your service. π«‘
π§ The kevent structure π§
The kevent structure is defined as:
struct kevent {
uintptr_t ident; /* identifier for this event */
short filter; /* filter for event */
u_short flags; /* action flags for kqueue */
u_int fflags; /* filter flag value */
int64_t data; /* filter data value */
void *udata; /* opaque user data identifier */
uint64_t ext[4]; /* extensions */
};
The fields of struct kevent are:
identβ Value used to identify this event. The exact interpretation is determined by the attached filter, but often is a file descriptor. classic.filterβ Identifies the kernel filter used to process this event. The pre-defined system filters are described below. iykyk.flagsβ Actions to perform on the event.fflagsβ Filter-specific flags.dataβ Filter-specific data value.udataβ Opaque user-defined value passed through the kernel unchanged. the kernel does not touch it, she simply passes it along. π¦extβ Extended data passed to and from kernel. Theext[0]andext[1]members use is defined by the filter. If the filter does not use them, the members are copied unchanged. Theext[2]andext[3]members are always passed through the kernel as-is, making additional context available to application.
π The flags field π
The flags field can contain the following values:
EV_ADDβ Adds the event to the kqueue. Re-adding an existing event will modify the parameters of the original event, and not result in a duplicate entry. Adding an event automatically enables it, unless overridden by theEV_DISABLEflag. one of a kind, no dupes. πEV_ENABLEβ Permitkevent()to return the event if it is triggered.EV_DISABLEβ Disable the event sokevent()will not return it. The filter itself is not disabled. important distinction bestie, don't get it twisted.EV_DISPATCHβ Disable the event source immediately after delivery of an event. SeeEV_DISABLEabove.EV_DELETEβ Removes the event from the kqueue. Events which are attached to file descriptors are automatically deleted on the last close of the descriptor. she cleans up after herself. π§ΉEV_RECEIPTβ This flag is useful for making bulk changes to a kqueue without draining any pending events. When passed as input, it forcesEV_ERRORto always be returned. When a filter is successfully added thedatafield will be zero. Note that if this flag is encountered and there is no remaining space ineventlistto hold theEV_ERRORevent, then subsequent changes will not get processed. β οΈ read that twice.EV_ONESHOTβ Causes the event to return only the first occurrence of the filter being triggered. After the user retrieves the event from the kqueue, it is deleted. one and done. π―EV_CLEARβ After the event is retrieved by the user, its state is reset. This is useful for filters which report state transitions instead of the current state. Note that some filters may automatically set this flag internally. sneaky but valid.EV_EOFβ Filters may set this flag to indicate filter-specific EOF condition.EV_ERRORβ See "RETURN VALUES" below. (we'll get there bestie, patience π«Ά)
π The predefined system filters π
The predefined system filters are listed below. Arguments may be passed to and from the filter via the fflags and data fields in the kevent structure.
π₯ EVFILT_READ π₯
Takes a descriptor as the identifier, and returns whenever there is data available to read. The behavior of the filter is slightly different depending on the descriptor type. it's giving "depends on the context". π
Sockets
Sockets which have previously been passed to listen() return when there is an incoming connection pending. data contains the size of the listen backlog.
Other socket descriptors return when there is data to be read, subject to the SO_RCVLOWAT value of the socket buffer. This may be overridden with a per-filter low water mark at the time the filter is added by setting the NOTE_LOWAT flag in fflags, and specifying the new low water mark in data. On return, data contains the number of bytes of protocol data available to read.
If the read direction of the socket has shutdown, then the filter also sets EV_EOF in flags, and returns the socket error (if any) in fflags. It is possible for EOF to be returned (indicating the connection is gone) while there is still data pending in the socket buffer. π plot twist but valid.
Vnodes
Returns when the file pointer is not at the end of file. data contains the offset from current position to end of file, and may be negative. (yes, negative. she can go there.)
This behavior is different from poll(2), where read events are triggered for regular files unconditionally. This event can be triggered unconditionally by setting the NOTE_FILE_POLL flag in fflags. noted for the record. π
Fifos, Pipes
Returns when the there is data to read; data contains the number of bytes available.
When the last writer disconnects, the filter will set EV_EOF in flags. This will be cleared by the filter when a new writer connects, at which point the filter will resume waiting for data to become available before returning. she's flexible like that. πΈ
BPF devices
Returns when the BPF buffer is full, the BPF timeout has expired, or when the BPF has "immediate mode" enabled and there is any data to read; data contains the number of bytes available.
π EVFILT_WRITE π
Takes a descriptor as the identifier, and returns whenever it is possible to write to the descriptor. For sockets, pipes and fifos, data will contain the amount of space remaining in the write buffer. The filter will set EV_EOF when the reader disconnects, and for the fifo case, this will be cleared when a new reader connects. Note that this filter is not supported for vnodes or BPF devices. π«
For sockets, the low water mark and socket error handling is identical to the EVFILT_READ case.
π EVFILT_EMPTY π
Takes a descriptor as the identifier, and returns whenever there is no remaining data in the write buffer. it's giving "the void is calling". πβ¨
π§΅ EVFILT_AIO π§΅
Events for this filter are not registered with kevent() directly but are registered via the aio_sigevent member of an asynchronous I/O request when it is scheduled via an asynchronous I/O system call such as aio_read(). The filter returns under the same conditions as aio_error(). For more details on this filter see sigevent(3) and aio(4). side quest unlocked. πΊοΈ
EVFILT_VNODEβ bestie, you hand it a file descriptor as the identifier and the events you wanna stalk infflags, and it comes back at you the second one (or more) of those events goes down on that descriptor. The events you can monitor are:NOTE_ATTRIBβ the file that descriptor points at had its attributes changed. glow up or glow down, either way, it's noted πNOTE_CLOSEβ a file descriptor referencing the monitored file got closed, and that closed descriptor did not have write access.NOTE_CLOSE_WRITEβ same energy, except the closed descriptor did have write access.
heads up: this note, plusNOTE_CLOSE, do not get activated when files are closed forcibly byunmount(2)orrevoke(2). In those casesNOTE_REVOKEgets sent instead. different note, different vibe.
NOTE_DELETEβ theunlink()system call was called on the file referenced by the descriptor. it's gone, bestie πNOTE_EXTENDβ for a regular file: the file referenced by the descriptor was extended. for a directory: it reports that a directory entry was added or removed, as the result of a rename operation. and ngl, theNOTE_EXTENDevent is not reported when a name is changed inside the directory. iykyk.NOTE_LINKβ the link count on the file changed. in particular,NOTE_LINKgets reported if a subdirectory was created or deleted inside the directory referenced by the descriptor.NOTE_OPENβ the file referenced by the descriptor was opened. main character entrance fr πͺβ¨NOTE_READβ a read occurred on the file referenced by the descriptor.NOTE_RENAMEβ the file referenced by the descriptor was renamed. new name, same file, iconic.NOTE_REVOKEβ access to the file was revoked viarevoke(2), or the underlying file system was unmounted. bye πNOTE_WRITEβ a write occurred on the file referenced by the descriptor.
On return, fflags contains the events which triggered the filter.
EVFILT_PROCβ you pass the process ID you wanna monitor as the identifier and the events you're watching for infflags, and it fires when the process does one or more of the requested events. here's the tea: if a process can normally see another process, it can attach an event to it. the events to monitor are:NOTE_EXITβ the process has exited. the exit status will be stored indata.NOTE_FORKβ the process has calledfork().NOTE_EXECβ the process has executed a new process viaexecve(2)or a similar call.NOTE_TRACKβ follow a process acrossfork()calls. the parent process registers a new kevent to monitor the child process using the samefflagsas the original event. the child process will signal an event withNOTE_CHILDset infflagsand the parent PID indata.
if the parent process fails to register a new kevent (usually due to resource limitations), it will signal an event with NOTE_TRACKERR set in fflags, and the child process will not signal a NOTE_CHILD event. rip to the tracking π
On return, fflags contains the events which triggered the filter.
EVFILT_PROCDESCβ takes the process descriptor created bypdfork(2)to monitor as the identifier and the events to watch for infflags, and returns when the associated process performs one or more of the requested events. the events to monitor are:NOTE_EXITβ the process has exited. the exit status will be stored indata.
On return, fflags contains the events which triggered the filter.
EVFILT_SIGNALβ takes the signal number to monitor as the identifier and returns when the given signal is delivered to the process. okay so this one coexists with thesignal()andsigaction()facilities, and it has a lower precedence. the filter will record all attempts to deliver a signal to a process, even if the signal has been marked asSIG_IGNβ except for theSIGCHLDsignal, which, if ignored, will not be recorded by the filter. event notification happens after normal signal delivery processing.datareturns the number of times the signal has occurred since the last call tokevent(). and psst β this filter automatically sets theEV_CLEARflag internally. it's giving self-sufficient queen πEVFILT_TIMERβ establishes an arbitrary timer identified by ident. when adding a timer,dataspecifies the moment to fire the timer (forNOTE_ABSTIME) or the timeout period. the timer will be periodic unlessEV_ONESHOTorNOTE_ABSTIMEis specified. on return,datacontains the number of times the timeout has expired since the last call tokevent(). for non-monotonic timers, this filter automatically sets theEV_CLEARflag internally.
the filter accepts the following flags in the fflags argument:
NOTE_SECONDSβdatais in seconds.NOTE_MSECONDSβdatais in milliseconds.NOTE_USECONDSβdatais in microseconds.NOTE_NSECONDSβdatais in nanoseconds.NOTE_ABSTIMEβ the specified expiration time is absolute.
if fflags is not set, the default is milliseconds. on return, fflags contains the events which triggered the filter.
if an existing timer is re-added, the existing timer will be effectively canceled (throwing away any undelivered record of previous timer expiration, so it's giving ghosting fr) and re-started using the new parameters contained in data and fflags.
also!! there's a system wide limit on the number of timers, and it's controlled by the kern.kq_calloutmax sysctl. don't go feral with timers bestie π«Ά
EVFILT_USERβ establishes a user event identified by ident which is not associated with any kernel mechanism but is triggered by user level code. the lower 24 bits of thefflagsmay be used for user defined flags and manipulated using the following:NOTE_FFNOPβ ignore the inputfflags.NOTE_FFANDβ bitwise ANDfflags.NOTE_FFORβ bitwise ORfflags.NOTE_FFCOPYβ copyfflags.NOTE_FFCTRLMASKβ control mask forfflags.NOTE_FFLAGSMASKβ user defined flag mask forfflags.
a user event is triggered for output with the following:
NOTE_TRIGGERβ cause the event to be triggered. go bestie, go π¬
On return, fflags contains the users defined flags in the lower 24 bits.
π CANCELLATION BEHAVIOUR π
okay so if nevents is non-zero β i.e., the function is potentially blocking β the call is a cancellation point. otherwise, i.e., if nevents is zero, the call is not cancellable. and the way cancellation works: it can only occur before any changes are made to the kqueue, or when the call was blocked and no changes to the queue were requested. no take-backs mid-edit, that's the rule π
π RETURN VALUES π
the kqueue() system call creates a new kernel event queue and returns a file descriptor. if there was an error creating the kernel event queue, a value of -1 is returned and errno set. sad girl hours π
the kevent() system call returns the number of events placed in the eventlist, up to the value given by nevents. if an error occurs while processing an element of the changelist and there is enough room in the eventlist, then the event will be placed in the eventlist with EV_ERROR set in flags and the system error in data. otherwise, -1 will be returned, and errno will be set to indicate the error condition. and if the time limit expires, then kevent() returns 0. periodt. β¨
β¨ EXAMPLES β¨
okay bestie, time for the main event β a whole working program that watches a file and yells at you the second somebody writes to it π¦ this is the classic kqueue starter pack, fr fr.
#include <sys/event.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int
main(int argc, char **argv)
{
struct kevent event; /* Event we want to monitor */
struct kevent tevent; /* Event triggered */
int kq, fd, ret;
if (argc != 2)
err(EXIT_FAILURE, "Usage: %s path\n", argv[0]);
fd = open(argv[1], O_RDONLY);
if (fd == -1)
err(EXIT_FAILURE, "Failed to open '%s'", argv[1]);
/* Create kqueue. */
kq = kqueue();
if (kq == -1)
err(EXIT_FAILURE, "kqueue() failed");
/* Initialize kevent structure. */
EV_SET(&event, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_WRITE,
0, NULL);
/* Attach event to the kqueue. */
ret = kevent(kq, &event, 1, NULL, 0, NULL);
if (ret == -1)
err(EXIT_FAILURE, "kevent register");
if (event.flags & EV_ERROR)
errx(EXIT_FAILURE, "Event error: %s", strerror(event.data));
for (;;) {
/* Sleep until something happens. */
ret = kevent(kq, NULL, 0, &tevent, 1, NULL);
if (ret == -1) {
err(EXIT_FAILURE, "kevent wait");
} else if (ret > 0) {
printf("Something was written in '%s'\n", argv[1]);
}
}
}
so what's actually happening here, step by step, because the vibes deserve a breakdown π
- the includes β
<sys/event.h>is the whole reason we're here, plus the usual<err.h>,<fcntl.h>,<stdio.h>,<stdlib.h>and<string.h>crowd. - two
struct kevents βeventis the one we're setting up to monitor,teventis the one that comes back at us when the thing triggers. two different jobs, two different structs, no cap. - arg check +
open()β no path argument?err(EXIT_FAILURE, "Usage: %s path\n", argv[0])and we're out. otherwise weopen(argv[1], O_RDONLY)and if that fails weerr(EXIT_FAILURE, "Failed to open '%s'", argv[1]). kq = kqueue();β the actual kqueue, born. if it returns-1,err(EXIT_FAILURE, "kqueue() failed")and bye πEV_SET()β we build the event:EV_SET(&event, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_WRITE, 0, NULL). that's: watch thisfd, with theEVFILT_VNODEfilter,EV_ADD | EV_CLEARflags, and we care aboutNOTE_WRITE. udata isNULL, deal with it.- registering it β
kevent(kq, &event, 1, NULL, 0, NULL)attaches it to the kqueue.ret == -1βerr(EXIT_FAILURE, "kevent register"). and ifevent.flags & EV_ERROR? that'serrx(EXIT_FAILURE, "Event error: %s", strerror(event.data))β registering didn't actually work, bestie π - the forever loop β
kevent(kq, NULL, 0, &tevent, 1, NULL)just sleeps until something happens. ifret == -1we die witherr(EXIT_FAILURE, "kevent wait"), and ifret > 0we get to screamprintf("Something was written in '%s'\n", argv[1])into the void. iconic behaviour, honestly.
and yes, it literally never exits on its own. it's a for (;;). it lives there now. it's the main character of your terminal π«Ά
π ERRORS π
okay so when things go wrong (and they will, bestie, that's just life π§οΈ), here's the whole receipt.
π«§ the kqueue() system call fails if:
[ENOMEM]β the kernel tried to allocate memory for the kernel queue and could not. it's giving "no room at the inn" π[ENOMEM]β theRLIMIT_KQUEUESrlimit (peepgetrlimit(2)) for the current user would be exceeded. you hit your kqueue budget, babes.[EMFILE]β the per-process descriptor table is full. every slot taken, not a single one left π₯²[ENFILE]β the system file table is full. not just you β the whole machine is booked out.
π«§ the kevent() system call fails if:
[EACCES]β the process does not have permission to register a filter. no permission, no vibes π«[EFAULT]β there was an error reading or writing thekeventstructure. the pointer situation is cursed.[EBADF]β the specified descriptor is invalid. that fd is not real, bestie.[EINTR]β a signal was delivered before the timeout expired and before any events were placed on the kqueue for return.[EINTR]β a cancellation request was delivered to the thread, but not yet handled.[EINVAL]β the specified time limit or filter is invalid. invalid input, invalid behaviour, moving on π[ENOENT]β the event could not be found to be modified or deleted. you're trying to edit something that isn't there, queen.[ENOMEM]β no memory was available to register the event, or β special case alert π¨ β for a timer, the maximum number of timers has been exceeded. that max is configurable via thekern.kq_calloutmaxsysctl.[ESRCH]β the specified process to attach to does not exist. ghost process, literally π»
ngl this one's important: when akevent()call fails with theEINTRerror, all changes in the changelist have been applied. not some of them. all of them. remember that π§
π SEE ALSO π
go read your other favs, they're all linked up like a friend group:
aio_error(2),aio_read(2),aio_return(2),poll(2),read(2),select(2),sigaction(2),write(2),pthread_setcancelstate(3),signal(3)
and for the deep lore girlies (this is the source material, the origin story π):
- Jonathan Lemon, βKqueue: A Generic and Scalable Event Notification Facilityβ, Proceedings of the FREENIX Track: 2001 USENIX Annual Technical Conference, USENIX Association, June 25-30, 2001.
π HISTORY π
the kqueue() and kevent() system calls first appeared in FreeBSD 4.1. ancient. legendary. still eating every day β¨
π AUTHORS π
the kqueue() system and this manual page were written by Jonathan Lemon <jlemon@FreeBSD.org>. absolute icon, we thank him for his service π«‘π
π BUGS π
- the timeout value is limited to 24 hours β longer timeouts will be silently reinterpreted as 24 hours. silently!! it will not tell you!! it will not warn you!! it just caps you at 24 hours and moves on π
- in versions older than FreeBSD 12.0,
<sys/event.h>failed to parse without including<sys/types.h>manually. so if you're time-travelling, include it yourself, bestie π°οΈ
and that's the whole page, bestie π thank you for coming to my kqueue seminar. this one's from the Debian crew, dated May 1, 2020, filed under KQUEUE(2). go forth and monitor your file descriptors, you absolute legend ππ
π¦ where this page comes from
- debian bookworm: freebsd-manpages 12.2-1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- debian trixie: freebsd-manpages 12.2-2 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu jammy: freebsd-manpages 12.2-1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu noble: freebsd-manpages 12.2-1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu questing: freebsd-manpages 12.2-2
- ubuntu resolute: freebsd-manpages 12.2-2build1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu stonking: freebsd-manpages 12.2-2build1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- Copyright (c) 2000 Jonathan Lemon
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms