تاریخچه systemd و دلایل پیروزی آن در توزیعهای لینوکس
بررسی فنی دلایل جایگزینی SysV init با systemd و نقش cgroups در مدیریت پردازشها. چرا تمام توزیعهای اصلی لینوکس در کمتر از 4 سال به این سیستم مهاجرت کردند؟
چرا systemd پیروز شد
تاریخچه systemd با دو موردی آغاز میشود که SysV init قادر به انجام آنها نبود. سیستم SysV init (مخفف System V init، سیستم راهاندازی که لینوکس از AT&T Unix به ارث برده بود) هیچ روشی برای توصیف وابستگیهای یک سرویس نداشت و نمیتوانست تشخیص دهد که پس از اجرا، کدام پردازشها به یک سرویس تعلق دارند. سیستم systemd به هر دو مورد با استفاده از قابلیتهای هسته (kernel) که از دسترس اسکریپتهای shell خارج بود، پاسخ داد: استفاده از control groups برای ردیابی پردازشها و استفاده از listening sockets که از پیش باز شدهاند برای تعیین ترتیب اجرا. باقی ماجرا، داستان گسترش این دو پاسخ به سایر بخشهای userland است؛ جایی که مخالفتها آغاز شد و باید گفت که برخی از این مخالفتها نیز بجا بودند.
عملکرد واقعی SysV init
در یک سیستم مبتنی بر SysV، پردازش PID 1 (اولین پردازشی که هسته سیستمعامل اجرا میکند) فایل /etc/inittab را میخواند، یک runlevel انتخاب میکرد و اسکریپتهای مربوط به آن سطح را اجرا مینمود. این اسکریپتها در مسیر /etc/init.d/ قرار داشتند. لینکهای نمادین (symbolic links) در /etc/rc3.d/ تعیین میکردند که کدام اسکریپت و با چه ترتیبی اجرا شود؛ بنابراین /etc/rc3.d/S20nginx به /etc/init.d/nginx اشاره میکرد و با آرگومان start فراخوانی میشد.
#!/bin/sh
### BEGIN INIT INFO
# Provides: nginx
# Required-Start: $local_fs $remote_fs $network $syslog
# Required-Stop: $local_fs $remote_fs $network $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
### END INIT INFO
case "$1" in
start)
start-stop-daemon --start --quiet --pidfile /run/nginx.pid \
--exec /usr/sbin/nginx
;;
stop)
start-stop-daemon --stop --quiet --retry TERM/30/KILL/5 \
--pidfile /run/nginx.pid
;;
esacعدد 20 در S20nginx نشاندهنده یک موقعیت است، نه یک وابستگی. این عدد میگوید که این اسکریپت پس از S19 و پیش از S21 اجرا میشود. این عدد دلیل ترتیب را بیان نمیکند، بنابراین هیچ مکانیزمی نمیتواند آن را بررسی کند و هیچ سیستمی نمیتواند بدون دخالت انسان برای اطمینان از ایمنی، دو اسکریپت نامرتبط را همزمان اجرا کند.
برنامه rc هر اسکریپت را به نوبت اجرا میکرد و منتظر خروج آن میماند. اسکریپتی که برای دریافت یک آدرس شبکه به مدت 30 ثانیه مسدود (block) میشد، کل فرآیند بوت را برای 30 ثانیه متوقف میکرد، حتی برای سرویسهایی که هیچ ارتباطی با شبکه نداشتند.
هدر LSB (مخفف Linux Standard Base) در ابتدای آن اسکریپتها، تلاشی برای حل این مشکل از درون بود. دبیان 6.0 در سال 2011 برنامه insserv را به پیشفرض تبدیل کرد: این برنامه Required-Start را از تمام اسکریپتها میخواند، یک گراف میساخت و لینکهای نمادین را دوباره شمارهگذاری میکرد. دبیان پس از آن توانست اسکریپتهای مستقل را با استفاده از startpar بهصورت همزمان اجرا کند. این کار کمککننده بود، اما به مشکل عمیقتر نرسید. وابستگی همچنان بر پایه خروج یک اسکریپت بود. بازگشت مقدار 0 توسط S20nginx تنها به معنای موفقیت یک تابع شل (shell function) است و لزوماً به این معنا نیست که nginx در حال پذیرش اتصالات است.
پنج موردی که هیچ اسکریپت init قادر به اصلاح آنها نبود
- راهاندازی موازی. ترتیببندی بر اساس نام فایل، یک ترتیب کلی برای تمام سرویسهای موجود در سیستم است؛ بنابراین سرعت بوت به اندازه مجموع زمان اجرای تکتک اجزا کند میشود.
- آمادگی. یک اسکریپت شروع، زمانی که daemon را fork میکند خاتمه مییابد، نه زمانی که daemon آماده پاسخگویی به درخواستها باشد؛ به همین دلیل اسکریپت بعدی اغلب خیلی زود اجرا میشود.
- نظارت. یک daemon دو بار fork میشود و والد آن خاتمه مییابد که باعث جدا شدن آن از ترمینال و انتساب مجدد آن به PID 1 میشود. سیستم init خروج یک فرزند را میبیند و هیچ پیوند قابلاطمینانی با فرآیندی که باقی مانده است ندارد.
- شروع بر اساس تقاضا. برنامه inetd (ابر-سرور اینترنتی) میتوانست با رسیدن یک اتصال، daemon را اجرا کند، اما این یک سیستم مجزا با فایل پیکربندی خاص خود بود و هیچ نقشی در ترتیببندی سایر موارد در هنگام بوت نداشت.
- کنترل منابع. هیچچیز در یک اسکریپت init نمیتوانست حافظه یا سهم CPU یک سرویس را محدود کند.
ulimitروی یک فرآیند اعمال میشد وniceتنها بر زمانبند (scheduler) تأثیر میگذاشت، بنابراین یک فرزند سرکش از یک سرویس، مانند هر فرآیند دیگری در سیستم به نظر میرسید.
شکاف نظارتی همان موردی است که در استفاده روزمره آسیبرسان بود. فایل PID راهکار جایگزین بود: daemon شناسه فرآیند خود را در /run/nginx.pid مینوشت و تابع stop آن فایل را دوباره میخواند. اگر daemon به صورت سخت (hard kill) کشته میشد، فایل باقی میماند. سپس هسته سیستمعامل آن شماره را برای چیز دیگری استفاده میکرد و start-stop-daemon --stop --pidfile سیگنالی را به هر چیزی که اکنون مالک آن PID بود ارسال میکرد. یک فایل PID قدیمی، دلیل اصلی این است که چگونه یک اسکریپت init فرآیند اشتباهی را میکشد.
launchd اولین راهحل برای مشکل سوکت بود
اپل در سال 2005، launchd را در Mac OS X 10.4 عرضه کرد که توسط Dave Zarzycki نوشته شده بود. یک پردازش جایگزین init، rc، xinetd، crond و watchdogd شد.
ایدهای که ارزش کپیبرداری داشت، فعالسازی سوکت (socket activation) بود. launchd ابتدا تمام سوکتهای در حال گوش دادن (listening socket) را ایجاد میکند و سپس دیمونها را اجرا مینماید. کلاینتی که به دیمونی متصل میشود که هنوز اجرا نشده است، با خطای connection refused مواجه نمیشود؛ زیرا هسته (kernel)، اتصال را در صف backlog آن سوکت نگه میدارد تا زمانی که دیمون تابع accept() را فراخوانی کند. در این حالت، ترتیببندی بین دو دیمون دیگر چیزی نیست که انسان بخواهد تعیین کند. سوکت خود این موضوع را مدیریت میکند.
launchd بر پایه Mach IPC (ارتباط بین پردازشی) ساخته شده بود که متعلق به هسته XNU اپل است و هیچ معادل مستقیمی در لینوکس ندارد. پورت کردن این کد هرگز واقعبینانه نبود. با این حال، این ایده به دنیای دیگر منتقل شد.
Upstart رویدادها را به واحد کاری تبدیل کرد
پروژه Upstart متعلق به Canonical که توسط Scott James Remnant نوشته شد، در اکتبر 2006 همراه با Ubuntu 6.10 عرضه شد. توزیعهای Fedora 9 تا Fedora 14 و همچنین RHEL 6 و Chrome OS از آن استفاده میکردند. این ابزار مفهوم runlevel را با رویداد (event) جایگزین کرد و هر job مشخص میکرد که چه رویدادهایی باید آن را شروع یا متوقف کنند.
# /etc/init/example.conf
start on filesystem and net-device-up IFACE!=lo
stop on runlevel [!2345]
respawn
respawn limit 10 5
exec /usr/local/bin/exampledبا افزایش تعداد jobها، دو مشکل نمایان شد. مشکل اول جهتگیری بود. یک job میگوید «وقتی این اتفاق افتاد مرا شروع کن»؛ بنابراین دانشِ اینکه چه چیزی به چه چیزی وابسته است، در فایل اشتباهی قرار میگیرد: یک سرویس میداند به چه چیزی نیاز دارد، اما نمیتواند بداند چه کسی در آینده به آن نیاز خواهد داشت. افزودن یک سرویس اغلب به معنای ویرایش یک job موجود بود تا رویداد جدیدی را منتشر کند.
مشکل دوم، ردیابی بود. Upstart برای دنبال کردن یک daemon که fork میکند، تعداد فراخوانیهای fork() را با ptrace میشمرد که شما آن را به صورت expect fork یا expect daemon پیکربندی میکردید. اگر تعداد forkها را اشتباه حدس میزدید، Upstart فرآیندی را نظارت میکرد که قبلاً خاتمه یافته بود، یا منتظر forkای میماند که قبلاً رخ داده بود. نشانه این مشکل، معلق ماندن initctl start بدون هیچ خطایی بود که فایل job هیچ راهی برای توضیح آن در اختیار شما نمیگذاشت.
Upstart همچنین مشارکتکنندگان را ملزم میکرد تا توافقنامه مشارکت Canonical را امضا کنند. این یک نقص فنی نبود، اما بر افرادی که روی آن کار میکردند تأثیر گذاشت.
بازنگری در PID 1، آوریل 2010
در 30 آوریل 2010، Lennart Poettering پستی با عنوان "Rethinking PID 1" منتشر کرد. Kay Sievers در این پروژه با او همکاری میکرد. این استدلال شامل چهار بخش بود:
- شروع کمتر. بسیاری از سرویسها میتوانند تا زمانی که واقعاً به آنها نیاز شود، منتظر بمانند.
- توقف تعیین ترتیب در جایی که یک socket میتواند آن را مشخص کند. تمام socketها را در یک مرحله باز کنید و سپس همه چیز را همزمان شروع کنید.
- ردیابی پردازشها با استفاده از control groups بهجای فایلهای PID.
- توصیف یک سرویس در یک فایل اعلانی (declarative)، بهطوری که یک توصیف در تمام توزیعها کار کند.
اولین نسخه در همان سال منتشر شد. Fedora 14 در نوامبر 2010، systemd را بهعنوان یک گزینه ارائه کرد و Fedora 15 در مه 2011 آن را به پیشفرض تبدیل نمود.
چرا cgroups نظارت را قابلاطمینان کرد
یک cgroup (مخفف control group) قابلیتی در هسته برای گروهبندی پردازشها است که در سال 2008 به نسخه 2.6.24 هسته Linux اضافه شد. systemd هر سرویس را در cgroup اختصاصی خودش قرار میدهد. هر پردازش فرزند، cgroup والد خود را به ارث میبرد و یک پردازش بدون دسترسی ویژه (unprivileged) نمیتواند خود را از آن خارج کند. بنابراین، تکنیک double-fork دیگر چیزی را پنهان نمیکند: PID 1 در هر لحظه دقیقاً مجموعهای از پردازشهای متعلق به یک واحد (unit) را در اختیار دارد. متوقف کردن یک سرویس به معنای کشتن تمام پردازشهای موجود در cgroup آن است؛ کاری که KillMode=control-group بهصورت پیشفرض انجام میدهد.
دستور systemctl status آن گروه را چاپ میکند:
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-08-11 09:14:22 UTC; 3min ago
Main PID: 1042 (nginx)
Tasks: 3 (limit: 4653)
Memory: 6.1M (peak: 7.4M)
CGroup: /system.slice/nginx.service
├─1042 "nginx: master process /usr/sbin/nginx"
├─1043 "nginx: worker process"
└─1044 "nginx: worker process"این بلوک، پاسخ کامل به مشکل فایلهای PID قدیمی (stale) است. هیچ فایلی وجود ندارد که قدیمی شود، زیرا این فهرست، وضعیت فعلی هسته است.
همین ساختار درختی، محدودیتها را نیز حمل میکند، زیرا cgroups پیش از آنکه کسی از آنها برای ردیابی استفاده کند، برای حسابرسی (accounting) ساخته شده بودند. MemoryMax=، CPUQuota= و TasksMax= هر کدام تنها یک خط هستند. اعمال محدودیت سختافزاری حافظه و CPU روی یک سرویس امروزه تنها با یک فایل drop-in انجام میشود، در حالی که در سال 2009، این کار نیازمند وصلهای (patch) برای یک اسکریپت shell بود که هیچکس آن را ننوشته بود.
چرا تمام توزیعها بین سالهای 2011 تا 2015 تغییر کردند
- Fedora 15، مه 2011.
- openSUSE 12.1، نوامبر 2011.
- Mageia 2، مه 2012.
- Arch Linux، پیشفرض برای نصبهای جدید از اکتبر 2012.
- RHEL 7، ژوئن 2014.
- SLES 12، اکتبر 2014.
- Debian 8، آوریل 2015.
- Ubuntu 15.04، آوریل 2015.
دلایل این تغییر عمدتاً فنی و ساده بودند، به همین دلیل این گذار با سرعت انجام شد.
- یک unit file در تمام توزیعها کار میکند، بنابراین پروژههای بالادستی (upstream) شروع به ارائه یک فایل
.serviceکردند و توزیعها دیگر نیازی به نگهداری اسکریپتهای shell برای هر بسته در هر نسخه نداشتند. - ردیابی نشستهای دسکتاپ پس از توقف نگهداری ConsoleKit در حدود سال 2012، به
systemd-logindمنتقل شد. GNOME به logind نیاز داشت، بنابراین توزیعی که از systemd استفاده نمیکرد، باید جایگزینی برای آن مییافت. آن جایگزین،elogind، همان logind استخراجشده از systemd است که بهصورت جداگانه نگهداری میشود. - udev، مدیر دستگاه، در آوریل 2012 در درخت منبع systemd ادغام شد. توزیعهایی که udev را ارائه میدادند، اکنون مخزن systemd را دنبال میکردند. Gentoo در واکنش به این موضوع،
eudevرا فورک کرد. - کانتینرها باعث شدند ردیابی مطمئن پردازشها و محدودیتهای هر سرویس اهمیت بیشتری پیدا کند، زیرا هر دو از ویژگیهای cgroup هستند. این پرسش که کدام supervisor مالک یک پردازش کانتینر است، همچنان در زمان بازگرداندن یک Docker Compose stack پس از reboot مطرح است.
تصمیم Debian پرحاشیهترین مورد بود. کمیته فنی در فوریه 2014 رأیگیری کرد، آرا مساوی شد و رئیس کمیته، Bdale Garbee، رأی نهایی را به نفع systemd داد. Ubuntu چند روز بعد اعلام کرد که به جای ادامه کار با Upstart، از Debian پیروی خواهد کرد. گروهی از توسعهدهندگان Debian در نوامبر 2014 این توزیع را با نام Devuan فورک کردند و Devuan 1.0 را در مه 2017 منتشر کردند.
ملاحظات، بیانشده بهطور منصفانه
دامنه. یک پروژه اکنون PID 1، دیمون لاگ، مدیریت نشستهای ورود، مدیریت دستگاه، دیمون پیکربندی شبکه، حلکننده DNS (سیستم نام دامنه)، کلاینت NTP (پروتکل زمان شبکه)، اجراکننده کانتینر و بوتلودر را ارائه میدهد. دفاع معمول مبنی بر اینکه اینها باینریهای جداگانهای هستند که مجبور به نصب آنها نیستید، درست است اما به این ایراد پاسخ نمیدهد. وقتی یک دسکتاپ به logind نیاز دارد و logind از درخت systemd جدا نمیشود، انتخاب دیگر آزاد نیست. این همان معنای وابستگی (coupling) در بحث است که اتفاق افتاد.
ژورنال باینری. journald به جای متن ساده، یک فرمت باینری نمایهشده مینویسد. شما به قابلیتهایی دست مییابید که متن هرگز ارائه نمیداد: فیلتر کردن بر اساس واحد و اولویت، فیلدهای ساختاریافته، و متادیتایی که برنامه فرستنده نمیتواند جعل کند، زیرا journald خودِ واحد و cgroup را ثبت میکند. journalctl -u nginx -p err --since "-1h" جایگزین grep با یک عبارت منظم تاریخ میشود. هزینه این کار نیز واقعی است. در ماشینی که بوت نمیشود، نمیتوانید لاگ را با less از یک شل نجات (rescue shell) بخوانید. در عوض باید journalctl را به دیسک mount شده اشاره دهید:
sudo journalctl --directory /mnt/var/log/journal --boot -1 --priority errیک تله دوم در اینجا وجود دارد که افراد را یکبار گرفتار میکند. journald لاگها را در /run/log/journal نگه میدارد که حافظه است، مگر اینکه /var/log/journal وجود داشته باشد. در سیستمی که این دایرکتوری وجود ندارد، journalctl -b -1 پس از ریبوت چیزی برای نمایش ندارد، که دقیقاً همان لحظهای است که به آن نیاز دارید. آن را بررسی و اصلاح کنید:
journalctl --disk-usage
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journaldjournalctl --disk-usage اکنون باید ژورنالهای آرشیو شده را در /var/log/journal گزارش دهد. اگر متن ساده نیز میخواهید، ForwardToSyslog=yes را در /etc/systemd/journald.conf تنظیم کنید و rsyslog را نصب نگه دارید.
قابلیت عیبیابی بوت. وقتی یک واحد (unit) متوقف میشود، کنسول یک خط را نشان میدهد و دیگر هیچ:
[ *** ] A start job is running for Wait for Network to be Configured (1min 32s / no limit)ابزارهایی برای پیشروی وجود دارند: systemctl list-jobs در حالی که متوقف است، systemd-analyze blame و systemd-analyze critical-chain پس از آن، و systemd.log_level=debug در خط فرمان هسته. نسخه منصفانه این شکایت این است که یک اسکریپت init توسط هر کسی که sh را میدانست از بالا تا پایین قابل خواندن بود، در حالی که یک واحد متوقفشده نیاز دارد بدانید به کدامیک از دهها دستور باید متوسل شوید. این یک هزینه واقعی است. این هزینه یکبار برای هر مدیر سیستم پرداخت میشود، و توسط بسیاری از مدیران بهطور همزمان پرداخت شد.
پیشفرضی که برای همه تغییر میکند. نسخه 230 از systemd در سال 2016 پیشفرض logind را تغییر داد تا فرآیندهای باقیمانده کاربر هنگام خروج (logout) کشته شوند. نشستهای جداشده tmux و screen با پایان یافتن نشستی که آنها را شروع کرده بود، از بین میرفتند. توزیعها KillUserProcesses=no را در /etc/systemd/logind.conf ارائه کردند و پاسخ پشتیبانیشده loginctl enable-linger <user> است. یک پیشفرض در یک پروژه، عادتی را که میلیونها نفر به آن متکی بودند تغییر داد، که این همان معنای عملی «بخش زیادی از فضای کاربری در یک مکان» است.
یک وابستگی پیشفرض، یک سطح امنیتی است. در مارس 2024، درب پشتی (backdoor) در xz-utils، سرویس sshd را در Debian و Ubuntu هدف قرار داد. نسخه اصلی OpenSSH به libsystemd لینک نمیشود. آن توزیعها آن را وصله کردند تا sshd بتواند آمادگی خود را به systemd گزارش دهد، و libsystemd کتابخانه liblzma را فراخوانی کرد، جایی که درب پشتی در آن قرار داشت. پروتکل آمادگی خود یک دیتاگرام واحد است که به سوکت نامگذاریشده در $NOTIFY_SOCKET ارسال میشود، بنابراین هرگز نیازی به هیچ کتابخانهای برای آن نبود. پاسخ systemd بارگذاری کتابخانههای فشردهسازی با dlopen بود، بنابراین آنها دیگر بهطور پیشفرض لینک نمیشوند. یک کلاس مرتبط از باگها شکل مشابهی را نشان میدهد: در سال 2017، مقدار User= که با یک رقم شروع میشد، نامعتبر تلقی میشد و واحد به جای شکست خوردن، با دسترسی root اجرا میشد، بنابراین یک غلط تایپی به یک ارتقای سطح دسترسی تبدیل شد. نسخههای بعدی از اجرای آن واحد خودداری میکنند.
تاریخچه در خط فرمان systemctl شما
هر مشکلی که در بالا ذکر شد، اکنون به یک دستورالعمل در فایلی تبدیل شده است که میتوانید آن را بخوانید.
- بوت ترتیبی به
After=وWants=تبدیل شده است وsystemd-analyze critical-chainنشان میدهد چه چیزی واقعاً بوت شما را معطل کرده است. - آمادگی (Readiness) به
Type=notifyتبدیل شده است، جایی که سرویس در صورت آماده بودن برای ارائه خدمات،READY=1را در$NOTIFY_SOCKETمینویسد.Type=forkingباPIDFile=همچنان برای دیمونهای قدیمی وجود دارد و این همان نوعی است که باstart operation timed out. Terminating.شکست میخورد، زمانی که فایل PID هرگز ظاهر نمیشود. - نظارت (Supervision) به cgroup تبدیل شده است، بنابراین
Restart=on-failureباRestartSec=جایگزین اسکریپتهای wrapper میشود وStartLimitBurst=از اجرای بیپایان یک حلقه کرش (crash loop) جلوگیری میکند. - inetd به یک unit از نوع
.socketتبدیل شده است که در کنار unit مربوط به.serviceقرار میگیرد. ulimitبهMemoryMax=،CPUQuota=وTasksMax=تبدیل شده است.- خط
su - appuser -cدر اسکریپتهای init بهUser=،NoNewPrivileges=yesوProtectSystem=strictتبدیل شده است، بنابراین اجرای یک سرویس با کاربری بدون دسترسی ریشه به جای یک کار اضافه، شکل پیشفرض یک unit است.
[Unit]
Description=Example API
Wants=network-online.target
After=network-online.target postgresql.service
Requires=postgresql.service
[Service]
Type=notify
ExecStart=/usr/local/bin/exampled
Restart=on-failure
RestartSec=2
MemoryMax=512M
CPUQuota=50%
TasksMax=128
User=exampled
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
StateDirectory=exampled
[Install]
WantedBy=multi-user.targetیک خط در آن فایل وجود دارد که همه یکبار در آن اشتباه میکنند. Requires=postgresql.service یک الزام است، نه یک ترتیب: این دستور میگوید اگر Postgres شکست بخورد، unit شما نیز شکست میخورد، و نمیگوید که Postgres را اول اجرا کن. بدون After=postgresql.service، هر دو در یک لحظه شروع میشوند و سرویس شما به پورتی متصل میشود که هنوز هیچچیز روی آن گوش نمیدهد. این دو مورد عمداً از هم جدا هستند، زیرا گاهی اوقات یکی را بدون دیگری میخواهید. ProtectSystem=strict فایلسیستم را برای این سرویس فقطخواندنی (read-only) میکند، و به همین دلیل است که StateDirectory= وجود دارد: این دستور یک مسیر قابلنوشتن در /var/lib به سرویس میدهد.
واضحترین مکان برای مشاهده سال 2005 روی یک سرور 2026، SSH در Ubuntu 24.04 است که از آگوست 2026 همراه با systemd 255 عرضه میشود. ssh.service بهصورت پیشفرض توسط سوکت فعال میشود: ssh.socket سوکت شنونده را نگه میدارد و sshd زمانی شروع میشود که یک اتصال برسد. بنابراین Port 2222 در /etc/ssh/sshd_config هیچ اثری ندارد، زیرا sshd آن فرآیندی نیست که پورت را باز کرده است. این تغییر باید در unit سوکت اعمال شود.
sudo systemctl edit ssh.socket[Socket]
ListenStream=
ListenStream=2222مقدار خالی ListenStream=، مقداری را که از unit بستهبندیشده به ارث رسیده است پاک میکند. اگر آن را حذف کنید، هر دو پورت را خواهید داشت، زیرا systemd به جای جایگزینی، به لیست اضافه میکند. سپس اعمال و بررسی کنید، و در تمام مدت یک نشست SSH دوم را باز نگه دارید:
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo ss -lntp | grep 2222ss باید یک سوکت روی پورت 2222 را نشان دهد که متعلق به systemd است، نه sshd. این طراحی launchd است که بیست سال بعد روی VPS شما پیاده شده است. اگر رفتار قدیمی را ترجیح میدهید، sudo systemctl disable --now ssh.socket و به دنبال آن sudo systemctl enable --now ssh.service به شما یک sshd طولانیمدت میدهد که دوباره Port را از تنظیمات خودش میخواند.
اینکه با کدامیک از این جزئیات مواجه شوید به نسخهای که اجرا میکنید بستگی دارد، بنابراین ارزش دارد که پیش از برنامهریزی برای ارتقا، تفاوت بین یک نسخه LTS و یک نسخه interim در اوبونتو را بدانید. در بیش از یک ماشین، این واقعیت که یک فایل unit در همه جا یکسان است، دلیلی است که مدیریت چندین سرور از یک مکان اکنون یک مسئله پیکربندی است تا یک مسئله اسکریپتنویسی شل. و هنگامی که unitهای خود را مینویسید، جفت سرویس و تایمر کاری را انجام میدهد که در سال 2009 بین یک اسکریپت init و یک خط cron تقسیم میکردید.
FAQ
چرا توزیعهای لینوکس، systemd را جایگزین SysV init کردند؟
به دو دلیل مهندسی و یک دلیل مربوط به نگهداری. SysV init سرویسها را بر اساس نام فایل مرتب میکرد که در واقع یک موقعیت مکانی بود، نه یک وابستگی؛ همچنین این سیستم ردِ هر دیمونی که از والد خود جدا (fork) میشد را گم میکرد، به همین دلیل فایلهای PID قدیمی ممکن بود باعث توقف پردازش اشتباه شوند. systemd مشکل ترتیببندی را با فعالسازی سوکت و دستورالعملهای وابستگی، و مشکل ردیابی را با استفاده از control groups حل کرد. دلیل مربوط به نگهداری، سرعت را تعیین کرد: یک فایل unit در تمام توزیعها کار میکند، بنابراین پروژههای بالادستی یک فایل .service ارائه دادند و مسئولان نگهداری توزیعها دیگر نیازی به نوشتن اسکریپت shell برای هر بسته نداشتند. Fedora 15 در مه 2011 تغییر کرد و Ubuntu 15.04 آخرین توزیع بزرگ بود که تا آوریل 2015 مقاومت کرد.
آیا systemd یک باینری غولپیکر است؟
خیر. درخت سورس این پروژه، برنامههای مجزای بسیاری را کامپایل میکند. PID 1 همان /usr/lib/systemd/systemd است، در حالی که journald، logind و udevd پردازشهای جداگانهای با باینریهای مخصوص خود هستند؛ برای مشاهده آنها در سیستم خود، ls /usr/lib/systemd/ را اجرا کنید. انتقادی که همچنان باقی است، مربوط به یکپارچگی انتشار (release coupling) است نه اندازه باینری: این برنامهها با هم منتشر میشوند و رابطهای داخلی مشترکی دارند، بنابراین توزیعها تمایل دارند آنها را به صورت یک مجموعه دریافت کنند و نرمافزارهایی مانند GNOME انتظار دارند که logind بهطور خاص در دسترس باشد.
آیا هنوز میتوانم لینوکس را بدون systemd اجرا کنم؟
بله. Devuan از sysvinit استفاده میکند، Gentoo بهصورت پیشفرض از OpenRC بهره میبرد، Void از runit استفاده میکند، Alpine از busybox init به همراه OpenRC استفاده میکند و Slackware اسکریپتهای سبک BSD را حفظ کرده است. هزینه این کار، تلاش برای حفظ سازگاری است. نرمافزارهای دسکتاپی که انتظار logind را دارند به elogind نیاز دارند که همان logind متعلق به systemd است که به عنوان یک بسته مستقل نگهداری میشود، و مقدار فزایندهای از نرمافزارهای سرور اکنون فقط فایل .service ارائه میدهند، بنابراین باید اسکریپت راهاندازی را خودتان بنویسید و نگهداری کنید.
چرا journal به جای فایل متنی ساده، باینری است؟
زیرا journald فیلدهای ساختاریافته را همراه با یک ایندکس ذخیره میکند که امکان فیلتر کردن بر اساس unit، اولویت و متادیتایی را فراهم میکند که برنامه فرستنده نمیتواند آن را جعل کند: journald به جای اعتماد به خط لاگ، unit، cgroup و UID واقعی را خودش ثبت میکند. بهای این کار این است که برای خواندن آن به journalctl نیاز دارید، حتی در سیستم نجات (rescue system) که در آن با استفاده از journalctl --directory /mnt/var/log/journal به دیسک mount شده اشاره میکنید. اگر متن ساده هم میخواهید، ForwardToSyslog=yes را در /etc/systemd/journald.conf تنظیم کنید.
چه چیزی جایگزین ویرایش اسکریپتهای /etc/init.d من شده است؟
فایلهای drop-in. فایل unit موجود در /usr/lib/systemd/system/ را ویرایش نکنید، زیرا با ارتقای بسته، تغییرات شما بازنویسی میشود. دستور sudo systemctl edit nginx.service را اجرا کنید تا systemd فایل /etc/systemd/system/nginx.service.d/override.conf را ایجاد کند که با unit اصلی بستهبندیشده ادغام میشود. systemctl cat nginx.service نتیجه ادغامشده را نشان میدهد و systemd-delta تمام overrideهای موجود در سیستم را فهرست میکند. پس از هر ویرایش دستی، sudo systemctl daemon-reload را اجرا کنید، در غیر این صورت دستور بعدی Warning: The unit file, source configuration file or drop-ins of nginx.service changed on disk. را چاپ میکند.