著者: Jude Nelson 日付: To: dng@lists.dyne.org 題目: [Dng] libudev-compat update
Hey everyone,
I've just pushed my first stab at libudev-compat to the vdev repository.
It took a while to work out how to remove the need for udev to send libudev
clients device events, but I think I've figured out something that works.
Instead of sending events via a netlink multicast group, the device manager
is expected to atomically put a serialized struct udev_device message into
a set of process-specific directories (i.e. as strings that can be parsed
by udev_device_new_from_nulstr()). Libudev-compat clients create these
directories when they create struct udev_monitor instances, so the device
manager need only write the event file somewhere privately, hard-link it
into each such directory (ensuring "atomic send" semantics), and then
unlink it. This means that pretty much any program you want can trivially
send device messages to other programs as long as it can write to their
directories, and inspect or even re-order pending messages.
Instead of a netlink socket, a struct udev_monitor in libudev-compat
contains an epoll handle, an inotify handle, and a socket pair. The epoll
handle is used to indicate the readiness of the inotify handle, the "sink"
end of the socket pair, or both, and is the pollable handle returned by
udev_monitor_get_fd(). Libudev-compat ensures that a struct udev_monitor's
handle will poll as ready-to-read if and only if there is at least one
unprocessed device event.
The inotify handle is set up to watch the process-specific directory for
IN_CREATE events, in a one-shot fashion (the directory itself is created as
part of udev_monitor_new()). When a client program receives a device event
with udev_monitor_receive_device(), libudev-compat resets the one-shot
inotify handle, scans the directory, and sends as many files' contents as
it can as messages to the "source" end of the monitor's socket pair.
Libudev-compat filters unwanted devices on the socket pair using a BPF
program in the same way that libudev does for netlinks sockets (i.e. the
logic is the same in both libraries). Devices events are sent in
lexicographic order by name, and are consumed from the "sink" end of the
socket pair.
Because we're not using netlink anymore, we have to preserve multicast
semantics across fork() as well--that is, if the process fork()'s and the
device manager subsequently sends a device event, both the parent and child
must receive it. To do so, libudev-compat maintains a global table of all
existing monitors, which it does as part of udev_monitor_new() and
udev_monitor_unref(). Upon fork(), as part of a pthread_atfork() handler,
the libudev-compat child creates a new events directory, and re-targets all
of its monitors' inotify handles to watch it instead. It also closes and
re-opens each monitor's socket pairs, so it will not accidentally consume
its parent's buffered device events. This ensures that the next time the
child calls udev_monitor_receive_device(), it will look in its own
directory, and it will receive and consume events independently of its
parent, as desired.
I have lightly tested libudev-compat's ability to do all of the above with
a helper program for vdev (vdevd/helpers/LINUX/event-put.c), and with a
test program that can be compiled with the "test" target in libudev-compat/
(i.e. "cd libudev-compat && make test"). It appears to work so far, but I
invite the community to review the code and try it themselves.
What I need to do next is create vdev scripts that will cause it to send
off events to libudev-compat clients, using event-put. To do so, I'll also
need to add helper scripts to generate and maintain /run/udev/, since the
struct udev_device's that libudev-compat clients will expect to receive
must include all of the metadata udev would generate for them.