這幾天在研究runc的實現過程, 注意到它使用了的go-systemd的socket activation
package, 出於好奇心,因而一探究竟.html
systemd是一套用來管理主機上各個Daemon運行的工具, 開發目標是提供更優秀的框架以表示系統服務間的依賴關係,並依此實現系統初始化時服務的 並行啓動.
上面提到的並行頗有意思, 不相干的Daemon並行啓動天然沒有什麼問題,但假若Daemon B依賴於Daemon A,那麼它就必須等到Daemon A完成啓動後才能啓動,這就變成了串行.若是避免這種串行呢? 這須要瞭解兩個Daemon的依賴性的本質.一般來講,若是Daemon B依賴Daemon A,那麼就說明Daemon B(Clinet)啓動時會向Daemon A(Server)發起鏈接.因爲Daemon A和Daemon B一般在一臺主機上, 所以它們之間的C-S鏈接一般使用Unix Socket完成.linux
而socket activation
的思想就是: Daemon B啓動時其實並不須要Daemon A真正運行起來,它只須要Daemon A創建的socket處於listen狀態就OK了. 而這個socket沒必要由Daemon A創建, 而是由systemd在系統初始化時就創建. 當Daemon B發起啓動時發起鏈接,systemd再將Daemon A啓動,
當Daemon A啓動後,而後將socket歸還給
Daemon A. 這個過程以下圖所示.git
vim server.c
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <sys/un.h> #include <systemd/sd-daemon.h> int main() { int listenfd, connfd; char recvbuf[1024]; ssize_t n; union { struct sockaddr sa; struct sockaddr_un un; }sa; if (sd_listen_fds(0)!=1) { fprintf(stderr,"No or too many fd received\n"); } listenfd = SD_LISTEN_FDS_START + 0; if ((connfd = accept(listenfd, (struct sockaddr*)NULL, NULL))<0) { fprintf(stderr ,"accept(): %m\n"); } for (;;) { if ((n = read(connfd, recvbuf, sizeof(recvbuf))) < 0) { fprintf(stderr, "read()\n"); } recvbuf[n] = '\0'; write(connfd, recvbuf, n); } close(connfd); close(listenfd); return 0; }
其中sd_listen_fds
將從systemd拿處處於listen狀態的socket.github
編譯生成可執行文件, 並將其移動到/usr/bin/目錄shell
gcc server.c -lsystemd -o foobard
mv foobard /usr/bin/
若是編譯提示找不到libsystemd或者<systemd/sd-daemon.h>,請參考文末附錄安裝libsystemdubuntu
vim /lib/systemd/system/foobar.socket
指定systemd要建立的unix socket路徑爲/run/foobar.skvim
[Socket] ListenStream=/run/foobar.sk [Install] WantedBy=sockets.target
vim /lib/systemd/system/foobar.service
[Service] ExecStart=/usr/bin/foobard
vim clinet.c
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <sys/un.h> int main() { int fd; char sendbuf[1024]; ssize_t n; 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 (connect(fd, (struct sockaddr*)&sa, sizeof(sa))<0){ fprintf(stderr, "connect()%m\n"); exit(1); } printf("connect success !\n"); for (;;) { fgets(sendbuf, 1024, stdin); if ((n = write(fd, sendbuf, strlen(sendbuf)))<0){ exit(1); } if ((n = read(fd, sendbuf, sizeof(sendbuf))) < 0){ exit(1); } sendbuf[n] = '\0'; printf("%s\n", sendbuf); memset(sendbuf, 0, sizeof(sendbuf)); } return 0; }
啓動socketbash
# systemctl enable foobar.socket # systemctl start foobar.socket # ls /run/foobar.sk /run/foobar.sk
啓動client框架
# ./client connect success ! abc abc
使用ps
命令可看到foobard已經啓動socket
# ps -aux | grep foobar root 19480 0.0 0.0 23148 1360 ? Ss 09:36 0:00 /usr/bin/foobard
在官網上下載安裝包,這裏以最新的systemd-221爲例,下載後解壓縮
# xz -d systemd-221.tar.xz # tar -xvf systemd-221.tar
進入systemd-221目錄
# cd systemd-221 systemd-221#./configure
若是提示 configure: error: Your intltool is too old. You need intltool 0.40.0 or later.
更新intltool(我使用的是ubuntu 16.04,其餘linux發行版可選擇本身的安裝方式或源碼安裝)
# apt-get install intltool
若是提示configure: error: * gperf not found,則
# apt-get install gerf
若是提示configure: error: * libmount support required but libraries not found,則
# apt-get install libmount-dev
當經過configure檢查以後,編譯安裝systemd
# make # make install
systemd for developer Pid Eins:systemd的使用方法
sd_listen_fds