Chapter 06無料公開

システムの起動と停止

こいし
こいし
2024.08.10に更新

カーネル

カーネルは、オペレーティングシステムの中核部分であり、ハードウェアとソフトウェアの間の仲介役を果たします。

以下にカーネルのイメージが格納されています。

[root@localhost boot]# ll /boot/vmlinuz-*64
-rwxr-xr-x. 1 root root 14407720  7月  3 02:21 /boot/vmlinuz-5.14.0-474.el9.x86_64


カーネルは以下のように構成されています。

カーネル本体
カーネル本体は、オペレーティングシステムの中核部分であり、ハードウェアリソースの管理、プロセス管理、メモリ管理、ファイルシステム管理、デバイス管理など、基本的なシステム機能を提供します。カーネル本体は、システムの起動時にメモリにロードされ、システムの動作を制御します。

主な役割

役割 説明
プロセスマネジメント カーネルは、プロセス(実行中のプログラム)の作成、スケジューリング、終了を管理します。複数のプロセスが同時に実行される場合、その実行順序を決定し、各プロセスに適切なCPU時間を割り当てます。
メモリ管理 カーネルは、システムメモリの割り当てと解放を管理します。各プロセスが使用するメモリ領域を保護し、異なるプロセスが互いのメモリ領域を不正にアクセスしないようにします。
デバイス管理 カーネルは、ハードウェアデバイス(ディスクドライブ、ネットワークインターフェイス、プリンターなど)との通信を管理します。デバイスドライバを通じてこれを行い、アプリケーションがハードウェアを効率的に利用できるようにします。
ファイルシステム管理 カーネルは、ファイルシステムの作成、読み取り、書き込み、削除などの操作を管理します。異なるファイルシステムタイプ(ext4、xfs、btrfsなど)に対応し、データの整合性とセキュリティを維持します。
ネットワーク管理 カーネルは、ネットワークプロトコルスタックを管理し、データの送受信を行います。これにより、システムがネットワークを介して他のデバイスと通信できます。


カーネルモジュール
カーネルモジュールは、カーネル本体の機能を拡張するためのコードです。これには、デバイスドライバ、ファイルシステム、ネットワークプロトコルなどが含まれます。カーネルモジュールはカーネルと一緒にビルドされ、カーネルの一部としてリンクされます。

主な特徴

特徴 説明
静的リンク カーネルと一緒にビルドされ、カーネルの一部としてメモリに常駐します。
基本機能の提供 重要な基本機能(例:デフォルトのファイルシステムサポートや基本的なデバイスドライバ)を提供します。
起動時にロード システムの起動時に自動的にロードされます。


ローダブルカーネルモジュール(LKM)
ローダブルカーネルモジュール(Loadable Kernel Module, LKM)は、カーネルの動作中に動的に追加(ロード)または削除(アンロード)できるモジュールです。LKMを使用することで、システムを再起動せずにカーネルの機能を拡張できます。

特徴 説明
動的リンク カーネルの実行中に追加や削除が可能。
柔軟な管理 必要に応じてモジュールをロードし、使用しなくなったらアンロードできるため、システムの柔軟性が向上します。
専用コマンド modprobe、insmod、rmmod、lsmodなどのコマンドを使用して管理。


ブートシーケンス

# フェーズ 説明
1 電源オン
2 BIOS/UEFI ・POST(Power-On Self Test): BIOSまたはUEFIがシステムハードウェアの基本的な診断を行い、正常であることを確認します。
・ブートデバイスの選択: BIOSまたはUEFIがブートデバイス(HDD、SSD、USBドライブなど)を検出し、ブートローダーを読み込むデバイスを選択します。
3 ブートローダ ・BIOS: ブートデバイスの最初のセクタ(MBR:Master Boot Record)からブートローダーが読み込まれメモリにロードする。
・UEFI: GPT(GUID Partition Table)からブートローダーが読み込まれメモリにロードする。
・GRUB(GRand Unified Bootloader): CentOSは通常、GRUB2を使用します。GRUB2はカーネル(vmlinuz)とinitramfsをメモリにロードし、カーネルを起動する。
4 カーネル ・カーネルの読み込み: GRUBが選択されたカーネルイメージをメモリにロードし、実行を開始します。
・initrd/initramfsの読み込み: 初期RAMディスク(initrd)または初期RAMファイルシステム(initramfs)が読み込まれます。これには、カーネルがルートファイルシステムにアクセスするために必要なドライバやツールが含まれています。
・デバイスの初期化: カーネルがデバイスドライバを初期化し、ハードウェアを検出します。
・ルートファイルシステムのマウント: 初期RAMディスク内のスクリプトが実行され、ルートファイルシステムがマウントされます。
5 init ・初期プロセスの開始: カーネルが初期プロセス(PID 1)として/sbin/initまたはシンボリックリンクされたシステム管理プロセス(例:systemd)を起動します。
・systemdの起動: CentOS 7以降では、systemdがデフォルトのinitシステムとして使用されます。
6 systemd ・ターゲットの起動: systemdはターゲット(旧ランレベル)に従ってサービスを起動します。default.targetは通常、multi-user.targetまたはgraphical.targetにリンクされています。
・ユニットファイルの読み込み: systemdはユニットファイルを読み込み、定義された順序でサービス、ソケット、マウントポイントなどを起動します。
・サービスの開始: systemdは依存関係に基づいてサービスを並列に起動し、システムの各コンポーネントを初期化します。

systemd

systemdとは?

systemdはLinuxオペレーティングシステムのシステムおよびサービスマネージャである。起動時に最初のプロセスとして実行されると(PID 1として)、ユーザー空間のサービスを立ち上げて維持するinitシステムとして動作する。Linuxカーネルが起動するとカーネルの初期化が行われ、その最終段階でPID1の/sbin/initを生成します。/sbin/initは/lib/systemd/systemdへのシンボリックリンクです。


systemdのプロセス間の通信

systemdのプロセス間通信(IPC: Inter-Process Communication)は、主に以下の2つのメカニズムを通じて行われます:

  1. Unixソケット
  2. D-Bus

Unixソケット

Unixソケットは、同じコンピュータ上で動作するプロセス間の通信(IPC: Inter-Process Communication)を実現するためのメカニズムです。ソケットはファイルの一種として扱われ、通常ファイルシステム上に特別なファイル(ソケットファイル)として存在します。


1. 主な特徴

  • ローカル通信: 同じマシン内での通信に使われます。
  • 低オーバーヘッド: ネットワークソケットに比べて通信のオーバーヘッドが少ないです。
  • ファイルシステムに統合: /tmp/や/var/run/などのディレクトリにソケットファイルが存在します。


2. Unixソケットを使用した通信

  • ソケットアクティベーション:systemdが特定のソケット(Unixソケットやネットワークソケット)を監視し、リクエストが来た時に対応するサービスを起動する仕組みです。これにより、サービスは必要なときにだけ起動され、リソースの節約が可能になります。
  • ソケットユニットとサービスユニットの連携:systemdのユニットファイルには、サービスユニット(.service)とソケットユニット(.socket)があります。ソケットユニットはソケットを定義し、対応するサービスユニットを指定します。
  • ソケットユニット(.socketファイル)は、systemdにおいてソケットを管理するためのユニットファイルです。ソケットユニットは、特定のソケット(Unixソケットやネットワークソケット)を定義し、そのソケットがアクティブになったときに対応するサービスを起動します。これを前述のソケットアクティベーションと呼びます。

D-Bus

D-Bus (Desktop Bus) は、Linuxシステムにおけるプロセス間通信 (IPC) のためのメッセージバスシステムです。D-Busは、複数のプロセス間でメッセージを送受信するための標準化された方法を提供し、システム全体およびユーザーセッション内での通信を容易にします。
D-Bus には、主に二つのバスがあります

1. システムバス (System Bus)

  • システム全体のデーモンやサービス(例:systemd、NetworkManager)が通信するためのバス。
  • 主にシステムレベルの設定やサービス管理に使用されます。

2. セッションバス (Session Bus)

  • ユーザーセッション内のアプリケーション間で通信するためのバス。
  • ユーザー固有のアプリケーションやサービス(例:デスクトップ環境の設定)に使用されます。

systemctl コマンド

systemctl は systemd の管理コマンドで、サービスやユニットの操作、システムの制御を行うために使用されます。以下では、systemctl コマンドの基本的な使い方と主要な機能について説明します。


書式

systemctl [OPTIONS...] COMMAND [UNIT...]


コマンド

コマンド 説明
start 指定されたサービスを起動します。
stop 指定されたサービスを停止します。
restart 指定されたサービスを再起動します。サービスが既に起動している場合は、一度停止してから再度起動します。
reload 指定されたサービスの設定を再読み込みします。サービス自体は停止せずに、設定のみを再読み込みします。
status 指定されたサービスのステータスを表示します。サービスの状態やログの概要を確認できます。
enable 指定されたサービスをブート時に有効にします。次回のシステム起動時に自動的に起動します。
disable 指定されたサービスをブート時に無効にします。次回のシステム起動時に自動的に起動しません。
is-active 指定されたサービスがアクティブかどうかを確認します。
is-enabled 指定されたサービスがブート時に有効かどうかを確認します。
reboot システムを再起動します。すべてのユーザーとプロセスに通知し、システムを再起動します。
poweroff システムをシャットダウンし、電源を切ります。すべてのユーザーとプロセスに通知し、システムをシャットダウンします。
suspend システムをサスペンド状態にします。RAMに現在の状態を保存し、最低限の電力を使用します。
list-units 現在ロードされているユニットの一覧を表示します。
daemon-reload ユニットファイルの変更を反映するために、systemdマネージャーを再読み込みします。


実行例

# サービスの起動
[testuser@localhost ~]$ sudo systemctl start firewalld.service

# サービスの情報確認。現在はactive
[testuser@localhost ~]$ sudo systemctl status firewalld.service
● firewalld.service - firewalld - dynamic firewall daemon
     Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled; preset: enabled)
     Active: active (running) since Thu 2024-08-08 21:56:06 JST; 8s ago
       Docs: man:firewalld(1)
   Main PID: 8066 (firewalld)
      Tasks: 2 (limit: 10948)
     Memory: 25.9M
        CPU: 309ms
     CGroup: /system.slice/firewalld.service
             mq8066 /usr/bin/python3 -s /usr/sbin/firewalld --nofork --nopid

 8月 08 21:56:06 localhost.localdomain systemd[1]: Starting firewalld - dynamic firewall daemon...
 8月 08 21:56:06 localhost.localdomain systemd[1]: Started firewalld - dynamic firewall daemon.

# サービスの停止
[testuser@localhost ~]$ sudo systemctl stop firewalld.service

# サービスの情報確認。現在はactive
[testuser@localhost ~]$ sudo systemctl status firewalld.service
○ firewalld.service - firewalld - dynamic firewall daemon
     Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled; preset: enabled)
     Active: inactive (dead) since Thu 2024-08-08 21:56:56 JST; 1s ago
   Duration: 49.421s
       Docs: man:firewalld(1)
    Process: 8066 ExecStart=/usr/sbin/firewalld --nofork --nopid $FIREWALLD_ARGS (code=exited, status=0/SUCCESS)
   Main PID: 8066 (code=exited, status=0/SUCCESS)
        CPU: 336ms

# システム起動時にfirewalld.serviceが起動しないように変更
[testuser@localhost ~]$ sudo systemctl disable firewalld.service
Removed "/etc/systemd/system/multi-user.target.wants/firewalld.service".
Removed "/etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service".

# 状態がdisabledになったことを確認
[testuser@localhost ~]$ sudo systemctl status firewalld.service
○ firewalld.service - firewalld - dynamic firewall daemon
     Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; preset: enabled)
     Active: inactive (dead)
       Docs: man:firewalld(1)

# システム再起動起動
[testuser@localhost ~]$ sudo systemctl reboot
[sudo] testuser のパスワード:

# システム再起動後、firewalld.serviceが起動していないことを確認
[testuser@localhost ~]$ systemctl status  firewalld.service
○ firewalld.service - firewalld - dynamic firewall daemon
     Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; prese>
     Active: inactive (dead)
       Docs: man:firewalld(1)


ログの管理

journalctl コマンド

journalctl コマンドは、systemd のログ管理ツールである journald によって収集されたログメッセージを表示するためのコマンドです。システムの状態やエラーメッセージ、サービスのログなど、さまざまなログ情報を確認することができます。


書式

journalctl [オプション]


オプション

オプション 説明
-u <サービス名> 指定したサービスのログメッセージのみを表示します。
-k カーネルからのログメッセージを表示します。
-e 最も最近のログエントリを表示します。
--since "<日時>" 指定した日時以降のログメッセージを表示します。日時は "YYYY-MM-DD HH:MM:SS" 形式。
--until "<日時>" 指定した日時までのログメッセージを表示します。日時は "YYYY-MM-DD HH:MM:SS" 形式。
_PID=<プロセスID> 指定したプロセスIDに関連するログメッセージを表示します。
-p <レベル> 指定したログレベルのメッセージを表示します。ログレベルには emerg, alert, crit, err, warning, notice, info, debug があります。
-f リアルタイムでログメッセージを表示し、新しいエントリが追加されるたびに表示します。
--vacuum-size=<サイズ> 指定したサイズを超えると古いログエントリを削除します。サイズは例えば 500M のように指定します。
--vacuum-time=<期間> 指定した期間より古いログエントリを削除します。期間は例えば 2weeks のように指定します。


ログの保存と削除
ログのサイズ制限を設定

journald の設定ファイル (/etc/systemd/journald.conf) を編集し、ログのサイズ制限を設定します。

[Journal]
SystemMaxUse=1G`


ソケットアクティベーションの動作確認

以下のように「example.socket」「example.service」というファイルを作成します。systemdは、ソケットユニットとサービスユニットの名前が同じであれば、自動的にそれらが対応するものと判断します。

ソケットユニットを作成します。以下のようにServiceディレクトリで対応するサービスを明示的に指定することもできます。

  1. 「.socket」ファイルの作成
vim /etc/systemd/system/example.socket
/etc/systemd/system/example.socket
[Unit]
Description=Example Socket

[Socket]
ListenStream=/run/example.sock

# 対応するサービスユニットを指定
Service=example.service

[Install]
WantedBy=sockets.target


  1. サービスユニットの作成

次にサービスユニットを作成します。

vim /etc/systemd/system/example.service
/etc/systemd/system/example.service
[Unit]
Description=Example Service

[Service]
ExecStart=/usr/bin/python3 /root/server.py
WorkingDirectory=/root

[Install]
WantedBy=multi-user.target
  1. ソケットファイルアクセス用のスクリプトの作成

上述したようにソケットファイルにアクセスすると対応するサービスが起動します。今回は「/run/example.sock」にアクセスをするスクリプトを作成します。

vim /root/socket_access.py
/root/socket_access.py
#!/usr/bin/env python3

import socket
import time

# ソケットファイルのパス
SOCKET_FILE = '/run/example.sock'

# ソケットにアクセスする関数
def access_socket():
    try:
        client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        client_socket.connect(SOCKET_FILE)
        print("Connected to socket")

    except socket.error as e:
        print(f"Connection failed: {e}, retrying in 1 second...")
        time.sleep(1)
    finally:
        client_socket.close()
        print("Socket closed")

if __name__ == '__main__':
    access_socket()
  1. serverのスクリプト

「socket_access.py」から「/run/example.sock」へのリクエストが上手く行くと「server.py」が起動します。「server.py」はカレントディレクトリにある「index.html」の内容を返します。

/root/server.py
#!/usr/bin/env python3

import socket
import os

# ソケットファイルのパス
SOCKET_FILE = '/run/example.sock'

# 既存のソケットファイルを削除(存在する場合)
if os.path.exists(SOCKET_FILE):
    os.remove(SOCKET_FILE)


def main():

    print("Creating socket...")

    try:
        # ソケットの作成
        server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        server.bind(SOCKET_FILE)
        server.listen()

        print(f"Server is listening on {SOCKET_FILE}")

    
        while True:
            server_connection, _ = server.accept()
            print("Connection established")
            
            try:
                while True:
                    data = server_connection.recv(1024)
                    if not data:
                        break
                    
                    request = data.decode()
                    print(f"Received request: {request}")
                    
                    # HTTPリクエストがGETであることを前提とする
                    if request.startswith("GET"):
                        # リクエストされたファイル名を取得する(簡易的な実装)
                        filename = request.split()[1].lstrip('/')
                        if filename == '':
                            filename = 'index.html'
                        
                        # ファイルが存在する場合はその内容を返す
                        if os.path.exists(filename):
                            with open(filename, 'rb') as file:
                                response_body = file.read()
                            response_headers = "HTTP/1.0 200 OK\r\n"
                            response_headers += f"Content-Length: {len(response_body)}\r\n"
                            response_headers += "Content-Type: text/html\r\n\r\n"
                            server_connection.sendall(response_headers.encode('ASCII') + response_body)
                        else:
                            response_body = b"<html><body><h1>404 Not Found</h1></body></html>"
                            response_headers = "HTTP/1.0 404 Not Found\r\n"
                            response_headers += f"Content-Length: {len(response_body)}\r\n"
                            response_headers += "Content-Type: text/html\r\n\r\n"
                            server_connection.sendall(response_headers.encode('ASCII') + response_body)
                    
                    # リクエスト処理の後、接続を閉じる
                    break

            finally:
                server_connection.close()
                print("Connection closed")

    finally:
        server.close()
        print("Socket closed.")

if __name__ == '__main__':
    main()


  1. アクセス用のクライアントスクリプト

「server.py」にアクセスするスクリプトを作成します。

vim /root/client.py
/root/client.py
#!/usr/bin/env python3

import socket
import time
import subprocess
from socket_access import access_socket  # ファイル名の相対パスでインポート

# ソケットファイルのパス
SOCKET_FILE = '/run/example.sock'

# ソケットに指定したバイト数を書き込む関数
def send_msg(sock, msg):
    total_sent_len = 0
    total_msg_len = len(msg)
    while total_sent_len < total_msg_len:
        sent_len = sock.send(msg[total_sent_len:])
        if sent_len == 0:
            raise RuntimeError('Socket connection broken')
        total_sent_len += sent_len

# ソケットから接続が終わるまでバイト列を読み込むジェネレータ関数
def recv_msg(sock, chunk_len=1024):
    while True:
        received_chunk = sock.recv(chunk_len)
        if len(received_chunk) == 0:
            break
        yield received_chunk

def is_service_active(service_name):
    result = subprocess.run(['systemctl', 'is-active', service_name], capture_output=True, text=True)
    return result.stdout.strip() == 'active'

def wait_for_service_activation(service_name):
    while not is_service_active(service_name):
        print(f"{service_name} is not active. Waiting for 1 second...")
        time.sleep(1)
    
    print(f"{service_name} is now active.")


def main():
    client_socket = None
    try:
        # ソケットにアクセスする関数を呼び出す
        access_socket()
        time.sleep(1)

        # example.serviceがアクティブになるまで待機する
        wait_for_service_activation('example.service')
        time.sleep(1)
                

        while True:
            try:
                client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
                print("Socket created.")
                client_socket.connect(SOCKET_FILE)
                break  # 接続成功でループを抜ける
            except socket.error as e:
                print(f"Connection failed: {e}, retrying in 1 second...")
                time.sleep(1)

        request_text = 'GET / HTTP/1.0\r\n\r\n'
        request_bytes = request_text.encode('ASCII')
        send_msg(client_socket, request_bytes)
        print("Request sent to server.")

        received_bytes = b"".join(recv_msg(client_socket))
        received_text = received_bytes.decode('ASCII')
        print("Response received from server:")
        print(received_text)

    except Exception as e:
        print(f"An error occurred: {e}")

    finally:
        if client_socket:
            client_socket.close()
            print("Socket closed.")


if __name__ == '__main__':
    main()


  1. index.htmlの作成
/root/index.html
<!doctype html>
<html>
    <head>
        <title>Hello World</title>
    </head>
    <body>
        <h1>Hello World</h1>
    </body>
</html>

  1. ソケット起動します
systemctl start example.socket
systemctl status example.socket ; systemctl status example.service


実行結果例

[root@localhost ~]# systemctl status example.socket ; systemctl status example.service
● example.socket - Example Socket
     Loaded: loaded (/etc/systemd/system/example.socket; disabled; preset: disabled)
     Active: active (listening) since Tue 2024-07-16 18:46:27 JST; 6s ago
      Until: Tue 2024-07-16 18:46:27 JST; 6s ago
   Triggers: ● example.service
     Listen: /run/example.sock (Stream)
     CGroup: /system.slice/example.socket

 716 18:46:27 localhost.localdomain systemd[1]: Listening on Example Socket.
○ example.service - Example Service
     Loaded: loaded (/etc/systemd/system/example.service; disabled; preset: disabled)
     Active: inactive (dead)
TriggeredBy: ● example.socket

 716 18:44:38 localhost.localdomain systemd[1]: Stopped Example Service.
 716 18:45:13 localhost.localdomain systemd[1]: Started Example Service.
 716 18:46:16 localhost.localdomain systemd[1]: Stopping Example Service...
 716 18:46:16 localhost.localdomain systemd[1]: example.service: Deactivated successfully.
 716 18:46:16 localhost.localdomain systemd[1]: Stopped Example Service.
  1. クライアント用のスクリプトの起動
python3 ./client.py
[root@localhost ~]# python3 ./client.py 
Socket created.
Request sent to server.
Response received from server:
HTTP/1.0 200 OK
Content-Length: 141
Content-Type: text/html

<!doctype html>
<html>
    <head>
        <title>Hello World</title>
    </head>
    <body>
        <h1>Hello World</h1>
    </body>
</html>

Socket closed.


停止方法

systemctl stop example.socket ;  systemctl stop example.service ; systemctl daemon-reload