bash while ループで `read` を含むスクリプトは、systemd サービスとして実行すると CPU 使用率が高くなります。

bash while ループで `read` を含むスクリプトは、systemd サービスとして実行すると CPU 使用率が高くなります。

イベントモニターから通知された入力イベントに応じて特定のアクションを実行するスクリプトを作成しました。次のようなものです。

$ cat script.sh
-----------------------------------------------------------
#!/usr/bin/bash

stdbuf -oL /usr/bin/event_monitor | while IFS= read LINE
do
    something with $LINE
done

ターミナルからスクリプトとして実行するとbash、スクリプトは通常の量のCPUを消費し、新しい行が印刷されたときにのみアクションを実行します。ただし、次の設定でサービスとして実行すると、

$ cat event.service
-----------------------------------------------------------
[Unit]
Description=Actions upon events

[Service]
Type=simple
ExecStart=/path/to/script.sh

[Install]
WantedBy=default.target

このevent_monitorコマンドは論理コア全体を制御して、プロセッサが許す限り頻繁に何も実行しないことstraceを明らかにします。readread()

$ strace -p $event_monitor_pid
-----------------------------------------------------------
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
read(0, "", 1)                          = 0
................ad nauseum

サービスはイベントを登録し、実際のイベントが発生したときに条件付きコマンドを実行します。ここで何が間違っているのでしょうか?

ps これは では発生しますcras_monitorが、 では発生しませんacpi_listenwhile基礎となるサービスが正常に起動したことを確認した後にのみループが開始されるようにしましたが、うまくいきませんでした。

event_monitor更新:のコードに関連する可能性のある部分を以下に示します。

...
#include <headers.h>
...
# Print to console function:
static void event_occurrence(void *context, int32_t attribute)
{
    printf("Some attribute has changed to %d.\n", attribute);
}
...
int main(int argc, char **argv)
{
    struct some_service_client *client # defined in headers
    int rc
...
# Some routine
...
    some_service_client_set_event_occurence_callback(client,event_occurence)
...
    rc = some_func(client)
...
    while (1) {
        int rc;
        char c;
        rc = read(STDIN_FILENO, &c, 1);
        if (rc < 0 || c == 'q')
            return 0;
    }
...
}


答え1

event_monitorループして CPU をすべて消費しているのは bash スクリプトではなく、プログラムです。

systemd で実行すると、STDIN には /dev/null が接続されます (または閉じられている場合もあります)。main のイベント モニター ループが を実行するとread(2)、EOF が取得され、ループが再び実行されます。

対話的に実行すると、event_monitor は stdin にターミナルが接続されるため、read(2)入力があるまでブロックされます。

event_monitor は、開いている場合、stdin の読み取り時にのみループする必要があります。EOF を受信した場合は、終了するか (この場合はおそらく望ましくありません)、長時間スリープする必要があります。

を変更できない場合はevent_monitor、サービスの stdin に FIFO (名前付きパイプ) を接続するとうまくいく可能性があります。systemd にはStandardInput、 を指定できる オプション (systemd.exec(5) のマニュアル ページに記載) がありますStandardInput=file:/run/event_monitor_ctl。その後は、名前付きパイプを作成するだけです/run/event_monitor_ctl。そのためには、名前付きパイプを作成するための設定ファイル (tmpfiles.d(5) を参照) を作成することで、systemd-tmpfiles を使用できます。

関連情報