Linux 環境での Raspberry Pi 向け OS 書き込みTips

このエントリは Raspberry Pi Advent Calendar 2015 の12月08日分です．
昨日は @2box2boさんの RaspberryPiと公式タッチディスプレイでマインクラフトするお話 | 流連荒亡 でした．公式ディスプレイ欲しいです……．

最近 Raspberry Pi はサブPC 的に使っててネタがない(普通に Linux Desktop なので……)ので紙製ケースの紹介でもしようかと思っていたのですが，Raspberry Pi に OS を書き込むのによく使う dd 関連のネタが少し溜まっているので今回はこれを紹介しようと思います．（NOOBS だとほぼ関係なくコピーするだけでいいんですが……）
Rasbina jessie / Debian stretch で検証していますが，Linux なら導入手順以外同じだと思います．Mac OS X / UNIX 系の OS でも使えると思います．

– 紙ケースの一例 –

dd(dataset definition) は GNU Coreutils の中に入っているのでほとんどの GNU/Linux だと標準で導入されていると思います．とても便利だけど使い方を誤るとシステムやデータをいとも簡単に壊してしまえます．注意して実行しましょう．

Coreutils – GNU core utilities

進捗状況確認

dd で sd 書き込み中にどのくらい進んだのだろうと確認したくなることがあります．

kill -SIGUSR1

dd のプロセスに対して SIGUSR1 シグナルを投げると進捗が確認できます．

dd のプロセス番号を確認

$ ps -ef|grep dd
:
root 9273 32218 0 17:57 pts/1 00:00:00 sudo dd of=/dev/sdz bs=4M
root 9276 9273 9 17:57 pts/1 00:00:00 dd of=/dev/sdz bs=4M

9276 なので以下のように

$ sudo kill -USR1 9276

で，こんな感じに表示されます．

0+243258 レコード入力
0+243258 レコード出力
1224679424 バイト (1.2 GB) コピーされました、 17.2794 秒、 70.9 MB/秒
0+302681 レコード入力
0+302681 レコード出力
1532985344 バイト (1.5 GB) コピーされました、 20.8063 秒、 73.7 MB/秒

なのでこんなとか

$ sudo pkill -SIGUSR1 ^dd

こんな感じで叩くと良い感じだと思います．

% watch -n30 'sudo pkill -SIGUSR1 ^dd`

pv(Pipe Viewer)

pv(Pipe Viewer) というパイプの状況を確認できるプログラムがあります．dd の間にこれを挟んで進捗状況を確認できます．

ivarch.com: Pipe Viewer

導入

$ apt install pv

利用例

% zcat 2015-11-21-raspbian-jessie.zip | pv | sudo dd of=/dev/sdz bs=4M
5.81GB 0:06:12 [15.2MB/s] [ <=> ]

-N で 名前の，-c でクラスタオプションになります．これを活用すると複数のパイプの監視もできます．

$ zcat ./2015-11-21-raspbian-jessie.zip | pv -cN zcat | xz | pv -cN xz | dd of=./2015-11-21-raspbian-jessie.xz
zcat: 8.41MB 0:00:05 [1.09MB/s] [ <=> ]
xz: 3.96MB 0:00:05 [1.06MB/s] [ <=> ]

GNU ddrescue / ddrescue

dd じゃないけど dd の代わりに GNU ddrescue を利用するとプログレスが表示されます．

$ sudo apt install gddrescue
$ sudo ddrescue /dev/zero /dev/null --force
GNU ddrescue 1.19
Press Ctrl-C to interrupt
rescued: 2969 MB, errsize: 0 B, current rate: 354 MB/s
ipos: 2969 MB, errors: 0, average rate: 742 MB/s
opos: 2969 MB, run time: 4 s, successful read: 0 s ago
Copying non-tried blocks... Pass 1 (forwards)

でも標準有力入力を受け付けないようです．

$ zcat ./2015-11-21-raspbian-jessie.zip | sudo ddrescue - /dev/sdz --force
ddrescue: Can't open input file: No such file or directory

類似の ddrescue だと標準入力もOKなようです．こちらの場合のコマンド名は dd_rescue です．

$ sudo apt install ddrescue
$ zcat ./2015-11-21-raspbian-jessie.zip | sudo dd_rescue - /dev/sdz
dd_rescue: (warning): input file is not seekable!
dd_rescue: (warning): Illegal seek
dd_rescue: (warning): Don't use sparse writes for non-seekable output
dd_rescue: (info): ipos: 91136.0k, opos: 91136.0k, xferd: 91136.0k
errs: 0, errxfer: 0.0k, succxfer: 91136.0k
+curr.rate: 144981kB/s, avg.rate: 144776kB/s, avg.load: 38.4%

ddすると重い/固まる

環境によって dd 実行中にとても重くなってマウスカーソルさえカクカク動くようになり並行して別の作業ができないようにます．

ionice

ionice を使って dd の優先度を下げることができます．

% zcat ./2015-11-21-raspbian-jessie.zip | sudo ionice -c2 -n7 dd of=/dev/sdz

pv -L

pv コマンドの -L オプションでパイプの帯域制限ができます．

-L RATE, --rate-limit RATE
Limit the transfer to a maximum of RATE bytes per second. A suffix of "k", "m", "g", or "t" can be
added to denote kilobytes (*1024), megabytes, and so on.

% zcat cros.img.gz | pv -L 8192k | sudo dd of=/dev/sdz

cgroup

リソース管理の cgroups で書き込み帯域制限をしてみます．以下は dd というグループを作成し，自分のシェルをそこに登録．SD Card のデバイスを書き込み制限 1k で設定し，dd で動作確認をしました．想定通り 1kB/s しか出なかったようです．

$ sudo mkdir /sys/fs/cgroup/blkio/dd
$ echo $$ | sudo tee -a /sys/fs/cgroup/blkio/dd/tasks
26041
$ ls -l /dev/sdz
brw-rw---- 1 root disk 179, 0 12月 6 18:42 /dev/sdz
$ echo "179:0 1024" | sudo tee -a /sys/fs/cgroup/blkio/dd/blkio.throttle.write_bps_device
179:0 1024
$ sudo dd if=/dev/zero of=/dev/sdz bs=4k count=10
10+0 レコード入力
10+0 レコード出力
40960 バイト (41 kB) コピーされました、 40.0136 秒、 1.0 kB/秒

書き込み速度が遅い

ブロックサイズ変更

dd コマンドはブロックサイズが 512バイトと小さいです．このサイズを変更することで1度に処理する容量が多くなり速度が改善されます．このサイズは bs オプションで設定できます．規定値と同じ 512バイトの場合は， bs=512．1MB の場合は bs=1M というようにして容量の単位（Yまで!)も指定できます．最近の私は 4~16M を指定しています．

$ zcat 2015-11-21-raspbian-jessie.zip | sudo dd of=/dev/sdz bs=4M

GNU ddrescue

GNU ddrescue は効率のいい処理を自動的に行うそうです．効率の良いブロックサイズを探すよりこれを導入したほうが早いかもしれません．

パーティション情報の削除

OS イメージをこれまで使っていた SD に上書きすると古いデータが残ってしまうことがあります．パーティション情報を削除してから書き込むと綺麗に行くようです．

Windows/Mac OS X の場合は SD Assosietion がフォーマッタを提供しているのでこれを利用すると良いと思います．

SD Card Formatter – SD Association

dd

dd コマンドでパーティション情報が入っているであろう先頭部分を消します．以下の例では 1M を 1回なので先頭の 1M が 0 で埋められます．2行目の hdparm はおまじないで kernel に書き換わったよと教えてあげています．最近は即時反映されるような感じですが，以前はこれを叩かないとうまく反映されないことが多かったです．(いちいち抜き差ししてみたり)

$ sudo dd if=/dev/zero of=/dev/sdz bs=1M count=1
$ sudo hdparm -z /dev/sdz

以下のように count を指定しない場合は全領域書き込みます．時間はかかるけど確実?

$ sudo dd of=/dev/zero of=/dev/sdz bs=10M

全領域書き込む場合は shred -z /dev/sdz でも良いですね．

wipefs

wipefs はパーティション情報の wipe をしてくれるツールです．一瞬で動作するし便利です．
util-linux パッケージ内の wipefs です．

デバイスだけ指定して実行すると現在のパーティションの状況が確認できます．-a オプションでパーティション情報が削除されます．便利．

$ sudo wipefs /dev/sdz
offset type
----------------------------------------------------------------
0x1fe dos [partition table]

$ sudo wipefs -a /dev/sdz
/dev/sdz: 2 bytes were erased at offset 0x000001fe (dos): 55 aa
/dev/sdz: calling ioctl to re-read partition table: 成功です
$ sudo wipefs /dev/sdz

mount中のファイルシステムに書き込もうとすると終了するscript

dd は便利ですが，書き込み先を間違えるとシステムやデータを破壊してしまいます．私もつい一昨日やってしまいましたorz
＃何故か /dev/mmcblk0 が /dev/sda へのシンボリックリンクとなっていた．
600GB の 先頭 1.5GB だからデータはほとんど救出できるだろうと思ったのですが，LUKS で暗号化していたのでメタデータが破壊され復旧は無理そうです．幸いデイリーバックアップがあるのでそちらから復旧中です．
（復旧中なのにどうやって書いているかというと Raspberry Pi 2 B にキーボードマウスモニタ取り付けて ReText で書いています．Web は midori でも重いので ssh -CY してファイルサーバのブラウザを使っています．使い慣れないキーボードが不便です><）

こういう悲しいことが起こらないようにできないものかと mount 中のデバイスに書き込もうとすると失敗させることができればいいのでは?と思ったのですがそういったオプションなどが見当たりませんでした．
それっぽいscript を書いてみました．

利用方法は，/usr/local/bin/dd としてこのスクリプトを用意して実行権をつけておいて通常の dd コマンドのように使うだけです．

$ wget -O - https://gist.githubusercontent.com/matoken/e051cefb78594520038d/raw/9ac20f31c590a043f1774f82068a99846ab7c4bb/dd.sh | sudo tee /usr/local/bin/dd
$ sudo chmod +x /usr/local/bin/dd
$ which dd
/usr/local/bin/dd
$ sudo which dd
/usr/local/bin/dd

マウント状態で書き込もうとすると失敗する．

$ sudo dd if=/dev/zero of=/dev/sdz
/dev/sdz seems to specify the file system in the mount.
Force Quit.

アンマウントして再実行すると dd が開始される．

$ sudo umount /dev/sdz1
$ sudo dd if=/dev/zero of=/dev/sdz1

あまりテスト出来ていないので何かあったら教えてもらえると助かります．

おわり

ということで Raspberry Pi や kobo の SD Card に何度も書き込んだ時の Tips まとめみたいなものでした．ここ間違ってるよとかもっといい方法あるよとかおしえてもらえると助かります．（特に誤って書き込まないようにする方法）