Linux Kernel Process Management and Scheduling
Linux Kernel プロセス管理とスケジューリング 完全ガイド
対象読者: Linux カーネル内部のプロセス管理メカニズムを深く理解したいシステムエンジニア、SRE、カーネル開発者 カーネルバージョン: Linux 5.x - 6.x 系列(EEVDF スケジューラは 6.6 以降) 最終更新: 2026年4月
目次
- プロセスの基本概念
- プロセスライフサイクル
- task_struct 構造体とプロセスディスクリプタ
- プロセスの状態遷移
- CFS (Completely Fair Scheduler)
- EEVDF スケジューラ (Linux 6.6+)
- リアルタイムスケジューリング
- CPU アフィニティと cgroups CPU コントローラ
- Nice 値と優先度
- コンテキストスイッチの仕組み
- スレッドとプロセス (NPTL)
- プロセス名前空間 (PID Namespace)
- /proc ファイルシステムによるプロセス情報
- プロセス管理ツール群
- パフォーマンスチューニング実践
- トラブルシューティング
1. プロセスの基本概念
1.1 プロセスとは何か
Linux におけるプロセスとは、実行中のプログラムのインスタンスである。カーネルの視点から見ると、プロセスはリソース(メモリ空間、ファイルディスクリプタ、シグナルハンドラ、CPU 時間など)を割り当てられた実行単位である。
+------------------------------------------------------------------+
| ユーザー空間 |
| |
| +-------------+ +-------------+ +-------------+ |
| | Process A | | Process B | | Process C | |
| | PID: 1234 | | PID: 1235 | | PID: 1236 | |
| | | | | | | |
| | .text | | .text | | .text | |
| | .data | | .data | | .data | |
| | .bss | | .bss | | .bss | |
| | heap | | heap | | heap | |
| | stack | | stack | | stack | |
| +------+------+ +------+------+ +------+------+ |
| | | | |
+---------|----------------|----------------|------------------------+
| | |
========|================|================|========= (syscall境界)
| | |
+---------v----------------v----------------v------------------------+
| カーネル空間 |
| |
| +-------------+ +-------------+ +-------------+ |
| | task_struct | | task_struct | | task_struct | |
| | (PCB) | | (PCB) | | (PCB) | |
| +------+------+ +------+------+ +------+------+ |
| | | | |
| v v v |
| +----------------------------------------------------+ |
| | スケジューラ (CFS / EEVDF / RT) | |
| +----------------------------------------------------+ |
| | | | |
| v v v |
| +----------+ +----------+ +----------+ |
| | CPU 0 | | CPU 1 | | CPU 2 | |
| +----------+ +----------+ +----------+ |
+------------------------------------------------------------------+
1.2 プロセスの種類
Linux カーネルは以下のプロセスの種類を区別する:
| 種類 | 説明 | 例 |
|---|---|---|
| 通常プロセス | ユーザーが起動する一般的なプログラム | bash, vim, gcc |
| デーモンプロセス | バックグラウンドで動作するサービス | sshd, crond, systemd |
| カーネルスレッド | カーネル空間でのみ動作するスレッド | kworker, ksoftirqd, migration |
| ゾンビプロセス | 終了したが親に回収されていないプロセス | defunct プロセス |
| 孤児プロセス | 親が先に終了したプロセス(init/systemd が引き取り) | 親が異常終了した子プロセス |
1.3 プロセス識別子
各プロセスは複数の識別子を持つ:
/* include/linux/types.h */
typedef int __kernel_pid_t;
/* カーネル内部でのプロセス識別子 */
struct task_struct {
pid_t pid; /* プロセスID - スレッドグループ内でユニーク */
pid_t tgid; /* スレッドグループID - getpid() が返す値 */
/* ... */
};
# プロセス識別子の確認
$ echo "PID: $$"
PID: 12345
$ echo "PPID: $PPID"
PPID: 12300
# スレッドの PID と TGID を確認
$ python3 -c "
import os, threading
print(f'Main: PID={os.getpid()}, TID={threading.get_native_id()}')
def show():
print(f'Thread: PID={os.getpid()}, TID={threading.get_native_id()}')
t = threading.Thread(target=show)
t.start()
t.join()
"
Main: PID=12345, TID=12345
Thread: PID=12345, TID=12346
1.4 プロセス階層
Linux のプロセスは木構造で管理される。全プロセスの祖先は PID 1 の init プロセス(現代のシステムでは systemd)である。
+--------+
| PID: 0 | (swapper/idle)
+---+----+
|
+---+----+
| PID: 1 | (systemd / init)
+---+----+
|
+------------+------------+
| | |
+----+----+ +---+----+ +----+----+
| PID: 2 | | systemd| | sshd |
| kthreadd| | -journald| PID: 800|
+----+----+ +--------+ +----+----+
| |
+------+------+ +-----+-----+
| | | | |
kworker ksoftirqd migration sshd sshd
(session1) (session2)
|
+--+--+
| bash |
+--+--+
|
+--+--+
| vim |
+-----+
# プロセスツリーの表示
$ pstree -p
systemd(1)─┬─agetty(650)
├─crond(630)
├─sshd(800)─┬─sshd(12300)───bash(12305)───vim(12400)
│ └─sshd(12500)───bash(12505)
├─systemd-journal(400)
├─systemd-logind(620)
└─systemd-udevd(380)
# 特定プロセスの親子関係
$ ps -ef --forest | head -20
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Mar01 ? 00:05:30 /usr/lib/systemd/systemd
root 2 0 0 Mar01 ? 00:00:02 [kthreadd]
root 3 2 0 Mar01 ? 00:00:00 \_ [rcu_gp]
root 4 2 0 Mar01 ? 00:00:00 \_ [rcu_par_gp]
root 5 2 0 Mar01 ? 00:00:00 \_ [slub_flushwq]
2. プロセスライフサイクル
2.1 fork() によるプロセス生成
Linux でプロセスを生成する基本的なシステムコールは fork() である。fork() は呼び出し元プロセスのほぼ完全なコピーを作成する。
fork() の動作:
親プロセス (PID: 1000) fork() 後
+-------------------+ +-------------------+
| .text (コード) | | 親 (PID: 1000) |
| .data (データ) | | fork() = 1001 |
| .bss | ==> +-------------------+
| heap | | 子 (PID: 1001) |
| stack | | fork() = 0 |
+-------------------+ +-------------------+
※ CoW で仮想メモリは共有
カーネル内部の fork() 処理フロー
ユーザー空間: fork() / vfork() / clone()
|
v
sys_fork() / sys_vfork() / sys_clone() [kernel/fork.c]
|
v
kernel_clone() [kernel/fork.c]
|
v
copy_process() [kernel/fork.c]
|
+---> dup_task_struct() -- task_struct の複製
| |
| +---> alloc_task_struct_node()
| +---> alloc_thread_stack_node()
| +---> arch_dup_task_struct()
|
+---> copy_creds() -- 資格情報のコピー
+---> sched_fork() -- スケジューラの初期化
| |
| +---> __sched_fork()
| +---> p->prio = current->normal_prio
| +---> set_task_cpu(p, cpu)
|
+---> copy_files() -- ファイルディスクリプタのコピー
+---> copy_fs() -- ファイルシステム情報のコピー
+---> copy_sighand() -- シグナルハンドラのコピー
+---> copy_signal() -- シグナル情報のコピー
+---> copy_mm() -- メモリ管理情報のコピー (CoW)
+---> copy_namespaces() -- 名前空間のコピー
+---> copy_thread() -- スレッド固有情報のコピー
+---> alloc_pid() -- PID の割り当て
|
v
wake_up_new_task() -- 新プロセスをランキューに追加
|
+---> activate_task()
+---> check_preempt_curr()
実践的な fork() のコード例
/* fork_example.c - fork() の動作を理解するための例 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int global_var = 100;
int main(void)
{
int local_var = 200;
pid_t pid;
printf("Before fork: PID=%d, global=%d, local=%d\n",
getpid(), global_var, local_var);
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
/* 子プロセス */
global_var += 10;
local_var += 20;
printf("Child: PID=%d, PPID=%d, global=%d, local=%d\n",
getpid(), getppid(), global_var, local_var);
exit(EXIT_SUCCESS);
} else {
/* 親プロセス */
global_var += 1;
local_var += 2;
printf("Parent: PID=%d, child=%d, global=%d, local=%d\n",
getpid(), pid, global_var, local_var);
int status;
waitpid(pid, &status, 0);
printf("Child exited with status: %d\n", WEXITSTATUS(status));
}
return 0;
}
# コンパイルと実行
$ gcc -o fork_example fork_example.c
$ ./fork_example
Before fork: PID=12345, global=100, local=200
Parent: PID=12345, child=12346, global=101, local=202
Child: PID=12346, PPID=12345, global=110, local=220
Child exited with status: 0
2.2 Copy-on-Write (CoW) メカニズム
fork() はメモリを実際にはコピーしない。代わりに Copy-on-Write (CoW) を使用する。
fork() 直後の状態 (CoW):
親プロセスのページテーブル 子プロセスのページテーブル
+--------+--------+ +--------+--------+
| 仮想 | 物理 | | 仮想 | 物理 |
| アドレス| フレーム| | アドレス| フレーム|
+--------+--------+ +--------+--------+
| 0x1000 | PF_A |--+ +--| 0x1000 | PF_A |
| 0x2000 | PF_B |--+-+ +-+--| 0x2000 | PF_B |
| 0x3000 | PF_C |--+-+-+--+-+--| 0x3000 | PF_C |
+--------+--------+ | | | | | +--------+--------+
v v v v v
物理メモリフレーム (読み取り専用マーク)
+------+------+------+
| PF_A | PF_B | PF_C |
| RO | RO | RO |
+------+------+------+
子プロセスが 0x2000 に書き込んだ後:
親プロセスのページテーブル 子プロセスのページテーブル
+--------+--------+ +--------+--------+
| 0x1000 | PF_A |--+ +--| 0x1000 | PF_A |
| 0x2000 | PF_B |--+ +-----| 0x2000 | PF_D | ← 新しいフレーム
| 0x3000 | PF_C |--+-+--+--+--| 0x3000 | PF_C |
+--------+--------+ | | | | +--------+--------+
v v v v
+------+------+------+------+
| PF_A | PF_B | PF_C | PF_D |
| RO | RW | RO | RW | ← B は親用に RW に戻る
+------+------+------+------+
2.3 clone() システムコール
clone() は fork() よりも細かい制御が可能で、スレッド作成にも使われる。
/* clone() のフラグ一覧 (include/uapi/linux/sched.h) */
#define CLONE_VM 0x00000100 /* 仮想メモリ空間を共有 */
#define CLONE_FS 0x00000200 /* ファイルシステム情報を共有 */
#define CLONE_FILES 0x00000400 /* ファイルディスクリプタテーブルを共有 */
#define CLONE_SIGHAND 0x00000800 /* シグナルハンドラを共有 */
#define CLONE_PIDFD 0x00001000 /* pidfd を返す */
#define CLONE_PTRACE 0x00002000 /* ptrace を継続 */
#define CLONE_VFORK 0x00004000 /* 子が exec/exit するまで親を停止 */
#define CLONE_PARENT 0x00008000 /* 親を共有(兄弟として作成) */
#define CLONE_THREAD 0x00010000 /* 同じスレッドグループ */
#define CLONE_NEWNS 0x00020000 /* 新しいマウント名前空間 */
#define CLONE_SYSVSEM 0x00040000 /* System V セマフォを共有 */
#define CLONE_SETTLS 0x00080000 /* TLS を設定 */
#define CLONE_PARENT_SETTID 0x00100000
#define CLONE_CHILD_CLEARTID 0x00200000
#define CLONE_NEWCGROUP 0x02000000 /* 新しい cgroup 名前空間 */
#define CLONE_NEWUTS 0x04000000 /* 新しい UTS 名前空間 */
#define CLONE_NEWIPC 0x08000000 /* 新しい IPC 名前空間 */
#define CLONE_NEWUSER 0x10000000 /* 新しいユーザー名前空間 */
#define CLONE_NEWPID 0x20000000 /* 新しい PID 名前空間 */
#define CLONE_NEWNET 0x40000000 /* 新しいネットワーク名前空間 */
fork() vs clone() vs vfork() の比較:
+------------------+----------------------------------------------------------+
| システムコール | 共有するリソース |
+------------------+----------------------------------------------------------+
| fork() | なし(全てコピー、ただし CoW で最適化) |
| | clone(SIGCHLD, 0) |
+------------------+----------------------------------------------------------+
| vfork() | メモリ空間を共有、親は子の exec/exit まで停止 |
| | clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0) |
+------------------+----------------------------------------------------------+
| pthread_create() | VM, Files, FS, Sighand を全て共有 |
| (内部的に clone) | clone(CLONE_VM | CLONE_FS | CLONE_FILES | |
| | CLONE_SIGHAND | CLONE_THREAD | |
| | CLONE_SYSVSEM | CLONE_SETTLS | |
| | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID, ...) |
+------------------+----------------------------------------------------------+
2.4 exec() ファミリ
exec() は現在のプロセスイメージを新しいプログラムで置き換える。
/* exec ファミリのシステムコール */
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, ... /*, (char *) NULL,
char *const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
/* カーネル内部では全て execve() に帰着 */
/* kernel/exec.c: do_execveat_common() */
exec() の内部処理フロー:
sys_execve()
|
v
do_execveat_common()
|
+---> alloc_bprm() -- バイナリパラメータ構造体の割り当て
+---> bprm_stack_limits() -- スタック制限の計算
+---> copy_string_kernel() -- ファイル名のコピー
+---> copy_strings() -- 引数と環境変数のコピー
|
+---> bprm_execve()
|
+---> exec_binprm()
|
+---> search_binary_handler()
|
+---> ELF handler (load_elf_binary)
| |
| +---> elf_map() -- セグメントのマッピング
| +---> set_brk() -- BSS/ヒープの設定
| +---> load_elf_interp() -- 動的リンカのロード
| +---> create_elf_tables() -- 補助ベクタの作成
| +---> START_THREAD() -- エントリポイントの設定
|
+---> Script handler (#! 処理)
+---> その他のバイナリフォーマット
2.5 プロセスの終了
/* プロセス終了のフロー */
/* exit() / _exit() → sys_exit_group() / sys_exit() */
/*
* kernel/exit.c: do_exit()
*
* 1. PF_EXITING フラグを設定
* 2. タイマーの削除
* 3. exit_mm() -- メモリマッピングの解放
* 4. exit_sem() -- セマフォのクリーンアップ
* 5. exit_files() -- ファイルディスクリプタの閉鎖
* 6. exit_fs() -- ファイルシステム情報の解放
* 7. exit_notify() -- 親プロセスへの通知 (SIGCHLD)
* → forget_original_parent() -- 子プロセスの養子縁組
* 8. task の状態を TASK_DEAD に変更
* 9. schedule() の呼び出し -- 二度と戻らない
*/
プロセス終了のフロー:
子プロセス 親プロセス
+----------+ +----------+
| running | | running |
+----+-----+ +----+-----+
| |
exit(status) |
| |
+----v-----+ |
| do_exit()| |
| - リソース解放 |
| - SIGCHLD送信 ------SIGCHLD-------> |
+----+-----+ +----v-----+
| | wait() |
+----v-----+ | waitpid()|
| ZOMBIE | <-- task_struct は +----+-----+
| (defunct) | まだ残っている |
+----+-----+ release_task()
| |
wait() による回収 +----v-----+
| | task_struct|
+----v-----+ | 完全解放 |
| 完全消滅 | +----------+
+----------+
# ゾンビプロセスの生成と確認
$ cat zombie_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
pid_t pid = fork();
if (pid == 0) {
printf("Child PID=%d exiting\n", getpid());
exit(0);
}
printf("Parent PID=%d, child=%d, sleeping 30s (child becomes zombie)\n",
getpid(), pid);
sleep(30); /* wait() を呼ばずにスリープ → 子はゾンビになる */
return 0;
}
$ gcc -o zombie_demo zombie_demo.c && ./zombie_demo &
Parent PID=12345, child=12346, sleeping 30s (child becomes zombie)
Child PID=12346 exiting
$ ps aux | grep -E "PID|zombie|defunct"
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
user 12345 0.0 0.0 2100 700 pts/0 S 10:00 0:00 ./zombie_demo
user 12346 0.0 0.0 0 0 pts/0 Z 10:00 0:00 [zombie_demo] <defunct>
3. task_struct 構造体とプロセスディスクリプタ
3.1 task_struct の概要
task_struct は Linux カーネルにおけるプロセスディスクリプタであり、プロセスに関する全情報を保持する巨大な構造体である。Linux 6.x 系列では約 700 行以上に及ぶ。
/*
* include/linux/sched.h - task_struct の主要フィールド(簡略版)
* 実際のカーネルソースでは数百のフィールドが定義されている
*/
struct task_struct {
/* ===== スケジューラ関連 ===== */
unsigned int __state; /* プロセスの状態 */
int on_rq; /* ランキュー上にあるか */
int prio; /* 動的優先度 */
int static_prio; /* 静的優先度 (nice値から算出) */
int normal_prio; /* 通常優先度 */
unsigned int rt_priority; /* リアルタイム優先度 */
unsigned int policy; /* スケジューリングポリシー */
const struct sched_class *sched_class; /* スケジューリングクラス */
struct sched_entity se; /* CFS スケジューリングエンティティ */
struct sched_rt_entity rt; /* RT スケジューリングエンティティ */
struct sched_dl_entity dl; /* Deadline スケジューリングエンティティ */
int nr_cpus_allowed;
cpumask_t cpus_mask; /* CPU アフィニティマスク */
/* ===== プロセス識別 ===== */
pid_t pid; /* プロセスID */
pid_t tgid; /* スレッドグループID */
struct task_struct __rcu *real_parent; /* 実際の親 */
struct task_struct __rcu *parent; /* ptrace時の親 */
struct list_head children; /* 子プロセスリスト */
struct list_head sibling; /* 兄弟プロセスリスト */
struct task_struct *group_leader; /* スレッドグループリーダー */
/* ===== タイミング情報 ===== */
u64 utime; /* ユーザーモードCPU時間 */
u64 stime; /* カーネルモードCPU時間 */
u64 start_time; /* プロセス開始時刻 (monotonic) */
u64 start_boottime; /* プロセス開始時刻 (boottime) */
/* ===== メモリ管理 ===== */
struct mm_struct *mm; /* メモリディスクリプタ */
struct mm_struct *active_mm; /* カーネルスレッド用 */
/* ===== ファイルシステム ===== */
struct fs_struct *fs; /* ファイルシステム情報 */
struct files_struct *files; /* オープンファイルテーブル */
/* ===== 名前空間 ===== */
struct nsproxy *nsproxy; /* 名前空間プロキシ */
/* ===== シグナル ===== */
struct signal_struct *signal; /* シグナル情報 */
struct sighand_struct __rcu *sighand; /* シグナルハンドラ */
sigset_t blocked; /* ブロックされたシグナル */
struct sigpending pending; /* 保留シグナル */
/* ===== 資格情報 ===== */
const struct cred __rcu *real_cred; /* 実際の資格情報 */
const struct cred __rcu *cred; /* 有効な資格情報 */
char comm[TASK_COMM_LEN]; /* 実行ファイル名 */
/* ===== cgroups ===== */
struct css_set __rcu *cgroups; /* cgroup 情報 */
/* ===== そのほか多数 ... ===== */
};
3.2 task_struct のメモリレイアウト
カーネルスタックと task_struct の関係:
Linux 4.9 以降 (CONFIG_THREAD_INFO_IN_TASK):
+-----------------------------+ 高いアドレス
| |
| カーネルスタック | (通常 16KB = 4ページ)
| (上から下に成長) |
| |
| ↓↓↓ |
| |
+-----------------------------+ 低いアドレス
| thread_info (先頭) |
+-----------------------------+
task_struct は別途 slab アロケータから割り当て:
+-----------------------------+
| struct task_struct | (kmem_cache: "task_struct")
| +- thread_info | ← task_struct 内に埋め込み
| +- __state |
| +- stack (カーネルスタックへのポインタ)
| +- prio |
| +- ... |
+-----------------------------+
current マクロ:
x86_64: per-CPU 変数 current_task から取得
ARM64: sp_el0 レジスタから取得
3.3 current マクロ
現在実行中のプロセスの task_struct にアクセスするための current マクロ:
/* x86_64 の場合: arch/x86/include/asm/current.h */
DECLARE_PER_CPU(struct task_struct *, current_task);
static __always_inline struct task_struct *get_current(void)
{
return this_cpu_read_stable(current_task);
}
#define current get_current()
/* 使用例 */
printk("Current process: %s (PID: %d)\n",
current->comm, current->pid);
3.4 プロセスリストの探索
/* カーネルモジュールでのプロセス一覧表示 */
#include <linux/module.h>
#include <linux/sched/signal.h>
static int __init list_processes_init(void)
{
struct task_struct *task;
printk(KERN_INFO "Process listing:\n");
printk(KERN_INFO "%-7s %-7s %-16s %-6s\n",
"PID", "TGID", "COMM", "STATE");
/* for_each_process マクロでタスクリストを走査 */
for_each_process(task) {
printk(KERN_INFO "%-7d %-7d %-16s %-6c\n",
task->pid,
task->tgid,
task->comm,
task_state_to_char(task));
}
return 0;
}
static void __exit list_processes_exit(void)
{
printk(KERN_INFO "Module unloaded\n");
}
module_init(list_processes_init);
module_exit(list_processes_exit);
MODULE_LICENSE("GPL");
4. プロセスの状態遷移
4.1 プロセス状態の定義
/* include/linux/sched.h - プロセス状態の定義 */
/* ビットマスクとして使用される状態値 */
#define TASK_RUNNING 0x00000000 /* 実行可能 / 実行中 */
#define TASK_INTERRUPTIBLE 0x00000001 /* シグナルで起床可能なスリープ */
#define TASK_UNINTERRUPTIBLE 0x00000002 /* シグナルで起床不可のスリープ */
#define __TASK_STOPPED 0x00000004 /* 停止状態 (SIGSTOP等) */
#define __TASK_TRACED 0x00000008 /* ptrace によるトレース中 */
#define EXIT_DEAD 0x00000010 /* 完全終了 */
#define EXIT_ZOMBIE 0x00000020 /* ゾンビ状態 */
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
#define TASK_PARKED 0x00000040 /* kthread_park() による停止 */
#define TASK_DEAD 0x00000080 /* 死亡状態 */
#define TASK_WAKEKILL 0x00000100 /* 致命的シグナルで起床 */
#define TASK_WAKING 0x00000200 /* 起床処理中 */
#define TASK_NOLOAD 0x00000400 /* loadavg に含めない */
#define TASK_NEW 0x00000800 /* 新規作成 */
#define TASK_RTLOCK_WAIT 0x00001000 /* RT mutex 待ち */
/* 複合状態 */
#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_IDLE (TASK_UNINTERRUPTIBLE | TASK_NOLOAD)
4.2 状態遷移図
+------------------+
| fork() / |
| clone() |
+--------+---------+
|
v
+--------+---------+
| TASK_NEW |
| (新規作成) |
+--------+---------+
|
wake_up_new_task()
|
v
+-------> +---------+---------+ <-------+
| | TASK_RUNNING | |
| | (実行可能/実行中) | |
| +---------+---------+ |
| / | \ |
| / | \ |
wake_up()| schedule() schedule() schedule() |wake_up()
シグナル | (CPU獲得) (IO待ち) (IO待ち) |完了通知
| | | | |
| v v v |
| +----+----+ +--+--------+ +-+--------+|
| |実行中 | |TASK_ | |TASK_ ||
| |(on CPU) | |INTERRUPT-| |UNINTERRUPT||
| +----+----+ |IBLE | |IBLE ||
| | |(シグナル | |(シグナル ||
+--------+ | で起床可) | | で起床不可)||
| +---+------+ +---+-------+|
| | | |
| +------+------+ |
| | |
| TASK_KILLABLE |
| (致命的シグナル |
| で起床可能) |
| |
| SIGSTOP / ptrace |
| | |
| v |
| +-----+--------+ |
| | __TASK_STOPPED| |
| | __TASK_TRACED | |
| +-----+--------+ |
| | |
| SIGCONT |
| +------------------------+
|
| exit() / signal
v
+-----+--------+
| EXIT_ZOMBIE | ← wait() されるまで残る
| (ゾンビ) |
+-----+--------+
|
wait() / waitpid()
|
v
+-----+--------+
| EXIT_DEAD | ← task_struct 解放
| (完全消滅) |
+--------------+
4.3 ps コマンドでのプロセス状態
# STAT カラムの意味
$ ps aux | head -5
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 169404 13284 ? Ss Mar01 5:30 /usr/lib/systemd/systemd
root 2 0.0 0.0 0 0 ? S Mar01 0:02 [kthreadd]
root 11 0.0 0.0 0 0 ? I< Mar01 0:00 [rcu_tasks_kthread]
root 12 0.0 0.0 0 0 ? S Mar01 0:05 [ksoftirqd/0]
STAT カラムの詳細:
第1文字 (メインの状態):
R = Running (TASK_RUNNING)
S = Sleeping (TASK_INTERRUPTIBLE)
D = Disk sleep (TASK_UNINTERRUPTIBLE)
T = Stopped (__TASK_STOPPED)
t = Tracing stop (__TASK_TRACED)
Z = Zombie (EXIT_ZOMBIE)
X = Dead (EXIT_DEAD) ← 通常見えない
I = Idle (TASK_IDLE) ← カーネルワーカースレッド
追加文字:
< = 高優先度 (nice < 0)
N = 低優先度 (nice > 0)
L = ページがメモリにロックされている (mlock)
s = セッションリーダー
l = マルチスレッド
+ = フォアグラウンドプロセスグループ
4.4 D 状態 (TASK_UNINTERRUPTIBLE) の調査
# D 状態のプロセスを検出
$ ps aux | awk '$8 ~ /D/ { print }'
root 3456 0.0 0.0 0 0 ? D 10:00 0:05 [nfsd]
# D 状態プロセスのカーネルスタックを確認
$ cat /proc/3456/stack
[<0>] nfs4_get_valid_delegation+0x2b/0x80 [nfsd]
[<0>] nfs4_open_delegation+0x1a5/0x360 [nfsd]
[<0>] nfsd4_process_open2+0x3c3/0x950 [nfsd]
[<0>] nfsd4_open+0x5d1/0x8f0 [nfsd]
# wchan (待機チャネル) の確認
$ cat /proc/3456/wchan
nfs4_get_valid_delegation
# 全 D 状態プロセスの一覧と待機場所
$ for pid in $(ps -eo pid,stat | awk '$2 ~ /^D/ {print $1}'); do
echo "=== PID: $pid ($(cat /proc/$pid/comm 2>/dev/null)) ==="
cat /proc/$pid/wchan 2>/dev/null
echo
cat /proc/$pid/stack 2>/dev/null | head -5
echo "---"
done
4.5 TASK_KILLABLE 状態
Linux 2.6.25 で導入された TASK_KILLABLE は、TASK_UNINTERRUPTIBLE の問題を軽減する。
/* TASK_KILLABLE の使用例 (カーネル内) */
/* fs/nfs/nfs3proc.c 等で使用 */
/* 従来の方法 - シグナルで起床不可 */
wait_event(wq, condition); /* TASK_UNINTERRUPTIBLE */
/* TASK_KILLABLE - 致命的シグナル(SIGKILL等)で起床可能 */
wait_event_killable(wq, condition); /* TASK_KILLABLE */
/* 実装 */
#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
5. CFS (Completely Fair Scheduler)
5.1 CFS の概要
CFS (Completely Fair Scheduler) は Linux 2.6.23 で Ingo Molnar によって導入された、通常プロセス向けのスケジューラである。CFS は「理想的なマルチタスクプロセッサ」をモデルとし、各プロセスに公平に CPU 時間を配分することを目指す。
CFS の基本概念:
理想的なプロセッサ (N プロセスが同時に動作):
+---------------------------------------------------------+
| CPU 時間の配分 (N=3 の場合) |
| |
| 理想: 各プロセスが常に 1/3 の CPU を使用 |
| |
| 時間 → 0 1 2 3 4 5 6 7 8 9 |
| Proc A: ███ ███ ███ ███ ███ ███ ███ ███ ███ ███|
| Proc B: ███ ███ ███ ███ ███ ███ ███ ███ ███ ███|
| Proc C: ███ ███ ███ ███ ███ ███ ███ ███ ███ ███|
| 33% 33% 33% 33% 33% 33% 33% 33% 33% 33%|
| |
| CFS の近似 (時分割): |
| |
| 時間 → 0 1 2 3 4 5 6 7 8 9 |
| Proc A: ████████ ████████ ████████ |
| Proc B: ████████ ████████ █████████|
| Proc C: (各プロセスが順番に実行、vruntime で制御) |
+---------------------------------------------------------+
5.2 仮想実行時間 (vruntime)
CFS の中核概念は 仮想実行時間 (virtual runtime, vruntime) である。vruntime は各プロセスが受け取った CPU 時間の重み付き合計値で、nice 値によって調整される。
/*
* kernel/sched/fair.c - vruntime の更新
*/
static void update_curr(struct cfs_rq *cfs_rq)
{
struct sched_entity *curr = cfs_rq->curr;
u64 now = rq_clock_task(rq_of(cfs_rq));
u64 delta_exec;
delta_exec = now - curr->exec_start;
curr->exec_start = now;
curr->sum_exec_runtime += delta_exec;
/* vruntime の計算: 実行時間を重みで割る */
curr->vruntime += calc_delta_fair(delta_exec, curr);
/* calc_delta_fair: delta * NICE_0_LOAD / weight */
update_min_vruntime(cfs_rq);
}
vruntime と nice 値の関係:
nice = 0 の weight: 1024 (NICE_0_LOAD)
nice = -20 の weight: 88761 (約87倍)
nice = +19 の weight: 15 (約1/68倍)
nice値 weight vruntime 増加率 (nice=0 比)
------ ------ ----------------------
-20 88761 ×0.012 (非常に遅い = 多くの CPU 時間)
-10 9548 ×0.107
-5 3121 ×0.328
0 1024 ×1.000 (基準)
+5 335 ×3.057
+10 110 ×9.309
+19 15 ×68.267 (非常に速い = 少ない CPU 時間)
vruntime が小さいプロセスほど、次に実行される可能性が高い。
nice が低い(高優先度)プロセスは vruntime がゆっくり増加するため、
結果的により多くの CPU 時間を受け取る。
5.3 レッドブラックツリー
CFS はレッドブラックツリー(自己平衡二分探索木)を使用して、実行可能なプロセスを vruntime の順に管理する。
CFS のレッドブラックツリー:
vruntime で順序付け (左 < 右)
最も左のノード = 次に実行すべきプロセス
[Process D]
vruntime=50
/ (黒) \
/ \
[Process B] [Process F]
vruntime=30 vruntime=70
/ (赤) \ / (赤) \
/ \ / \
[Process A] [Process C] [Process E] [Process G]
vruntime=10 vruntime=40 vruntime=60 vruntime=90
(黒) (黒) (黒) (黒)
←── 最小 vruntime 最大 vruntime ──→
(次に実行) (最近たくさん実行した)
rb_leftmost キャッシュ → Process A (O(1) でアクセス)
操作の計算量:
- 次のプロセスの選択: O(1) (leftmost キャッシュ)
- プロセスの追加: O(log n)
- プロセスの削除: O(log n)
/*
* kernel/sched/fair.c - CFS のスケジューリング操作
*/
/* 次に実行すべきタスクを選択 */
static struct sched_entity *
pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
/* rb_leftmost: 最小 vruntime のエンティティ */
struct sched_entity *left = __pick_first_entity(cfs_rq);
/* ... buddy ヒントやスキップ処理 ... */
return left;
}
/* O(1) で最小 vruntime のエンティティを取得 */
struct sched_entity *__pick_first_entity(struct cfs_rq *cfs_rq)
{
struct rb_node *left = rb_first_cached(&cfs_rq->tasks_timeline);
if (!left)
return NULL;
return __node_2_se(left);
}
/* エンティティをRBツリーに追加 */
static void __enqueue_entity(struct cfs_rq *cfs_rq,
struct sched_entity *se)
{
struct rb_node **link = &cfs_rq->tasks_timeline.rb_root.rb_node;
struct rb_node *parent = NULL;
struct sched_entity *entry;
bool leftmost = true;
while (*link) {
parent = *link;
entry = __node_2_se(parent);
if (entity_before(se, entry)) {
link = &parent->rb_left;
} else {
link = &parent->rb_right;
leftmost = false;
}
}
rb_link_node(&se->run_node, parent, link);
rb_insert_color_cached(&se->run_node,
&cfs_rq->tasks_timeline, leftmost);
}
5.4 CFS のタイムスライス計算
/*
* CFS はタイムスライスを直接使用しない。
* 代わりに sched_period (スケジューリング周期) を使用し、
* これを重みに応じて分配する。
*/
/* kernel/sched/fair.c */
/*
* スケジューリング周期: 全プロセスが少なくとも1回実行される期間
*
* nr_running <= sched_nr_latency の場合:
* period = sysctl_sched_latency (デフォルト: 6ms)
*
* nr_running > sched_nr_latency の場合:
* period = nr_running * sysctl_sched_min_granularity
* (sched_min_granularity デフォルト: 0.75ms)
*/
static u64 __sched_period(unsigned long nr_running)
{
if (unlikely(nr_running > sched_nr_latency))
return nr_running * sysctl_sched_min_granularity;
else
return sysctl_sched_latency;
}
# CFS 関連のカーネルパラメータ確認
$ sysctl kernel.sched_latency_ns
kernel.sched_latency_ns = 6000000 # 6ms
$ sysctl kernel.sched_min_granularity_ns
kernel.sched_min_granularity_ns = 750000 # 0.75ms
$ sysctl kernel.sched_wakeup_granularity_ns
kernel.sched_wakeup_granularity_ns = 1000000 # 1ms
# CFS 帯域幅制限
$ sysctl kernel.sched_cfs_bandwidth_slice_us
kernel.sched_cfs_bandwidth_slice_us = 5000 # 5ms
# CFS のスケジューリング統計
$ cat /proc/schedstat
version 15
timestamp 4294967295
cpu0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
...
# 特定プロセスのスケジューリング統計
$ cat /proc/self/sched
bash (12345, #threads: 1)
-------------------------------------------------------------------
se.exec_start : 12345678.901234
se.vruntime : 567890.123456
se.sum_exec_runtime : 1234567.890123
se.nr_migrations : 150
nr_switches : 5000
nr_voluntary_switches : 4500
nr_involuntary_switches : 500
se.load.weight : 1024
se.runnable_weight : 1024
se.avg.load_sum : 47500
se.avg.runnable_sum : 47500
se.avg.util_sum : 47500
se.avg.load_avg : 98
se.avg.runnable_avg : 98
se.avg.util_avg : 98
policy : 0
prio : 120
clock-delta : 35
5.5 CFS グループスケジューリング
CFS グループスケジューリング:
cgroup 階層で CPU 時間を公平に配分する仕組み
ルート cgroup
/ \
グループ A グループ B
(cpu.weight=100) (cpu.weight=300)
/ | \ |
P1 P2 P3 P4
(nice=0) (nice=5) (nice=0) (nice=0)
CPU 時間の配分:
- グループ A: 100/(100+300) = 25%
- P1: 25% × 1024/(1024+335+1024) = 25% × 43% = 10.7%
- P2: 25% × 335/(1024+335+1024) = 25% × 14% = 3.5%
- P3: 25% × 1024/(1024+335+1024) = 25% × 43% = 10.7%
- グループ B: 300/(100+300) = 75%
- P4: 75%
各レベルで RB ツリーが存在:
ルート RB ツリー: [GroupA_se, GroupB_se]
GroupA の RB ツリー: [P1_se, P2_se, P3_se]
GroupB の RB ツリー: [P4_se]
6. EEVDF スケジューラ (Linux 6.6+)
6.1 EEVDF の概要
EEVDF (Earliest Eligible Virtual Deadline First) は Linux 6.6 で CFS を置き換えた新しいスケジューラである。Peter Zijlstra によって実装され、従来の CFS の問題点を解決する。
CFS から EEVDF への進化:
CFS の問題点:
1. sleeper bonus の複雑さ
→ スリープ後に復帰したタスクへの報酬の扱いが複雑
2. スケジューリング遅延の保証がない
→ 低優先度タスクが長時間待たされる可能性
3. buddy ヒューリスティクスの不透明さ
→ next buddy, last buddy 等のアドホックな最適化
EEVDF の改善:
1. デッドラインベースのタスク選択
→ 仮想デッドラインに基づく明確な選択基準
2. 遅延制約の明示的サポート
→ タスクがどれくらいの遅延を許容するか指定可能
3. シンプルで理論的に裏付けされたアルゴリズム
→ 1996年の EEVDF 論文に基づく
6.2 EEVDF の仕組み
EEVDF のコア概念:
1. Virtual Runtime (vruntime) - CFS と同様
→ タスクが消費した CPU 時間の重み付き値
2. Virtual Deadline (vdeadline)
→ タスクが次のタイムスライスを完了すべき仮想時間
vdeadline = eligible_time + (slice / weight)
3. Eligible Time
→ タスクが次のスライスを受け取る資格が発生する時間
→ lag が 0 以上のタスクは eligible
4. Lag
→ タスクが受け取るべき CPU 時間と実際に受け取った CPU 時間の差
→ lag > 0: タスクは不足している(優先すべき)
→ lag < 0: タスクは過剰に受け取っている
→ lag = Σ(expected_service) - Σ(actual_service)
タスク選択アルゴリズム:
1. eligible なタスク (lag >= 0) の中から
2. 最も早い vdeadline を持つタスクを選択
+---------+---------+----------+--------+---------+
| Task | vruntime| lag |eligible| vdeadline|
+---------+---------+----------+--------+---------+
| Task A | 100 | +5 ms | Yes | 115 | ← 選択
| Task B | 95 | +2 ms | Yes | 120 |
| Task C | 110 | -3 ms | No | --- | ← 除外
| Task D | 98 | +1 ms | Yes | 125 |
+---------+---------+----------+--------+---------+
6.3 EEVDF のカーネル実装
/*
* kernel/sched/fair.c (Linux 6.6+)
* EEVDF のタスク選択
*/
/*
* sched_entity に追加されたフィールド:
*/
struct sched_entity {
/* ... 既存フィールド ... */
u64 vruntime; /* 仮想実行時間 */
u64 deadline; /* 仮想デッドライン */
u64 min_deadline;/* サブツリーの最小デッドライン */
s64 vlag; /* 仮想遅延 */
u64 slice; /* 要求スライス */
};
/*
* EEVDF のタスク選択 (概略)
* pick_eevdf() は RB ツリーの中から
* eligible かつ最小 deadline のタスクを O(log n) で見つける
*/
static struct sched_entity *pick_eevdf(struct cfs_rq *cfs_rq)
{
struct rb_node *node = cfs_rq->tasks_timeline.rb_root.rb_node;
struct sched_entity *best = NULL;
while (node) {
struct sched_entity *se = __node_2_se(node);
/* min_deadline による枝刈り */
if (deadline_gt(min_deadline, cfs_rq, node, best))
break;
/* eligible チェック (vlag >= 0 に相当) */
if (entity_eligible(cfs_rq, se)) {
/* best よりデッドラインが早ければ更新 */
if (!best || deadline_gt(deadline, se, best))
best = se;
}
/* 左の子を探索 (より小さい vruntime) */
if (node->rb_left &&
!deadline_gt(min_deadline, cfs_rq, node->rb_left, best))
node = node->rb_left;
else
node = node->rb_right;
}
return best;
}
6.4 スライス長の制御
# EEVDF のスライス長関連パラメータ (Linux 6.6+)
# sysctl 経由で調整可能
# 最小スケジューリング粒度
$ sysctl kernel.sched_min_granularity_ns
kernel.sched_min_granularity_ns = 750000 # 0.75ms
# ベーススライス (EEVDF)
$ sysctl kernel.sched_base_slice_ns
kernel.sched_base_slice_ns = 750000 # 0.75ms (Linux 6.12+)
# デバッグ: EEVDF の統計確認
$ cat /proc/<pid>/sched | grep -E "deadline|vlag|slice"
se.deadline : 123456.789012
se.vlag : -0.123456
se.slice : 3000000.000000
6.5 CFS と EEVDF の比較
+-----------------------------+---------------------------+---------------------------+
| 特性 | CFS (Linux 2.6.23-6.5) | EEVDF (Linux 6.6+) |
+-----------------------------+---------------------------+---------------------------+
| タスク選択基準 | 最小 vruntime | eligible + 最小 deadline |
| 計算量 | O(1) / O(log n) | O(log n) (枝刈りで高速) |
| データ構造 | RB ツリー | RB ツリー (augmented) |
| 遅延保証 | ベストエフォート | 明示的な遅延境界 |
| sleeper 処理 | sleeper bonus | vlag ベース |
| タイムスライス | sched_period / weight | slice / weight → deadline |
| buddy ヒューリスティクス | next/last/skip buddy | 不要 (削除) |
| sysctl パラメータ | sched_latency_ns 他 | sched_base_slice_ns 他 |
| レイテンシ nice (latency_nice)| 非対応 | 対応予定 |
+-----------------------------+---------------------------+---------------------------+
7. リアルタイムスケジューリング
7.1 リアルタイムスケジューリングポリシー
Linux は POSIX リアルタイムスケジューリングをサポートする。リアルタイムタスクは通常タスク(CFS/EEVDF)よりも常に高い優先度を持つ。
スケジューリングクラスの優先度階層:
最高優先度
+------------------------------------------------+
| dl_sched_class (SCHED_DEADLINE) | deadline スケジューラ
| → 絶対的な最高優先度 |
+------------------------------------------------+
| rt_sched_class (SCHED_FIFO, SCHED_RR) | リアルタイムスケジューラ
| → 優先度 1-99 |
+------------------------------------------------+
| fair_sched_class (SCHED_NORMAL, SCHED_BATCH, | CFS / EEVDF
| SCHED_IDLE) |
| → nice -20 ~ +19 (優先度 100-139) |
+------------------------------------------------+
| idle_sched_class | アイドルスケジューラ
| → CPU がアイドルの時のみ |
+------------------------------------------------+
最低優先度
カーネル内部の優先度値 (0-139):
0-99: リアルタイム優先度 (値が大きいほど高優先度)
100-139: 通常優先度 (nice -20=100, nice 0=120, nice +19=139)
7.2 SCHED_FIFO (First In, First Out)
SCHED_FIFO の動作:
- 同一優先度内では先着順
- より高い優先度のタスクがプリエンプト可能
- タイムスライスなし(自発的に CPU を手放すまで実行)
時間 →
優先度99: |AAAAAAAAAAAA| |AAAAAAA...(完了まで)
優先度50: | |BBBBBBB| |BBBBBBB...
優先度 1: | | | | |CCC...
A(優先度99)が実行中 → Aが完了/ブロック
→ B(優先度50)が実行開始 → A が復帰して B をプリエンプト
→ A が完了 → B が再開
# SCHED_FIFO でプロセスを実行
$ sudo chrt -f 50 ./my_realtime_app
# 実行中のプロセスのスケジューリングポリシーを変更
$ sudo chrt -f -p 50 <PID>
# 現在のスケジューリングポリシーを確認
$ chrt -p <PID>
pid 12345's current scheduling policy: SCHED_FIFO
pid 12345's current scheduling priority: 50
7.3 SCHED_RR (Round Robin)
SCHED_RR の動作:
- SCHED_FIFO と同様だが、タイムスライス(デフォルト 100ms)あり
- 同一優先度のタスク間でラウンドロビン
時間 → (タイムスライス = 100ms)
優先度50: |AAA|BBB|AAA|BBB|AAA|BBB|... (A と B が交互に実行)
優先度 1: | | | | | | |CCC (50が全てブロックしたら)
# SCHED_RR でプロセスを実行
$ sudo chrt -r 30 ./my_app
# RR のタイムスライスを確認
$ chrt -m
SCHED_OTHER min/max priority : 0/0
SCHED_FIFO min/max priority : 1/99
SCHED_RR min/max priority : 1/99
SCHED_BATCH min/max priority : 0/0
SCHED_IDLE min/max priority : 0/0
SCHED_DEADLINE min/max priority : 0/0
# RR のデフォルトタイムクォンタム
$ cat /proc/sys/kernel/sched_rr_timeslice_ms
100
7.4 SCHED_DEADLINE
Linux 3.14 で導入された Earliest Deadline First (EDF) ベースのスケジューラ。
SCHED_DEADLINE のパラメータ:
runtime: タスクが period 内で使用可能な最大 CPU 時間
deadline: タスクが runtime を完了すべき相対的な期限
period: タスクの実行周期
制約: runtime <= deadline <= period
例: runtime=10ms, deadline=30ms, period=50ms
時間 →
|<-- period (50ms) --->|<-- period (50ms) --->|
| | |
|RRRR | |RRRR | |
|<10ms> | |<10ms> | |
| <deadline 30ms> | <deadline 30ms> |
| ↑ | ↑ |
| この時点までに | この時点までに |
| 10ms実行完了 | 10ms実行完了 |
帯域幅テスト:
Σ(runtime_i / period_i) <= (CPU数 × sched_rt_runtime_us / sched_rt_period_us)
/* SCHED_DEADLINE の設定例 */
#include <sched.h>
#include <linux/sched.h>
#include <linux/types.h>
struct sched_attr {
__u32 size;
__u32 sched_policy;
__u64 sched_flags;
__s32 sched_nice;
__u32 sched_priority;
__u64 sched_runtime; /* ナノ秒 */
__u64 sched_deadline; /* ナノ秒 */
__u64 sched_period; /* ナノ秒 */
};
int main(void)
{
struct sched_attr attr;
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.sched_policy = SCHED_DEADLINE;
attr.sched_runtime = 10000000; /* 10 ms */
attr.sched_deadline = 30000000; /* 30 ms */
attr.sched_period = 50000000; /* 50 ms */
/* sched_setattr() で設定 */
if (syscall(SYS_sched_setattr, 0, &attr, 0) < 0) {
perror("sched_setattr");
return 1;
}
/* リアルタイムループ */
while (1) {
/* 周期的な処理 */
do_work();
sched_yield(); /* period の残りをスリープ */
}
return 0;
}
# SCHED_DEADLINE でプロセスを実行
$ sudo chrt -d --sched-runtime 10000000 \
--sched-deadline 30000000 \
--sched-period 50000000 0 ./deadline_app
# RT 帯域幅制限の確認
$ cat /proc/sys/kernel/sched_rt_runtime_us
950000 # RT タスクは 1 秒あたり最大 0.95 秒使用可能
$ cat /proc/sys/kernel/sched_rt_period_us
1000000 # 1 秒周期
7.5 リアルタイムスロットリング
# RT スロットリングの設定
# デフォルト: RT タスクは 1 秒中 0.95 秒まで (5% はその他タスク用)
$ sysctl kernel.sched_rt_runtime_us
kernel.sched_rt_runtime_us = 950000
$ sysctl kernel.sched_rt_period_us
kernel.sched_rt_period_us = 1000000
# RT スロットリングを無効化(危険!)
$ sudo sysctl -w kernel.sched_rt_runtime_us=-1
# RT スロットリングの監視
$ cat /proc/sched_debug | grep -A5 "rt_rq"
rt_rq[0]:
.rt_nr_running : 2
.rt_throttled : 0
.rt_time : 0.450000
.rt_runtime : 950.000000
8. CPU アフィニティと cgroups CPU コントローラ
8.1 CPU アフィニティ
CPU アフィニティは、プロセスを特定の CPU コアに固定する機能である。
CPU アフィニティの概念:
アフィニティマスク: ビットマスクで表現
CPU: 7 6 5 4 3 2 1 0
[0][0][0][0][1][1][1][1] = 0x0F (CPU 0-3)
[1][1][1][1][0][0][0][0] = 0xF0 (CPU 4-7)
[0][0][0][0][0][0][0][1] = 0x01 (CPU 0 のみ)
[1][1][1][1][1][1][1][1] = 0xFF (全CPU)
ユースケース:
- レイテンシが重要なプロセスを専用 CPU に固定
- NUMA 対応: プロセスをローカルメモリに近い CPU に配置
- CPU キャッシュの効率化
- ジッター (jitter) の低減
# 現在のアフィニティを確認
$ taskset -p $$
pid 12345's current affinity mask: ff
# CPU 0,1 にプロセスを固定
$ taskset -c 0,1 ./my_app
# 実行中のプロセスのアフィニティを変更
$ taskset -pc 0-3 12345
pid 12345's current affinity list: 0-7
pid 12345's new affinity list: 0-3
# CPU アフィニティのプログラム例
$ cat affinity_example.c
/* affinity_example.c */
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
cpu_set_t mask;
int num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
/* 現在のアフィニティを取得 */
CPU_ZERO(&mask);
if (sched_getaffinity(0, sizeof(mask), &mask) < 0) {
perror("sched_getaffinity");
return 1;
}
printf("Available CPUs: ");
for (int i = 0; i < num_cpus; i++) {
if (CPU_ISSET(i, &mask))
printf("%d ", i);
}
printf("\n");
/* CPU 0 と CPU 1 に制限 */
CPU_ZERO(&mask);
CPU_SET(0, &mask);
CPU_SET(1, &mask);
if (sched_setaffinity(0, sizeof(mask), &mask) < 0) {
perror("sched_setaffinity");
return 1;
}
printf("Now restricted to CPUs 0 and 1\n");
printf("Running on CPU %d\n", sched_getcpu());
return 0;
}
8.2 cgroups v2 CPU コントローラ
cgroups v2 CPU コントローラの階層:
/sys/fs/cgroup/
├── cgroup.controllers # 利用可能なコントローラ一覧
├── cgroup.subtree_control # 子 cgroup に有効化するコントローラ
├── cpu.max # CPU 帯域幅制限
├── cpu.weight # CPU 配分の重み
├── cpu.stat # CPU 使用統計
│
├── web-servers/
│ ├── cpu.max # "100000 100000" (100%)
│ ├── cpu.weight # 100
│ ├── cpu.stat
│ └── tasks
│
├── batch-jobs/
│ ├── cpu.max # "50000 100000" (50%)
│ ├── cpu.weight # 50
│ ├── cpu.stat
│ └── tasks
│
└── system/
├── cpu.max # "max 100000" (制限なし)
├── cpu.weight # 200
└── tasks
# cgroups v2 CPU コントローラの設定
# 1. CPU コントローラの有効化
$ echo "+cpu" | sudo tee /sys/fs/cgroup/cgroup.subtree_control
# 2. cgroup の作成
$ sudo mkdir -p /sys/fs/cgroup/myapp
# 3. CPU 配分の重み設定 (1-10000, デフォルト 100)
$ echo 200 | sudo tee /sys/fs/cgroup/myapp/cpu.weight
# 4. CPU 帯域幅制限の設定
# 書式: "$MAX $PERIOD" (マイクロ秒)
# 100ms 周期中に 50ms まで使用可能 (50%)
$ echo "50000 100000" | sudo tee /sys/fs/cgroup/myapp/cpu.max
# 制限なし
$ echo "max 100000" | sudo tee /sys/fs/cgroup/myapp/cpu.max
# 5. プロセスを cgroup に追加
$ echo $PID | sudo tee /sys/fs/cgroup/myapp/cgroup.procs
# 6. 統計の確認
$ cat /sys/fs/cgroup/myapp/cpu.stat
usage_usec 1234567
user_usec 1000000
system_usec 234567
nr_periods 5000
nr_throttled 120
throttled_usec 6000000
# 7. cpuset による CPU 固定 (cgroups v2)
$ echo "+cpuset" | sudo tee /sys/fs/cgroup/cgroup.subtree_control
$ echo "0-3" | sudo tee /sys/fs/cgroup/myapp/cpuset.cpus
$ echo "0" | sudo tee /sys/fs/cgroup/myapp/cpuset.mems
8.3 cgroups v1 CPU コントローラ (レガシー)
# cgroups v1 (一部のシステムでまだ使用される)
# CPU 比率 (cpu.shares)
$ sudo cgcreate -g cpu:/mygroup
$ echo 512 | sudo tee /sys/fs/cgroup/cpu/mygroup/cpu.shares
# デフォルト 1024、512 は半分の配分
# CPU 帯域幅制限 (CFS bandwidth)
$ echo 50000 | sudo tee /sys/fs/cgroup/cpu/mygroup/cpu.cfs_quota_us
$ echo 100000 | sudo tee /sys/fs/cgroup/cpu/mygroup/cpu.cfs_period_us
# 100ms 中 50ms = 50%
# RT 帯域幅制限
$ echo 200000 | sudo tee /sys/fs/cgroup/cpu/mygroup/cpu.rt_runtime_us
$ echo 1000000 | sudo tee /sys/fs/cgroup/cpu/mygroup/cpu.rt_period_us
# プロセスを cgroup に追加
$ sudo cgclassify -g cpu:/mygroup <PID>
# または
$ echo <PID> | sudo tee /sys/fs/cgroup/cpu/mygroup/cgroup.procs
8.4 systemd による CPU 制御
# systemd サービスの CPU 制御
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target
[Service]
ExecStart=/usr/bin/myapp
CPUWeight=200 # cgroup v2 の cpu.weight (1-10000)
CPUQuota=150% # CPU 使用率制限 (150% = 1.5 CPU)
AllowedCPUs=0-3 # 使用可能な CPU コア
CPUAffinity=0 1 2 3 # CPU アフィニティ
# Nice 値の設定
Nice=-5
# スケジューリングポリシー
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=50
[Install]
WantedBy=multi-user.target
# 一時的な変更
$ sudo systemctl set-property myapp.service CPUQuota=80%
$ sudo systemctl set-property myapp.service CPUWeight=300
# 確認
$ systemctl show myapp.service | grep -i cpu
CPUWeight=200
CPUQuota=150%
AllowedCPUs=0-3
CPUAffinity=0 1 2 3
9. Nice 値と優先度
9.1 Nice 値の仕組み
Nice 値と優先度の対応:
Nice値 カーネル優先度 CPU配分(概算) 用途
------ ------------- ----------- ----
-20 100 ~88倍 (vs nice 0) 最高優先の重要プロセス
-15 105 ~30倍 リアルタイムに近い処理
-10 110 ~10倍 高優先の本番サービス
-5 115 ~3倍 やや重要なプロセス
0 120 1倍 (基準) デフォルト
+5 125 ~1/3 バックグラウンドジョブ
+10 130 ~1/10 低優先バッチ処理
+15 135 ~1/30 アイドル時にのみ処理
+19 139 ~1/68 最低優先度
nice 値の1段階の差 ≒ CPU 配分で約 10% の変化
(正確には 1.25 倍: 1024/820 ≒ 1.25)
# nice コマンドの使用
$ nice -n 10 ./batch_job # nice +10 で起動
$ nice -n -5 ./important_app # nice -5 で起動 (要 root)
# renice で実行中プロセスの nice 値を変更
$ renice 15 -p 12345 # PID 12345 の nice を 15 に
$ renice -5 -p 12345 # 要 root
$ renice 10 -u username # ユーザーの全プロセスを nice 10 に
# 確認
$ ps -o pid,ni,pri,comm -p 12345
PID NI PRI COMMAND
12345 10 30 batch_job
# /proc から確認
$ cat /proc/12345/stat | awk '{print "nice:", $19, "priority:", $18}'
nice: 10 priority: 30
# RLIMIT_NICE による制限
$ ulimit -e
0 # nice 値を下げられる範囲 (0 = nice 0 まで)
# 非特権ユーザーが nice を下げられるように設定
# /etc/security/limits.conf
# username - nice -5
9.2 autogroup (per-TTY group scheduling)
# autogroup: TTY セッションごとにスケジューリンググループを作成
# デスクトップのレスポンスを維持しつつ、コンパイル等の重い処理を並行実行
# autogroup の有効化確認
$ sysctl kernel.sched_autogroup_enabled
kernel.sched_autogroup_enabled = 1
# autogroup の nice 値を表示
$ cat /proc/self/autogroup
/autogroup-234 nice 0
# autogroup の nice 値を変更
$ echo 10 > /proc/self/autogroup # このセッションの autogroup nice を 10 に
# 使用例:
# ターミナル 1 (コンパイル):
$ echo 5 > /proc/self/autogroup # autogroup nice を 5 に
$ make -j$(nproc) # 大規模コンパイル
# ターミナル 2 (通常作業):
$ echo 0 > /proc/self/autogroup # デフォルトの nice 0
$ vim # レスポンスが維持される
10. コンテキストスイッチの仕組み
10.1 コンテキストスイッチとは
コンテキストスイッチは、CPU が一つのプロセス/スレッドの実行から別のプロセス/スレッドの実行に切り替わる処理である。
コンテキストスイッチの全体像:
プロセス A (実行中) プロセス B (待機中)
+------------------+ +------------------+
| ユーザーレジスタ | | ユーザーレジスタ |
| - RAX, RBX, ... | | - RAX, RBX, ... |
| - RSP (スタック) | | - RSP (スタック) |
| - RIP (命令ptr) | | - RIP (命令ptr) |
| - RFLAGS | | - RFLAGS |
| - FPU/SSE/AVX | | - FPU/SSE/AVX |
+--------+---------+ +--------+---------+
| ^
| 1. レジスタ保存 |
v | 5. レジスタ復元
+--------+---------+ +--------+---------+
| カーネルスタック | | カーネルスタック |
| (thread.sp) | | (thread.sp) |
+--------+---------+ +--------+---------+
| ^
| 2. スタック切替 | 4. スタック切替
v |
+--------+-----------------------------------------+---------+
| schedule() / __schedule() |
| | |
| 3. context_switch() |
| 3a. switch_mm() - ページテーブル切替 |
| 3b. switch_to() - CPU 状態切替 |
+------------------------------------------------------------+
10.2 コンテキストスイッチの詳細フロー
/*
* kernel/sched/core.c - __schedule()
* スケジューラのメインルーチン
*/
static void __sched notrace __schedule(unsigned int sched_mode)
{
struct task_struct *prev, *next;
struct rq *rq;
int cpu;
cpu = smp_processor_id();
rq = cpu_rq(cpu);
prev = rq->curr;
/* 1. 次に実行するタスクを選択 */
next = pick_next_task(rq, prev, &rf);
if (likely(prev != next)) {
rq->nr_switches++;
rq->curr = next;
/* 2. コンテキストスイッチの実行 */
context_switch(rq, prev, next, &rf);
/* ここでは next のコンテキストで実行中 */
}
}
/*
* context_switch() の内部
*/
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next, struct rq_flags *rf)
{
prepare_task_switch(rq, prev, next);
/*
* switch_mm_irqs_off(): メモリ空間の切替
* - ページテーブルの変更 (CR3 レジスタの更新)
* - TLB のフラッシュ (必要に応じて)
* - ASID/PCID の管理
*/
if (!next->mm) {
/* カーネルスレッドの場合 */
enter_lazy_tlb(prev->active_mm, next);
next->active_mm = prev->active_mm;
} else {
switch_mm_irqs_off(prev->active_mm, next->mm, next);
}
/*
* switch_to(): CPU レジスタの切替
* - 汎用レジスタの保存/復元
* - スタックポインタの切替
* - FPU/SSE/AVX レジスタの管理
* - per-CPU 変数 current_task の更新
*/
switch_to(prev, next, prev);
/* ここからは next のコンテキスト */
return finish_task_switch(prev);
}
10.3 コンテキストスイッチのコスト
# コンテキストスイッチ回数の監視
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 4521000 234000 3456000 0 0 0 0 1234 5678 5 2 93 0 0
3 0 0 4520000 234000 3456000 0 0 0 8 1456 6234 8 3 89 0 0
^^^^
コンテキストスイッチ/秒
# 特定プロセスのコンテキストスイッチ統計
$ grep ctxt /proc/<PID>/status
voluntary_ctxt_switches: 1234 # 自発的スイッチ (IO待ち等)
nonvoluntary_ctxt_switches: 567 # 非自発的スイッチ (タイムスライス超過)
# pidstat でコンテキストスイッチを監視
$ pidstat -w 1
Linux 6.6.0 (hostname) 04/10/2026 _x86_64_ (8 CPU)
10:00:01 AM UID PID cswch/s nvcswch/s Command
10:00:02 AM 0 1 0.99 0.00 systemd
10:00:02 AM 0 800 5.94 0.00 sshd
10:00:02 AM 1000 12345 15.84 2.97 my_app
10:00:02 AM 1000 12400 0.99 0.00 vim
# perf でコンテキストスイッチを詳細分析
$ sudo perf stat -e context-switches,cpu-migrations \
-p <PID> -- sleep 10
Performance counter stats for process id '12345':
15,234 context-switches
123 cpu-migrations
10.001234 seconds time elapsed
# perf sched でスケジューリングイベントの記録
$ sudo perf sched record -- sleep 5
$ sudo perf sched latency
Task | Runtime ms | Switches | Avg delay ms |
--------------------------------------------------------
my_app:12345 | 1234.567 | 5000 | 0.050 |
sshd:800 | 12.345 | 100 | 0.020 |
kworker/0:1:3456 | 5.678 | 500 | 0.010 |
10.4 自発的 vs 非自発的コンテキストスイッチ
自発的コンテキストスイッチ (voluntary):
- プロセスが自ら CPU を手放す
- IO 待ち、sleep()、mutex ロック待ち等
- 対策不要(正常な動作)
プロセス → sys_read() → ディスクIO待ち → TASK_INTERRUPTIBLE → schedule()
↓
コンテキストスイッチ
非自発的コンテキストスイッチ (involuntary):
- スケジューラによる強制的な CPU 剥奪
- タイムスライス超過、より高優先度のタスクの到着
- 多すぎる場合は CPU 不足のサイン
プロセス → 計算処理中 → タイムスライス超過 → set_tsk_need_resched()
↓
コンテキストスイッチ
10.5 TLB フラッシュの最適化
PCID (Process Context ID) による TLB フラッシュの回避:
従来 (PCID なし):
switch_mm() → CR3 書き換え → TLB 全フラッシュ → コスト大
PCID あり (Intel Haswell 以降):
switch_mm() → CR3 書き換え (PCID タグ付き) → TLB フラッシュ不要
※ 各プロセスに PCID (12bit, 最大 4096) を割り当て
※ TLB エントリに PCID タグを付与
+--------+--------+------------------+
| PCID | 仮想 | 物理 |
| (12bit)| アドレス| アドレス |
+--------+--------+------------------+
| 1 | 0x1000 | 0xABC000 | Process A
| 1 | 0x2000 | 0xDEF000 | Process A
| 2 | 0x1000 | 0x123000 | Process B
| 2 | 0x2000 | 0x456000 | Process B
+--------+--------+------------------+
→ プロセス切替時に TLB フラッシュ不要
11. スレッドとプロセス (NPTL)
11.1 Linux におけるスレッドの実装
Linux カーネルはプロセスとスレッドを区別しない。どちらも task_struct で表現され、clone() のフラグによってリソースの共有レベルが決まる。
プロセスとスレッドの比較:
プロセス (fork):
+---Process A---+ +---Process B---+
| task_struct | | task_struct |
| pid: 1000 | | pid: 1001 |
| tgid: 1000 | | tgid: 1001 |
+-------+-------+ +-------+-------+
| mm_struct | | mm_struct | ← 独立した仮想メモリ
| (独自) | | (独自) |
+-------+-------+ +-------+-------+
| files_struct | | files_struct | ← 独立したファイルテーブル
| (独自) | | (独自) |
+---------------+ +---------------+
スレッド (clone with CLONE_VM | CLONE_FILES | ...):
+---Thread 1----+ +---Thread 2----+
| task_struct | | task_struct |
| pid: 1000 | | pid: 1001 |
| tgid: 1000 | | tgid: 1000 | ← 同じ TGID
+-------+-------+ +-------+-------+
\ /
\ /
v v
+--------mm_struct--------+ ← 共有された仮想メモリ
| (共有) |
+---------+----------+----+
| |
+---------+----------+----+ ← 共有されたファイルテーブル
| files_struct (共有) |
+-------------------------+
11.2 NPTL (Native POSIX Threads Library)
NPTL の歴史:
LinuxThreads (初期)
→ NGPT (IBM の実装)
→ NPTL (Ulrich Drepper, Ingo Molnar) ← 現在の標準
NPTL の特徴:
- 1:1 スレッドモデル (1 ユーザースレッド = 1 カーネルスレッド)
- clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
CLONE_THREAD | CLONE_SYSVSEM | CLONE_SETTLS |
CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID)
- futex によるスレッド同期
- TLS (Thread Local Storage) のサポート
- POSIX 準拠のシグナル配送
/* pthread 使用例 */
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
void *thread_func(void *arg)
{
int thread_num = *(int *)arg;
pid_t tid = syscall(SYS_gettid);
printf("Thread %d: PID=%d, TID=%d, TGID(getpid)=%d\n",
thread_num, tid, tid, getpid());
/* このスレッドの /proc エントリ */
char path[64];
snprintf(path, sizeof(path), "/proc/%d/task/%d/status", getpid(), tid);
printf("Thread %d status: %s\n", thread_num, path);
sleep(2);
return NULL;
}
int main(void)
{
pthread_t threads[4];
int thread_nums[4];
printf("Main: PID=%d, TID=%ld\n", getpid(), syscall(SYS_gettid));
for (int i = 0; i < 4; i++) {
thread_nums[i] = i;
pthread_create(&threads[i], NULL, thread_func, &thread_nums[i]);
}
for (int i = 0; i < 4; i++)
pthread_join(threads[i], NULL);
return 0;
}
# コンパイルと実行
$ gcc -pthread -o thread_demo thread_demo.c
$ ./thread_demo
Main: PID=12345, TID=12345
Thread 0: PID=12346, TID=12346, TGID(getpid)=12345
Thread 1: PID=12347, TID=12347, TGID(getpid)=12345
Thread 2: PID=12348, TID=12348, TGID(getpid)=12345
Thread 3: PID=12349, TID=12349, TGID(getpid)=12345
# スレッドの確認
$ ps -T -p 12345
PID SPID TTY TIME CMD
12345 12345 pts/0 00:00:00 thread_demo
12345 12346 pts/0 00:00:00 thread_demo
12345 12347 pts/0 00:00:00 thread_demo
12345 12348 pts/0 00:00:00 thread_demo
12345 12349 pts/0 00:00:00 thread_demo
# /proc でスレッドを確認
$ ls /proc/12345/task/
12345 12346 12347 12348 12349
# NPTL バージョンの確認
$ getconf GNU_LIBPTHREAD_VERSION
NPTL 2.38
11.3 スレッドのスタック
マルチスレッドプロセスのメモリレイアウト:
高いアドレス
+-----------------------------------+
| カーネル空間 |
+-----------------------------------+ 0x7FFFFFFFFFFF (x86_64)
| |
| メインスレッドのスタック ↓ | (通常 8MB)
| |
+-----------------------------------+
| ガードページ (読取不可) |
+-----------------------------------+
| |
| Thread 3 スタック ↓ | (pthread_attr_t で設定)
| (デフォルト 8MB) |
+-----------------------------------+
| ガードページ |
+-----------------------------------+
| |
| Thread 2 スタック ↓ |
| |
+-----------------------------------+
| ガードページ |
+-----------------------------------+
| |
| Thread 1 スタック ↓ |
| |
+-----------------------------------+
| : |
| mmap 領域 (共有ライブラリ等) |
| : |
+-----------------------------------+
| |
| ヒープ ↑ |
| |
+-----------------------------------+
| BSS (初期化されていないデータ) |
+-----------------------------------+
| データセグメント |
+-----------------------------------+
| テキストセグメント (コード) |
+-----------------------------------+ 0x00400000
低いアドレス
12. プロセス名前空間 (PID Namespace)
12.1 PID 名前空間の概要
PID 名前空間は、プロセスに独立した PID 空間を提供する。コンテナ技術の基盤の一つ。
PID 名前空間の階層:
ルート PID 名前空間 (ホスト)
+--------------------------------------------------+
| PID 1: systemd |
| PID 2: kthreadd |
| PID 100: container_runtime |
| |
| 子 PID 名前空間 (コンテナ A) |
| +--------------------------------------------+ |
| | PID 1 (ホストでは PID 200): container_init | |
| | PID 2 (ホストでは PID 201): nginx | |
| | PID 3 (ホストでは PID 202): worker | |
| +--------------------------------------------+ |
| |
| 子 PID 名前空間 (コンテナ B) |
| +--------------------------------------------+ |
| | PID 1 (ホストでは PID 300): container_init | |
| | PID 2 (ホストでは PID 301): mysql | |
| +--------------------------------------------+ |
+--------------------------------------------------+
重要な性質:
- 親名前空間は子名前空間のプロセスを見える
- 子名前空間は親名前空間のプロセスを見えない
- 各名前空間に PID 1 が存在し、init の役割を果たす
- PID 1 が死ぬと、その名前空間内の全プロセスが終了
/* PID 名前空間の作成例 */
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mount.h>
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
static int child_func(void *arg)
{
printf("Child: PID in new namespace = %d\n", getpid());
printf("Child: PPID in new namespace = %d\n", getppid());
/* /proc をマウントして新しい PID 空間を反映 */
mount("proc", "/proc", "proc", 0, NULL);
/* 新しい名前空間で bash を起動 */
execlp("bash", "bash", NULL);
perror("execlp");
return 1;
}
int main(void)
{
printf("Parent: PID = %d\n", getpid());
pid_t child_pid = clone(child_func,
child_stack + STACK_SIZE,
CLONE_NEWPID | CLONE_NEWNS | SIGCHLD,
NULL);
if (child_pid < 0) {
perror("clone");
exit(1);
}
printf("Parent: child's PID in parent namespace = %d\n", child_pid);
waitpid(child_pid, NULL, 0);
return 0;
}
# unshare コマンドで PID 名前空間を作成
$ sudo unshare --pid --mount --fork bash
# 新しい名前空間内で確認
# PID 1 = この bash
$ echo $$
1
# proc を再マウント
$ mount -t proc proc /proc
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 8500 3200 pts/0 S 10:00 0:00 bash
root 10 0.0 0.0 10500 3800 pts/0 R+ 10:00 0:00 ps aux
# ホスト側からは元の PID で見える
$ ps aux | grep unshare
root 54321 0.0 0.0 8500 3200 pts/0 S+ 10:00 0:00 bash
12.2 名前空間の種類一覧
Linux 名前空間の種類 (参考):
+------------------+----------+-------------------------------------------+
| 名前空間 | フラグ | 分離するリソース |
+------------------+----------+-------------------------------------------+
| PID | CLONE_NEWPID | プロセスID |
| Network | CLONE_NEWNET | ネットワークスタック |
| Mount | CLONE_NEWNS | マウントポイント |
| UTS | CLONE_NEWUTS | ホスト名・ドメイン名 |
| IPC | CLONE_NEWIPC | System V IPC, POSIX メッセージキュー |
| User | CLONE_NEWUSER | UID/GID マッピング |
| Cgroup | CLONE_NEWCGROUP | cgroup ルート |
| Time (5.6+) | CLONE_NEWTIME | CLOCK_MONOTONIC, CLOCK_BOOTTIME |
+------------------+----------+-------------------------------------------+
# 現在のプロセスの名前空間を確認
$ ls -la /proc/self/ns/
lrwxrwxrwx 1 user user 0 Apr 10 10:00 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 user user 0 Apr 10 10:00 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 user user 0 Apr 10 10:00 mnt -> 'mnt:[4026531841]'
lrwxrwxrwx 1 user user 0 Apr 10 10:00 net -> 'net:[4026531840]'
lrwxrwxrwx 1 user user 0 Apr 10 10:00 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 user user 0 Apr 10 10:00 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 user user 0 Apr 10 10:00 time -> 'time:[4026531834]'
lrwxrwxrwx 1 user user 0 Apr 10 10:00 user -> 'user:[4026531837]'
lrwxrwxrwx 1 user user 0 Apr 10 10:00 uts -> 'uts:[4026531838]'
13. /proc ファイルシステムによるプロセス情報
13.1 /proc/ の構造
# プロセスごとの /proc ディレクトリ構造
$ ls -la /proc/12345/
total 0
dr-xr-xr-x 9 user user 0 Apr 10 10:00 .
dr-xr-xr-x 300 root root 0 Mar 1 00:00 ..
dr-xr-xr-x 2 user user 0 Apr 10 10:00 attr/
-rw-r--r-- 1 user user 0 Apr 10 10:00 autogroup
-r-------- 1 user user 0 Apr 10 10:00 auxv
-r--r--r-- 1 user user 0 Apr 10 10:00 cgroup
--w------- 1 user user 0 Apr 10 10:00 clear_refs
-r--r--r-- 1 user user 0 Apr 10 10:00 cmdline
-rw-r--r-- 1 user user 0 Apr 10 10:00 comm
-rw-r--r-- 1 user user 0 Apr 10 10:00 coredump_filter
-r--r--r-- 1 user user 0 Apr 10 10:00 cpuset
lrwxrwxrwx 1 user user 0 Apr 10 10:00 cwd -> /home/user
-r-------- 1 user user 0 Apr 10 10:00 environ
lrwxrwxrwx 1 user user 0 Apr 10 10:00 exe -> /usr/bin/bash
dr-x------ 2 user user 0 Apr 10 10:00 fd/
dr-x------ 2 user user 0 Apr 10 10:00 fdinfo/
-rw-r--r-- 1 user user 0 Apr 10 10:00 gid_map
-r-------- 1 user user 0 Apr 10 10:00 io
-r--r--r-- 1 user user 0 Apr 10 10:00 limits
-r--r--r-- 1 user user 0 Apr 10 10:00 maps
-rw------- 1 user user 0 Apr 10 10:00 mem
-r--r--r-- 1 user user 0 Apr 10 10:00 mountinfo
-r--r--r-- 1 user user 0 Apr 10 10:00 mounts
dr-xr-xr-x 6 user user 0 Apr 10 10:00 net/
dr-x--x--x 2 user user 0 Apr 10 10:00 ns/
-rw-r--r-- 1 user user 0 Apr 10 10:00 oom_adj
-r--r--r-- 1 user user 0 Apr 10 10:00 oom_score
-rw-r--r-- 1 user user 0 Apr 10 10:00 oom_score_adj
-r-------- 1 user user 0 Apr 10 10:00 pagemap
-r-------- 1 user user 0 Apr 10 10:00 personality
lrwxrwxrwx 1 user user 0 Apr 10 10:00 root -> /
-rw-r--r-- 1 user user 0 Apr 10 10:00 sched
-r--r--r-- 1 user user 0 Apr 10 10:00 schedstat
-r--r--r-- 1 user user 0 Apr 10 10:00 smaps
-r--r--r-- 1 user user 0 Apr 10 10:00 smaps_rollup
-r--r--r-- 1 user user 0 Apr 10 10:00 stack
-r--r--r-- 1 user user 0 Apr 10 10:00 stat
-r--r--r-- 1 user user 0 Apr 10 10:00 statm
-r--r--r-- 1 user user 0 Apr 10 10:00 status
-r-------- 1 user user 0 Apr 10 10:00 syscall
dr-xr-xr-x 12 user user 0 Apr 10 10:00 task/
-rw-r--r-- 1 user user 0 Apr 10 10:00 uid_map
-r--r--r-- 1 user user 0 Apr 10 10:00 wchan
13.2 重要な /proc/ ファイルの詳細
# ---- /proc/<PID>/status - 人間が読みやすい形式 ----
$ cat /proc/12345/status
Name: nginx
Umask: 0022
State: S (sleeping)
Tgid: 12345
Ngid: 0
Pid: 12345
PPid: 1
TracerPid: 0
Uid: 33 33 33 33
Gid: 33 33 33 33
FDSize: 128
Groups: 33
NStgid: 12345
NSpid: 12345
NSpgid: 12345
NSsid: 12345
VmPeak: 123456 kB
VmSize: 120000 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 45000 kB
VmRSS: 43000 kB
RssAnon: 10000 kB
RssFile: 33000 kB
RssShmem: 0 kB
VmData: 15000 kB
VmStk: 136 kB
VmExe: 1200 kB
VmLib: 8500 kB
VmPTE: 200 kB
VmSwap: 0 kB
Threads: 4
SigQ: 0/63372
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000040001000
SigCgt: 0000000198016a07
CapInh: 0000000000000000
CapPrm: 00000000a80425fb
CapEff: 00000000a80425fb
CapBnd: 00000000a80425fb
CapAmb: 0000000000000000
Seccomp: 2
Cpus_allowed: ff
Cpus_allowed_list: 0-7
Mems_allowed: 00000001
voluntary_ctxt_switches: 15234
nonvoluntary_ctxt_switches: 567
# ---- /proc/<PID>/stat - カーネルが使う形式 (1行) ----
$ cat /proc/12345/stat
12345 (nginx) S 1 12345 12345 0 -1 4194304 1234 0 0 0 500 100 0 0 20 0 4 0 12345678 123456789 10750 18446744073709551615 94620344832 94620346789 140724532101888 0 0 0 0 16781312 17410047 0 0 0 17 2 0 0 0 0 0 ...
# 各フィールドの意味:
# (1) pid: 12345
# (2) comm: (nginx)
# (3) state: S
# (4) ppid: 1
# (5) pgrp: 12345
# (6) session: 12345
# (7) tty_nr: 0
# (14) utime: 500 (clock ticks)
# (15) stime: 100 (clock ticks)
# (18) priority: 20
# (19) nice: 0
# (20) num_threads: 4
# ---- /proc/<PID>/maps - メモリマッピング ----
$ cat /proc/12345/maps | head -20
55a1b2c00000-55a1b2c10000 r--p 00000000 08:01 1234567 /usr/sbin/nginx
55a1b2c10000-55a1b2ca0000 r-xp 00010000 08:01 1234567 /usr/sbin/nginx
55a1b2ca0000-55a1b2cd0000 r--p 000a0000 08:01 1234567 /usr/sbin/nginx
55a1b2cd0000-55a1b2cd4000 r--p 000cf000 08:01 1234567 /usr/sbin/nginx
55a1b2cd4000-55a1b2ce0000 rw-p 000d3000 08:01 1234567 /usr/sbin/nginx
55a1b3800000-55a1b3a00000 rw-p 00000000 00:00 0 [heap]
7f1234000000-7f1234400000 rw-p 00000000 00:00 0
7f1234500000-7f1234520000 r--p 00000000 08:01 2345678 /usr/lib/x86_64-linux-gnu/libc.so.6
7f1234520000-7f12346b0000 r-xp 00020000 08:01 2345678 /usr/lib/x86_64-linux-gnu/libc.so.6
7ffd12340000-7ffd12360000 rw-p 00000000 00:00 0 [stack]
# ---- /proc/<PID>/io - I/O 統計 ----
$ cat /proc/12345/io
rchar: 12345678 # read() で読み取ったバイト数
wchar: 9876543 # write() で書き込んだバイト数
syscr: 5000 # read() システムコール回数
syscw: 3000 # write() システムコール回数
read_bytes: 8000000 # 実際にディスクから読み取ったバイト数
write_bytes: 6000000 # 実際にディスクに書き込んだバイト数
cancelled_write_bytes: 0 # キャンセルされた書き込みバイト数
# ---- /proc/<PID>/limits - リソース制限 ----
$ cat /proc/12345/limits
Limit Soft Limit Hard Limit Units
Max cpu time unlimited unlimited seconds
Max file size unlimited unlimited bytes
Max data size unlimited unlimited bytes
Max stack size 8388608 unlimited bytes
Max core file size 0 unlimited bytes
Max resident set unlimited unlimited bytes
Max processes 63372 63372 processes
Max open files 1024 1048576 files
Max locked memory 67108864 67108864 bytes
Max address space unlimited unlimited bytes
Max file locks unlimited unlimited locks
Max pending signals 63372 63372 signals
Max msgqueue size 819200 819200 bytes
Max nice priority 0 0
Max realtime priority 0 0
Max realtime timeout unlimited unlimited us
# ---- /proc/<PID>/fd/ - ファイルディスクリプタ ----
$ ls -la /proc/12345/fd/
total 0
lrwx------ 1 user user 64 Apr 10 10:00 0 -> /dev/null
lrwx------ 1 user user 64 Apr 10 10:00 1 -> /var/log/nginx/access.log
lrwx------ 1 user user 64 Apr 10 10:00 2 -> /var/log/nginx/error.log
lrwx------ 1 user user 64 Apr 10 10:00 3 -> socket:[12345678]
lrwx------ 1 user user 64 Apr 10 10:00 4 -> socket:[12345679]
lrwx------ 1 user user 64 Apr 10 10:00 5 -> anon_inode:[eventpoll]
13.3 システム全体の /proc ファイル
# ---- /proc/stat - システム全体の CPU 統計 ----
$ cat /proc/stat
cpu 1234567 12345 234567 8901234 56789 12345 2345 0 0 0
cpu0 300000 3000 60000 2200000 14000 3000 600 0 0 0
cpu1 310000 3100 58000 2250000 14200 3100 580 0 0 0
cpu2 312000 3050 59000 2225000 14300 3150 590 0 0 0
cpu3 312567 3195 57567 2226234 14289 3095 575 0 0 0
intr 123456789 50 0 0 0 0 0 0 0 1 0 0 0 0 0 0 ...
ctxt 987654321 # コンテキストスイッチ総数
btime 1711843200 # ブート時刻 (Unix timestamp)
processes 234567 # fork() 総数
procs_running 3 # 実行中プロセス数
procs_blocked 0 # IO 待ちプロセス数
softirq 56789012 0 12345678 123 4567890 5678901 0 234567 12345678 0 21620175
# ---- /proc/loadavg - ロードアベレージ ----
$ cat /proc/loadavg
0.50 0.75 0.85 3/450 12400
# 1分 5分 15分 実行可能/合計 最新PID
# ---- /proc/schedstat - スケジューラ統計 ----
$ cat /proc/schedstat | head -5
version 15
timestamp 4294967295
cpu0 0 0 0 0 0 0 0 0 0
...
# ---- /proc/sched_debug - スケジューラデバッグ情報 ----
$ cat /proc/sched_debug | head -40
Sched Debug Version: v0.11, 6.6.0-generic
ktime : 12345678.901234
sched_clk : 12345679.012345
cpu_clk : 12345679.012345
jiffies : 4312345678
sched_clock_stable() : 1
sysctl_sched
.sysctl_sched_latency : 6.000000
.sysctl_sched_min_granularity : 0.750000
.sysctl_sched_idle_min_granularity : 0.750000
.sysctl_sched_wakeup_granularity : 1.000000
.sysctl_sched_child_runs_first : 0
.sysctl_sched_features : 6237751
.sysctl_sched_tunable_scaling : 1 (logarithmic)
cpu#0
.nr_running : 2
.nr_switches : 12345678
.nr_uninterruptible : 0
cfs_rq[0]:/
.exec_clock : 12345.678900
.MIN_vruntime : 567890.123456
.min_vruntime : 567891.234567
.max_vruntime : 567900.345678
.spread : 10.111111
.nr_spread_over : 0
.nr_running : 2
.load : 2048
14. プロセス管理ツール群
14.1 ps コマンド
# ---- ps の基本的な使い方 ----
# 全プロセスを詳細表示 (BSD スタイル)
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 169404 13284 ? Ss Mar01 5:30 /usr/lib/systemd/systemd
root 2 0.0 0.0 0 0 ? S Mar01 0:02 [kthreadd]
www-data 12345 2.5 1.2 345678 98765 ? Sl 10:00 1:23 nginx: worker
user 23456 0.1 0.5 234567 45678 pts/0 S+ 10:30 0:05 vim myfile.c
# UNIX スタイル
$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Mar01 ? 00:05:30 /usr/lib/systemd/systemd
# カスタムフォーマット
$ ps -eo pid,ppid,user,ni,pri,pcpu,pmem,vsz,rss,stat,wchan:20,comm \
--sort=-pcpu | head -20
PID PPID USER NI PRI %CPU %MEM VSZ RSS STAT WCHAN COMMAND
12345 1 www-data 0 20 2.5 1.2 345678 98765 Sl - nginx
23456 12300 user 0 20 0.5 0.5 234567 45678 S+ poll_schedule_timeout vim
34567 1 mysql 0 20 0.3 2.0 456789 12345 Ssl futex_wait_queue mysqld
# スレッドも表示
$ ps -eLf
UID PID PPID LWP C NLWP STIME TTY TIME CMD
www-data 12345 1 12345 0 4 10:00 ? 00:00:30 nginx: worker
www-data 12345 1 12346 0 4 10:00 ? 00:00:25 nginx: worker
www-data 12345 1 12347 0 4 10:00 ? 00:00:28 nginx: worker
www-data 12345 1 12348 0 4 10:00 ? 00:00:20 nginx: worker
# プロセスツリー表示
$ ps auxf
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 169404 13284 ? Ss Mar01 5:30 /usr/lib/systemd/systemd
root 800 0.0 0.0 15600 7200 ? Ss Mar01 0:15 \_ sshd: /usr/sbin/sshd
root 12300 0.0 0.0 17200 9800 ? Ss 10:00 0:00 \_ sshd: user [priv]
user 12305 0.0 0.0 17500 10200 pts/0 Ss 10:00 0:00 \_ -bash
user 12400 0.0 0.0 23400 9800 pts/0 S+ 10:30 0:05 \_ vim myfile.c
# スケジューリング情報の表示
$ ps -eo pid,cls,rtprio,ni,pri,comm --sort=-rtprio | head -10
PID CLS RTPRIO NI PRI COMMAND
50 FF 99 - 139 migration/0
51 FF 99 - 139 migration/1
56 FF 50 - 90 irq/18-ahci
12345 TS - 0 20 nginx
23456 TS - 10 30 batch_job
# 特定のスケジューリングポリシーのプロセスのみ
$ ps -eo pid,cls,rtprio,comm | awk '$2 == "FF" || $2 == "RR"'
PID CLS RTPRIO COMMAND
50 FF 99 migration/0
51 FF 99 migration/1
56 FF 50 irq/18-ahci
14.2 top / htop
# ---- top の使い方 ----
$ top
top - 10:30:00 up 40 days, 2:30, 2 users, load average: 0.50, 0.75, 0.85
Tasks: 450 total, 3 running, 445 sleeping, 0 stopped, 2 zombie
%Cpu(s): 5.2 us, 2.1 sy, 0.0 ni, 91.5 id, 1.0 wa, 0.0 hi, 0.2 si, 0.0 st
MiB Mem : 16384.0 total, 4521.0 free, 6543.0 used, 5320.0 buff/cache
MiB Swap: 8192.0 total, 8192.0 free, 0.0 used. 9200.0 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12345 www-data 20 0 345678 98765 34567 S 5.0 0.6 1:23.45 nginx
23456 mysql 20 0 456789 234567 123456 S 3.0 1.4 0:45.67 mysqld
34567 user 20 0 23400 9800 7200 S 0.5 0.1 0:05.12 vim
# top の便利なキーバインド:
# 1 : CPU ごとの表示を切り替え
# H : スレッド表示を切り替え
# f : 表示フィールドの選択
# o : フィルタの追加 (例: COMMAND=nginx)
# u : ユーザーでフィルタ
# c : コマンドのフルパス表示
# V : ツリー表示
# P : CPU 使用率でソート
# M : メモリ使用率でソート
# k : プロセスの kill
# r : renice
# ---- htop の使い方 ----
# htop はより視覚的で操作しやすい
$ htop
# htop の特徴的な機能:
# F2 : Setup (表示のカスタマイズ)
# F3 : 検索
# F4 : フィルタ
# F5 : ツリー表示
# F6 : ソートカラムの選択
# F9 : シグナル送信
# Space : プロセスのタグ付け
# t : ツリー表示トグル
# H : ユーザースレッドの表示/非表示
# K : カーネルスレッドの表示/非表示
# htop のコマンドラインオプション
$ htop -d 5 # 更新間隔 0.5 秒
$ htop -u www-data # 特定ユーザーのみ
$ htop -p 12345,23456 # 特定 PID のみ
$ htop -t # ツリー表示で開始
14.3 pidstat
# ---- pidstat: プロセスごとの詳細統計 ----
# CPU 使用率を 1 秒間隔で表示
$ pidstat 1
Linux 6.6.0 (hostname) 04/10/2026 _x86_64_ (8 CPU)
10:30:01 AM UID PID %usr %system %guest %wait %CPU CPU Command
10:30:02 AM 1000 12345 3.00 1.00 0.00 0.50 4.00 2 nginx
10:30:02 AM 1000 23456 1.00 0.50 0.00 0.00 1.50 5 mysqld
# コンテキストスイッチ統計
$ pidstat -w 1
10:30:01 AM UID PID cswch/s nvcswch/s Command
10:30:02 AM 1000 12345 50.00 5.00 nginx
10:30:02 AM 1000 23456 20.00 2.00 mysqld
# スレッドごとの統計
$ pidstat -t -p 12345 1
10:30:01 AM UID TGID TID %usr %system %guest %wait %CPU CPU Command
10:30:02 AM 1000 12345 - 4.00 1.50 0.00 0.50 5.50 - nginx
10:30:02 AM 1000 - 12345 1.00 0.50 0.00 0.10 1.50 2 |__nginx
10:30:02 AM 1000 - 12346 1.00 0.30 0.00 0.10 1.30 3 |__nginx
10:30:02 AM 1000 - 12347 1.00 0.40 0.00 0.15 1.40 5 |__nginx
10:30:02 AM 1000 - 12348 1.00 0.30 0.00 0.15 1.30 7 |__nginx
# メモリ統計
$ pidstat -r 1
10:30:01 AM UID PID minflt/s majflt/s VSZ RSS %MEM Command
10:30:02 AM 1000 12345 10.00 0.00 345678 98765 0.60 nginx
# IO 統計
$ pidstat -d 1
10:30:01 AM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
10:30:02 AM 1000 23456 500.00 200.00 0.00 0 mysqld
14.4 schedtool / chrt / taskset
# ---- schedtool: スケジューリングパラメータの管理 ----
# (パッケージのインストールが必要: apt install schedtool)
# 現在の設定を表示
$ schedtool 12345
PID 12345: PRIO 0, POLICY N: SCHED_NORMAL , NICE 0, AFFINITY 0xff
# SCHED_FIFO に変更
$ sudo schedtool -F -p 50 12345
PID 12345: PRIO 50, POLICY F: SCHED_FIFO , NICE 0, AFFINITY 0xff
# SCHED_RR に変更して CPU 0-3 に固定
$ sudo schedtool -R -p 30 -a 0x0f 12345
# SCHED_BATCH に変更
$ sudo schedtool -B 12345
# SCHED_IDLE に変更
$ sudo schedtool -I 12345
# ---- chrt: スケジューリングポリシーの管理 ----
# 現在の設定を表示
$ chrt -p 12345
pid 12345's current scheduling policy: SCHED_OTHER
pid 12345's current scheduling priority: 0
# SCHED_FIFO で新しいプロセスを起動
$ sudo chrt -f 50 ./my_realtime_app
# 既存プロセスを SCHED_RR に変更
$ sudo chrt -r -p 30 12345
# SCHED_DEADLINE で起動
$ sudo chrt -d --sched-runtime 5000000 \
--sched-deadline 10000000 \
--sched-period 16666666 0 ./periodic_task
# 優先度の範囲を確認
$ chrt -m
SCHED_OTHER min/max priority : 0/0
SCHED_FIFO min/max priority : 1/99
SCHED_RR min/max priority : 1/99
SCHED_BATCH min/max priority : 0/0
SCHED_IDLE min/max priority : 0/0
SCHED_DEADLINE min/max priority : 0/0
# ---- taskset: CPU アフィニティの管理 ----
# 現在のアフィニティを表示
$ taskset -p 12345
pid 12345's current affinity mask: ff
# リスト形式で表示
$ taskset -cp 12345
pid 12345's current affinity list: 0-7
# CPU 0,1 に固定して起動
$ taskset -c 0,1 ./my_app
# 16進マスクで指定
$ taskset 0x0f ./my_app # CPU 0-3
# 既存プロセスのアフィニティを変更
$ taskset -pc 0-3 12345
pid 12345's current affinity list: 0-7
pid 12345's new affinity list: 0-3
15. パフォーマンスチューニング実践
15.1 レイテンシ重視の設定
# ---- レイテンシ重視のチューニング ----
# 1. スケジューラのレイテンシパラメータを調整
$ sudo sysctl -w kernel.sched_latency_ns=4000000 # 4ms (デフォルト 6ms)
$ sudo sysctl -w kernel.sched_min_granularity_ns=500000 # 0.5ms (デフォルト 0.75ms)
$ sudo sysctl -w kernel.sched_wakeup_granularity_ns=500000 # 0.5ms
# 2. CPU isolation (カーネルパラメータ)
# GRUB に追加: isolcpus=4-7 nohz_full=4-7 rcu_nocbs=4-7
# → CPU 4-7 をレイテンシが重要なアプリケーション専用に
# 3. IRQ アフィニティの設定
$ echo 0f > /proc/irq/default_smp_affinity # IRQ を CPU 0-3 に制限
$ for irq in $(ls /proc/irq/ | grep -E '^[0-9]+$'); do
echo 0f > /proc/irq/$irq/smp_affinity 2>/dev/null
done
# 4. 透過的ヒュージページの無効化 (レイテンシジッター防止)
$ echo never > /sys/kernel/mm/transparent_hugepage/enabled
$ echo never > /sys/kernel/mm/transparent_hugepage/defrag
# 5. watchdog の無効化 (不要な割り込みの排除)
$ sudo sysctl -w kernel.watchdog=0
# 6. RT スロットリングの調整
$ sudo sysctl -w kernel.sched_rt_runtime_us=-1 # 無制限 (注意!)
# /etc/sysctl.d/99-low-latency.conf に永続化
cat << 'EOF' | sudo tee /etc/sysctl.d/99-low-latency.conf
kernel.sched_latency_ns = 4000000
kernel.sched_min_granularity_ns = 500000
kernel.sched_wakeup_granularity_ns = 500000
kernel.sched_migration_cost_ns = 500000
kernel.sched_autogroup_enabled = 0
EOF
$ sudo sysctl --system
15.2 スループット重視の設定
# ---- スループット重視のチューニング ----
# 1. スケジューラのレイテンシを大きくしてコンテキストスイッチを減らす
$ sudo sysctl -w kernel.sched_latency_ns=24000000 # 24ms
$ sudo sysctl -w kernel.sched_min_granularity_ns=3000000 # 3ms
$ sudo sysctl -w kernel.sched_wakeup_granularity_ns=4000000 # 4ms
# 2. マイグレーションコストを上げてCPUキャッシュを活用
$ sudo sysctl -w kernel.sched_migration_cost_ns=5000000 # 5ms
# 3. バッチジョブは SCHED_BATCH を使用
$ schedtool -B -e make -j$(nproc)
# または
$ chrt -b 0 make -j$(nproc)
# 4. NUMA 対応の最適化
$ numactl --cpunodebind=0 --membind=0 ./my_app # NUMA node 0 に固定
$ numactl --interleave=all ./memory_intensive_app # メモリをインターリーブ
# /etc/sysctl.d/99-throughput.conf
cat << 'EOF' | sudo tee /etc/sysctl.d/99-throughput.conf
kernel.sched_latency_ns = 24000000
kernel.sched_min_granularity_ns = 3000000
kernel.sched_wakeup_granularity_ns = 4000000
kernel.sched_migration_cost_ns = 5000000
kernel.sched_autogroup_enabled = 0
EOF
15.3 perf によるスケジューラ分析
# ---- perf を使ったスケジューラの詳細分析 ----
# スケジューリングイベントの記録
$ sudo perf sched record -a -- sleep 10
# レイテンシ分析
$ sudo perf sched latency --sort max
-----------------------------------------------
Task | Runtime ms | Switches | Avg delay ms | Max delay ms |
-----------------------------------------------
nginx:12345 | 234.567 ms | 1500 | 0.050 ms | 2.345 ms |
mysqld:23456 | 567.890 ms | 3000 | 0.030 ms | 1.234 ms |
kworker/2:1:3456 | 12.345 ms | 500 | 0.010 ms | 0.567 ms |
# スケジューリングのタイムライン表示
$ sudo perf sched map | head -30
*A0 999.962 secs A0 => perf:54321
*A0 B0 1000.000 secs B0 => migration/0:50
A0 *B0 C0 1000.001 secs C0 => nginx:12345
A0 B0 *C0 1000.002 secs
A0 *B0 C0 D0 1000.003 secs D0 => mysqld:23456
*A0 B0 C0 D0 1000.004 secs
# コンテキストスイッチの統計
$ sudo perf stat -e sched:sched_switch,sched:sched_wakeup \
-a -- sleep 10
Performance counter stats for 'system wide':
45,678 sched:sched_switch
34,567 sched:sched_wakeup
10.001234567 seconds time elapsed
# スケジューラ関連の tracepoint 一覧
$ sudo perf list 'sched:*'
sched:sched_kthread_stop [Tracepoint event]
sched:sched_kthread_stop_ret [Tracepoint event]
sched:sched_migrate_task [Tracepoint event]
sched:sched_process_exec [Tracepoint event]
sched:sched_process_exit [Tracepoint event]
sched:sched_process_fork [Tracepoint event]
sched:sched_process_free [Tracepoint event]
sched:sched_process_wait [Tracepoint event]
sched:sched_stat_blocked [Tracepoint event]
sched:sched_stat_iowait [Tracepoint event]
sched:sched_stat_runtime [Tracepoint event]
sched:sched_stat_sleep [Tracepoint event]
sched:sched_stat_wait [Tracepoint event]
sched:sched_switch [Tracepoint event]
sched:sched_wait_task [Tracepoint event]
sched:sched_wake_idle_without_ipi [Tracepoint event]
sched:sched_wakeup [Tracepoint event]
sched:sched_wakeup_new [Tracepoint event]
sched:sched_waking [Tracepoint event]
16. トラブルシューティング
16.1 高い CPU 使用率の調査
# 1. どのプロセスが CPU を使っているか特定
$ top -b -n1 -o %CPU | head -15
# 2. CPU を大量に使うプロセスの詳細
$ pidstat -u -p <PID> 1 5
# 3. ユーザー空間 vs カーネル空間の比率
$ pidstat -u -p <PID> 1
# %usr が高い → アプリケーションコードの問題
# %system が高い → システムコール/カーネル処理の問題
# 4. perf でプロファイリング
$ sudo perf top -p <PID>
$ sudo perf record -g -p <PID> -- sleep 30
$ sudo perf report
# 5. スタックトレースの確認
$ sudo perf record -g -F 99 -p <PID> -- sleep 10
$ sudo perf script | stackcollapse-perf.pl | flamegraph.pl > flamegraph.svg
16.2 D 状態プロセスの調査
# 1. D 状態のプロセスを検出
$ ps aux | awk '$8 ~ /^D/'
# 2. カーネルスタックの確認
$ cat /proc/<PID>/stack
# 3. wchan の確認
$ cat /proc/<PID>/wchan
# 4. 全 D 状態プロセスの一括調査
$ for pid in $(ps -eo pid,stat | awk '$2 ~ /^D/ {print $1}'); do
echo "=== PID $pid: $(cat /proc/$pid/comm 2>/dev/null) ==="
echo "wchan: $(cat /proc/$pid/wchan 2>/dev/null)"
echo "stack:"
cat /proc/$pid/stack 2>/dev/null
echo "---"
done
# 5. IO 関連の D 状態が疑われる場合
$ iostat -x 1
$ iotop
16.3 ゾンビプロセスの処理
# 1. ゾンビプロセスの検出
$ ps aux | awk '$8 == "Z" {print}'
# 2. ゾンビの親プロセスを特定
$ ps -o pid,ppid,stat,comm | awk '$3 == "Z" {print "Zombie PID:", $1, "Parent PID:", $2}'
# 3. 親プロセスに SIGCHLD を送信して回収を促す
$ kill -SIGCHLD <PPID>
# 4. それでも解消しない場合、親プロセスを終了
$ kill <PPID>
# → 子のゾンビは init/systemd に引き取られ、自動的に回収される
# 5. ゾンビの数を監視
$ while true; do
zombie_count=$(ps aux | awk '$8 == "Z"' | wc -l)
echo "$(date): Zombie processes: $zombie_count"
sleep 5
done
# 6. systemd 環境でのゾンビ問題の調査
$ journalctl -u <service-name> | grep -i "wait\|zombie\|defunct"
16.4 スケジューリング遅延の調査
# 1. 全体的なスケジューリング遅延
$ sudo perf sched record -a -- sleep 10
$ sudo perf sched latency --sort max
# 2. 特定プロセスの遅延
$ cat /proc/<PID>/sched | grep -E "wait_max|wait_sum|wait_count"
# 3. schedstat でランキューの待ち時間を確認
$ cat /proc/<PID>/schedstat
# 3つの数値: 実行時間、ランキュー待ち時間、実行回数 (ナノ秒)
# 例: 1234567890 567890123 5000
# → 平均待ち時間 = 567890123 / 5000 = 113578 ns ≈ 0.11ms
# 4. bpftrace でリアルタイム監視
$ sudo bpftrace -e '
tracepoint:sched:sched_switch {
if (args->prev_comm == "nginx") {
printf("nginx (PID %d) switched out, state=%d\n",
args->prev_pid, args->prev_state);
}
}
'
# 5. ランキューの長さを監視
$ sudo bpftrace -e '
tracepoint:sched:sched_wakeup {
@rqlen = lhist(curtask->se.statistics.nr_wakeups, 0, 100, 10);
}
interval:s:5 { print(@rqlen); clear(@rqlen); }
'
16.5 実用的なワンライナー集
# CPU 使用率 TOP 10 プロセス
$ ps aux --sort=-pcpu | head -11
# メモリ使用率 TOP 10 プロセス
$ ps aux --sort=-rss | head -11
# 特定ユーザーのプロセス数
$ ps -u www-data --no-headers | wc -l
# ゾンビプロセス数
$ ps aux | awk '$8=="Z"' | wc -l
# D 状態プロセス数
$ ps aux | awk '$8~/^D/' | wc -l
# プロセスごとのスレッド数 TOP 10
$ ps -eo user,pid,nlwp,comm --sort=-nlwp | head -11
# プロセスごとのオープンファイル数 TOP 10
$ for pid in $(ps -eo pid --no-headers); do
count=$(ls /proc/$pid/fd 2>/dev/null | wc -l)
echo "$count $pid $(cat /proc/$pid/comm 2>/dev/null)"
done | sort -rn | head -10
# 1分あたりのフォーク数
$ prev=$(awk '/processes/ {print $2}' /proc/stat); sleep 60; \
curr=$(awk '/processes/ {print $2}' /proc/stat); \
echo "Forks per minute: $((curr - prev))"
# コンテキストスイッチ率
$ prev=$(awk '/ctxt/ {print $2}' /proc/stat); sleep 1; \
curr=$(awk '/ctxt/ {print $2}' /proc/stat); \
echo "Context switches/sec: $((curr - prev))"
# 特定プロセスの CPU アフィニティとスケジューリングポリシー
$ pid=12345
$ echo "Affinity: $(taskset -cp $pid 2>/dev/null)"
$ echo "Policy: $(chrt -p $pid 2>/dev/null)"
$ echo "Nice: $(awk '{print $19}' /proc/$pid/stat 2>/dev/null)"
まとめ
本記事では Linux カーネルのプロセス管理とスケジューリングについて、以下のトピックを包括的に解説した:
- プロセスライフサイクル: fork/clone/exec/exit の内部動作と CoW メカニズム
- task_struct: カーネルのプロセスディスクリプタの構造と主要フィールド
- プロセス状態: TASK_RUNNING から EXIT_ZOMBIE までの状態遷移
- CFS: 仮想実行時間とレッドブラックツリーによる公平なスケジューリング
- EEVDF: Linux 6.6+ の新スケジューラの仮想デッドラインベースの選択
- リアルタイムスケジューリング: FIFO/RR/DEADLINE の使い分け
- CPU アフィニティと cgroups: リソース制御の実践
- コンテキストスイッチ: CPU 状態の保存/復元の内部メカニズム
- NPTL: Linux のスレッド実装と 1:1 モデル
- 名前空間: コンテナの基盤となる PID 分離
- ツールと診断: ps, top, htop, pidstat, perf 等による分析手法
Linux のプロセス管理は複雑だが、基本原理を理解することで、効果的なパフォーマンスチューニングとトラブルシューティングが可能になる。
参考資料
-
カーネルソースコード
kernel/fork.c- プロセス生成kernel/exit.c- プロセス終了kernel/sched/core.c- スケジューラコアkernel/sched/fair.c- CFS / EEVDFkernel/sched/rt.c- リアルタイムスケジューラkernel/sched/deadline.c- DEADLINE スケジューラinclude/linux/sched.h- task_struct 定義
-
書籍
- "Understanding the Linux Kernel" - Daniel P. Bovet, Marco Cesati
- "Linux Kernel Development" - Robert Love
- "Linux System Programming" - Robert Love
-
カーネルドキュメント
Documentation/scheduler/- スケジューラのドキュメントDocumentation/admin-guide/cgroup-v2.rst- cgroups v2Documentation/scheduler/sched-deadline.rst- SCHED_DEADLINE
-
論文
- "Earliest Eligible Virtual Deadline First: A Flexible and Accurate Mechanism for Proportional Share Resource Allocation" - Stoica et al. (1996)