National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 72 24°C PM2.5 13
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 17

開機自動執行:systemd 與 cron

讓程式在開機時啟動、當掉時重新啟動、定時執行
Interaction導覽 25 minute(s)

Learning Objectives

  • 寫出 systemd 的 unit 檔,讓 Python 程式開機自動執行、意外結束時自動重新啟動
  • 用 systemctl 與 journalctl 啟用、查看狀態與讀取程式的輸出
  • 把程式改寫成適合長時間執行的服務:處理感測器錯誤、輸出立刻送出
  • 讀懂並寫出 cron 的五個時間欄位,安排定時執行的工作
  • 依需求選擇 systemd 服務或 cron

拔掉螢幕之後,程式誰來啟動

第 14 課的記錄程式、第 15 課的網頁、第 16 課的 MQTT 程式,到目前為止都是你連上終端機、手動執行的。 真的放到教室裡的樹莓派沒有人顧,會遇到:

  • 停電又復電:開機之後,程式要自己跑起來。
  • 程式意外結束:感測器鬆脫、網路斷線、程式有 bug,要能自動重新啟動。
  • 定時工作:每天凌晨把資料匯出備份、每 10 分鐘拍一次快照。

Linux 有兩個常用的工具:

工具 適合
systemd 服務 一直執行的程式(記錄程式、網頁伺服器、MQTT 用戶端),開機啟動、當掉重啟、統一看日誌
cron 在固定時間執行一次就結束的工作(每天備份、每小時統計)

Raspberry Pi OS 和 Ubuntu 一樣用 systemd 管理開機流程(「Linux 系統管理」軌也會用到 systemctl)。

systemd 的 unit 檔

要讓 systemd 管理你的程式,寫一個 unit 檔放在 /etc/systemd/system/,檔名就是服務的名稱,例如 envmon.service:

[Unit]
Description=Classroom environment logger
After=network-online.target
Wants=network-online.target

[Service]
User=student
WorkingDirectory=/home/student/envmon
ExecStart=/home/student/envmon/.venv/bin/python /home/student/envmon/logger.py
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
段落與設定 意思
Description 說明文字,systemctl status 會顯示
After/Wants=network-online.target 等網路連上之後再啟動(程式要連 MQTT broker 時需要)
User 用哪個使用者的身分執行;不寫就是 root,權限太大
WorkingDirectory 工作目錄:程式裡寫相對路徑的 log.csv 會存在這裡
ExecStart 要執行的指令。用絕對路徑;用了第 04 課的虛擬環境,就寫虛擬環境裡 python 的完整路徑
Restart=on-failure 程式以非 0 的結束碼結束、被訊號終止等異常情況時,自動重新啟動
RestartSec=5 重啟前等 5 秒(預設只等 0.1 秒),避免一直失敗時瘋狂重啟
WantedBy=multi-user.target enable 之後,開機進到一般的多使用者模式時就啟動

範例裡的使用者是 student、資料夾是 /home/student/envmon,換成你自己的。

把記錄程式變成服務

在樹莓派的終端機操作。輸出經過節錄,日期與程序編號(PID)每台機器都不一樣。

  1. 1

    先確認程式手動執行沒問題

    服務只是「替你執行同一個指令」。手動執行就出錯的程式,交給 systemd 也一樣會出錯。看到輸出後按 Ctrl+C 停止。

  2. 2

    建立 unit 檔

    /etc/systemd/system/ 只有管理員能寫,所以用 sudo。在 nano 裡貼上上一段的內容,Ctrl+O 存檔、Ctrl+X 離開,再用 cat 確認。

  3. 3

    讓 systemd 讀新的設定,啟用並立刻啟動

    新增或修改 unit 檔之後都要 daemon-reload。enable 設定開機自動啟動,加上 --now 同時立刻啟動。

  4. 4

    看服務的狀態

    Loaded 那一行的 enabled 表示開機會啟動;Active: active (running) 表示正在執行。

  5. 5

    讀程式的輸出(日誌)

    服務的 print 輸出會進到 systemd 的日誌。-u 指定服務,-f 持續顯示新的內容(Ctrl+C 離開)。

  6. 6

    試試看:程式被強制結束,會自己回來

    用 kill -9 強制結束程式(模擬當機)。等 5 秒(RestartSec)再看狀態,PID 換了一個新的號碼,表示 systemd 已經重新啟動它。

  7. 7

    其他常用的指令

    指令 作用
    sudo systemctl restart envmon.service 改了程式之後重新啟動
    sudo systemctl stop envmon.service 停止(開機時仍會啟動)
    sudo systemctl disable envmon.service 取消開機啟動
這段導覽完成了

適合當服務的程式

交給 systemd 的程式沒有人盯著看,寫法要注意兩件事:

  1. 輸出要立刻送出:Python 發現輸出不是終端機時,會先把 print 的內容存在緩衝區,累積一段才一次寫出, 日誌裡就會很久看不到新的內容。print(..., flush=True)(或在 unit 檔加 Environment=PYTHONUNBUFFERED=1)讓每一行立刻送出。
  2. 能處理的錯誤自己處理:感測器鬆脫(smbus2 會丟出 OSError)、接觸不良偶爾讀到離譜的數值,不應該讓整個記錄程式停掉。 用 try/except 接住、記下錯誤、略過這一筆,繼續下一輪。Restart=on-failure 是最後一道防線,不是第一道。

下一個範例每秒讀一次溫度、寫進 env.csv。第 2 次讀取時感測器接觸不良,讀到 118.6°C: 程式檢查讀值合不合理,不合理就丟出 ValueError,主迴圈接住它、略過這一筆。另外加一顆心跳燈,每跑一輪就切換一次, 站在教室裡看一眼就知道程式還活著。

逐步看:讀值異常,程式繼續跑

溫度感測器在 1 秒左右出現一次異常的讀值。看第 12 行丟出的例外怎麼一路傳回第 19 行的呼叫處、被第 23 行接住,以及心跳燈每一輪都換一次狀態。

cron:在固定的時間執行

cron 是 Linux 的排程服務,依照每個使用者的 crontab(排程表)在指定的時間執行指令。 每一行是五個時間欄位加上要執行的指令:

┌───── 分(0~59)
│ ┌───── 時(0~23)
│ │ ┌───── 日(1~31)
│ │ │ ┌───── 月(1~12)
│ │ │ │ ┌───── 星期(0~7,0 與 7 都是星期日)
│ │ │ │ │
*/10 * * * *  指令
寫法 意思 例子
* 每一個值 時欄寫 *:每個小時
a-b 範圍 8-17:8 點到 17 點
a,b 列舉 0,30:0 分與 30 分
*/n 每隔 n */15:0、15、30、45 分

例如 30 7 * * 1-5 是「星期一到星期五的 7:30」,0 2 * * * 是「每天凌晨 2:00」。 另外 @reboot 可以代替五個欄位,表示 cron 啟動時(開機)執行一次。

cron 用的是系統的時區;樹莓派設成台灣時間,0 2 * * * 就是台灣的凌晨兩點。 下一個範例寫一個小函式,把一個欄位展開成實際的數字,看 cron 是怎麼理解這些符號的。

逐步看:展開 cron 欄位

expand 先用逗號切開,每一段再判斷有沒有斜線(間隔)與連字號(範圍)。看 */15 怎麼變成 0、15、30、45。

用 crontab 排定時工作

在樹莓派的終端機操作,排兩個工作:每 10 分鐘拍一次環境快照、每天凌晨 2 點匯出 CSV。輸出經過節錄。

  1. 1

    編輯自己的排程表

    第一次執行 crontab -e 會請你選編輯器(選 nano 最簡單)。在檔案最後加上兩行,存檔離開後就生效,不用重新開機。

  2. 2

    列出排程表確認

    兩個重點:

    • 全部用絕對路徑。cron 執行時的環境很陽春,PATH 只有 /usr/bin:/bin,也不會進入你的虛擬環境;寫 python 或相對路徑常常找不到。
    • 把輸出導向記錄檔:>> 檔案 2>&1 把一般輸出與錯誤訊息都附加到 cron.log,出問題時才查得到原因。
  3. 3

    過一段時間檢查記錄檔

    沒有出現預期的輸出時,先看記錄檔裡的錯誤訊息,再手動執行同一行指令比對。

這段導覽完成了

systemd 還是 cron?

需求 選擇
程式一直執行(迴圈、網頁伺服器、MQTT 訂閱) systemd 服務
當掉要自動重新啟動 systemd 服務(Restart=on-failure)
在固定時間做一件事就結束 cron
開機時執行一次的小工作 cron 的 @reboot 也可以;但要等網路或其他服務就緒時,用 systemd 比較好控制順序

不要用 cron 每分鐘「檢查程式還在不在、不在就啟動」:那正是 systemd 已經幫你做好的事。 systemd 也有計時器(timer)可以取代 cron,功能更多,之後有需要再學。

小測驗:systemd

1.

新增 /etc/systemd/system/envmon.service 之後,下面哪一組指令能讓它「現在就啟動,而且以後開機也自動啟動」?

2.

服務設定 Restart=on-failure、RestartSec=5,程式因為未處理的例外結束(結束碼不是 0),systemd 會怎麼做?

3.

服務正常執行,但 journalctl -u envmon.service 很久都看不到 print 的新內容,最可能的原因是?

第 1 次作答

小測驗:cron

1.

crontab 裡的 30 7 * * 1-5 指令 什麼時候執行?

2.

用本課的 expand 函式,下面的程式會印出什麼?

def expand(field, low, high):
    values = []
    for part in field.split(","):
        step = 1
        if "/" in part:
            part, step = part.split("/")
            step = int(step)
        if part == "*":
            start, end = low, high
        elif "-" in part:
            start, end = [int(x) for x in part.split("-")]
        else:
            start = end = int(part)
        values += list(range(start, end + 1, step))
    return values

print(expand("0-20/10,45", 0, 59))
3.

crontab 寫 */10 * * * * python logger.py,手動執行沒問題,cron 卻一直沒跑出結果。最該先改什麼?

第 1 次作答

重點整理

  • 一直執行的程式交給 systemd:unit 檔放在 /etc/systemd/system/,寫好 ExecStart(絕對路徑)、User、Restart=on-failure、WantedBy=multi-user.target。
  • daemon-reload → enable --now → status;輸出用 journalctl -u 服務名 -f 看。
  • 服務程式要 print(..., flush=True),能處理的錯誤用 try/except 自己處理,重啟是最後一道防線。
  • 定時工作交給 cron:五個欄位是分、時、日、月、星期,*、a-b、a,b、*/n;@reboot 開機執行一次。
  • cron 裡全部用絕對路徑,並把輸出導向記錄檔。

下一課把前面學過的感測、顯示、記錄、網頁與自動執行整合成一個教室環境監測站。

這一課完成了!
Next lesson:專題:教室環境監測站
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。