Linux Kernel Block IO Layer
Linux カーネル ブロック I/O レイヤー 完全ガイド
著者: AI Generated Technical Documentation 最終更新日: 2026-04-10 対象カーネルバージョン: Linux 6.x 系列 対象読者: SRE、カーネル開発者、システムアーキテクト
目次
- はじめに
- ブロック I/O レイヤーのアーキテクチャ
- ブロックデバイスモデル
- I/O スケジューラ
- マルチキュー ブロックレイヤー (blk-mq)
- Bio 構造体とリクエスト処理
- デバイスマッパー (dm)
- LVM (論理ボリュームマネージャ)
- RAID (mdadm, dm-raid)
- NVMe ドライバとアーキテクチャ
- SCSI サブシステム
- ブロック I/O cgroups (blkio コントローラ)
- ダイレクト I/O とバッファード I/O
- I/O アカウンティングと統計情報
- デッドラインとレイテンシ管理
- ライトバリアとデータ整合性
- ツール: iostat, blktrace, fio, hdparm, smartctl
- パフォーマンスチューニング実践
- トラブルシューティング
- まとめとベストプラクティス
1. はじめに
1.1 ブロック I/O レイヤーとは
Linux カーネルのブロック I/O レイヤーは、ユーザー空間のアプリケーションとストレージデバイスの間に位置する重要なサブシステムである。このレイヤーは、ファイルシステムからの I/O リクエストを受け取り、最適化・スケジューリングした上でデバイスドライバに渡す役割を担う。
ブロックデバイスは、データを固定サイズのブロック(通常 512 バイトまたは 4096 バイト)単位でアクセスするデバイスであり、HDD、SSD、NVMe、仮想ディスクなどが含まれる。
1.2 歴史的背景
Linux カーネルのブロック I/O レイヤーは、以下のような進化を遂げてきた:
| バージョン | 主要な変更点 |
|---|---|
| Linux 2.4 | 単一リクエストキューモデル |
| Linux 2.6 | CFQ、deadline スケジューラの導入 |
| Linux 3.13 | blk-mq(マルチキューブロックレイヤー)の導入 |
| Linux 4.12 | BFQ スケジューラのメインライン統合 |
| Linux 5.0 | レガシーシングルキューの段階的廃止開始 |
| Linux 5.15 | レガシーブロックレイヤーの完全削除 |
| Linux 6.x | blk-mq のさらなる最適化、io_uring との統合強化 |
1.3 全体アーキテクチャ概要
┌─────────────────────────────────────────────────────────────┐
│ ユーザー空間 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ アプリ │ │ データベース│ │ io_uring │ │ libaio │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
├───────┼──────────────┼──────────────┼──────────────┼──────────┤
│ ▼ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ VFS (仮想ファイルシステム) │ │
│ └────────────────────────┬─────────────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ファイルシステム層 │ │
│ │ (ext4, XFS, btrfs, f2fs 等) │ │
│ └────────────────────────┬─────────────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ページキャッシュ │ │
│ └────────────────────────┬─────────────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ブロック I/O レイヤー (blk-mq) │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Bio 生成・処理 │ │ │
│ │ └──────────────────┬──────────────────────────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ I/O スケジューラ │ │ │
│ │ │ (mq-deadline, BFQ, kyber, none) │ │ │
│ │ └──────────────────┬──────────────────────────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ デバイスマッパー / MD (オプション) │ │ │
│ │ └──────────────────┬──────────────────────────┘ │ │
│ └─────────────────────┼────────────────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ デバイスドライバ層 │ │
│ │ (NVMe, SCSI/SATA, virtio-blk 等) │ │
│ └────────────────────────┬─────────────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ハードウェア │ │
│ │ (SSD, HDD, NVMe SSD, SAN 等) │ │
│ └──────────────────────────────────────────────────────┘ │
│ カーネル空間 │
└─────────────────────────────────────────────────────────────┘
2. ブロック I/O レイヤーのアーキテクチャ
2.1 レイヤー構成の詳細
ブロック I/O レイヤーは、Linux カーネルにおけるストレージ I/O の中核を担うサブシステムである。このレイヤーは以下の主要コンポーネントで構成される:
2.1.1 リクエスト生成層
ファイルシステムやダイレクト I/O からの要求を bio 構造体に変換する層。
/* bio 構造体の基本的な生成フロー */
struct bio *bio = bio_alloc(GFP_NOIO, nr_vecs);
bio_set_dev(bio, bdev);
bio->bi_iter.bi_sector = sector;
bio->bi_opf = REQ_OP_READ;
bio_add_page(bio, page, len, offset);
submit_bio(bio);
2.1.2 マージ・ソート層
隣接するセクタへの I/O リクエストをマージし、効率的なリクエストを生成する。
マージ前: マージ後:
[セクタ 100-107] [セクタ 100-115]
[セクタ 108-115] → (1つのリクエストに統合)
2.1.3 スケジューリング層
I/O スケジューラがリクエストの実行順序を決定する。
2.1.4 ディスパッチ層
スケジュールされたリクエストをデバイスドライバのハードウェアキューに投入する。
2.2 データフローの詳細
2.2.1 書き込みパス
アプリケーション write() 呼び出し
│
▼
VFS: vfs_write()
│
▼
ファイルシステム: ext4_file_write_iter()
│
▼
ページキャッシュにデータコピー
(バッファードI/Oの場合)
│
▼
dirty ページとしてマーク
│
▼
writeback スレッドが起動
│
▼
bio 構造体を生成
│
▼
submit_bio() でブロックレイヤーに投入
│
▼
blk-mq: ソフトウェアキューに挿入
│
▼
I/O スケジューラで順序決定
│
▼
ハードウェアディスパッチキューへ
│
▼
デバイスドライバが実行
│
▼
完了割り込み → コールバック呼び出し
2.2.2 読み込みパス
アプリケーション read() 呼び出し
│
▼
VFS: vfs_read()
│
▼
ページキャッシュを確認
│
┌───┴───┐
│ │
ヒット ミス
│ │
▼ ▼
直接返却 bio を生成して読み込み
│
▼
submit_bio()
│
▼
blk-mq 処理
│
▼
デバイスから読み込み
│
▼
ページキャッシュに格納
│
▼
アプリケーションに返却
2.3 ソースコードの構成
ブロック I/O レイヤーの主要なソースコードは以下のディレクトリに配置されている:
linux/
├── block/ # ブロックレイヤーのコア
│ ├── blk-core.c # コア機能
│ ├── blk-mq.c # マルチキューブロックレイヤー
│ ├── blk-mq-sched.c # MQ スケジューラインターフェース
│ ├── blk-merge.c # リクエストマージロジック
│ ├── blk-settings.c # キュー設定
│ ├── blk-stat.c # 統計情報
│ ├── blk-cgroup.c # cgroup 統合
│ ├── blk-throttle.c # I/O スロットリング
│ ├── bio.c # bio 構造体管理
│ ├── elevator.c # I/O スケジューラフレームワーク
│ ├── mq-deadline.c # mq-deadline スケジューラ
│ ├── bfq-iosched.c # BFQ スケジューラ
│ ├── kyber-iosched.c # Kyber スケジューラ
│ └── genhd.c # 汎用ハードディスク管理
├── drivers/
│ ├── block/ # ブロックデバイスドライバ
│ ├── nvme/ # NVMe ドライバ
│ ├── scsi/ # SCSI サブシステム
│ └── md/ # MD RAID / デバイスマッパー
├── include/
│ └── linux/
│ ├── blkdev.h # ブロックデバイスヘッダー
│ ├── blk-mq.h # blk-mq ヘッダー
│ ├── bio.h # bio ヘッダー
│ └── genhd.h # gendisk ヘッダー
└── fs/
└── block_dev.c # ブロックデバイスファイル操作
2.4 カーネルコンフィグオプション
ブロック I/O レイヤーに関連する主要なカーネルコンフィグオプション:
# 基本ブロックレイヤー設定
CONFIG_BLOCK=y # ブロックレイヤーサポート
CONFIG_BLK_DEV=y # ブロックデバイスサポート
# I/O スケジューラ
CONFIG_MQ_IOSCHED_DEADLINE=y # mq-deadline スケジューラ
CONFIG_MQ_IOSCHED_KYBER=y # Kyber スケジューラ
CONFIG_IOSCHED_BFQ=y # BFQ スケジューラ
# blk-mq
CONFIG_BLK_MQ_PCI=y # PCI 用 blk-mq ヘルパー
CONFIG_BLK_MQ_VIRTIO=y # virtio 用 blk-mq ヘルパー
CONFIG_BLK_MQ_RDMA=y # RDMA 用 blk-mq ヘルパー
# cgroup
CONFIG_BLK_CGROUP=y # ブロック I/O cgroup
CONFIG_BLK_DEV_THROTTLING=y # I/O スロットリング
# デバイスマッパー
CONFIG_BLK_DEV_DM=y # デバイスマッパー
CONFIG_DM_CRYPT=y # dm-crypt (暗号化)
CONFIG_DM_SNAPSHOT=y # スナップショット
CONFIG_DM_THIN_PROVISIONING=y # シンプロビジョニング
CONFIG_DM_MIRROR=y # ミラーリング
# MD RAID
CONFIG_BLK_DEV_MD=y # MD RAID サポート
CONFIG_MD_RAID0=y # RAID 0
CONFIG_MD_RAID1=y # RAID 1
CONFIG_MD_RAID10=y # RAID 10
CONFIG_MD_RAID456=y # RAID 4/5/6
# NVMe
CONFIG_BLK_DEV_NVME=y # NVMe サポート
CONFIG_NVME_CORE=y # NVMe コア
CONFIG_NVME_FABRICS=y # NVMe over Fabrics
# デバッグ
CONFIG_BLK_DEBUG_FS=y # debugfs サポート
CONFIG_BLK_DEV_IO_TRACE=y # blktrace サポート
2.5 確認コマンド
# 現在のカーネル設定を確認
zcat /proc/config.gz | grep -i "CONFIG_BLK"
# ブロックデバイスの一覧
lsblk -a
# ブロックデバイスの詳細情報
lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,SCHED,RQ-SIZE,MODEL
# カーネルモジュールの確認
lsmod | grep -E "blk|nvme|scsi|md|dm"
# ブロックデバイスのメジャー・マイナー番号
cat /proc/devices | head -30
3. ブロックデバイスモデル
3.1 struct block_device
block_device 構造体は、カーネル内でブロックデバイスを表現する主要なデータ構造である。
/* include/linux/blk_types.h (簡略化) */
struct block_device {
sector_t bd_start_sect; /* パーティション開始セクタ */
sector_t bd_nr_sectors; /* セクタ数 */
struct gendisk *bd_disk; /* 所属する gendisk */
struct request_queue *bd_queue; /* リクエストキュー */
struct super_block *bd_super; /* マウントされたスーパーブロック */
dev_t bd_dev; /* デバイス番号 */
struct inode *bd_inode; /* ブロックデバイス inode */
int bd_openers; /* オープンカウンタ */
spinlock_t bd_size_lock; /* サイズ変更用ロック */
void *bd_claiming; /* 排他アクセス用 */
void *bd_holder; /* ホルダーポインタ */
int bd_holders; /* ホルダーカウンタ */
bool bd_write_holder; /* 書き込みホルダーフラグ */
struct kobject *bd_holder_dir; /* sysfs ホルダーディレクトリ */
unsigned bd_partno; /* パーティション番号 */
unsigned long bd_stamp; /* 統計用タイムスタンプ */
atomic_t bd_writers; /* 書き込みオープンカウンタ */
struct mutex bd_fsfreeze_mutex; /* freeze 用ミューテックス */
int bd_fsfreeze_count; /* freeze カウンタ */
struct partition_meta_info *bd_meta_info; /* パーティションメタ情報 */
};
3.2 struct gendisk
gendisk 構造体は、物理ディスクデバイス全体を表現する。
/* include/linux/blkdev.h (簡略化) */
struct gendisk {
int major; /* メジャー番号 */
int first_minor; /* 最初のマイナー番号 */
int minors; /* パーティション数 */
char disk_name[DISK_NAME_LEN]; /* デバイス名 */
unsigned short events; /* イベントマスク */
unsigned short event_flags; /* イベントフラグ */
struct xarray part_tbl; /* パーティションテーブル */
struct block_device *part0; /* パーティション 0 */
const struct block_device_operations *fops; /* 操作関数テーブル */
struct request_queue *queue; /* リクエストキュー */
void *private_data; /* ドライバ固有データ */
struct bio_set bio_split; /* bio 分割用プール */
int flags; /* デバイスフラグ */
unsigned long state; /* デバイス状態 */
struct mutex open_mutex; /* オープン用ミューテックス */
unsigned open_partitions; /* オープン中のパーティション数 */
struct backing_dev_info *bdi; /* バッキングデバイス情報 */
struct kobject *slave_dir; /* slave sysfs ディレクトリ */
struct timer_rand_state *random; /* エントロピー源 */
atomic_t sync_io; /* RAID 用同期 I/O カウンタ */
struct disk_events *ev; /* イベント管理 */
unsigned int nr_zones; /* ゾーン数 (ZNS) */
unsigned int max_open_zones; /* 最大オープンゾーン数 */
unsigned int max_active_zones; /* 最大アクティブゾーン数 */
unsigned long *conv_zones_bitmap; /* 従来型ゾーンビットマップ */
unsigned long *seq_zones_wlock; /* シーケンシャルゾーン書き込みロック */
};
3.3 struct request_queue
リクエストキューは、ブロックデバイスへの I/O リクエストを管理する中核構造体である。
/* include/linux/blkdev.h (簡略化) */
struct request_queue {
struct request *last_merge; /* 最後のマージ候補 */
struct elevator_queue *elevator; /* I/O スケジューラ */
struct blk_queue_stats *stats; /* 統計情報 */
struct rq_qos *rq_qos; /* QoS チェーン */
const struct blk_mq_ops *mq_ops; /* MQ 操作関数 */
struct blk_mq_ctx __percpu *queue_ctx; /* ソフトウェアキューコンテキスト */
unsigned int queue_depth; /* キュー深度 */
struct blk_mq_hw_ctx **queue_hw_ctx; /* ハードウェアキューコンテキスト */
unsigned int nr_hw_queues; /* HW キュー数 */
struct queue_limits limits; /* キュー制限 */
unsigned int nr_requests; /* 最大リクエスト数 */
unsigned int dma_pad_mask; /* DMA パディングマスク */
unsigned int rq_timeout; /* リクエストタイムアウト */
struct timer_list timeout; /* タイムアウトタイマー */
atomic_t nr_active_requests_shared_tags; /* 共有タグのアクティブリクエスト数 */
struct blk_flush_queue *fq; /* フラッシュキュー */
struct list_head requeue_list; /* 再キューイングリスト */
spinlock_t requeue_lock; /* 再キューイングロック */
struct delayed_work requeue_work; /* 再キューイングワーク */
struct blk_mq_tag_set *tag_set; /* タグセット */
struct list_head tag_set_list; /* タグセットリスト */
struct dentry *debugfs_dir; /* debugfs ディレクトリ */
struct dentry *sched_debugfs_dir; /* スケジューラ debugfs */
struct dentry *rqos_debugfs_dir; /* RQ QoS debugfs */
struct mutex debugfs_mutex; /* debugfs ミューテックス */
bool mq_sysfs_init_done; /* sysfs 初期化完了フラグ */
};
3.4 block_device_operations
ブロックデバイスの操作関数テーブル:
/* include/linux/blkdev.h */
struct block_device_operations {
void (*submit_bio)(struct bio *bio);
int (*poll_bio)(struct bio *bio, struct io_comp_batch *iob,
unsigned int flags);
int (*open)(struct gendisk *disk, blk_mode_t mode);
void (*release)(struct gendisk *disk);
int (*ioctl)(struct block_device *bdev, blk_mode_t mode,
unsigned cmd, unsigned long arg);
int (*compat_ioctl)(struct block_device *bdev, blk_mode_t mode,
unsigned cmd, unsigned long arg);
unsigned int (*check_events)(struct gendisk *disk,
unsigned int clearing);
void (*unlock_native_capacity)(struct gendisk *disk);
int (*getgeo)(struct block_device *, struct hd_geometry *);
int (*set_read_only)(struct block_device *bdev, bool ro);
void (*free_disk)(struct gendisk *disk);
void (*swap_slot_free_notify)(struct block_device *,
unsigned long);
int (*report_zones)(struct gendisk *, sector_t sector,
unsigned int nr_zones,
report_zones_cb cb, void *data);
char *(*devnode)(struct gendisk *disk, umode_t *mode);
int (*alternative_gpt_sector)(struct gendisk *disk,
sector_t *sector);
struct module *owner;
const struct pr_ops *pr_ops;
};
3.5 デバイスの登録と初期化
ブロックデバイスドライバが新しいデバイスを登録する基本的なフロー:
/* ブロックデバイス登録の例 */
#include <linux/blkdev.h>
#include <linux/blk-mq.h>
#define MY_BLOCK_MAJOR 0 /* 動的割り当て */
#define MY_BLOCK_MINORS 16
#define SECTOR_SIZE 512
#define NR_SECTORS 2048
static struct my_block_dev {
struct gendisk *gd;
struct blk_mq_tag_set tag_set;
struct request_queue *queue;
spinlock_t lock;
u8 *data; /* データ格納領域 */
} mydev;
/* blk-mq 操作関数 */
static blk_status_t my_queue_rq(struct blk_mq_hw_ctx *hctx,
const struct blk_mq_queue_data *bd)
{
struct request *rq = bd->rq;
struct my_block_dev *dev = hctx->queue->queuedata;
struct bio_vec bvec;
struct req_iterator iter;
sector_t sector = blk_rq_pos(rq);
blk_mq_start_request(rq);
rq_for_each_segment(bvec, rq, iter) {
unsigned int len = bvec.bv_len;
void *buf = page_address(bvec.bv_page) + bvec.bv_offset;
unsigned int offset = sector * SECTOR_SIZE;
if (rq_data_dir(rq) == WRITE)
memcpy(dev->data + offset, buf, len);
else
memcpy(buf, dev->data + offset, len);
sector += len / SECTOR_SIZE;
}
blk_mq_end_request(rq, BLK_STS_OK);
return BLK_STS_OK;
}
static const struct blk_mq_ops my_mq_ops = {
.queue_rq = my_queue_rq,
};
static int __init my_block_init(void)
{
int ret;
/* データ領域確保 */
mydev.data = vzalloc(NR_SECTORS * SECTOR_SIZE);
if (!mydev.data)
return -ENOMEM;
/* blk-mq タグセット初期化 */
mydev.tag_set.ops = &my_mq_ops;
mydev.tag_set.nr_hw_queues = 1;
mydev.tag_set.queue_depth = 128;
mydev.tag_set.numa_node = NUMA_NO_NODE;
mydev.tag_set.cmd_size = 0;
mydev.tag_set.flags = BLK_MQ_F_SHOULD_MERGE;
ret = blk_mq_alloc_tag_set(&mydev.tag_set);
if (ret)
goto err_data;
/* gendisk 割り当て */
mydev.gd = blk_mq_alloc_disk(&mydev.tag_set, NULL, &mydev);
if (IS_ERR(mydev.gd)) {
ret = PTR_ERR(mydev.gd);
goto err_tag;
}
/* gendisk 設定 */
mydev.gd->major = MY_BLOCK_MAJOR;
mydev.gd->first_minor = 0;
mydev.gd->minors = MY_BLOCK_MINORS;
snprintf(mydev.gd->disk_name, DISK_NAME_LEN, "myblk");
set_capacity(mydev.gd, NR_SECTORS);
/* キュー設定 */
blk_queue_logical_block_size(mydev.gd->queue, SECTOR_SIZE);
blk_queue_physical_block_size(mydev.gd->queue, SECTOR_SIZE);
/* デバイス登録 */
ret = add_disk(mydev.gd);
if (ret)
goto err_disk;
pr_info("myblk: registered with major %d\n", mydev.gd->major);
return 0;
err_disk:
put_disk(mydev.gd);
err_tag:
blk_mq_free_tag_set(&mydev.tag_set);
err_data:
vfree(mydev.data);
return ret;
}
static void __exit my_block_exit(void)
{
del_gendisk(mydev.gd);
put_disk(mydev.gd);
blk_mq_free_tag_set(&mydev.tag_set);
vfree(mydev.data);
pr_info("myblk: unregistered\n");
}
module_init(my_block_init);
module_exit(my_block_exit);
MODULE_LICENSE("GPL");
3.6 sysfs インターフェース
各ブロックデバイスは /sys/block/ 配下に sysfs エントリを持つ:
# sysfs の構造
/sys/block/sda/
├── alignment_offset # アライメントオフセット
├── dev # デバイス番号 (major:minor)
├── discard_alignment # discard アライメント
├── events # デバイスイベント
├── events_async # 非同期イベント
├── events_poll_msecs # イベントポーリング間隔
├── ext_range # 拡張範囲
├── hidden # 隠しデバイスフラグ
├── inflight # 処理中の I/O 数
├── range # パーティション範囲
├── removable # リムーバブルフラグ
├── ro # 読み取り専用フラグ
├── size # サイズ(セクタ数)
├── stat # I/O 統計情報
├── capability # ケイパビリティ
├── queue/ # キュー設定
│ ├── add_random # エントロピーソースフラグ
│ ├── chunk_sectors # チャンクセクタ数
│ ├── dax # DAX サポート
│ ├── discard_granularity # discard 粒度
│ ├── discard_max_bytes # 最大 discard サイズ
│ ├── discard_max_hw_bytes # HW 最大 discard
│ ├── hw_sector_size # HW セクタサイズ
│ ├── io_poll # I/O ポーリング
│ ├── io_poll_delay # I/O ポーリング遅延
│ ├── iostats # I/O 統計有効フラグ
│ ├── logical_block_size # 論理ブロックサイズ
│ ├── max_hw_sectors_kb # HW 最大セクタ
│ ├── max_integrity_segments # 最大整合性セグメント
│ ├── max_sectors_kb # 最大セクタ(KB)
│ ├── max_segment_size # 最大セグメントサイズ
│ ├── max_segments # 最大セグメント数
│ ├── minimum_io_size # 最小 I/O サイズ
│ ├── nomerges # マージ無効化
│ ├── nr_requests # リクエスト数
│ ├── nr_zones # ゾーン数
│ ├── optimal_io_size # 最適 I/O サイズ
│ ├── physical_block_size # 物理ブロックサイズ
│ ├── read_ahead_kb # 先読みサイズ
│ ├── rotational # 回転メディアフラグ
│ ├── rq_affinity # リクエストアフィニティ
│ ├── scheduler # I/O スケジューラ
│ ├── write_cache # ライトキャッシュ
│ ├── write_same_max_bytes # write_same 最大
│ ├── write_zeroes_max_bytes # write_zeroes 最大
│ └── zoned # ゾーンモデル
├── slaves/ # スレーブデバイス
├── holders/ # ホルダーデバイス
├── trace/ # tracing 設定
└── sda1/ # パーティション
├── dev
├── partition
├── size
├── start
└── stat
# 実用的な確認コマンド
# キューの設定確認
cat /sys/block/sda/queue/scheduler
cat /sys/block/sda/queue/nr_requests
cat /sys/block/sda/queue/read_ahead_kb
cat /sys/block/sda/queue/rotational
# I/O 統計確認
cat /sys/block/sda/stat
# フィールド: read_ios read_merges read_sectors read_ticks
# write_ios write_merges write_sectors write_ticks
# in_flight io_ticks time_in_queue
# discard_ios discard_merges discard_sectors discard_ticks
# flush_ios flush_ticks
# デバイス情報
cat /sys/block/sda/queue/logical_block_size
cat /sys/block/sda/queue/physical_block_size
cat /sys/block/sda/queue/hw_sector_size
4. I/O スケジューラ
4.1 I/O スケジューラの概要
I/O スケジューラは、ブロックデバイスへの I/O リクエストの実行順序を最適化するカーネルコンポーネントである。Linux 5.0 以降、全てのブロックデバイスはマルチキュー(blk-mq)ベースとなり、利用可能なスケジューラは以下の4種類となった。
# 利用可能なスケジューラの確認
cat /sys/block/sda/queue/scheduler
# 出力例: [mq-deadline] kyber bfq none
# スケジューラの変更
echo "bfq" > /sys/block/sda/queue/scheduler
# 永続的な設定(GRUB)
# /etc/default/grub
GRUB_CMDLINE_LINUX="elevator=mq-deadline"
# udev ルールによる設定
# /etc/udev/rules.d/60-ioscheduler.rules
# HDD には mq-deadline
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", \
ATTR{queue/scheduler}="mq-deadline"
# SSD には none(noop)
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", \
ATTR{queue/scheduler}="none"
# NVMe には none
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/scheduler}="none"
4.2 mq-deadline スケジューラ
4.2.1 アルゴリズム概要
mq-deadline は、レガシーの deadline スケジューラを blk-mq 対応にしたものである。各リクエストにデッドライン(期限)を設定し、期限切れのリクエストを優先的に処理する。
mq-deadline の内部構造:
┌─────────────────────────────────────────┐
│ mq-deadline │
│ │
│ ┌──────────────────┐ ┌──────────────┐ │
│ │ Read FIFO │ │ Read Sorted │ │
│ │ (期限順) │ │ (セクタ順) │ │
│ └──────────────────┘ └──────────────┘ │
│ │
│ ┌──────────────────┐ ┌──────────────┐ │
│ │ Write FIFO │ │ Write Sorted │ │
│ │ (期限順) │ │ (セクタ順) │ │
│ └──────────────────┘ └──────────────┘ │
│ │
│ ディスパッチ決定ロジック: │
│ 1. 期限切れリクエストがあれば優先 │
│ 2. なければソート済みリストから選択 │
│ 3. reads_starved に基づく R/W バランス │
└─────────────────────────────────────────┘
4.2.2 パラメータ
# mq-deadline パラメータの確認と設定
ls /sys/block/sda/queue/iosched/
# 読み込みデッドライン(ミリ秒): デフォルト 500ms
cat /sys/block/sda/queue/iosched/read_expire
echo 300 > /sys/block/sda/queue/iosched/read_expire
# 書き込みデッドライン(ミリ秒): デフォルト 5000ms
cat /sys/block/sda/queue/iosched/write_expire
echo 3000 > /sys/block/sda/queue/iosched/write_expire
# 書き込みの飢餓防止カウンタ: デフォルト 2
# 読み込みを何回連続処理したら書き込みに切り替えるか
cat /sys/block/sda/queue/iosched/writes_starved
echo 4 > /sys/block/sda/queue/iosched/writes_starved
# FIFO バッチ数: デフォルト 16
# セクタ順からFIFO順への切り替えバッチサイズ
cat /sys/block/sda/queue/iosched/fifo_batch
echo 8 > /sys/block/sda/queue/iosched/fifo_batch
# フロントマージ: デフォルト 1(有効)
cat /sys/block/sda/queue/iosched/front_merges
4.2.3 ユースケース
mq-deadline が適するケース:
- HDD(回転メディア)
- シーク時間が重要な環境
- 読み込みレイテンシを保証したい場合
- データベースワークロード(特に OLTP)
- 仮想マシンホスト
mq-deadline が適さないケース:
- 高速 NVMe SSD(オーバーヘッドが不要)
- 純粋なシーケンシャルワークロード
4.3 BFQ (Budget Fair Queueing) スケジューラ
4.3.1 アルゴリズム概要
BFQ は、CFQ の後継として設計された公平性重視のスケジューラである。各プロセスに「バジェット」(セクタ数の予算)を割り当て、公平な帯域幅配分を実現する。
BFQ の内部構造:
┌──────────────────────────────────────────────┐
│ BFQ │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ BFQ キュー(プロセスごと) │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │Queue1│ │Queue2│ │Queue3│ │Queue4│ │ │
│ │ │予算:X│ │予算:Y│ │予算:Z│ │予算:W│ │ │
│ │ └──────┘ └──────┘ └──────┘ └──────┘ │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ B-WF2Q+ スケジューラ │ │
│ │ (重み付き公平キューイング) │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 低レイテンシヒューリスティック │ │
│ │ - アイドリング │ │
│ │ - インタラクティブ検出 │ │
│ │ - ソフトリアルタイム検出 │ │
│ └─────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
4.3.2 パラメータ
# BFQ パラメータの確認
ls /sys/block/sda/queue/iosched/
# タイムスライス(ミリ秒)
cat /sys/block/sda/queue/iosched/slice_idle
echo 8 > /sys/block/sda/queue/iosched/slice_idle
# 低レイテンシモード: デフォルト 1(有効)
cat /sys/block/sda/queue/iosched/low_latency
echo 1 > /sys/block/sda/queue/iosched/low_latency
# 最大バジェット(セクタ数)
cat /sys/block/sda/queue/iosched/max_budget
# タイムアウト(ミリ秒)
cat /sys/block/sda/queue/iosched/timeout_sync
cat /sys/block/sda/queue/iosched/timeout_async
# 厳密保証モード
cat /sys/block/sda/queue/iosched/strict_guarantees
# 重みの設定(cgroup 経由)
echo 500 > /sys/fs/cgroup/blkio/mygroup/blkio.bfq.weight
4.3.3 ユースケース
BFQ が適するケース:
- デスクトップ環境(インタラクティブ性能重視)
- HDD でのマルチタスク環境
- I/O 帯域幅の公平配分が必要な場合
- 低速ストレージでのレスポンス改善
- コンテナ環境での I/O 隔離
BFQ が適さないケース:
- 高速 NVMe SSD(オーバーヘッドが大きい)
- 単一ワークロードの高スループット環境
- CPU リソースが限られた環境
4.4 Kyber スケジューラ
4.4.1 アルゴリズム概要
Kyber は、高速デバイス向けに設計された軽量スケジューラである。トークンベースのスロットリング機構を用いて、読み込みと書き込みのレイテンシを制御する。
Kyber の内部構造:
┌──────────────────────────────────────────┐
│ Kyber │
│ │
│ ┌────────────────────────────────────┐ │
│ │ レイテンシモニタリング │ │
│ │ Read target: 2ms │ │
│ │ Write target: 10ms │ │
│ └────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ トークンバケット │ │
│ │ ┌──────────┐ ┌───────────┐ │ │
│ │ │Read用 │ │Write用 │ │ │
│ │ │トークン │ │トークン │ │ │
│ │ └──────────┘ └───────────┘ │ │
│ └────────────────────────────────────┘ │
│ │
│ ドメインキュー: │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ Read │ │ Write │ │Discard │ │
│ │ Queue │ │ Queue │ │ Queue │ │
│ └──────────┘ └──────────┘ └────────┘ │
└──────────────────────────────────────────┘
4.4.2 パラメータ
# Kyber パラメータの確認
ls /sys/block/nvme0n1/queue/iosched/
# 読み込みレイテンシターゲット(マイクロ秒): デフォルト 2000 (2ms)
cat /sys/block/nvme0n1/queue/iosched/read_lat_nsec
echo 2000000 > /sys/block/nvme0n1/queue/iosched/read_lat_nsec
# 書き込みレイテンシターゲット(マイクロ秒): デフォルト 10000 (10ms)
cat /sys/block/nvme0n1/queue/iosched/write_lat_nsec
echo 10000000 > /sys/block/nvme0n1/queue/iosched/write_lat_nsec
4.4.3 ユースケース
Kyber が適するケース:
- 高速 SSD/NVMe
- 読み込みレイテンシの制御が必要
- 軽量なスケジューリングが必要
- マルチキュー対応デバイス
Kyber が適さないケース:
- HDD(シーク最適化がない)
- 公平性が重要な環境
- 詳細な QoS 制御が必要な場合
4.5 none (noop) スケジューラ
# none スケジューラの設定
echo "none" > /sys/block/nvme0n1/queue/scheduler
# none スケジューラの特徴:
# - FIFO(先入れ先出し)のみ
# - リクエストのマージは行う
# - 並べ替えは行わない
# - オーバーヘッドが最小
none が適するケース:
- 高速 NVMe SSD
- デバイス側にスケジューラがある場合
- 仮想マシンゲスト(ホストがスケジューリング)
- 最低レイテンシが必要な場合
none が適さないケース:
- HDD(シーク最適化なし)
- レイテンシ制御が必要な場合
4.6 スケジューラ選択ガイド
デバイス種別別推奨スケジューラ:
┌────────────────┬─────────────┬──────────────────────────┐
│ デバイス種別 │ 推奨 │ 理由 │
├────────────────┼─────────────┼──────────────────────────┤
│ NVMe SSD │ none │ デバイスが十分高速 │
│ SATA SSD │ mq-deadline │ 適度なスケジューリング │
│ HDD (サーバ) │ mq-deadline │ デッドライン保証 │
│ HDD (デスク) │ bfq │ インタラクティブ性能 │
│ 仮想ディスク │ none/kyber │ ホストがスケジューリング │
│ SD カード │ bfq │ 低速デバイスでの公平性 │
│ USB メモリ │ bfq │ 低速デバイスでの公平性 │
└────────────────┴─────────────┴──────────────────────────┘
4.7 スケジューラのパフォーマンス比較
# fio を使用したスケジューラのベンチマーク比較
# テスト用スクリプト
#!/bin/bash
DEVICE="/dev/sda"
SCHEDULERS="mq-deadline bfq kyber none"
for sched in $SCHEDULERS; do
echo "Testing scheduler: $sched"
echo "$sched" > /sys/block/sda/queue/scheduler
echo "--- Random Read 4K ---"
fio --name=randread --ioengine=libaio --iodepth=32 \
--rw=randread --bs=4k --direct=1 --size=1G \
--numjobs=4 --time_based --runtime=30 \
--filename=$DEVICE --group_reporting
echo "--- Random Write 4K ---"
fio --name=randwrite --ioengine=libaio --iodepth=32 \
--rw=randwrite --bs=4k --direct=1 --size=1G \
--numjobs=4 --time_based --runtime=30 \
--filename=$DEVICE --group_reporting
echo "--- Sequential Read 128K ---"
fio --name=seqread --ioengine=libaio --iodepth=16 \
--rw=read --bs=128k --direct=1 --size=1G \
--numjobs=1 --time_based --runtime=30 \
--filename=$DEVICE --group_reporting
echo ""
done
5. マルチキュー ブロックレイヤー (blk-mq)
5.1 blk-mq の概要
blk-mq(Multi-Queue Block Layer)は、Linux カーネル 3.13 で導入された新しいブロック I/O 処理フレームワークである。従来のシングルキューモデルがマルチコア CPU 環境でボトルネックとなっていた問題を解決するために設計された。
従来のシングルキューモデル:
┌─────────┐
CPU 0 ─┐ │ │
CPU 1 ──┼──→ [単一リクエストキュー] ──→│ デバイス │
CPU 2 ──┤ (ロック競合の温床) │ │
CPU 3 ─┘ └─────────┘
blk-mq マルチキューモデル:
CPU 0 ──→ [SW Queue 0] ──┐
CPU 1 ──→ [SW Queue 1] ──┼──→ [HW Queue 0] ──→ ┌─────────┐
CPU 2 ──→ [SW Queue 2] ──┤ │ デバイス │
CPU 3 ──→ [SW Queue 3] ──┼──→ [HW Queue 1] ──→ │ │
└────────────────────→ └─────────┘
5.2 ソフトウェアキュー (blk_mq_ctx)
ソフトウェアキューは CPU ごとに1つ割り当てられ、ロック競合を大幅に削減する。
/* include/linux/blk-mq.h */
struct blk_mq_ctx {
struct {
spinlock_t lock;
struct list_head rq_lists[HCTX_MAX_TYPES]; /* リクエストリスト */
} ____cacheline_aligned_in_smp;
unsigned int cpu; /* 関連 CPU */
unsigned short index_hw[HCTX_MAX_TYPES]; /* HW キューインデックス */
struct blk_mq_hw_ctx *hctxs[HCTX_MAX_TYPES]; /* HW キューへのポインタ */
struct request_queue *queue; /* リクエストキュー */
struct blk_mq_ctxs *ctxs; /* コンテキスト集合 */
struct kobject kobj; /* sysfs オブジェクト */
};
5.3 ハードウェアキュー (blk_mq_hw_ctx)
ハードウェアキューは、デバイスのハードウェアキュー(サブミッションキュー)に対応する。
/* include/linux/blk-mq.h (簡略化) */
struct blk_mq_hw_ctx {
struct {
spinlock_t lock;
struct list_head dispatch; /* ディスパッチリスト */
unsigned long state; /* BLK_MQ_S_* フラグ */
} ____cacheline_aligned_in_smp;
struct delayed_work run_work; /* 実行ワーク */
cpumask_var_t cpumask; /* CPU マスク */
int next_cpu; /* 次の CPU */
int next_cpu_batch; /* CPU バッチ */
unsigned long flags; /* フラグ */
void *sched_data; /* スケジューラデータ */
struct request_queue *queue; /* リクエストキュー */
struct blk_flush_queue *fq; /* フラッシュキュー */
void *driver_data; /* ドライバデータ */
struct sbitmap ctx_map; /* コンテキストマップ */
struct blk_mq_ctx *dispatch_from; /* ディスパッチ元 */
unsigned int dispatch_busy; /* ディスパッチビジー状態 */
unsigned short type; /* キュータイプ */
unsigned short nr_ctx; /* コンテキスト数 */
struct blk_mq_ctx **ctxs; /* コンテキスト配列 */
spinlock_t dispatch_wait_lock; /* ディスパッチ待機ロック */
wait_queue_entry_t dispatch_wait; /* ディスパッチ待機 */
atomic_t wait_index; /* 待機インデックス */
struct blk_mq_tags *tags; /* タグ */
struct blk_mq_tags *sched_tags; /* スケジューラタグ */
unsigned long queued; /* キューイング済みカウンタ */
unsigned long run; /* 実行カウンタ */
unsigned int numa_node; /* NUMA ノード */
unsigned int queue_num; /* キュー番号 */
atomic_t nr_active; /* アクティブリクエスト数 */
struct hlist_node cpuhp_online; /* CPU ホットプラグ(オンライン) */
struct hlist_node cpuhp_dead; /* CPU ホットプラグ(デッド) */
struct kobject kobj; /* sysfs オブジェクト */
struct dentry *debugfs_dir; /* debugfs ディレクトリ */
struct dentry *sched_debugfs_dir; /* スケジューラ debugfs */
struct list_head hctx_list; /* HW キューリスト */
};
5.4 タグセット (blk_mq_tag_set)
タグセットは、デバイスドライバが設定する blk-mq の構成パラメータを定義する。
/* include/linux/blk-mq.h */
struct blk_mq_tag_set {
const struct blk_mq_ops *ops; /* 操作関数テーブル */
unsigned int nr_hw_queues; /* HW キュー数 */
unsigned int queue_depth; /* キュー深度 */
unsigned int reserved_tags; /* 予約タグ数 */
unsigned int cmd_size; /* コマンドサイズ */
int numa_node; /* NUMA ノード */
unsigned int timeout; /* タイムアウト */
unsigned int flags; /* フラグ */
void *driver_data; /* ドライバデータ */
atomic_t active_queues_shared_sbitmap; /* 共有ビットマップのアクティブキュー数 */
struct sbitmap_queue __bitmap_tags; /* ビットマップタグ */
struct sbitmap_queue __breserved_tags; /* 予約ビットマップタグ */
struct blk_mq_tags **tags; /* タグ配列 */
struct blk_mq_tags *shared_tags; /* 共有タグ */
struct mutex tag_list_lock; /* タグリストロック */
struct list_head tag_list; /* タグリスト */
struct srcu_struct *srcu; /* SRCU */
};
5.5 blk-mq 操作関数
/* include/linux/blk-mq.h */
struct blk_mq_ops {
/* リクエスト処理(必須) */
blk_status_t (*queue_rq)(struct blk_mq_hw_ctx *,
const struct blk_mq_queue_data *);
/* リクエスト完了処理 */
void (*complete)(struct request *);
/* リクエスト初期化 */
int (*init_request)(struct blk_mq_tag_set *set,
struct request *, unsigned int,
unsigned int);
/* リクエスト解放 */
void (*exit_request)(struct blk_mq_tag_set *set,
struct request *, unsigned int);
/* HW キュー初期化 */
int (*init_hctx)(struct blk_mq_hw_ctx *, void *, unsigned int);
/* HW キュー解放 */
void (*exit_hctx)(struct blk_mq_hw_ctx *, unsigned int);
/* ポーリング */
int (*poll)(struct blk_mq_hw_ctx *, struct io_comp_batch *);
/* タイムアウト処理 */
enum blk_eh_timer_return (*timeout)(struct request *);
/* CPU マッピング */
void (*map_queues)(struct blk_mq_tag_set *set);
/* ビジーチェック */
bool (*busy)(struct request_queue *);
/* コミット(バッチ) */
void (*commit_rqs)(struct blk_mq_hw_ctx *);
/* キューイングの準備 */
void (*queue_rqs)(struct request **rqlist);
};
5.6 リクエスト処理フロー
1. submit_bio() が呼ばれる
│
▼
2. bio → request に変換(または既存 request にマージ)
│
▼
3. CPU に対応するソフトウェアキュー (blk_mq_ctx) に挿入
│
▼
4. I/O スケジューラがある場合:
├── スケジューラのキューに挿入
└── スケジューラのディスパッチロジックに従って
ハードウェアキューに転送
│
I/O スケジューラがない場合 (none):
└── 直接ハードウェアキューに転送
│
▼
5. ハードウェアキュー (blk_mq_hw_ctx) に到着
│
▼
6. blk_mq_ops->queue_rq() が呼ばれる
│
▼
7. デバイスドライバがリクエストを処理
│
▼
8. 完了時: blk_mq_end_request() が呼ばれる
│
▼
9. コールバック実行、bio 完了通知
5.7 blk-mq の設定と監視
# HW キュー数の確認
ls /sys/block/nvme0n1/mq/
# 各 HW キューの情報
cat /sys/block/nvme0n1/mq/0/cpu_list
cat /sys/block/nvme0n1/mq/0/nr_reserved
cat /sys/block/nvme0n1/mq/0/nr_tags
# キュー深度の確認
cat /sys/block/nvme0n1/queue/nr_requests
# キュー深度の変更
echo 256 > /sys/block/nvme0n1/queue/nr_requests
# rq_affinity の設定
# 0: 完了を任意の CPU で処理
# 1: 同じ CPU グループで処理
# 2: 同じ CPU で処理
cat /sys/block/nvme0n1/queue/rq_affinity
echo 2 > /sys/block/nvme0n1/queue/rq_affinity
# I/O ポーリング設定
cat /sys/block/nvme0n1/queue/io_poll
echo 1 > /sys/block/nvme0n1/queue/io_poll
# debugfs によるデバッグ情報
mount -t debugfs none /sys/kernel/debug
ls /sys/kernel/debug/block/nvme0n1/
cat /sys/kernel/debug/block/nvme0n1/state
cat /sys/kernel/debug/block/nvme0n1/hctx0/tags
cat /sys/kernel/debug/block/nvme0n1/hctx0/tags_bitmap
5.8 NUMA 対応
blk-mq の NUMA 最適化:
NUMA ノード 0 NUMA ノード 1
┌─────────────────────┐ ┌─────────────────────┐
│ CPU 0 CPU 1 │ │ CPU 4 CPU 5 │
│ CPU 2 CPU 3 │ │ CPU 6 CPU 7 │
│ │ │ │
│ SW Queue 0-3 │ │ SW Queue 4-7 │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ HW Queue 0 │ │ HW Queue 1 │
│ (ローカルメモリ使用) │ │ (ローカルメモリ使用) │
└─────────┬───────────┘ └─────────┬───────────┘
│ │
▼ ▼
┌────────────────────────────────────────┐
│ NVMe コントローラ │
│ (複数の SQ/CQ ペア) │
└────────────────────────────────────────┘
6. Bio 構造体とリクエスト処理
6.1 Bio 構造体の詳細
bio 構造体は、ブロック I/O の基本単位であり、単一の I/O 操作を表現する。
/* include/linux/blk_types.h (簡略化) */
struct bio {
struct bio *bi_next; /* リクエスト内の次の bio */
struct block_device *bi_bdev; /* ターゲットデバイス */
blk_opf_t bi_opf; /* 操作タイプとフラグ */
unsigned short bi_flags; /* BIO_* フラグ */
unsigned short bi_ioprio; /* I/O 優先度 */
blk_status_t bi_status; /* 完了ステータス */
atomic_t __bi_remaining; /* 残りの完了カウント */
struct bvec_iter bi_iter; /* イテレータ(位置情報) */
blk_qc_t bi_cookie; /* ポーリング用クッキー */
bio_end_io_t *bi_end_io; /* 完了コールバック */
void *bi_private; /* コールバック用データ */
union {
struct bio_crypt_ctx *bi_crypt_context; /* 暗号化コンテキスト */
};
unsigned short bi_vcnt; /* bio_vec の数 */
unsigned short bi_max_vecs; /* 最大 bio_vec 数 */
atomic_t __bi_cnt; /* 参照カウント */
struct bio_vec *bi_io_vec; /* bio_vec 配列 */
struct bio_set *bi_pool; /* 割り当て元プール */
struct bio_vec bi_inline_vecs[]; /* インライン bio_vec */
};
/* bio イテレータ */
struct bvec_iter {
sector_t bi_sector; /* 開始セクタ */
unsigned int bi_size; /* 残りバイト数 */
unsigned int bi_idx; /* 現在の bio_vec インデックス */
unsigned int bi_bvec_done; /* 現在の bio_vec の処理済みバイト数 */
};
/* bio_vec: メモリページへの参照 */
struct bio_vec {
struct page *bv_page; /* ページ */
unsigned int bv_len; /* 長さ */
unsigned int bv_offset; /* ページ内オフセット */
};
6.2 Bio のライフサイクル
Bio のライフサイクル:
1. 割り当て: bio_alloc() / bio_alloc_bioset()
│
▼
2. 設定: bio_set_dev(), bio->bi_opf の設定
│
▼
3. ページ追加: bio_add_page()
│ ↓ (繰り返し)
│
▼
4. 投入: submit_bio()
│
▼
5. ブロックレイヤー処理:
├── マージ判定
├── リクエスト生成
└── スケジューリング
│
▼
6. デバイスドライバ処理
│
▼
7. 完了: bio_endio()
│
▼
8. コールバック: bi_end_io() 呼び出し
│
▼
9. 解放: bio_put()
6.3 Bio 操作の実例
/* Bio を使った読み込みの例 */
#include <linux/bio.h>
#include <linux/blkdev.h>
static void my_bio_end_io(struct bio *bio)
{
if (bio->bi_status)
pr_err("Bio I/O error: %d\n", blk_status_to_errno(bio->bi_status));
else
pr_info("Bio I/O completed successfully\n");
/* ページの処理 */
struct bio_vec bvec;
struct bvec_iter iter;
bio_for_each_segment(bvec, bio, iter) {
struct page *page = bvec.bv_page;
/* ページの処理 ... */
__free_page(page);
}
bio_put(bio);
}
static int submit_read_bio(struct block_device *bdev,
sector_t sector, unsigned int nr_sectors)
{
struct bio *bio;
struct page *page;
unsigned int remaining = nr_sectors * 512;
/* bio 割り当て(最大ページ数を指定) */
bio = bio_alloc(bdev, DIV_ROUND_UP(remaining, PAGE_SIZE),
REQ_OP_READ, GFP_KERNEL);
if (!bio)
return -ENOMEM;
bio->bi_iter.bi_sector = sector;
bio->bi_end_io = my_bio_end_io;
/* ページを追加 */
while (remaining > 0) {
unsigned int len = min_t(unsigned int, remaining, PAGE_SIZE);
page = alloc_page(GFP_KERNEL);
if (!page) {
bio_put(bio);
return -ENOMEM;
}
if (bio_add_page(bio, page, len, 0) < len) {
__free_page(page);
/* bio が満杯 - 分割が必要 */
break;
}
remaining -= len;
}
/* bio を投入 */
submit_bio(bio);
return 0;
}
6.4 Bio のマージ
マージの種類:
1. バックマージ (Back Merge):
既存のリクエストの末尾に新しい bio を追加
[リクエスト: セクタ 100-200] + [Bio: セクタ 200-300]
→ [リクエスト: セクタ 100-300]
2. フロントマージ (Front Merge):
既存のリクエストの先頭に新しい bio を追加
[Bio: セクタ 0-100] + [リクエスト: セクタ 100-200]
→ [リクエスト: セクタ 0-200]
3. リクエストマージ (Request Merge):
隣接する2つのリクエストを統合
[リクエスト A: セクタ 100-200] + [リクエスト B: セクタ 200-300]
→ [リクエスト: セクタ 100-300]
# マージの統計確認
cat /proc/diskstats
# フィールド 5 (read_merges) と 9 (write_merges) を確認
# マージの無効化
echo 2 > /sys/block/sda/queue/nomerges
# 0: マージ有効(デフォルト)
# 1: 同じプロセスからのみマージ
# 2: マージ完全無効
6.5 リクエスト処理
/* リクエスト構造体(簡略化) */
struct request {
struct request_queue *q; /* リクエストキュー */
struct blk_mq_ctx *mq_ctx; /* ソフトウェアキュー */
struct blk_mq_hw_ctx *mq_hctx; /* ハードウェアキュー */
blk_opf_t cmd_flags; /* コマンドフラグ */
req_flags_t rq_flags; /* リクエストフラグ */
int tag; /* タグ */
int internal_tag; /* 内部タグ */
unsigned int __data_len; /* データ長 */
sector_t __sector; /* 開始セクタ */
struct bio *bio; /* 最初の bio */
struct bio *biotail; /* 最後の bio */
union {
struct list_head queuelist; /* キューリスト */
struct request *rq_next; /* 次のリクエスト */
};
struct gendisk *rq_disk; /* gendisk */
struct block_device *part; /* パーティション */
u64 start_time_ns; /* 開始時刻 */
u64 io_start_time_ns; /* I/O 開始時刻 */
unsigned short stats_sectors; /* 統計用セクタ数 */
unsigned short nr_phys_segments; /* 物理セグメント数 */
enum mq_rq_state state; /* リクエスト状態 */
atomic_t ref; /* 参照カウント */
unsigned long deadline; /* デッドライン */
rq_end_io_fn *end_io; /* 完了コールバック */
void *end_io_data; /* コールバックデータ */
};
6.6 I/O 操作タイプ
/* I/O 操作タイプ (blk_opf_t) */
enum req_op {
REQ_OP_READ = 0, /* 読み込み */
REQ_OP_WRITE = 1, /* 書き込み */
REQ_OP_FLUSH = 2, /* フラッシュ */
REQ_OP_DISCARD = 3, /* discard (TRIM) */
REQ_OP_SECURE_ERASE = 5, /* セキュア消去 */
REQ_OP_WRITE_ZEROES = 9, /* ゼロ書き込み */
REQ_OP_ZONE_OPEN = 10, /* ゾーンオープン */
REQ_OP_ZONE_CLOSE = 11, /* ゾーンクローズ */
REQ_OP_ZONE_FINISH = 12, /* ゾーンフィニッシュ */
REQ_OP_ZONE_APPEND = 13, /* ゾーン追記 */
REQ_OP_ZONE_RESET = 15, /* ゾーンリセット */
REQ_OP_ZONE_RESET_ALL = 17, /* 全ゾーンリセット */
REQ_OP_DRV_IN = 34, /* ドライバ入力 */
REQ_OP_DRV_OUT = 35, /* ドライバ出力 */
};
/* リクエストフラグ */
#define REQ_FAILFAST_DEV (1ULL << 8) /* デバイスエラー時即失敗 */
#define REQ_FAILFAST_TRANSPORT (1ULL << 9) /* トランスポートエラー時即失敗 */
#define REQ_FAILFAST_DRIVER (1ULL << 10) /* ドライバエラー時即失敗 */
#define REQ_SYNC (1ULL << 11) /* 同期 I/O */
#define REQ_META (1ULL << 12) /* メタデータ I/O */
#define REQ_PRIO (1ULL << 13) /* 優先 I/O */
#define REQ_NOMERGE (1ULL << 14) /* マージ禁止 */
#define REQ_IDLE (1ULL << 15) /* アイドル I/O */
#define REQ_INTEGRITY (1ULL << 16) /* 整合性チェック */
#define REQ_FUA (1ULL << 17) /* Force Unit Access */
#define REQ_PREFLUSH (1ULL << 18) /* 事前フラッシュ */
#define REQ_RAHEAD (1ULL << 19) /* 先読み */
#define REQ_BACKGROUND (1ULL << 20) /* バックグラウンド I/O */
#define REQ_NOWAIT (1ULL << 21) /* 待機なし */
#define REQ_POLLED (1ULL << 22) /* ポーリング */
7. デバイスマッパー (dm)
7.1 デバイスマッパーの概要
デバイスマッパーは、Linux カーネルが提供する汎用ブロックデバイスマッピングフレームワークである。仮想ブロックデバイスを作成し、I/O を実際のブロックデバイスにマッピングする。
デバイスマッパーのアーキテクチャ:
┌─────────────────────────────────────────┐
│ 仮想ブロックデバイス │
│ (/dev/dm-0) │
├─────────────────────────────────────────┤
│ デバイスマッパーコア │
│ │
│ ┌─────────────────────────────────┐ │
│ │ マッピングテーブル │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ ターゲットタイプ │ │ │
│ │ │ (linear, striped, mirror│ │ │
│ │ │ snapshot, crypt, thin, │ │ │
│ │ │ cache, raid ...) │ │ │
│ │ └─────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ 物理ブロックデバイス │
│ /dev/sda /dev/sdb /dev/nvme0n1 │
└─────────────────────────────────────────┘
7.2 dm ターゲットタイプ
# 利用可能なターゲットの確認
dmsetup targets
# 主要なターゲットタイプ:
# linear - 線形マッピング
# striped - ストライプマッピング
# mirror - ミラーリング
# snapshot - スナップショット
# snapshot-origin - スナップショットオリジン
# crypt - 暗号化
# thin - シンプロビジョニング
# thin-pool - シンプール
# cache - キャッシュ
# raid - RAID
# error - エラーターゲット
# zero - ゼロターゲット
# delay - 遅延ターゲット
# flakey - テスト用
# integrity - データ整合性
# writecache - ライトキャッシュ
7.3 dmsetup の使用方法
# デバイスマッパーデバイスの一覧
dmsetup ls
dmsetup info
# デバイスの詳細情報
dmsetup info /dev/dm-0
# マッピングテーブルの確認
dmsetup table
dmsetup table /dev/mapper/vg0-lv0
# ステータスの確認
dmsetup status
dmsetup status /dev/mapper/vg0-lv0
# 線形デバイスの作成例
# テーブル形式: 開始セクタ 長さ ターゲット パラメータ
echo "0 2097152 linear /dev/sda1 0" | dmsetup create mylinear
# ストライプデバイスの作成例
echo "0 4194304 striped 2 128 /dev/sda1 0 /dev/sdb1 0" | \
dmsetup create mystripe
# エラーデバイスの作成(テスト用)
echo "0 1048576 error" | dmsetup create myerror
# 遅延デバイスの作成(テスト用)
# 読み込み遅延 100ms、書き込み遅延 200ms
echo "0 2097152 delay /dev/sda1 0 100 /dev/sda1 0 200" | \
dmsetup create mydelay
# デバイスの削除
dmsetup remove mylinear
# テーブルの入れ替え(ライブ)
echo "0 4194304 linear /dev/sdb1 0" | dmsetup load mylinear
dmsetup resume mylinear
# デバイスの一時停止・再開
dmsetup suspend mylinear
dmsetup resume mylinear
# メッセージの送信
dmsetup message mydevice 0 "some_command"
7.4 dm-crypt(暗号化)
# dm-crypt を使ったディスク暗号化
# LUKS でデバイスをフォーマット
cryptsetup luksFormat /dev/sda2
# LUKS デバイスを開く
cryptsetup open --type luks /dev/sda2 encrypted_disk
# 暗号化デバイスが /dev/mapper/encrypted_disk に作成される
ls -la /dev/mapper/encrypted_disk
# ファイルシステムの作成
mkfs.ext4 /dev/mapper/encrypted_disk
# マウント
mount /dev/mapper/encrypted_disk /mnt/encrypted
# デバイスの状態確認
cryptsetup status encrypted_disk
# 出力例:
# /dev/mapper/encrypted_disk is active.
# type: LUKS2
# cipher: aes-xts-plain64
# keysize: 512 bits
# key location: dm-crypt
# device: /dev/sda2
# sector size: 512
# offset: 32768 sectors
# size: 1048576 sectors
# mode: read/write
# LUKS ヘッダー情報
cryptsetup luksDump /dev/sda2
# パフォーマンスベンチマーク
cryptsetup benchmark
# 出力例:
# Algorithm | Key | Encryption | Decryption
# aes-cbc 128b 657.4 MiB/s 3129.0 MiB/s
# aes-cbc 256b 541.2 MiB/s 2474.3 MiB/s
# aes-xts 256b 2476.5 MiB/s 2445.7 MiB/s
# aes-xts 512b 2070.1 MiB/s 2058.2 MiB/s
# デバイスを閉じる
umount /mnt/encrypted
cryptsetup close encrypted_disk
# /etc/crypttab での自動マウント設定
# <name> <device> <key_file> <options>
# encrypted_disk /dev/sda2 none luks,discard
7.5 dm-thin(シンプロビジョニング)
# シンプールの作成
# メタデータデバイスとデータデバイスが必要
# メタデータ用 LV の作成
lvcreate -L 1G -n thin_meta vg0
# データ用 LV の作成
lvcreate -L 100G -n thin_data vg0
# シンプールの作成
lvconvert --type thin-pool \
--poolmetadata vg0/thin_meta \
vg0/thin_data
# シンボリューム(仮想プロビジョニング)の作成
# 実際のディスク使用量は必要に応じて増加
lvcreate -V 500G --thin -n thin_vol1 vg0/thin_data
# シンボリュームのスナップショット
lvcreate -s --name snap1 vg0/thin_vol1
# シンプールの状態確認
lvs -o +data_percent,metadata_percent vg0
# dmsetup でのシンプール状態
dmsetup status vg0-thin_data-tpool
# 出力例: 0 209715200 thin-pool 22 307200/524288 12800/131072 - rw discard_passdown queue_if_no_space
7.6 dm-cache
# dm-cache: SSD を HDD のキャッシュとして使用
# キャッシュデバイス (SSD)
CACHE_DEV="/dev/nvme0n1p1"
# キャッシュメタデータデバイス (SSD の別パーティション)
CACHE_META="/dev/nvme0n1p2"
# オリジンデバイス (HDD)
ORIGIN_DEV="/dev/sda1"
# LVM で dm-cache を設定
# キャッシュプール LV の作成
lvcreate -L 50G -n cache_data vg0 /dev/nvme0n1
lvcreate -L 1G -n cache_meta vg0 /dev/nvme0n1
# キャッシュプールに変換
lvconvert --type cache-pool \
--poolmetadata vg0/cache_meta \
--cachemode writeback \
vg0/cache_data
# 既存の LV にキャッシュを追加
lvconvert --type cache \
--cachepool vg0/cache_data \
vg0/data_lv
# キャッシュモードの変更
lvchange --cachemode writethrough vg0/data_lv
# キャッシュ統計の確認
dmsetup status vg0-data_lv
lvs -o +cache_read_hits,cache_read_misses,cache_write_hits,cache_write_misses
# キャッシュポリシー
# smq (stochastic multiqueue) - デフォルト
# cleaner - キャッシュの内容をオリジンに書き戻す
8. LVM (論理ボリュームマネージャ)
8.1 LVM の概要
LVM(Logical Volume Manager)は、デバイスマッパーの上に構築された論理ボリューム管理システムである。物理ディスクを抽象化し、柔軟なボリューム管理を可能にする。
LVM のアーキテクチャ:
┌────────────────────────────────────────────────┐
│ 論理ボリューム (LV) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ /dev/vg0 │ │ /dev/vg0 │ │ /dev/vg0 │ │
│ │ /lv_root │ │ /lv_home │ │ /lv_data │ │
│ │ 20GB │ │ 50GB │ │ 100GB │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ └─────────────┼───────────┘ │
│ ▼ │
├────────────────────────────────────────────────┤
│ ボリュームグループ (VG) │
│ vg0 (170GB) │
├────────────────────────────────────────────────┤
│ 物理ボリューム (PV) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ /dev/sda1│ │ /dev/sdb1│ │/dev/sdc1 │ │
│ │ 80GB │ │ 50GB │ │ 40GB │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└────────────────────────────────────────────────┘
8.2 物理ボリューム (PV) の管理
# 物理ボリュームの作成
pvcreate /dev/sda1
pvcreate /dev/sdb1
pvcreate /dev/sdc1
# 物理ボリュームの一覧
pvs
# 出力例:
# PV VG Fmt Attr PSize PFree
# /dev/sda1 vg0 lvm2 a-- 80.00g 10.00g
# /dev/sdb1 vg0 lvm2 a-- 50.00g 0
# /dev/sdc1 vg0 lvm2 a-- 40.00g 40.00g
# 物理ボリュームの詳細表示
pvdisplay /dev/sda1
# 出力例:
# --- Physical volume ---
# PV Name /dev/sda1
# VG Name vg0
# PV Size 80.00 GiB / not usable 3.00 MiB
# Allocatable yes
# PE Size 4.00 MiB
# Total PE 20479
# Free PE 2560
# Allocated PE 17919
# PV UUID xxxxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxxxx
# 物理エクステントのマッピング表示
pvdisplay -m /dev/sda1
# PV の削除(VG から先に除去する必要あり)
pvremove /dev/sdc1
# PV のサイズ変更
pvresize /dev/sda1
pvresize --setphysicalvolumesize 60G /dev/sda1
# PV の移動(データの移行)
pvmove /dev/sda1 /dev/sdc1
# 進捗表示付き
pvmove -i 5 /dev/sda1 /dev/sdc1
8.3 ボリュームグループ (VG) の管理
# ボリュームグループの作成
vgcreate vg0 /dev/sda1 /dev/sdb1
# PE サイズ指定での作成
vgcreate -s 16M vg0 /dev/sda1 /dev/sdb1
# VG の一覧
vgs
# 出力例:
# VG #PV #LV #SN Attr VSize VFree
# vg0 2 3 0 wz--n- 130.00g 10.00g
# VG の詳細表示
vgdisplay vg0
# PV の追加
vgextend vg0 /dev/sdc1
# PV の除去
vgreduce vg0 /dev/sdc1
# VG の名前変更
vgrename vg0 vg_data
# VG の無効化・有効化
vgchange -an vg0 # 無効化
vgchange -ay vg0 # 有効化
# VG のエクスポート・インポート(マイグレーション用)
vgexport vg0
vgimport vg0
# VG の削除
vgremove vg0
# VG のバックアップ・リストア
vgcfgbackup vg0 -f /tmp/vg0_backup
vgcfgrestore vg0 -f /tmp/vg0_backup
8.4 論理ボリューム (LV) の管理
# 論理ボリュームの作成
lvcreate -L 20G -n lv_root vg0
lvcreate -L 50G -n lv_home vg0
lvcreate -l 100%FREE -n lv_data vg0 # 残り全容量
# 特定の PV を使用
lvcreate -L 20G -n lv_fast vg0 /dev/nvme0n1p1
# ストライプ LV の作成
lvcreate -L 100G -n lv_stripe -i 3 -I 64K vg0
# ミラー LV の作成
lvcreate -L 20G -n lv_mirror --type mirror -m 1 vg0
# RAID1 LV の作成
lvcreate -L 20G -n lv_raid1 --type raid1 -m 1 vg0
# RAID5 LV の作成
lvcreate -L 100G -n lv_raid5 --type raid5 -i 3 vg0
# LV の一覧
lvs
# 出力例:
# LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
# lv_root vg0 -wi-ao---- 20.00g
# lv_home vg0 -wi-ao---- 50.00g
# lv_data vg0 -wi-ao---- 100.00g
# LV の詳細表示
lvdisplay /dev/vg0/lv_root
# LV のサイズ拡張
lvextend -L +10G /dev/vg0/lv_root
# ファイルシステムも同時に拡張
lvextend -L +10G -r /dev/vg0/lv_root
# パーセント指定
lvextend -l +50%FREE /dev/vg0/lv_data
# LV の縮小(注意: データ損失の可能性あり)
# ファイルシステムを先に縮小
resize2fs /dev/vg0/lv_home 40G
lvreduce -L 40G /dev/vg0/lv_home
# LV の削除
lvremove /dev/vg0/lv_data
# LV の名前変更
lvrename vg0 lv_old lv_new
# LV のスナップショット
lvcreate -L 5G -s -n snap_root /dev/vg0/lv_root
# スナップショットの状態確認
lvs -o +snap_percent
# スナップショットのマージ(元に戻す)
lvconvert --merge /dev/vg0/snap_root
8.5 LVM の高度な設定
# /etc/lvm/lvm.conf の主要な設定項目
# フィルタ設定(どのデバイスをスキャンするか)
# devices {
# filter = [ "a|/dev/sd.*|", "a|/dev/nvme.*|", "r|.*|" ]
# global_filter = [ "a|/dev/sd.*|", "r|.*|" ]
# }
# メタデータ設定
# metadata {
# pvmetadatasize = 255
# pvmetadatacopies = 2
# }
# アクティベーション設定
# activation {
# auto_activation_volume_list = [ "vg0", "@tag1" ]
# thin_pool_autoextend_threshold = 80
# thin_pool_autoextend_percent = 20
# }
# LVM キャッシュの設定
lvconvert --type cache --cachepool vg0/cache_pool vg0/lv_data
# LVM シンプロビジョニング
# シンプール作成
lvcreate --type thin-pool -L 100G -n thin_pool vg0
# シンボリューム作成
lvcreate -V 500G --thin -n thin_vol vg0/thin_pool
# LVM VDO (Virtual Data Optimizer) - 重複排除と圧縮
lvcreate --type vdo -L 100G -V 1T -n vdo_vol vg0
# LVM の監視
lvs -o +lv_health_status
vgs -o +vg_missing_pv_count
9. RAID (mdadm, dm-raid)
9.1 RAID の概要
RAID レベルの比較:
┌──────┬──────┬──────┬──────┬──────┬──────────────────┐
│Level │最小 │耐障害│容量 │R性能 │W性能 │
│ │台数 │台数 │効率 │ │ │
├──────┼──────┼──────┼──────┼──────┼──────────────────┤
│ 0 │ 2 │ 0 │100% │ N倍 │ N倍 │
│ 1 │ 2 │ N-1 │ 50% │ N倍 │ 1倍 │
│ 5 │ 3 │ 1 │(N-1) │(N-1) │ (N-1)倍 │
│ │ │ │/N │ 倍 │ (パリティ計算有) │
│ 6 │ 4 │ 2 │(N-2) │(N-2) │ (N-2)倍 │
│ │ │ │/N │ 倍 │ (パリティ計算有) │
│ 10 │ 4 │ N/2 │ 50% │ N倍 │ N/2倍 │
└──────┴──────┴──────┴──────┴──────┴──────────────────┘
9.2 mdadm による RAID 構築
# RAID 0 の作成(ストライプ)
mdadm --create /dev/md0 --level=0 --raid-devices=2 \
/dev/sda1 /dev/sdb1
# RAID 1 の作成(ミラー)
mdadm --create /dev/md0 --level=1 --raid-devices=2 \
/dev/sda1 /dev/sdb1
# RAID 5 の作成
mdadm --create /dev/md0 --level=5 --raid-devices=3 \
/dev/sda1 /dev/sdb1 /dev/sdc1
# スペアディスク付き RAID 5
mdadm --create /dev/md0 --level=5 --raid-devices=3 \
--spare-devices=1 \
/dev/sda1 /dev/sdb1 /dev/sdc1 /dev/sdd1
# RAID 6 の作成
mdadm --create /dev/md0 --level=6 --raid-devices=4 \
/dev/sda1 /dev/sdb1 /dev/sdc1 /dev/sdd1
# RAID 10 の作成
mdadm --create /dev/md0 --level=10 --raid-devices=4 \
/dev/sda1 /dev/sdb1 /dev/sdc1 /dev/sdd1
# チャンクサイズの指定
mdadm --create /dev/md0 --level=5 --raid-devices=3 \
--chunk=256K \
/dev/sda1 /dev/sdb1 /dev/sdc1
# ビットマップ付き(リビルド高速化)
mdadm --create /dev/md0 --level=1 --raid-devices=2 \
--bitmap=internal \
/dev/sda1 /dev/sdb1
9.3 mdadm の管理コマンド
# RAID の状態確認
cat /proc/mdstat
# 出力例:
# Personalities : [raid1] [raid6] [raid5] [raid4]
# md0 : active raid5 sdc1[2] sdb1[1] sda1[0]
# 2095104 blocks super 1.2 level 5, 512k chunk, algorithm 2 [3/3] [UUU]
# unused devices: <none>
# 詳細情報
mdadm --detail /dev/md0
# 各ディスクの情報
mdadm --examine /dev/sda1
# ディスクの追加
mdadm --add /dev/md0 /dev/sdd1
# ディスクの削除(先に故障マークを付ける)
mdadm --fail /dev/md0 /dev/sda1
mdadm --remove /dev/md0 /dev/sda1
# RAID の拡張(ディスク追加)
mdadm --grow /dev/md0 --raid-devices=4 --add /dev/sdd1
# チャンクサイズの変更
mdadm --grow /dev/md0 --chunk=512K
# リビルドの進捗確認
watch cat /proc/mdstat
# リビルド速度の調整
echo 200000 > /proc/sys/dev/raid/speed_limit_min
echo 500000 > /proc/sys/dev/raid/speed_limit_max
# ビットマップの追加
mdadm --grow /dev/md0 --bitmap=internal
# ビットマップの削除
mdadm --grow /dev/md0 --bitmap=none
# RAID の停止
mdadm --stop /dev/md0
# RAID の再構成
mdadm --assemble /dev/md0 /dev/sda1 /dev/sdb1 /dev/sdc1
# 自動構成
mdadm --assemble --scan
# 設定ファイルの生成
mdadm --detail --scan >> /etc/mdadm.conf
# スクラブ(整合性チェック)
echo check > /sys/block/md0/md/sync_action
# 進捗確認
cat /sys/block/md0/md/sync_completed
# 不一致数の確認
cat /sys/block/md0/md/mismatch_cnt
# 修復
echo repair > /sys/block/md0/md/sync_action
# RAID の監視
mdadm --monitor --mail=admin@example.com --delay=300 /dev/md0
# デーモンとして起動
mdadm --monitor --daemonize --mail=admin@example.com /dev/md0
9.4 dm-raid
# dm-raid は LVM のバックエンドとして使用される
# LVM RAID1 の作成
lvcreate --type raid1 -m 1 -L 10G -n lv_raid1 vg0
# LVM RAID5 の作成
lvcreate --type raid5 -i 3 -L 100G -n lv_raid5 vg0
# LVM RAID10 の作成
lvcreate --type raid10 -i 2 -m 1 -L 50G -n lv_raid10 vg0
# RAID の同期状態確認
lvs -o +raid_sync_action,raid_mismatch_count vg0/lv_raid1
# RAID レベルの変更(ライブ変換)
# RAID1 → RAID5
lvconvert --type raid5 vg0/lv_raid1
# ミラー数の変更
lvconvert -m 2 vg0/lv_raid1 # 3重ミラーに変更
# 整合性チェック
lvchange --syncaction check vg0/lv_raid1
# 整合性修復
lvchange --syncaction repair vg0/lv_raid1
9.5 RAID パフォーマンスチューニング
# チャンクサイズの最適化
# シーケンシャル I/O 主体: 大きいチャンク (256K-1M)
# ランダム I/O 主体: 小さいチャンク (64K-128K)
# stripe_cache_size の調整(RAID 5/6)
echo 8192 > /sys/block/md0/md/stripe_cache_size
# デフォルトは 256、大きくするとメモリ使用量が増加
# 値はストライプ数(各ストライプのサイズ = チャンクサイズ × デバイス数)
# group_thread_cnt の調整
echo 4 > /sys/block/md0/md/group_thread_cnt
# 先読みの設定
blockdev --setra 4096 /dev/md0 # 2MB の先読み
# I/O スケジューラの設定
echo "none" > /sys/block/md0/queue/scheduler
# RAID の sysfs パラメータ
cat /sys/block/md0/md/level # RAID レベル
cat /sys/block/md0/md/chunk_size # チャンクサイズ
cat /sys/block/md0/md/degraded # 劣化ディスク数
cat /sys/block/md0/md/array_state # アレイ状態
cat /sys/block/md0/md/sync_speed # 同期速度
cat /sys/block/md0/md/sync_completed # 同期進捗
10. NVMe ドライバとアーキテクチャ
10.1 NVMe の概要
NVMe(Non-Volatile Memory Express)は、SSD 等のフラッシュストレージ向けに設計された高性能ストレージプロトコルである。PCIe バスに直接接続し、AHCI/SCSI の制約を排除することで、低レイテンシ・高スループットを実現する。
NVMe アーキテクチャ:
┌────────────────────────────────────────────────┐
│ NVMe コントローラ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ Admin Queue Pair │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Admin SQ │ │ Admin CQ │ │ │
│ │ └─────────────┘ └─────────────┘ │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ I/O Queue Pairs │ │
│ │ ┌─────────┐┌─────────┐ CPU 0 用 │ │
│ │ │ I/O SQ 1││ I/O CQ 1│ │ │
│ │ └─────────┘└─────────┘ │ │
│ │ ┌─────────┐┌─────────┐ CPU 1 用 │ │
│ │ │ I/O SQ 2││ I/O CQ 2│ │ │
│ │ └─────────┘└─────────┘ │ │
│ │ ┌─────────┐┌─────────┐ CPU 2 用 │ │
│ │ │ I/O SQ 3││ I/O CQ 3│ │ │
│ │ └─────────┘└─────────┘ │ │
│ │ ... │ │
│ │ (最大 64K キューペア) │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ NVMe ネームスペース │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │ NS 1 │ │ NS 2 │ │ │
│ │ │ (nvme0n1)│ │ (nvme0n2)│ │ │
│ │ └──────────┘ └──────────┘ │ │
│ └──────────────────────────────────────────┘ │
└────────────────────────────────────────────────┘
10.2 Linux NVMe ドライバの構造
Linux NVMe ドライバスタック:
┌─────────────────────────────────────────┐
│ ブロックレイヤー (blk-mq) │
├─────────────────────────────────────────┤
│ NVMe コアモジュール │
│ (drivers/nvme/host/core.c) │
├──────────┬──────────┬───────────────────┤
│ NVMe PCIe│ NVMe FC │ NVMe TCP/RDMA │
│(pci.c) │(fc.c) │(tcp.c / rdma.c) │
│ │ │(NVMe-oF) │
├──────────┴──────────┴───────────────────┤
│ PCIe / FC / TCP / RDMA │
└─────────────────────────────────────────┘
10.3 NVMe デバイスの管理
# NVMe デバイスの一覧
nvme list
# 出力例:
# Node SN Model Namespace Usage Format FW Rev
# /dev/nvme0n1 S5GXNG0R800001 Samsung SSD 980 PRO 1TB 1 500.11 GB / 1000.20 GB 512 B + 0 B 5B2QGXA7
# コントローラ情報
nvme id-ctrl /dev/nvme0
# ネームスペース情報
nvme id-ns /dev/nvme0n1
# SMART 情報
nvme smart-log /dev/nvme0
# 出力例:
# Smart Log for NVME device:nvme0 namespace-id:ffffffff
# critical_warning : 0
# temperature : 35 C
# available_spare : 100%
# available_spare_threshold : 10%
# percentage_used : 1%
# endurance group critical warning summary: 0
# data_units_read : 12,345,678
# data_units_written : 23,456,789
# host_read_commands : 123,456,789
# host_write_commands : 234,567,890
# controller_busy_time : 1234
# power_cycles : 56
# power_on_hours : 2345
# unsafe_shutdowns : 3
# media_errors : 0
# num_err_log_entries : 0
# エラーログ
nvme error-log /dev/nvme0
# ファームウェアログ
nvme fw-log /dev/nvme0
# フィーチャーの取得
nvme get-feature /dev/nvme0 -f 0x01 # Arbitration
nvme get-feature /dev/nvme0 -f 0x02 # Power Management
nvme get-feature /dev/nvme0 -f 0x07 # Number of Queues
nvme get-feature /dev/nvme0 -f 0x0a # Timestamp
# フィーチャーの設定
nvme set-feature /dev/nvme0 -f 0x02 -v 0 # 最大パフォーマンスモード
# フォーマット
nvme format /dev/nvme0n1 -s 1 # セキュア消去レベル 1
# サニタイズ
nvme sanitize /dev/nvme0 -a 2 # ブロック消去
# ネームスペースの管理
nvme create-ns /dev/nvme0 --nsze=1000000 --ncap=1000000 \
--flbas=0 --dps=0
# I/O キュー数の確認
nvme get-feature /dev/nvme0 -f 7
# キュー情報
cat /sys/class/nvme/nvme0/queue_count
# NVMe パスの確認(マルチパス)
nvme list-subsys /dev/nvme0n1
# NVMe-oF(NVMe over Fabrics)の接続
nvme discover -t tcp -a 192.168.1.100 -s 4420
nvme connect -t tcp -n nqn.2024-01.com.example:subsys0 \
-a 192.168.1.100 -s 4420
# NVMe-oF 接続の切断
nvme disconnect -n nqn.2024-01.com.example:subsys0
10.4 NVMe カーネルパラメータ
# NVMe モジュールパラメータ
# I/O キュー数の制限
modprobe nvme io_queue_depth=1024
# ポーリングキュー数
modprobe nvme poll_queues=4
# マルチパスモード
modprobe nvme_core multipath=Y
# 確認
cat /sys/module/nvme_core/parameters/multipath
cat /sys/module/nvme/parameters/io_queue_depth
cat /sys/module/nvme/parameters/poll_queues
# sysfs パラメータ
# コントローラ情報
cat /sys/class/nvme/nvme0/model
cat /sys/class/nvme/nvme0/serial
cat /sys/class/nvme/nvme0/firmware_rev
cat /sys/class/nvme/nvme0/transport
cat /sys/class/nvme/nvme0/state
cat /sys/class/nvme/nvme0/queue_count
cat /sys/class/nvme/nvme0/sqsize
# ネームスペース情報
cat /sys/block/nvme0n1/queue/hw_sector_size
cat /sys/block/nvme0n1/queue/max_hw_sectors_kb
cat /sys/block/nvme0n1/queue/nr_requests
10.5 NVMe のパフォーマンス最適化
# NVMe のパフォーマンスチューニング
# 1. I/O スケジューラを none に設定
echo "none" > /sys/block/nvme0n1/queue/scheduler
# 2. 先読みの調整
echo 256 > /sys/block/nvme0n1/queue/read_ahead_kb
# 3. rq_affinity の設定
echo 2 > /sys/block/nvme0n1/queue/rq_affinity
# 4. I/O ポーリングの有効化(低レイテンシ)
echo 1 > /sys/block/nvme0n1/queue/io_poll
# 5. nomerges(NVMe では通常不要)
echo 0 > /sys/block/nvme0n1/queue/nomerges
# 6. IRQ アフィニティの最適化
# 各 NVMe キューの IRQ を対応する CPU に固定
# /proc/interrupts で NVMe IRQ を確認
cat /proc/interrupts | grep nvme
# IRQ アフィニティの設定
echo 1 > /proc/irq/XX/smp_affinity # CPU 0
echo 2 > /proc/irq/YY/smp_affinity # CPU 1
# 7. NUMA ローカルアクセスの確認
cat /sys/block/nvme0n1/device/numa_node
# fio による NVMe ベンチマーク
# ランダム読み込み
fio --name=nvme-randread --ioengine=io_uring --iodepth=128 \
--rw=randread --bs=4k --direct=1 --numjobs=4 \
--filename=/dev/nvme0n1 --time_based --runtime=60 \
--group_reporting
# ランダム書き込み
fio --name=nvme-randwrite --ioengine=io_uring --iodepth=128 \
--rw=randwrite --bs=4k --direct=1 --numjobs=4 \
--filename=/dev/nvme0n1 --time_based --runtime=60 \
--group_reporting
# シーケンシャル読み込み
fio --name=nvme-seqread --ioengine=io_uring --iodepth=32 \
--rw=read --bs=128k --direct=1 --numjobs=1 \
--filename=/dev/nvme0n1 --time_based --runtime=60
# 混合ワークロード
fio --name=nvme-mixed --ioengine=io_uring --iodepth=64 \
--rw=randrw --rwmixread=70 --bs=4k --direct=1 \
--numjobs=4 --filename=/dev/nvme0n1 \
--time_based --runtime=60 --group_reporting
11. SCSI サブシステム
11.1 SCSI サブシステムの概要
SCSI(Small Computer System Interface)サブシステムは、Linux カーネルにおける汎用的なストレージ I/O フレームワークである。SCSI/SAS/SATA/USB ストレージデバイスをサポートする。
SCSI サブシステムのアーキテクチャ:
┌─────────────────────────────────────────────┐
│ ブロックレイヤー │
│ (blk-mq) │
├─────────────────────────────────────────────┤
│ SCSI Upper Level Driver │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ sd │ │ sr │ │ st │ │ sg │ │
│ │(disk)│ │(cdrom│ │(tape)│ │(generic│ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
├─────────────────────────────────────────────┤
│ SCSI Middle Layer │
│ (scsi_lib.c, scsi.c, scsi_error.c) │
│ - コマンド管理 │
│ - エラー回復 │
│ - キュー管理 │
│ - デバイスモデル │
├─────────────────────────────────────────────┤
│ SCSI Lower Level Driver │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ ahci │ │ mpt3 │ │ hpsa │ │megara│ │
│ │(SATA)│ │sas │ │(HP) │ │id_sas│ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
├─────────────────────────────────────────────┤
│ ハードウェア (HBA) │
└─────────────────────────────────────────────┘
11.2 SCSI デバイスの管理
# SCSI デバイスの一覧
lsscsi
# 出力例:
# [0:0:0:0] disk ATA Samsung SSD 870 2B6Q /dev/sda
# [1:0:0:0] disk ATA WDC WD40EFAX-68J 0A81 /dev/sdb
# [2:0:0:0] cd/dvd HL-DT-ST BD-RE BU40N 1.03 /dev/sr0
# 詳細表示
lsscsi -v
lsscsi -l # 長いフォーマット
lsscsi -g # sg デバイス表示
lsscsi -d # デバイスノード表示
# SCSI ホストの情報
ls /sys/class/scsi_host/
cat /sys/class/scsi_host/host0/proc_name
cat /sys/class/scsi_host/host0/model_name
# SCSI デバイスの詳細情報
cat /sys/class/scsi_device/0:0:0:0/device/vendor
cat /sys/class/scsi_device/0:0:0:0/device/model
cat /sys/class/scsi_device/0:0:0:0/device/rev
cat /sys/class/scsi_device/0:0:0:0/device/type
# SCSI デバイスのスキャン
echo "- - -" > /sys/class/scsi_host/host0/scan
# 特定の LUN をスキャン
echo "0 0 1" > /sys/class/scsi_host/host0/scan
# SCSI デバイスの削除
echo 1 > /sys/class/scsi_device/0:0:0:0/device/delete
# SCSI コマンドのタイムアウト設定
echo 60 > /sys/class/scsi_device/0:0:0:0/device/timeout
# キュー深度の設定
echo 64 > /sys/class/scsi_device/0:0:0:0/device/queue_depth
# SCSI ログの有効化
echo "scsi log all" > /proc/scsi/scsi
# または
echo 7 > /proc/sys/dev/scsi/logging_level
# sg_inq コマンドでの詳細情報
sg_inq /dev/sda
# sg_modes でモードページ表示
sg_modes /dev/sda
# sg_logs でログページ表示
sg_logs /dev/sda
# SCSI コマンドの発行
sg_raw /dev/sda 12 00 00 00 24 00 # INQUIRY コマンド
11.3 SCSI エラーハンドリング
# SCSI エラーハンドリング階層:
# Level 1: コマンドリトライ
# - ドライバがコマンドをリトライ
# - デフォルト: 最大5回
# Level 2: コマンドアボート (eh_abort_handler)
# - 個別コマンドのアボート
# Level 3: デバイスリセット (eh_device_reset_handler)
# - ターゲットデバイスのリセット
# Level 4: バスリセット (eh_bus_reset_handler)
# - SCSI バス全体のリセット
# Level 5: ホストリセット (eh_host_reset_handler)
# - HBA コントローラのリセット
# エラーハンドリングの設定
cat /sys/class/scsi_device/0:0:0:0/device/eh_timeout
echo 10 > /sys/class/scsi_device/0:0:0:0/device/eh_timeout
# SCSI デバイスの状態
cat /sys/class/scsi_device/0:0:0:0/device/state
# running, cancel, deleted, quiesce, offline, transport-offline, blocked, created
# dmesg での SCSI エラー確認
dmesg | grep -i "scsi\|sd \|I/O error"
11.4 SCSI マルチパス
# multipath の設定
# multipath-tools のインストール
# apt install multipath-tools # Debian/Ubuntu
# yum install device-mapper-multipath # RHEL/CentOS
# multipath の有効化
systemctl enable multipathd
systemctl start multipathd
# /etc/multipath.conf の設定例
cat << 'EOF' > /etc/multipath.conf
defaults {
polling_interval 10
path_selector "round-robin 0"
path_grouping_policy multibus
uid_attribute ID_SERIAL
rr_min_io_rq 1
failback immediate
no_path_retry fail
user_friendly_names yes
}
blacklist {
devnode "^(ram|raw|loop|fd|md|dm-|sr|scd|st)[0-9]*"
devnode "^sd[a-b]$"
devnode "^nvme"
}
devices {
device {
vendor "NETAPP"
product "LUN.*"
path_grouping_policy group_by_prio
path_selector "round-robin 0"
prio alua
failback immediate
rr_weight uniform
no_path_retry queue
}
}
multipaths {
multipath {
wwid 3600508b400105e210000600001760000
alias data_lun0
}
}
EOF
# multipath デバイスの確認
multipath -ll
# 出力例:
# data_lun0 (3600508b400105e210000600001760000) dm-2 vendor,product
# size=100G features='0' hwhandler='0' wp=rw
# |-+- policy='round-robin 0' prio=1 status=active
# | `- 1:0:0:0 sda 8:0 active ready running
# `-+- policy='round-robin 0' prio=1 status=enabled
# `- 2:0:0:0 sdb 8:16 active ready running
# パスの追加・削除
multipath -r # 再設定
# パスの手動切り替え
dmsetup message data_lun0 0 "switch_group 1"
# 統計情報
multipathd show paths
multipathd show maps
12. ブロック I/O cgroups (blkio コントローラ)
12.1 blkio cgroup の概要
ブロック I/O cgroup は、コンテナやプロセスグループごとに I/O リソースの制御と監視を行う機構である。
blkio cgroup の階層:
cgroup v2 の場合:
/sys/fs/cgroup/
├── io.max # I/O 帯域幅/IOPS 制限
├── io.weight # I/O 重み
├── io.stat # I/O 統計情報
├── io.cost.qos # I/O コスト QoS
├── io.cost.model # I/O コストモデル
├── io.pressure # I/O プレッシャー
├── io.latency # I/O レイテンシ制御
└── child_groups/
├── io.max
├── io.weight
└── ...
cgroup v1 の場合:
/sys/fs/cgroup/blkio/
├── blkio.throttle.read_bps_device # 読み込みバイト/秒制限
├── blkio.throttle.write_bps_device # 書き込みバイト/秒制限
├── blkio.throttle.read_iops_device # 読み込みIOPS制限
├── blkio.throttle.write_iops_device # 書き込みIOPS制限
├── blkio.weight # 重み
├── blkio.weight_device # デバイス別重み
└── blkio.throttle.io_service_bytes # 統計情報
12.2 cgroup v2 での I/O 制御
# cgroup v2 が有効か確認
mount | grep cgroup2
cat /sys/fs/cgroup/cgroup.controllers
# I/O コントローラの有効化
echo "+io" > /sys/fs/cgroup/cgroup.subtree_control
# テスト用 cgroup の作成
mkdir /sys/fs/cgroup/test_group
# I/O 帯域幅制限(io.max)
# 形式: MAJ:MIN rbps=BYTES wbps=BYTES riops=NUM wiops=NUM
echo "8:0 rbps=104857600 wbps=52428800" > /sys/fs/cgroup/test_group/io.max
# /dev/sda (8:0) に読み込み100MB/s、書き込み50MB/sの制限
# IOPS 制限
echo "8:0 riops=1000 wiops=500" > /sys/fs/cgroup/test_group/io.max
# 複合制限(帯域幅とIOPSの両方)
echo "8:0 rbps=104857600 wbps=52428800 riops=10000 wiops=5000" > \
/sys/fs/cgroup/test_group/io.max
# I/O 重み(io.weight)
# 1-10000、デフォルト100
echo "default 100" > /sys/fs/cgroup/test_group/io.weight
echo "8:0 200" > /sys/fs/cgroup/test_group/io.weight
# I/O レイテンシ制御(io.latency)
echo "8:0 target=5000" > /sys/fs/cgroup/test_group/io.latency
# ターゲットレイテンシ 5ms
# I/O コスト QoS(io.cost.qos)
echo "8:0 enable=1 ctrl=auto" > /sys/fs/cgroup/test_group/io.cost.qos
# プロセスを cgroup に追加
echo $PID > /sys/fs/cgroup/test_group/cgroup.procs
# I/O 統計情報の確認(io.stat)
cat /sys/fs/cgroup/test_group/io.stat
# 出力例:
# 8:0 rbytes=1234567890 wbytes=987654321 rios=12345 wios=6789 dbytes=0 dios=0
# I/O プレッシャーの確認
cat /sys/fs/cgroup/test_group/io.pressure
# 出力例:
# some avg10=0.00 avg60=0.00 avg300=0.00 total=0
# full avg10=0.00 avg60=0.00 avg300=0.00 total=0
# 制限の解除
echo "8:0 rbps=max wbps=max riops=max wiops=max" > \
/sys/fs/cgroup/test_group/io.max
12.3 cgroup v1 での I/O 制御
# cgroup v1 の設定
# 読み込み帯域幅制限
echo "8:0 104857600" > /sys/fs/cgroup/blkio/test_group/blkio.throttle.read_bps_device
# 書き込み帯域幅制限
echo "8:0 52428800" > /sys/fs/cgroup/blkio/test_group/blkio.throttle.write_bps_device
# 読み込み IOPS 制限
echo "8:0 1000" > /sys/fs/cgroup/blkio/test_group/blkio.throttle.read_iops_device
# 書き込み IOPS 制限
echo "8:0 500" > /sys/fs/cgroup/blkio/test_group/blkio.throttle.write_iops_device
# 重みの設定(BFQ スケジューラ使用時)
echo 500 > /sys/fs/cgroup/blkio/test_group/blkio.bfq.weight
# 統計情報
cat /sys/fs/cgroup/blkio/test_group/blkio.throttle.io_service_bytes
cat /sys/fs/cgroup/blkio/test_group/blkio.throttle.io_serviced
12.4 systemd での I/O 制御
# systemd サービスでの I/O 制限
# /etc/systemd/system/myservice.service
# [Service]
# IOWeight=200
# IODeviceWeight=/dev/sda 100
# IOReadBandwidthMax=/dev/sda 100M
# IOWriteBandwidthMax=/dev/sda 50M
# IOReadIOPSMax=/dev/sda 10000
# IOWriteIOPSMax=/dev/sda 5000
# systemctl での設定
systemctl set-property myservice.service IOReadBandwidthMax="/dev/sda 100M"
systemctl set-property myservice.service IOWriteBandwidthMax="/dev/sda 50M"
# スライスでの設定
systemctl set-property user.slice IOWeight=100
# 一時的な設定(再起動でリセット)
systemctl set-property --runtime myservice.service IOReadBandwidthMax="/dev/sda 100M"
# systemd-cgtop で I/O 使用量の確認
systemd-cgtop -d 1
# systemd-run でテスト
systemd-run --scope -p IOReadBandwidthMax="/dev/sda 50M" \
dd if=/dev/sda of=/dev/null bs=1M count=1024
# 確認
systemctl show myservice.service | grep -i io
12.5 Docker/コンテナでの I/O 制御
# Docker での I/O 制限
# 帯域幅制限
docker run -it --device-read-bps /dev/sda:100mb \
--device-write-bps /dev/sda:50mb \
ubuntu
# IOPS 制限
docker run -it --device-read-iops /dev/sda:1000 \
--device-write-iops /dev/sda:500 \
ubuntu
# 重み設定
docker run -it --blkio-weight 300 ubuntu
# docker-compose.yml での設定
# services:
# myapp:
# blkio_config:
# weight: 300
# device_read_bps:
# - path: /dev/sda
# rate: '100mb'
# device_write_bps:
# - path: /dev/sda
# rate: '50mb'
# device_read_iops:
# - path: /dev/sda
# rate: 1000
# device_write_iops:
# - path: /dev/sda
# rate: 500
# Kubernetes での I/O 制限(PodSpec アノテーション経由、実装依存)
# 注: Kubernetes 標準にはブロック I/O の直接制御はない
# 多くの場合、RuntimeClass やカスタムリソースで対応
13. ダイレクト I/O とバッファード I/O
13.1 バッファード I/O
バッファード I/O のフロー:
書き込み:
アプリ → ページキャッシュ → (後でまとめて) → デバイス
読み込み:
アプリ ← ページキャッシュ ← (キャッシュミス時) ← デバイス
┌──────────────────────────────────────────────────┐
│ ユーザー空間 │
│ ┌─────────────────────┐ │
│ │ アプリケーション │ │
│ │ buf[4096] │ │
│ └──────────┬──────────┘ │
│ │ write(fd, buf, 4096) │
├─────────────┼────────────────────────────────────┤
│ ▼ │
│ ┌──────────────────────┐ │
│ │ VFS / ファイルシステム│ │
│ └──────────┬──────────┘ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ ページキャッシュ │ ← write() はここで完了 │
│ │ (メモリ上のコピー) │ │
│ └──────────┬──────────┘ │
│ ▼ (非同期、writeback) │
│ ┌──────────────────────┐ │
│ │ ブロックレイヤー │ │
│ └──────────┬──────────┘ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ デバイスドライバ │ │
│ └──────────────────────┘ │
│ カーネル空間 │
└──────────────────────────────────────────────────┘
13.2 ダイレクト I/O
ダイレクト I/O のフロー:
書き込み:
アプリ → (ページキャッシュをバイパス) → デバイス
読み込み:
アプリ ← (ページキャッシュをバイパス) ← デバイス
┌──────────────────────────────────────────────────┐
│ ユーザー空間 │
│ ┌─────────────────────┐ │
│ │ アプリケーション │ │
│ │ buf[4096] │ ← アラインメント必須 │
│ └──────────┬──────────┘ │
│ │ write(fd, buf, 4096) │
│ │ (O_DIRECT フラグ) │
├─────────────┼────────────────────────────────────┤
│ ▼ │
│ ┌──────────────────────┐ │
│ │ VFS / ファイルシステム│ │
│ └──────────┬──────────┘ │
│ │ (ページキャッシュをバイパス) │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ ブロックレイヤー │ │
│ └──────────┬──────────┘ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ デバイスドライバ │ │
│ └──────────┬──────────┘ │
│ ▼ (完了を待つ) │
│ write() はデバイスへの書き込み完了後に返却 │
│ カーネル空間 │
└──────────────────────────────────────────────────┘
13.3 実装例
/* ダイレクト I/O の C 言語実装例 */
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#define BLOCK_SIZE 4096
#define FILE_SIZE (1024 * 1024 * 100) /* 100MB */
int main(void)
{
int fd;
void *buf;
ssize_t ret;
/* O_DIRECT フラグでファイルを開く */
fd = open("/data/testfile", O_RDWR | O_CREAT | O_DIRECT, 0644);
if (fd < 0) {
perror("open");
return 1;
}
/* アラインメントされたバッファを確保 */
/* O_DIRECT では通常、512バイトまたは論理ブロックサイズにアラインが必要 */
ret = posix_memalign(&buf, BLOCK_SIZE, BLOCK_SIZE);
if (ret != 0) {
fprintf(stderr, "posix_memalign failed: %s\n", strerror(ret));
close(fd);
return 1;
}
/* バッファにデータを書き込み */
memset(buf, 'A', BLOCK_SIZE);
/* ダイレクト I/O で書き込み */
ret = pwrite(fd, buf, BLOCK_SIZE, 0);
if (ret != BLOCK_SIZE) {
perror("pwrite");
free(buf);
close(fd);
return 1;
}
printf("Direct I/O write: %zd bytes written\n", ret);
/* ダイレクト I/O で読み込み */
memset(buf, 0, BLOCK_SIZE);
ret = pread(fd, buf, BLOCK_SIZE, 0);
if (ret != BLOCK_SIZE) {
perror("pread");
free(buf);
close(fd);
return 1;
}
printf("Direct I/O read: %zd bytes read\n", ret);
printf("First byte: %c\n", ((char *)buf)[0]);
free(buf);
close(fd);
return 0;
}
/* コンパイルと実行 */
// gcc -o dio_test dio_test.c
// ./dio_test
13.4 Writeback の制御
# Writeback パラメータ
# dirty ページの閾値(比率)
cat /proc/sys/vm/dirty_ratio # デフォルト: 20%
cat /proc/sys/vm/dirty_background_ratio # デフォルト: 10%
# dirty ページの閾値(バイト)
cat /proc/sys/vm/dirty_bytes
cat /proc/sys/vm/dirty_background_bytes
# writeback の実行間隔
cat /proc/sys/vm/dirty_writeback_centisecs # デフォルト: 500 (5秒)
# dirty ページの最大保持時間
cat /proc/sys/vm/dirty_expire_centisecs # デフォルト: 3000 (30秒)
# チューニング例: データベースサーバ
echo 5 > /proc/sys/vm/dirty_ratio
echo 1 > /proc/sys/vm/dirty_background_ratio
echo 100 > /proc/sys/vm/dirty_writeback_centisecs
echo 500 > /proc/sys/vm/dirty_expire_centisecs
# チューニング例: ストリーミング書き込み
echo 40 > /proc/sys/vm/dirty_ratio
echo 10 > /proc/sys/vm/dirty_background_ratio
# /etc/sysctl.conf での永続化
# vm.dirty_ratio = 5
# vm.dirty_background_ratio = 1
# vm.dirty_writeback_centisecs = 100
# vm.dirty_expire_centisecs = 500
# 現在の dirty ページ状態確認
cat /proc/vmstat | grep dirty
cat /proc/meminfo | grep Dirty
# 強制的な同期
sync
echo 3 > /proc/sys/vm/drop_caches # ページキャッシュのクリア
# デバイス別の writeback 設定
cat /sys/class/bdi/*/read_ahead_kb
cat /sys/class/bdi/*/min_ratio
cat /sys/class/bdi/*/max_ratio
echo 50 > /sys/class/bdi/8:0/max_ratio # sda の最大 dirty 比率を 50% に
14. I/O アカウンティングと統計情報
14.1 /proc/diskstats
# /proc/diskstats のフォーマット
cat /proc/diskstats
# フィールド:
# 1: メジャー番号
# 2: マイナー番号
# 3: デバイス名
# 4: 読み込み完了数
# 5: 読み込みマージ数
# 6: 読み込みセクタ数
# 7: 読み込みに費やした時間(ms)
# 8: 書き込み完了数
# 9: 書き込みマージ数
# 10: 書き込みセクタ数
# 11: 書き込みに費やした時間(ms)
# 12: 処理中の I/O 数
# 13: I/O に費やした時間(ms)
# 14: 重み付き I/O 時間(ms)
# 15: discard 完了数 (4.18+)
# 16: discard マージ数 (4.18+)
# 17: discard セクタ数 (4.18+)
# 18: discard に費やした時間(ms) (4.18+)
# 19: flush 完了数 (5.5+)
# 20: flush に費やした時間(ms) (5.5+)
# 出力例:
# 8 0 sda 12345 6789 1234567 12345 67890 12345 6789012 34567 0 12345 46912 0 0 0 0 123 456
# スクリプトで解析
awk '{
if ($3 ~ /^sd[a-z]$/) {
printf "%-8s reads:%-10s writes:%-10s read_mb:%-10.2f write_mb:%-10.2f\n",
$3, $4, $8, $6*512/1024/1024, $10*512/1024/1024
}
}' /proc/diskstats
14.2 /sys/block/*/stat
# デバイス別統計
cat /sys/block/sda/stat
# フィールドは /proc/diskstats の4番目以降と同じ
# パーティション別統計
cat /sys/block/sda/sda1/stat
# 差分計算によるスループット測定スクリプト
#!/bin/bash
DEVICE="sda"
INTERVAL=1
read_stat() {
cat /sys/block/$DEVICE/stat
}
OLD=$(read_stat)
while true; do
sleep $INTERVAL
NEW=$(read_stat)
OLD_R_SECTORS=$(echo $OLD | awk '{print $3}')
NEW_R_SECTORS=$(echo $NEW | awk '{print $3}')
OLD_W_SECTORS=$(echo $OLD | awk '{print $7}')
NEW_W_SECTORS=$(echo $NEW | awk '{print $7}')
R_MB=$(echo "scale=2; ($NEW_R_SECTORS - $OLD_R_SECTORS) * 512 / 1048576" | bc)
W_MB=$(echo "scale=2; ($NEW_W_SECTORS - $OLD_W_SECTORS) * 512 / 1048576" | bc)
echo "$(date '+%H:%M:%S') Read: ${R_MB} MB/s Write: ${W_MB} MB/s"
OLD=$NEW
done
14.3 I/O プレッシャー (PSI)
# I/O プレッシャーの確認
cat /proc/pressure/io
# 出力例:
# some avg10=0.50 avg60=0.30 avg300=0.10 total=1234567
# full avg10=0.20 avg60=0.10 avg300=0.05 total=567890
# some: 少なくとも1つのタスクが I/O 待ち
# full: 全てのタスクが I/O 待ち
# avg10/avg60/avg300: 10秒/60秒/300秒の移動平均(%)
# cgroup ごとの I/O プレッシャー
cat /sys/fs/cgroup/mygroup/io.pressure
# PSI による監視(プログラマティック)
# PSI ファイルディスクリプタをポーリングして閾値超過を検出可能
14.4 iotop と pidstat
# iotop: プロセスごとの I/O 使用量をリアルタイム表示
iotop
iotop -oP # アクティブなプロセスのみ表示
iotop -b -n 5 # バッチモード、5回分
# pidstat: プロセスごとの I/O 統計
pidstat -d 1
# 出力例:
# Time UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
# 12:00:01 0 1234 100.00 200.00 0.00 0 mysqld
# 特定プロセスの監視
pidstat -d -p 1234 1
# /proc/[pid]/io によるプロセス I/O 統計
cat /proc/1234/io
# 出力例:
# rchar: 1234567890 # read() で読み込んだバイト数
# wchar: 987654321 # write() で書き込んだバイト数
# syscr: 12345 # read syscall 回数
# syscw: 6789 # write syscall 回数
# read_bytes: 100000000 # 実際にデバイスから読み込んだバイト数
# write_bytes: 50000000 # 実際にデバイスに書き込んだバイト数
# cancelled_write_bytes: 0 # キャンセルされた書き込みバイト数
15. デッドラインとレイテンシ管理
15.1 I/O レイテンシの構成要素
I/O レイテンシの分解:
アプリケーション
│
├── ソフトウェアキュー滞在時間
│
├── I/O スケジューラ処理時間
│
├── ハードウェアキュー滞在時間
│
├── デバイスドライバ処理時間
│
├── デバイス内処理時間
│ ├── コマンドキュー待ち
│ ├── シーク時間(HDD)
│ ├── 回転待ち(HDD)
│ └── データ転送時間
│
└── 完了処理時間(割り込み、コールバック)
合計 = ソフトウェアオーバーヘッド + デバイスレイテンシ
15.2 レイテンシ制御メカニズム
# 1. I/O スケジューラによるデッドライン制御
# mq-deadline
echo "mq-deadline" > /sys/block/sda/queue/scheduler
echo 300 > /sys/block/sda/queue/iosched/read_expire # 読み込み 300ms
echo 3000 > /sys/block/sda/queue/iosched/write_expire # 書き込み 3000ms
# kyber - レイテンシターゲット
echo "kyber" > /sys/block/nvme0n1/queue/scheduler
echo 2000000 > /sys/block/nvme0n1/queue/iosched/read_lat_nsec # 2ms
echo 10000000 > /sys/block/nvme0n1/queue/iosched/write_lat_nsec # 10ms
# 2. cgroup v2 の io.latency
echo "8:0 target=5000" > /sys/fs/cgroup/mygroup/io.latency
# 3. wbt (Writeback Throttling)
cat /sys/block/sda/queue/wbt_lat_usec
echo 75000 > /sys/block/sda/queue/wbt_lat_usec # 75ms
# 4. リクエストタイムアウト
echo 30 > /sys/block/sda/device/timeout # SCSI デバイス
# 5. I/O 優先度(ionice)
ionice -c 1 -n 0 -p 1234 # リアルタイムクラス、優先度0
ionice -c 2 -n 4 -p 1234 # ベストエフォートクラス、優先度4
ionice -c 3 -p 1234 # アイドルクラス
# ionice クラス:
# 1: リアルタイム (0-7、0が最高)
# 2: ベストエフォート (0-7、0が最高)
# 3: アイドル(他にI/Oがない時のみ実行)
15.3 レイテンシの測定
# ioping: I/O レイテンシの測定
ioping -c 10 /dev/sda
# 出力例:
# 4 KiB <<< /dev/sda (block device 232.9 GiB): request=1 time=523.4 us
# 4 KiB <<< /dev/sda (block device 232.9 GiB): request=2 time=145.3 us
# ...
# min/avg/max/mdev = 123.4 us / 234.5 us / 523.4 us / 89.1 us
# シーケンシャルレイテンシ
ioping -c 10 -s 1M -S 256M /dev/sda
# fio でのレイテンシ測定
fio --name=latency --ioengine=libaio --iodepth=1 \
--rw=randread --bs=4k --direct=1 --size=1G \
--numjobs=1 --time_based --runtime=30 \
--filename=/dev/sda --lat_percentiles=1
# blktrace でのレイテンシ分析
blktrace -d /dev/sda -o - | blkparse -i - -d latency.bin
btt -i latency.bin -l latency.d2c
16. ライトバリアとデータ整合性
16.1 ライトバリアの仕組み
ライトバリアの目的:
電源障害時のデータ整合性を保証する
ライトバリアなし:
write A → write B → write C
(デバイスのキャッシュにより順序が入れ替わる可能性)
ライトバリアあり:
write A → FLUSH → write B (FUA) → FLUSH → write C
(順序が保証される)
REQ_PREFLUSH: リクエスト前にキャッシュをフラッシュ
REQ_FUA: Force Unit Access(キャッシュをバイパスして直接書き込み)
16.2 データ整合性の制御
# ライトキャッシュの確認
hdparm -W /dev/sda
# 出力例: write-caching = 1 (on)
# ライトキャッシュの無効化
hdparm -W 0 /dev/sda
# ライトキャッシュの有効化
hdparm -W 1 /dev/sda
# NVMe のライトキャッシュ確認
nvme get-feature /dev/nvme0 -f 0x06 # Volatile Write Cache
# ファイルシステムのバリア設定
# ext4: barrier=1(デフォルト有効)
mount -o barrier=1 /dev/sda1 /mnt
mount -o nobarrier /dev/sda1 /mnt # バリア無効(危険)
# XFS: デフォルトでバリア有効
mount -o nobarrier /dev/sda1 /mnt # バリア無効(危険)
# ライトキャッシュの sysfs 確認
cat /sys/block/sda/queue/write_cache
# write back: ライトキャッシュ有効
# write through: ライトキャッシュ無効
# SCSI デバイスの Write Cache Enable
sdparm --get WCE /dev/sda
sdparm --set WCE=0 /dev/sda # 無効化
16.3 データ整合性拡張 (DIF/DIX)
# Data Integrity Field (DIF) / Data Integrity Extensions (DIX)
# DIF サポートの確認
sg_readcap -l /dev/sda | grep -i protect
# 整合性メタデータの確認
cat /sys/block/sda/integrity/format
cat /sys/block/sda/integrity/read_verify
cat /sys/block/sda/integrity/write_generate
cat /sys/block/sda/integrity/device_is_integrity_capable
# DIF/DIX 対応ファイルシステムのマウント
# XFS with integrity
mkfs.xfs -m integrity=1 /dev/sda1
# dm-integrity: ブロックレベルの整合性チェック
# デバイスの作成
integritysetup format /dev/sdb1
integritysetup open /dev/sdb1 integ_dev
# dm-integrity のステータス
integritysetup status integ_dev
# LUKS2 + integrity (認証暗号化)
cryptsetup luksFormat --type luks2 \
--integrity hmac-sha256 /dev/sdb1
17. ツール
17.1 iostat
# iostat: I/O 統計情報の表示
# 基本的な使用方法
iostat
iostat 1 10 # 1秒間隔、10回
# 拡張統計
iostat -x 1
# 出力例:
# Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz aqu-sz %util
# sda 50.00 6400.0 10.00 16.7 2.50 128.00 100.0 12800 20.00 16.7 5.00 128.00 0.0 0.0 0.0 0.0 0.00 0.00 0.63 12.50
# フィールドの意味:
# r/s: 1秒あたりの読み込みリクエスト数
# rkB/s: 1秒あたりの読み込みKB
# rrqm/s: 1秒あたりの読み込みマージ数
# %rrqm: マージされた読み込みの割合
# r_await: 読み込みの平均待ち時間(ms)
# rareq-sz: 平均読み込みリクエストサイズ(KB)
# w/s: 1秒あたりの書き込みリクエスト数
# wkB/s: 1秒あたりの書き込みKB
# w_await: 書き込みの平均待ち時間(ms)
# aqu-sz: 平均キュー長
# %util: デバイス使用率
# 特定デバイスのみ
iostat -x sda 1
# パーティション別
iostat -p sda 1
# MB/s で表示
iostat -m -x 1
# JSON 形式で出力
iostat -x -o JSON 1 1
# デバイスマッパーデバイスの表示
iostat -x -N 1
17.2 blktrace / blkparse
# blktrace: ブロック I/O のトレーシング
# トレースの開始(30秒間)
blktrace -d /dev/sda -w 30 -o trace
# トレースの解析
blkparse -i trace -o trace_output.txt
# ワンライナー(リアルタイム表示)
blktrace -d /dev/sda -o - | blkparse -i -
# 出力例:
# 8,0 1 1 0.000000000 1234 A WS 12345678 + 8 <- (8,1) 12345670
# 8,0 1 2 0.000001234 1234 Q WS 12345678 + 8 [myapp]
# 8,0 1 3 0.000002345 1234 G WS 12345678 + 8 [myapp]
# 8,0 1 4 0.000003456 1234 P N [myapp]
# 8,0 1 5 0.000004567 1234 I WS 12345678 + 8 [myapp]
# 8,0 1 6 0.000005678 1234 D WS 12345678 + 8 [myapp]
# 8,0 1 7 0.000100000 1234 C WS 12345678 + 8 [0]
# アクション文字の意味:
# A: remap (デバイスマッパー等)
# Q: queued (キューイング)
# G: get request (リクエスト取得)
# P: plug (プラグ)
# I: insert (挿入)
# D: dispatch (ディスパッチ)
# C: complete (完了)
# M: merge (マージ)
# S: sleep (スリープ)
# U: unplug (アンプラグ)
# btt: blktrace の統計分析
btt -i trace.blktrace.0
# D2C レイテンシ(ディスパッチから完了まで)
# Q2C レイテンシ(キューイングから完了まで)
# 特定のプロセスのみトレース
blktrace -d /dev/sda -a issue -a complete
# iowatcher: 可視化ツール
iowatcher -t trace -o graph.svg
17.3 fio (Flexible I/O Tester)
# fio: 柔軟な I/O ベンチマークツール
# 基本的なランダム読み込みテスト
fio --name=randread --ioengine=libaio --iodepth=32 \
--rw=randread --bs=4k --direct=1 --size=4G \
--numjobs=4 --time_based --runtime=60 \
--filename=/dev/sda --group_reporting
# 基本的なランダム書き込みテスト
fio --name=randwrite --ioengine=libaio --iodepth=32 \
--rw=randwrite --bs=4k --direct=1 --size=4G \
--numjobs=4 --time_based --runtime=60 \
--filename=/dev/sda --group_reporting
# シーケンシャル読み込み
fio --name=seqread --ioengine=libaio --iodepth=16 \
--rw=read --bs=1M --direct=1 --size=4G \
--numjobs=1 --time_based --runtime=60 \
--filename=/dev/sda
# 混合ワークロード
fio --name=mixed --ioengine=libaio --iodepth=64 \
--rw=randrw --rwmixread=70 --bs=4k --direct=1 \
--size=4G --numjobs=4 --time_based --runtime=60 \
--filename=/dev/sda --group_reporting
# レイテンシ測定
fio --name=latency --ioengine=libaio --iodepth=1 \
--rw=randread --bs=4k --direct=1 --size=1G \
--numjobs=1 --time_based --runtime=60 \
--filename=/dev/sda --percentile_list=50:90:95:99:99.9:99.99
# io_uring エンジン
fio --name=io_uring_test --ioengine=io_uring --iodepth=128 \
--rw=randread --bs=4k --direct=1 --fixedbufs=1 \
--registerfiles=1 --hipri=1 --sqthread_poll=1 \
--size=4G --numjobs=1 --time_based --runtime=60 \
--filename=/dev/nvme0n1
# fio ジョブファイル例
cat << 'EOF' > /tmp/fio_job.ini
[global]
ioengine=libaio
direct=1
time_based
runtime=60
group_reporting
filename=/dev/sda
[seq-read]
rw=read
bs=128k
iodepth=16
numjobs=1
[rand-read]
rw=randread
bs=4k
iodepth=32
numjobs=4
[rand-write]
rw=randwrite
bs=4k
iodepth=32
numjobs=4
[mixed-rw]
rw=randrw
rwmixread=70
bs=4k
iodepth=64
numjobs=4
EOF
fio /tmp/fio_job.ini
# JSON 出力
fio --name=test --ioengine=libaio --iodepth=32 \
--rw=randread --bs=4k --direct=1 --size=1G \
--output-format=json --output=results.json
17.4 hdparm
# hdparm: HDD/SSD パラメータの設定と確認
# デバイス情報の表示
hdparm -I /dev/sda
# 基本的な読み込み速度テスト
hdparm -t /dev/sda
# 出力例:
# /dev/sda:
# Timing buffered disk reads: 440 MB in 3.00 seconds = 146.56 MB/sec
# キャッシュ経由の速度テスト
hdparm -T /dev/sda
# 出力例:
# /dev/sda:
# Timing cached reads: 12345 MB in 2.00 seconds = 6172.50 MB/sec
# ライトキャッシュの確認・設定
hdparm -W /dev/sda # 確認
hdparm -W 1 /dev/sda # 有効化
hdparm -W 0 /dev/sda # 無効化
# 先読みの設定
hdparm -a /dev/sda # 確認
hdparm -a 256 /dev/sda # 256セクタに設定
# DMA の確認
hdparm -d /dev/sda
# アコースティック管理(HDD の騒音)
hdparm -M /dev/sda # 確認
hdparm -M 128 /dev/sda # 静音モード
hdparm -M 254 /dev/sda # パフォーマンスモード
# APM(Advanced Power Management)
hdparm -B /dev/sda # 確認
hdparm -B 254 /dev/sda # 最大パフォーマンス
hdparm -B 127 /dev/sda # バランス
# セキュリティ機能
hdparm --security-freeze /dev/sda # セキュリティフリーズ
# TRIM サポートの確認
hdparm -I /dev/sda | grep -i trim
# スタンバイタイムアウト
hdparm -S 60 /dev/sda # 5分後にスタンバイ(値 × 5秒)
17.5 smartctl
# smartctl: S.M.A.R.T. 情報の表示と管理
# S.M.A.R.T. 情報の表示
smartctl -a /dev/sda
# S.M.A.R.T. ヘルス状態
smartctl -H /dev/sda
# 出力例:
# SMART overall-health self-assessment test result: PASSED
# S.M.A.R.T. 属性の表示
smartctl -A /dev/sda
# 主要な属性:
# ID 属性名 値 最悪 閾値 状態
# 5 Reallocated_Sector_Ct 100 100 10 OK (代替セクタ数)
# 9 Power_On_Hours 90 90 0 OK (稼働時間)
# 12 Power_Cycle_Count 99 99 0 OK (電源投入回数)
#187 Reported_Uncorrectable 100 100 0 OK (修正不能エラー)
#188 Command_Timeout 100 100 0 OK (コマンドタイムアウト)
#197 Current_Pending_Sector 100 100 0 OK (保留中の不良セクタ)
#198 Offline_Uncorrectable 100 100 0 OK (オフライン修正不能)
#199 UDMA_CRC_Error_Count 100 100 0 OK (CRC エラー)
# NVMe の S.M.A.R.T. 情報
smartctl -a /dev/nvme0
# 重要な項目:
# Critical Warning: 0x00
# Temperature: 35 Celsius
# Available Spare: 100%
# Available Spare Threshold: 10%
# Percentage Used: 1%
# Data Units Read: 12,345,678
# Data Units Written: 23,456,789
# Media and Data Integrity Errors: 0
# セルフテストの実行
smartctl -t short /dev/sda # 短縮テスト(2分程度)
smartctl -t long /dev/sda # 拡張テスト(数時間)
smartctl -t conveyance /dev/sda # 搬送テスト
# テスト結果の確認
smartctl -l selftest /dev/sda
# エラーログ
smartctl -l error /dev/sda
# S.M.A.R.T. の有効化
smartctl -s on /dev/sda
# smartd デーモンの設定
# /etc/smartd.conf
# /dev/sda -a -o on -S on -s (S/../.././02|L/../../6/03) -m admin@example.com
# -a: 全属性監視
# -o on: オフラインテスト有効
# -S on: S.M.A.R.T. 有効化
# -s: テストスケジュール (短縮:毎日2時、拡張:毎土曜3時)
# -m: 警告メール送信先
# systemd でのサービス管理
systemctl enable smartd
systemctl start smartd
18. パフォーマンスチューニング実践
18.1 ワークロード別チューニングガイド
18.1.1 データベースサーバ (MySQL/PostgreSQL)
# データベースサーバのブロック I/O チューニング
# --- I/O スケジューラ ---
# SSD の場合
echo "none" > /sys/block/nvme0n1/queue/scheduler
# HDD の場合
echo "mq-deadline" > /sys/block/sda/queue/scheduler
# --- キュー設定 ---
echo 256 > /sys/block/nvme0n1/queue/nr_requests
echo 2 > /sys/block/nvme0n1/queue/rq_affinity
# --- 先読みの無効化(ランダムI/O主体)---
echo 0 > /sys/block/nvme0n1/queue/read_ahead_kb
# --- vm パラメータ ---
echo 5 > /proc/sys/vm/dirty_ratio
echo 1 > /proc/sys/vm/dirty_background_ratio
echo 100 > /proc/sys/vm/dirty_writeback_centisecs
echo 500 > /proc/sys/vm/dirty_expire_centisecs
# --- スワップの最小化 ---
echo 1 > /proc/sys/vm/swappiness
# --- ファイルシステム ---
# ext4 の場合
# mount -o noatime,nodiratime,barrier=1,data=ordered /dev/nvme0n1p1 /data
# XFS の場合
# mount -o noatime,nodiratime,logbufs=8,logbsize=256k /dev/nvme0n1p1 /data
# --- /etc/sysctl.conf ---
cat << 'EOF' >> /etc/sysctl.conf
vm.dirty_ratio = 5
vm.dirty_background_ratio = 1
vm.dirty_writeback_centisecs = 100
vm.dirty_expire_centisecs = 500
vm.swappiness = 1
EOF
# --- 永続化 udev ルール ---
cat << 'EOF' > /etc/udev/rules.d/60-db-ioscheduler.rules
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/scheduler}="none"
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/read_ahead_kb}="0"
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/nr_requests}="256"
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/rq_affinity}="2"
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", \
ATTR{queue/scheduler}="mq-deadline"
EOF
18.1.2 ファイルサーバ / NAS
# ファイルサーバのチューニング
# --- シーケンシャル I/O 主体 ---
echo 4096 > /sys/block/sda/queue/read_ahead_kb
# --- 大きな I/O サイズ ---
echo 1024 > /sys/block/sda/queue/max_sectors_kb
# --- vm パラメータ ---
echo 40 > /proc/sys/vm/dirty_ratio
echo 10 > /proc/sys/vm/dirty_background_ratio
# --- Samba/NFS 設定との連携 ---
# ページキャッシュを活用するためバッファード I/O を使用
18.1.3 仮想化ホスト (KVM/QEMU)
# KVM ホストのチューニング
# --- virtio-blk / virtio-scsi ---
# ゲストに none スケジューラを設定(ホストがスケジューリング)
# QEMU 設定: -drive file=disk.qcow2,if=virtio,cache=none,aio=native
# --- ホスト側 ---
echo "none" > /sys/block/nvme0n1/queue/scheduler
echo 256 > /sys/block/nvme0n1/queue/nr_requests
# --- vm パラメータ ---
echo 10 > /proc/sys/vm/dirty_ratio
echo 5 > /proc/sys/vm/dirty_background_ratio
# --- cgroup による I/O 隔離 ---
# 各 VM に I/O 帯域幅制限を設定
echo "259:0 rbps=536870912 wbps=268435456" > \
/sys/fs/cgroup/machine.slice/machine-vm1.scope/io.max
18.1.4 コンテナプラットフォーム (Kubernetes)
# Kubernetes ノードのチューニング
# --- systemd スライスでの I/O 制限 ---
# /etc/systemd/system/kubepods.slice.d/10-io-limit.conf
# [Slice]
# IOWeight=100
# IOReadBandwidthMax=/dev/nvme0n1 1G
# IOWriteBandwidthMax=/dev/nvme0n1 500M
# --- ノードの設定 ---
echo "none" > /sys/block/nvme0n1/queue/scheduler
echo 128 > /sys/block/nvme0n1/queue/nr_requests
# --- Pod での I/O 制限(annotation ベース、runtime 依存)---
# k8s 1.26+ では cgroup v2 ベースの I/O 管理をサポート
18.2 チューニングチェックリスト
#!/bin/bash
# ブロック I/O チューニングチェックスクリプト
echo "=== Block I/O Tuning Check ==="
echo ""
for dev in /sys/block/sd* /sys/block/nvme*; do
[ -d "$dev" ] || continue
name=$(basename $dev)
echo "--- $name ---"
echo " Scheduler: $(cat $dev/queue/scheduler 2>/dev/null)"
echo " Nr Requests: $(cat $dev/queue/nr_requests 2>/dev/null)"
echo " Read Ahead KB: $(cat $dev/queue/read_ahead_kb 2>/dev/null)"
echo " Rotational: $(cat $dev/queue/rotational 2>/dev/null)"
echo " RQ Affinity: $(cat $dev/queue/rq_affinity 2>/dev/null)"
echo " Max Sectors KB: $(cat $dev/queue/max_sectors_kb 2>/dev/null)"
echo " IO Poll: $(cat $dev/queue/io_poll 2>/dev/null)"
echo " Write Cache: $(cat $dev/queue/write_cache 2>/dev/null)"
echo " Nomerges: $(cat $dev/queue/nomerges 2>/dev/null)"
echo " Add Random: $(cat $dev/queue/add_random 2>/dev/null)"
echo " Logical BS: $(cat $dev/queue/logical_block_size 2>/dev/null)"
echo " Physical BS: $(cat $dev/queue/physical_block_size 2>/dev/null)"
echo ""
done
echo "=== VM Parameters ==="
echo " dirty_ratio: $(cat /proc/sys/vm/dirty_ratio)"
echo " dirty_background_ratio: $(cat /proc/sys/vm/dirty_background_ratio)"
echo " dirty_writeback_centisecs: $(cat /proc/sys/vm/dirty_writeback_centisecs)"
echo " dirty_expire_centisecs: $(cat /proc/sys/vm/dirty_expire_centisecs)"
echo " swappiness: $(cat /proc/sys/vm/swappiness)"
echo ""
echo "=== Current Dirty Pages ==="
echo " $(cat /proc/meminfo | grep Dirty)"
echo " $(cat /proc/meminfo | grep Writeback)"
echo ""
echo "=== I/O Pressure ==="
if [ -f /proc/pressure/io ]; then
cat /proc/pressure/io
fi
echo ""
echo "=== RAID Status ==="
if [ -f /proc/mdstat ]; then
cat /proc/mdstat
fi
echo ""
echo "=== Device Mapper ==="
dmsetup ls 2>/dev/null || echo " No dm devices"
echo ""
echo "=== LVM ==="
pvs 2>/dev/null || echo " No LVM"
vgs 2>/dev/null
lvs 2>/dev/null
18.3 パフォーマンス監視ダッシュボード
# リアルタイム監視スクリプト
#!/bin/bash
# block_io_monitor.sh - ブロック I/O リアルタイム監視
INTERVAL=1
DEVICE=${1:-"sda"}
echo "Monitoring /dev/$DEVICE (Press Ctrl+C to stop)"
echo ""
# ヘッダー
printf "%-10s %8s %8s %10s %10s %8s %8s %6s\n" \
"Time" "r/s" "w/s" "rMB/s" "wMB/s" "r_await" "w_await" "%util"
printf "%-10s %8s %8s %10s %10s %8s %8s %6s\n" \
"--------" "------" "------" "--------" "--------" "------" "------" "-----"
PREV_STAT=$(cat /sys/block/$DEVICE/stat)
PREV_TIME=$(date +%s%N)
while true; do
sleep $INTERVAL
CURR_STAT=$(cat /sys/block/$DEVICE/stat)
CURR_TIME=$(date +%s%N)
# Parse previous stats
read PR PM PS PT PW PWM PWS PWT PI PIO PIOT <<< $PREV_STAT
# Parse current stats
read CR CM CS CT CW CWM CWS CWT CI CIO CIOT <<< $CURR_STAT
# Calculate deltas
DR=$((CR - PR)) # Read IOs
DS=$((CS - PS)) # Read sectors
DT=$((CT - PT)) # Read time (ms)
DW=$((CW - PW)) # Write IOs
DWS=$((CWS - PWS)) # Write sectors
DWT=$((CWT - PWT)) # Write time (ms)
DTIO=$((CIOT - PIOT)) # Total IO time (ms)
# Calculate rates
RPS=$(echo "scale=1; $DR / $INTERVAL" | bc)
WPS=$(echo "scale=1; $DW / $INTERVAL" | bc)
RMB=$(echo "scale=2; $DS * 512 / 1048576 / $INTERVAL" | bc)
WMB=$(echo "scale=2; $DWS * 512 / 1048576 / $INTERVAL" | bc)
# Average latencies
if [ $DR -gt 0 ]; then
RAWAIT=$(echo "scale=2; $DT / $DR" | bc)
else
RAWAIT="0.00"
fi
if [ $DW -gt 0 ]; then
WAWAIT=$(echo "scale=2; $DWT / $DW" | bc)
else
WAWAIT="0.00"
fi
# Utilization
UTIL=$(echo "scale=1; $DTIO / ($INTERVAL * 10)" | bc)
printf "%-10s %8s %8s %10s %10s %8s %8s %6s\n" \
"$(date +%H:%M:%S)" "$RPS" "$WPS" "$RMB" "$WMB" \
"$RAWAIT" "$WAWAIT" "$UTIL"
PREV_STAT=$CURR_STAT
PREV_TIME=$CURR_TIME
done
19. トラブルシューティング
19.1 一般的な問題と対処法
19.1.1 I/O レイテンシが高い
# 診断手順
# 1. どのデバイスが問題か特定
iostat -x 1 5
# 2. キュー深度とレイテンシの確認
# await が高い → デバイスが遅い or キューが深すぎる
# aqu-sz が大きい → 処理が追いついていない
# 3. プロセスの特定
iotop -oP
# 4. blktrace で詳細分析
blktrace -d /dev/sda -w 10 -o /tmp/trace
blkparse -i /tmp/trace -d /tmp/trace.bin
btt -i /tmp/trace.bin
# 5. I/O パターンの確認
# ランダム vs シーケンシャル
# 読み込み vs 書き込み
# I/O サイズ
# 対処法:
# - I/O スケジューラの変更
# - キュー深度の調整
# - 先読みの調整
# - ファイルシステムのチューニング
# - ハードウェアのアップグレード
19.1.2 スループットが出ない
# 診断手順
# 1. ハードウェアの理論値確認
# NVMe: ~3-7 GB/s (PCIe Gen3/4 x4)
# SATA SSD: ~550 MB/s
# HDD: ~150-200 MB/s
# 2. ベンチマーク
fio --name=seqread --ioengine=libaio --iodepth=32 \
--rw=read --bs=128k --direct=1 --size=4G \
--numjobs=1 --time_based --runtime=30 \
--filename=/dev/nvme0n1
# 3. ボトルネックの特定
# CPU 使用率確認
mpstat -P ALL 1
# IRQ 分散確認
cat /proc/interrupts | grep nvme
# NUMA アフィニティ確認
numactl --hardware
cat /sys/block/nvme0n1/device/numa_node
# 4. 対処法
# - iodepth の増加
# - numjobs の調整
# - bs の最適化
# - NUMA ローカルアクセスの確保
# - IRQ アフィニティの最適化
19.1.3 I/O エラー
# I/O エラーの診断
# 1. dmesg でエラー確認
dmesg | grep -i "i/o error\|medium error\|hardware error\|timeout"
# 2. SMART 情報確認
smartctl -H /dev/sda
smartctl -A /dev/sda | grep -E "Reallocated|Pending|Uncorrectable"
# 3. SCSI エラーの確認
cat /var/log/messages | grep -i "scsi\|sd \[sd"
# 4. ファイルシステムの整合性チェック
# ext4
e2fsck -n /dev/sda1 # 読み取り専用チェック
# XFS
xfs_repair -n /dev/sda1 # 読み取り専用チェック
# 5. バッドブロックの確認
badblocks -sv /dev/sda
# 6. dm/RAID の状態確認
cat /proc/mdstat
mdadm --detail /dev/md0
dmsetup status
19.1.4 デッドロック / ハング
# I/O ハングの診断
# 1. 処理中の I/O 確認
cat /sys/block/sda/inflight
# 2. ブロックされたタスクの確認
echo w > /proc/sysrq-trigger # 全タスクのスタックトレース
dmesg | grep "blocked for more than"
# 3. hung_task の設定
cat /proc/sys/kernel/hung_task_timeout_secs
echo 120 > /proc/sys/kernel/hung_task_timeout_secs
# 4. I/O タイムアウト
cat /sys/block/sda/device/timeout
echo 30 > /sys/block/sda/device/timeout
# 5. ブロックレイヤーのデバッグ
echo 1 > /sys/block/sda/trace/enable
cat /sys/kernel/debug/block/sda/hctx0/busy
# 6. SysRq による強制対処
echo b > /proc/sysrq-trigger # リブート(最終手段)
19.2 デバッグツールと手法
# ftrace によるブロック I/O のトレース
echo 1 > /sys/kernel/debug/tracing/events/block/enable
cat /sys/kernel/debug/tracing/trace_pipe | head -100
# 特定のイベントのみ
echo 1 > /sys/kernel/debug/tracing/events/block/block_rq_issue/enable
echo 1 > /sys/kernel/debug/tracing/events/block/block_rq_complete/enable
# bpftrace によるカスタム監視
# I/O レイテンシのヒストグラム
bpftrace -e 'tracepoint:block:block_rq_issue {
@start[args->dev, args->sector] = nsecs;
}
tracepoint:block:block_rq_complete
/@start[args->dev, args->sector]/ {
@us = hist((nsecs - @start[args->dev, args->sector]) / 1000);
delete(@start[args->dev, args->sector]);
}'
# biolatency (BCC tools)
biolatency -D 1 10
# biosnoop
biosnoop -d /dev/sda
# biotop
biotop 1 10
20. まとめとベストプラクティス
20.1 設計原則
- ワークロードを理解する: ランダム vs シーケンシャル、読み込み vs 書き込みの比率を把握
- 適切なハードウェアを選択: NVMe SSD はランダム I/O に、HDD はシーケンシャル/大容量に
- レイヤーごとの最適化: ファイルシステム、ブロックレイヤー、デバイスドライバそれぞれをチューニング
- 測定してから最適化: 推測ではなく、実測に基づいて判断
20.2 推奨設定サマリー
# NVMe SSD の推奨設定
echo "none" > /sys/block/nvme0n1/queue/scheduler
echo 128 > /sys/block/nvme0n1/queue/nr_requests
echo 2 > /sys/block/nvme0n1/queue/rq_affinity
# HDD の推奨設定
echo "mq-deadline" > /sys/block/sda/queue/scheduler
echo 128 > /sys/block/sda/queue/nr_requests
echo 4096 > /sys/block/sda/queue/read_ahead_kb
# VM パラメータ(一般的なサーバ)
echo 10 > /proc/sys/vm/dirty_ratio
echo 5 > /proc/sys/vm/dirty_background_ratio
echo 500 > /proc/sys/vm/dirty_writeback_centisecs
echo 3000 > /proc/sys/vm/dirty_expire_centisecs
20.3 監視すべきメトリクス
| メトリクス | 正常範囲 | 警告閾値 | 取得方法 |
|---|---|---|---|
| I/O レイテンシ (SSD) | < 1ms | > 5ms | iostat -x |
| I/O レイテンシ (HDD) | < 10ms | > 50ms | iostat -x |
| キュー深度 | < 32 | > 128 | iostat -x (aqu-sz) |
| %util (SSD) | < 80% | > 95% | iostat -x |
| %util (HDD) | < 70% | > 90% | iostat -x |
| I/O 待ち時間 | < 5% | > 20% | vmstat (wa) |
| SMART エラー | 0 | > 0 | smartctl |
| RAID 劣化 | 0 | > 0 | mdadm --detail |
20.4 定期メンテナンス
# 週次チェック
smartctl -H /dev/sda # SMART ヘルス
cat /proc/mdstat # RAID 状態
lvs -o +lv_health_status # LVM ヘルス
fstrim -v / # SSD TRIM
# 月次チェック
smartctl -t long /dev/sda # SMART 拡張テスト
echo check > /sys/block/md0/md/sync_action # RAID スクラブ
20.5 参考資料
- Linux カーネルドキュメント: https://www.kernel.org/doc/html/latest/block/
- blk-mq 設計文書: https://www.kernel.org/doc/html/latest/block/blk-mq.html
- NVMe 仕様: https://nvmexpress.org/specifications/
- LVM2 ドキュメント: https://sourceware.org/lvm2/
- mdadm マニュアル: https://raid.wiki.kernel.org/
本文書について
本文書は Linux カーネルのブロック I/O レイヤーに関する包括的な技術リファレンスです。Linux カーネル 6.x 系列を対象としていますが、基本的なアーキテクチャの概念は以前のバージョンにも適用されます。実際の運用においては、使用しているカーネルバージョン、ハードウェア構成、ワークロードの特性に応じて適切なチューニングを行ってください。
Generated by AI - 2026-04-10