SSD Nodes Learn Hosting plans →
Panduan Matt ConnorOleh Matt Connor · Dikemas kini 2026-08-29

Cara Pilih Type= systemd: simple, forking, notify

Servis systemd aktif tapi daemon mati? Ketahui cara memilih Type= yang betul untuk simple, forking, notify atau oneshot bagi mengelak ralat pemantauan PID utama unit anda.

Mengapa systemd melaporkan unit sebagai aktif sedangkan proses telah mati

Unit servis systemd kekal active selagi proses yang ditetapkan oleh systemd sebagai proses utama masih hidup, dan Type= dalam bahagian [Service] menentukan proses yang mana satu. Jika anda memilih nilai yang salah, systemd akhirnya memantau shell wrapper atau proses induk yang jangka hayatnya singkat, sedangkan daemon yang anda perlukan telah mati di dalam unit yang sama. Unit tersebut melaporkan perkara yang benar tentang proses yang diarahkan untuk dipantau.

Menukar polisi restart tidak akan membantu dalam situasi ini. Restart= bertindak apabila proses utama keluar, jadi Restart=always tidak akan dicetuskan selagi PID (process identifier) utama merujuk kepada sesuatu yang masih berjalan. Betulkan Type= terlebih dahulu. Tindakan systemd selepas proses utama benar-benar keluar adalah keputusan berasingan, yang diterangkan dalam panduan untuk Restart= dan RestartSec=.

Apakah yang ditentukan oleh Type=

Setiap nilai Type= menjawab dua soalan serentak. Bilakah systemd menganggap unit ini telah bermula, dan proses manakah yang merupakan proses utama.

Jawapan pertama mengawal aturan. Unit yang menamakan unit anda dalam After= akan menunggu sehingga systemd mengisytiharkan unit anda telah bermula. Type= yang melaporkan "started" terlalu awal membolehkan unit yang bergantung berjalan sebelum servis anda bersedia untuk melayan permintaan.

Jawapan kedua mengawal penyeliaan. systemd meletakkan setiap proses yang dijana oleh unit ke dalam cgroup (control group), iaitu ciri kernel yang mengumpulkan proses supaya ia boleh dihadkan dan ditamatkan bersama-sama. cgroup ialah cara systemctl stop melakukan pembersihan: KillMode= secara lalai menggunakan control-group, jadi menghentikan unit akan menghantar isyarat kepada setiap proses di dalamnya. PID utama adalah lebih khusus. Ia merupakan proses tunggal yang mana penamatannya akan menamatkan unit tersebut, dan status keluar proses itu menjadi hasil bagi unit berkenaan. Membaca cgroup seolah-olah ia adalah PID utama merupakan punca kekeliruan.

Type=simple melaporkan servis bermula sebelum binari dijalankan

Type=simple ialah tetapan lalai apabila ExecStart= ditetapkan dan tiada Type= mahupun BusName= disertakan. systemd mencipta proses tersebut, menganggap unit itu telah bermula serta-merta, dan melayan proses tersebut sebagai PID utama. Unit susulan akan bermula dengan segera, walaupun sebelum binari servis sempat dilaksanakan.

Perincian terakhir itu menjelaskan situasi yang sering mengejutkan. Kesilapan ejaan pada laluan ExecStart= masih menghasilkan tugasan permulaan yang berjaya, dan kegagalan hanya muncul seketika kemudian apabila pelaksanaan gagal. systemd merekodkan kes tersebut dengan kod keluar 203, yang dalam jadualnya sendiri dinamakan EXEC dan ditakrifkan sebagai kegagalan untuk melaksanakan binari servis. Oleh itu, systemctl start yang kembali tanpa ralat tidak membuktikan bahawa binari anda wujud.

Gunakan simple untuk program yang kekal di latar depan dan tidak pernah mengalihkan dirinya ke latar belakang. Ini merangkumi kebanyakan daemon moden dan hampir apa sahaja yang anda tulis sendiri.

Type=exec menunggu program untuk benar-benar bermula

Type=exec adalah simple dengan satu langkah tambahan. systemd menganggap unit telah bermula hanya selepas proses fork dan pelaksanaan binari berjaya dilakukan. Binari yang tiada atau User= yang tidak dapat diselesaikan kini akan menyebabkan tugasan permulaan gagal, bukannya melaporkan kejayaan dan gagal secara senyap sejurus selepas itu.

Type=exec diperkenalkan dalam systemd 240, jadi setiap pengedaran pelayan semasa memilikinya. Ubuntu 24.04 menyertakan systemd 255 dan Debian 13 menyertakan systemd 257, setakat Ogos 2026. Semak versi anda dengan systemctl --version.

Kosnya ialah satu langkah penyelarasan tambahan semasa permulaan. Kelebihannya ialah status keluar yang tepat daripada systemctl start. Bagi program latar depan, utamakan exec berbanding simple.

Type=forking, dan bagaimana PID utama hilang

Type=forking memberitahu systemd bahawa proses dalam ExecStart= akan melakukan fork kepada child dan kemudian keluar dengan sengaja. systemd menunggu proses pertama itu keluar dan hanya selepas itu menganggap unit tersebut telah bermula. Child yang ditinggalkan itulah daemon. Tabiat ini bermula sejak era SysV, apabila tiada apa-apa yang menyelia daemon sebaik sahaja skrip init kembali dan fail PID menjadi satu-satunya rekod tentang apa yang sedang berjalan, satu batasan yang terletak berhampiran dengan punca mengapa systemd menggantikan skrip init.

Kesukarannya ialah identiti. Proses yang dilancarkan oleh systemd sudah tiada, jadi systemd perlu menentukan yang mana satu antara yang terselamat adalah proses utama. Tetapkan PIDFile= kepada fail yang ditulis oleh daemon, biasanya laluan di bawah /run, dan systemd akan membaca PID daripadanya. systemd juga menyemak sama ada PID dalam fail tersebut merujuk kepada proses yang memang milik servis ini, jadi fail lama yang menamakan proses yang tidak berkaitan akan ditolak dan bukannya dipercayai.

Tanpa PIDFile=, GuessMainPID= akan terpakai, dan ia ditetapkan kepada yes secara lalai. Tekaan ini hanya boleh dipercayai apabila servis stabil kepada satu proses tunggal. Manual menyatakan hadnya dengan jelas: jika daemon terdiri daripada lebih daripada satu proses, tekaan tersebut boleh menjadi salah, dan pengesanan kegagalan akan berhenti berfungsi. Unit juga boleh berakhir dengan PID utama 0, yang bermaksud systemd tidak mempunyai apa-apa untuk diselia.

Kebanyakan daemon yang melakukan fork juga mempunyai suis yang mengekalkan proses di latar depan (foreground). Gunakan suis tersebut dengan Type=exec dan padamkan baris PIDFile=. Kurang bahagian yang bergerak bermakna kurang cara untuk kehilangan PID.

Type=oneshot untuk kerja yang selesai

Type=oneshot menjangkakan proses berjalan dan kemudian tamat. systemd hanya menganggap unit telah dimulakan selepas proses tersebut tamat, yang menjadikan oneshot bentuk yang sesuai untuk sebarang tugasan yang perlu ditunggu oleh unit lain. Ini juga merupakan tetapan lalai tersirat apabila unit tidak menentukan Type= mahupun ExecStart=.

Dua kelakuan adalah khusus untuk oneshot. Ia merupakan satu-satunya jenis yang menerima lebih daripada satu baris ExecStart=, dan baris-baris tersebut dijalankan mengikut urutan. Had masa permulaannya juga dilumpuhkan secara lalai, jadi oneshot yang tergantung akan menunggu selama-lamanya melainkan anda menetapkan TimeoutStartSec= sendiri.

Selepas proses tamat, unit kembali kepada status tidak aktif. RemainAfterExit=yes mengekalkannya sebagai active tanpa sebarang proses yang berjalan. Ini adalah versi sengaja bagi simptom di bahagian atas halaman ini, dan ia adalah betul apabila tugas unit tersebut adalah untuk meninggalkan status tertentu dan bukannya mengekalkan sesuatu agar terus berjalan: memuatkan set peraturan firewall, atau menghidupkan tindanan (stack) kontena. Ia merupakan corak di sebalik tindanan Docker Compose yang kembali selepas but semula, di mana unit menjalankan arahan compose, tamat, dan kekal aktif kerana kontena yang dimulakannya terus berjalan walaupun unit tersebut telah tamat. Unit oneshot juga merupakan apa yang dicetuskan oleh jadual, yang merupakan separuh lagi daripada menjalankan tugasan pada pemasa systemd dan bukannya cron.

Type=notify membolehkan servis menyatakan bila ia sudah bersedia

Type=notify memindahkan keputusan tersebut kepada servis. systemd menahan tugasan permulaan sehingga proses menghantar READY=1 melalui soket Unix yang laluannya diterima dalam pemboleh ubah persekitaran NOTIFY_SOCKET. Antara muka C untuk ini ialah sd_notify(3), dan banyak pelayan sudah menyokongnya.

Ini merupakan jawapan yang tepat kepada soalan "adakah ia sudah bermula". simple dan exec melaporkan servis telah bermula sebelum servis tersebut membaca konfigurasi atau membuka soket pendengarannya, jadi unit yang bergantung kepadanya mungkin bermula terlalu awal dan gagal pada sambungan pertama. notify melaporkan servis telah bermula tepat pada saat servis itu sendiri menyatakan ia sudah bersedia.

systemd hanya menerima mesej tersebut daripada proses utama, itulah maksud NotifyAccess=main, dan Type=notify membayangkan perkara yang sama. Jika mesej datang daripada proses anak atau pembantu, tetapkan NotifyAccess=all. Skrip shell boleh memanggil systemd-notify --ready, tetapi ia berjalan sebagai proses berasingan yang singkat hayatnya, jadi ia memerlukan NotifyAccess=all dan systemd mungkin tidak dapat mengaitkan mesej yang penghantarnya telah pun tamat. Servis yang menggunakan protokol itu sendiri adalah lebih boleh dipercayai.

Dua tetapan berkaitan yang perlu diketahui. Type=notify-reload, yang tersedia sejak systemd 253, melanjutkan jabat tangan yang sama kepada muat semula (reloads), jadi systemctl reload akan kembali apabila servis melaporkan muat semula telah selesai dan bukannya kembali sebaik sahaja isyarat dihantar. WatchdogSec= meminta servis pemberitahuan untuk menghantar mesej keep-alive pada selang masa tertentu, dan systemd akan menganggap kegagalan memenuhi tempoh masa tersebut sebagai satu kegagalan servis.

Type=dbus dan Type=idle

Type=dbus menunggu sehingga servis mengambil nama pada D-Bus, iaitu bas mesej yang digunakan oleh servis sistem dan desktop untuk berkomunikasi antara satu sama lain. Ia memerlukan BusName=, dan ia menjadi lalai sebaik sahaja BusName= ditetapkan. Gunakannya hanya untuk servis yang benar-benar mendaftarkan nama bas.

Type=idle berkelakuan seperti simple, tetapi ia melengahkan pelaksanaan program sehingga tugasan yang beratur telah dihantar, dengan had masa lima saat. Ia wujud supaya output konsol semasa but tidak bercampur dengan mesej status. Ia bukan alat penyusunan, dan ia tidak sesuai digunakan pada servis biasa.

Mengapa skrip pembungkus menyebabkan systemd tersalah mengesan PID

Berikut adalah bentuk yang menghasilkan simptom asal.

[Service]
Type=simple
ExecStart=/opt/app/run.sh
#!/bin/bash
export APP_ENV=production
/opt/app/bin/server --config /etc/app.yaml &
/opt/app/bin/exporter --port 9101

systemd merekodkan shell sebagai PID utama. Shell kekal hidup sementara exporter berjalan di latar depan. Jika server mati, shell tidak menyedarinya, jadi PID utama masih hidup, unit masih dianggap active, dan Restart= tidak mempunyai apa-apa untuk diambil tindakan. Kedua-dua proses kekal dalam cgroup unit sepanjang masa, jadi systemctl stop masih melakukan pembersihan dengan betul. Penyeliaan yang terjejas, bukan pembersihan.

Penyelesaiannya bergantung pada berapa banyak proses yang berjalan lama dalam unit tersebut.

Jika hanya ada satu, gantikan shell dengan proses tersebut.

#!/bin/bash
export APP_ENV=production
exec /opt/app/bin/server --config /etc/app.yaml

exec menggantikan shell dengan program yang dinamakan dan mengekalkan PID yang sama, jadi PID yang direkodkan oleh systemd kini milik daemon tersebut. Lebih baik lagi, buang pembungkus itu. Environment= dan EnvironmentFile= membawa pemboleh ubah, dan ExecStartPre= membawa langkah penyediaan, supaya systemd boleh melancarkan daemon secara terus dan mengetahui PID-nya melalui pembinaan.

Jika ada dua, tiada PID tunggal yang mewakili unit tersebut. Pecahkan kepada dua unit dan susun mengikut urutan dengan After= dan Wants=. Satu unit bagi setiap proses adalah aturan yang diselia dengan baik oleh systemd, dan ia merupakan satu-satunya cara setiap proses mendapat gelagat mulakan semula (restart) yang tersendiri.

Perubahan ExitType=cgroup

ExitType= telah ditambah dalam systemd 250. Nilai lalai ialah main: unit dianggap berhenti apabila proses utama tamat. Dengan ExitType=cgroup, unit dianggap berjalan selagi mana-mana proses dalam cgroup-nya masih hidup.

[Service]
Type=simple
ExitType=cgroup
ExecStart=/opt/app/launcher

Ini menyelesaikan satu masalah khusus. Pelancar yang memulakan kerja sebenar dan kemudian keluar akan menyebabkan systemd menganggap unit tersebut telah berhenti dan mematikan proses yang masih hidup di bawah ExitType=main. Dengan ExitType=cgroup, unit tersebut mengikuti keseluruhan kumpulan sebaliknya.

Fahami perkara yang tidak diselesaikan oleh tetapan ini. ExitType=cgroup mengekalkan unit aktif selagi sekurang-kurangnya satu proses masih hidup, jadi unit yang memegang dua daemon akan kekal aktif selepas salah satu daripadanya mati. Ia membetulkan kes pelancar. Ia tidak menukar satu unit menjadi penyelia kepada beberapa proses bebas. ExitType= juga tidak boleh digabungkan dengan Type=oneshot.

Cgroup juga merupakan tempat perakaunan sumber dilakukan, jadi had seperti MemoryMax= dan CPUQuota= terpakai kepada setiap proses yang dijana oleh unit tersebut, tidak kira apa yang Type= nyatakan tentang PID utama. Bahagian tersebut diterangkan dalam mengehadkan memori dan CPU servis dengan systemd.

Cara mencari proses yang sebenarnya dipantau oleh systemd

Lakukan langkah ini pada unit yang sedang anda nyahpepijat, mengikut urutan. Baca perkara yang dimuatkan oleh systemd, kemudian baca perkara yang dijejakinya, dan bandingkan dengan jadual proses.

systemctl cat app.service
systemctl show -p Type,ExitType,GuessMainPID,PIDFile,MainPID,RemainAfterExit app.service

systemctl cat mencetak fail unit bersama setiap drop-in yang terpakai padanya, supaya anda membaca perkara yang dimuatkan oleh systemd dan bukannya fail yang anda ingat pernah disunting. systemctl show mencetak nilai berkesan, termasuk nilai lalai yang tidak pernah anda tulis. Catatkan nilai MainPID sebelum meneruskan.

systemd-cgls --unit=app.service
ps -o pid,ppid,stat,etime,args -p "$(systemctl show -p MainPID --value app.service)"

systemd-cgls menyenaraikan setiap proses dalam cgroup unit tersebut. Baris ps menerangkan proses tunggal yang diselia oleh systemd. Baca kedua-duanya bersama-sama. MainPID bernilai 0 bermakna systemd tidak mempunyai proses untuk dipantau. MainPID yang merujuk kepada shell sementara cgroup juga mengandungi daemon anda adalah kes wrapper yang dinyatakan di atas. Cgroup dengan lebih banyak proses daripada yang dijangkakan bermakna terdapat pelancar atau daemon forking yang terlibat.

systemctl status app.service
journalctl -u app.service -b

systemctl status mencetak baris status dan pepohon cgroup bersama-sama, jadi ia sering menjawab kedua-dua soalan serentak. journalctl -u yang dihadkan kepada but semasa dengan -b menunjukkan peristiwa mula dan henti yang direkodkan oleh systemd untuk unit tersebut, berserta kod keluar yang dikesannya. Jika daemon menulis ke fail lognya sendiri dan bukannya journal, baca fail tersebut juga, kerana systemd hanya boleh merekodkan perkara yang sampai kepadanya.

Apabila anda menukar Type=, muat semula dan mulakan semula.

systemd-analyze verify /etc/systemd/system/app.service
sudo systemctl daemon-reload
sudo systemctl restart app.service

systemd-analyze verify menghurai fail dan melaporkan tetapan yang tidak dapat diterimanya. daemon-reload menyebabkan systemd membaca semula fail unit daripada cakera. Type= yang diubah tidak terpakai pada unit yang sedang berjalan, jadi mulakan semula adalah wajib, bukan pilihan.

Kemudian uji perubahan tersebut. Ambil PID proses yang sebenarnya anda perlukan daripada systemd-cgls dan tamatkan proses tersebut. Jalankan systemctl is-active app.service serta-merta selepas itu. Jika Type= adalah betul, unit tersebut akan keluar daripada status aktif. Jika ia kekal aktif, systemd masih memantau sesuatu yang lain.

Jenis Type= perkhidmatan systemd yang manakah perlu anda gunakan

  • Program yang kekal di latar depan (foreground): Type=exec.
  • Program yang menyokong pemberitahuan kesediaan (readiness notification): Type=notify, dan notify-reload jika ia turut mengesahkan muat semula (reload).
  • Daemon yang perlu beralih ke latar belakang (background): Type=forking dengan PIDFile=, atau suis latar depannya dengan Type=exec.
  • Skrip yang melakukan kerja dan kemudian keluar (exit): Type=oneshot, ditambah dengan RemainAfterExit=yes apabila tujuannya adalah untuk meninggalkan status (state) di belakang.
  • Pelancar (launcher) yang keluar sementara anak prosesnya terus berjalan: Type=simple dengan ExitType=cgroup.

Jika anda tidak pasti jenis yang diperlukan oleh daemon pihak ketiga, baca fail unit yang dibekalkan terlebih dahulu. Menjalankan systemctl cat pada unit yang dibekalkan oleh pengedaran (distribution) akan menunjukkan Type= yang dipilih oleh pembangun asal, dan pilihan tersebut telah diuji oleh lebih ramai orang berbanding pilihan anda.

FAQ

Mengapakah unit systemd saya kekal aktif apabila proses telah mati?

Kerana proses yang dianggap oleh systemd sebagai proses utama masih hidup. systemd memantau satu PID bagi setiap servis, yang dipilih berdasarkan Type=, bukannya setiap proses dalam cgroup unit tersebut. Skrip pembungkus (wrapper script) yang dimulakan dengan Type=simple biasanya menjadi punca: shell tersebut adalah PID utama, jadi unit kekal aktif apabila daemon yang dilancarkan oleh shell di latar belakang telah tamat. Jalankan systemctl show -p MainPID app.service, kemudian senaraikan cgroup unit tersebut dengan systemd-cgls --unit=app.service, dan bandingkan kedua-duanya.

Apakah perbezaan antara Type=simple dan Type=exec?

Type=simple menganggap unit telah bermula sebaik sahaja systemd mencipta proses tersebut, sebelum binari dilaksanakan, jadi laluan yang salah dalam ExecStart= masih memberikan tugasan permulaan yang berjaya diikuti dengan kegagalan. Type=exec menunggu sehingga pelaksanaan berjaya, supaya kegagalan dilaporkan oleh tugasan permulaan itu sendiri. Kedua-duanya menganggap proses yang sama sebagai PID utama. Type=exec memerlukan systemd 240 atau lebih baharu.

Adakah saya masih memerlukan PIDFile= dengan Type=forking?

Ya, pada bila-bila masa daemon menulis fail tersebut. Tanpanya, systemd akan kembali kepada GuessMainPID=, yang merupakan satu tekaan dan hanya boleh dipercayai untuk servis yang menetap dalam satu proses tunggal. Apabila tekaan itu salah atau mustahil, pengesanan kegagalan dan permulaan semula automatik akan berhenti berfungsi untuk unit tersebut. Halakan PIDFile= ke laluan tepat yang ditulis oleh daemon, biasanya di bawah /run.

Bilakah saya perlu menggunakan RemainAfterExit=yes?

Apabila tujuan unit tersebut adalah untuk menukar keadaan sistem dan bukannya untuk memastikan sesuatu proses terus berjalan. Unit Type=oneshot yang memuatkan peraturan firewall atau memulakan timbunan kontena akan tamat sebaik sahaja tugasnya selesai, dan tanpa RemainAfterExit=yes unit tersebut menjadi tidak aktif, yang menyebabkan systemctl stop tidak mempunyai apa-apa untuk dihentikan dan tiada cara untuk menjalankan pembersihan ExecStop=. Dengan tetapan ini, unit kekal aktif tanpa sebarang proses, dan itu adalah tujuan yang diingini di sini.

Adakah menukar Type= memerlukan daemon-reload?

Ya, dan juga perlu memulakan semula unit tersebut. systemctl daemon-reload menyebabkan systemd membaca semula fail unit pada cakera, tetapi instans yang sedang berjalan mengekalkan Type= yang digunakan semasa ia bermula. Jalankan sudo systemctl daemon-reload dan kemudian sudo systemctl restart app.service sebelum melakukan ujian, jika tidak, anda masih memerhatikan gelagat penyeliaan yang lama.