打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
systemd for Developers I

systemdnot only brings improvements for administrators and users, it alsobrings a (small) number of new APIs with it. In this blog story (which mightbecome the first of a series) I hope to shed some light on one of themost important new APIs in systemd:

Socket Activation

In the original blogstory about systemd I tried to explain why socket activation is awonderful technology to spawn services. Let's reiterate the backgroundhere a bit.

The basic idea of socket activation is not new. The inetdsuperserver was a standard component of most Linux and Unix systemssince time began: instead of spawning all local Internet servicesalready at boot, the superserver would listen on behalf of theservices and whenever a connection would come in an instance of therespective service would be spawned. This allowed relatively weakmachines with few resources to offer a big variety of services at thesame time. However it quickly got a reputation for being somewhatslow: since daemons would be spawned for each incoming connection alot of time was spent on forking and initialization of the services-- once for each connection, instead of once for them all.

Spawning one instance per connection was how inetd was primarilyused, even though inetd actually understood another mode: on the firstincoming connection it would notice this via poll() (orselect()) and spawn a single instance for all futureconnections. (This was controllable with thewait/nowait options.) That way the first connectionwould be slow to set up, but subsequent ones would be as fast as witha standalone service. In this mode inetd would work in a trueon-demand mode: a service would be made available lazily when it wasrequired.

inetd's focus was clearly on AF_INET (i.e. Internet) sockets. Astime progressed and Linux/Unix left the server niche and becameincreasingly relevant on desktops, mobile and embedded environmentsinetd was somehow lost in the troubles of time. Its reputation forbeing slow, and the fact that Linux' focus shifted away from onlyInternet servers made a Linux machine running inetd (or one of its newerimplementations, like xinetd) the exception, not the rule.

When Apple engineers worked on optimizing the MacOS boot time theyfound a new way to make use of the idea of socket activation: theyshifted the focus away from AF_INET sockets towards AF_UNIXsockets. And they noticed that on-demand socket activation was onlypart of the story: much more powerful is socket activation when usedfor all local services including those which need to be startedanyway on boot. They implemented these ideas in launchd, a central buildingblock of modern MacOS X systems, and probably the main reason whyMacOS is so fast booting up.

But, before we continue, let's have a closer look what the benefitsof socket activation for non-on-demand, non-Internet services indetail are. Consider the four services Syslog, D-Bus, Avahi and theBluetooth daemon. D-Bus logs to Syslog, hence on traditional Linuxsystems it would get started after Syslog. Similarly, Avahi requiresSyslog and D-Bus, hence would get started after both. FinallyBluetooth is similar to Avahi and also requires Syslog and D-Bus butdoes not interface at all with Avahi. Sinceoin a traditionalSysV-based system only one service can be in the process of gettingstarted at a time, the following serialization of startup would takeplace: Syslog → D-Bus → Avahi → Bluetooth (Of course, Avahi andBluetooth could be started in the opposite order too, but we have topick one here, so let's simply go alphabetically.). To illustratethis, here's a plot showing the order of startup beginning with systemstartup (at the top).

Certain distributions tried to improve this strictly serializedstart-up: since Avahi and Bluetooth are independent from each other,they can be started simultaneously. The parallelization is increased,the overall startup time slightly smaller. (This is visualized in themiddle part of the plot.)

Socket activation makes it possible to start all four servicescompletely simultaneously, without any kind of ordering. Since thecreation of the listening sockets is moved outside of the daemonsthemselves we can start them all at the same time, and they are ableto connect to each other's sockets right-away. I.e. in a single stepthe /dev/log and /run/dbus/system_bus_socket socketsare created, and in the next step all four services are spawnedsimultaneously. When D-Bus then wants to log to syslog, it just writesits messages to /dev/log. As long as the socket buffer doesnot run full it can go on immediately with what else it wants to dofor initialization. As soon as the syslog service catches up it willprocess the queued messages. And if the socket buffer runs full thenthe client logging will temporarily block until the socket is writableagain, and continue the moment it can write its log messages. Thatmeans the scheduling of our services is entirely done by the kernel:from the userspace perspective all services are run at the same time,and when one service cannot keep up the others needing it willtemporarily block on their request but go on as soon as theserequests are dispatched. All of this is completely automatic andinvisible to userspace. Socket activation hence allows us todrastically parallelize start-up, enabling simultaneous start-up ofservices which previously were thought to strictly requireserialization. Most Linux services use sockets as communicationchannel. Socket activation allows starting of clients and servers ofthese channels at the same time.

But it's not just about parallelization. It offers a number ofother benefits:

  • We no longer need to configure dependencies explicitly. Since thesockets are initialized before all services they are simply available,and no userspace ordering of service start-up needs to take placeanymore. Socket activation hence drastically simplifies configurationand development of services.
  • If a service dies its listening socket stays around, not losing asingle message. After a restart of the crashed service it can continueright where it left off.
  • If a service is upgraded we can restart the service while keepingaround its sockets, thus ensuring the service is continouslyresponsive. Not a single connection is lost during the upgrade.
  • We can even replace a service during runtime in a way that isinvisible to the client. For example, all systems running systemdstart up with a tiny syslog daemon at boot which passes all logmessages written to /dev/log on to the kernel messagebuffer. That way we provide reliable userspace logging starting fromthe first instant of boot-up. Then, when the actual rsyslog daemon isready to start we terminate the mini daemon and replace it with thereal daemon. And all that while keeping around the original loggingsocket and sharing it between the two daemons and not losing a singlemessage. Since rsyslog flushes the kernel log buffer to disk afterstart-up all log messages from the kernel, from early-boot and fromruntime end up on disk.

For another explanation of this idea consult the original blogstory about systemd.

Socket activation has been available in systemd since itsinception. On Fedora 15 a number of services have been modified toimplement socket activation, including Avahi, D-Bus and rsyslog (to continue with the example above).

systemd's socket activation is quite comprehensive. Not only classicsockets are support but related technologies as well:

  • AF_UNIX sockets, in the flavours SOCK_DGRAM, SOCK_STREAM and SOCK_SEQPACKET; both in the filesystem and in the abstract namespace
  • AF_INET sockets, i.e. TCP/IP and UDP/IP; both IPv4 and IPv6
  • Unix named pipes/FIFOs in the filesystem
  • AF_NETLINK sockets, to subscribe to certain kernel features. Thisis currently used by udev, but could be useful for othernetlink-related services too, such as audit.
  • Certain special files like /proc/kmsg or device nodes like /dev/input/*.
  • POSIX Message Queues

A service capable of socket activation must be able to receive itspreinitialized sockets from systemd, instead of creating theminternally. For most services this requires (minimal)patching. However, since systemd actually provides inetd compatibilitya service working with inetd will also work with systemd -- which isquite useful for services like sshd for example.

So much about the background of socket activation, let's now have alook how to patch a service to make it socket activatable. Let's startwith a theoretic service foobard. (In a later blog post we'll focus onreal-life example.)

Our little (theoretic) service includes code like the following forcreating sockets (most services include code like this in one way oranother):

/* Source Code Example #1: ORIGINAL, NOT SOCKET-ACTIVATABLE SERVICE */...union {        struct sockaddr sa;        struct sockaddr_un un;} sa;int fd;fd = socket(AF_UNIX, SOCK_STREAM, 0);if (fd < 0) {        fprintf(stderr, "socket(): %m\n");        exit(1);}memset(&sa, 0, sizeof(sa));sa.un.sun_family = AF_UNIX;strncpy(sa.un.sun_path, "/run/foobar.sk", sizeof(sa.un.sun_path));if (bind(fd, &sa.sa, sizeof(sa)) < 0) {        fprintf(stderr, "bind(): %m\n");        exit(1);}if (listen(fd, SOMAXCONN) < 0) {        fprintf(stderr, "listen(): %m\n");        exit(1);}...

A socket activatable service may use the following code instead:

/* Source Code Example #2: UPDATED, SOCKET-ACTIVATABLE SERVICE */...#include "sd-daemon.h"...int fd;if (sd_listen_fds(0) != 1) {        fprintf(stderr, "No or too many file descriptors received.\n");        exit(1);}fd = SD_LISTEN_FDS_START + 0;...

systemd might pass you more than one socket (based onconfiguration, see below). In this example we are interested in oneonly. sd_listen_fds()returns how many file descriptors are passed. We simply compare thatwith 1, and fail if we got more or less. The file descriptors systemdpasses to us are inherited one after the other beginning with fd#3. (SD_LISTEN_FDS_START is a macro defined to 3). Our code hence justtakes possession of fd #3.

As you can see this code is actually much shorter than theoriginal. This of course comes at the price that our little servicewith this change will no longer work in a non-socket-activationenvironment. With minimal changes we can adapt our example to work nicelyboth with and without socket activation:

/* Source Code Example #3: UPDATED, SOCKET-ACTIVATABLE SERVICE WITH COMPATIBILITY */...#include "sd-daemon.h"...int fd, n;n = sd_listen_fds(0);if (n > 1) {        fprintf(stderr, "Too many file descriptors received.\n");        exit(1);} else if (n == 1)        fd = SD_LISTEN_FDS_START + 0;else {        union {                struct sockaddr sa;                struct sockaddr_un un;        } sa;        fd = socket(AF_UNIX, SOCK_STREAM, 0);        if (fd < 0) {                fprintf(stderr, "socket(): %m\n");                exit(1);        }        memset(&sa, 0, sizeof(sa));        sa.un.sun_family = AF_UNIX;        strncpy(sa.un.sun_path, "/run/foobar.sk", sizeof(sa.un.sun_path));        if (bind(fd, &sa.sa, sizeof(sa)) < 0) {                fprintf(stderr, "bind(): %m\n");                exit(1);        }        if (listen(fd, SOMAXCONN) < 0) {                fprintf(stderr, "listen(): %m\n");                exit(1);        }}...

With this simple change our service can now make use of socketactivation but still works unmodified in classic environments. Now,let's see how we can enable this service in systemd. For this we haveto write two systemd unit files: one describing the socket, the otherdescribing the service. First, here's foobar.socket:

[Socket]ListenStream=/run/foobar.sk[Install]WantedBy=sockets.target

And here's the matching service file foobar.service:

[Service]ExecStart=/usr/bin/foobard

If we place these two files in /etc/systemd/system we canenable and start them:

# systemctl enable foobar.socket# systemctl start foobar.socket

Now our little socket is listening, but our service not runningyet. If we now connect to /run/foobar.sk the service will beautomatically spawned, for on-demand service start-up. With amodification of foobar.service we can start our servicealready at startup, thus using socket activation only forparallelization purposes, not for on-demand auto-spawning anymore:

[Service]ExecStart=/usr/bin/foobard[Install]WantedBy=multi-user.target

And now let's enable this too:

# systemctl enable foobar.service# systemctl start foobar.service

Now our little daemon will be started at boot and on-demand,whatever comes first. It can be started fully in parallel with itsclients, and when it dies it will be automatically restarted when itis used the next time.

A single .socket file can include multiple ListenXXX stanzas, whichis useful for services that listen on more than one socket. In thiscase all configured sockets will be passed to the service in the exactorder they are configured in the socket unit file. Also,you may configure various socket settings in the .socketfiles.

In real life it's a good idea to include description strings inthese unit files, to keep things simple we'll leave this out of ourexample. Speaking of real-life: our next installment will cover anactual real-life example. We'll add socket activation to the CUPSprinting server.

The sd_listen_fds() function call is defined in sd-daemon.hand sd-daemon.c. Thesetwo files are currently drop-in .c sources which projects shouldsimply copy into their source tree. Eventually we plan to turn thisinto a proper shared library, however using the drop-in files allowsyou to compile your project in a way that is compatible with socketactivation even without any compile time dependencies onsystemd. sd-daemon.c is liberally licensed, should compilefine on the most exotic Unixes and the algorithms are trivial enoughto be reimplemented with very little code if the license shouldnonetheless be a problem for your project. sd-daemon.ccontains a couple of other API functions besidessd_listen_fds() that are useful when implementing socketactivation in a project. For example, there's sd_is_socket()which can be used to distuingish and identify particular sockets whena service gets passed more than one.

Let me point out that the interfaces used here are in no way bounddirectly to systemd. They are generic enough to be implemented inother systems as well. We deliberately designed them as simple andminimal as possible to make it possible for others to adopt similarschemes.

Stay tuned for the next installment. As mentioned, it will cover areal-life example of turning an existing daemon into asocket-activatable one: the CUPS printing service. However, I hopethis blog story might already be enough to get you started if you planto convert an existing service into a socket activatable one. Weinvite everybody to convert upstream projects to this scheme. If youhave any questions join us on #systemd on freenode.

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
防火墙 iptables firewalld CentOS 7:/etc/init.d/iptables: No such file or directory
技术|systemctl 命令完全指南
systemctl使用指南:Centos 7.x systemd对比Centos 6.x daemon
Systemd 介绍与日常使用
Docker Machine 详解
Windows下使用VNC连接CentOS7远程桌面
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服