makeのときにCPU個数を自動で調べて-jオプションのジョブ数に指定する簡単な方法
makeコマンドで並列に処理するには、-jオプションでジョブ数を指定します。
$ make -j4
ジョブの個数にはビルドするホストのCPUの個数を指定したいので、スクリプトの中で定数で書いてしまうのはかっこ悪いです。(特に他の人に渡すようなビルドスクリプトでは。)並列に処理するジョブの個数はビルドの時間にダイレクトに効いてくるので適切な個数をセットしたいものです。
昨日の記事でperfをビルドするときには、makeのジョブ数が自動でCPUの個数にセットされていました。これはどうやっているのか調べました。
#
# Do a parallel build with multiple jobs, based on the number of CPUs online
# in this system: 'make -j8' on a 8-CPU system, etc.
#
# (To override it, run 'make JOBS=1' and similar.)
#
ifeq ($(JOBS),)
JOBS := $(shell grep -c ^processor /proc/cpuinfo 2>/dev/null)
ifeq ($(JOBS),)
JOBS := 1
endif
endif
/proc/cpuinfo の中の先頭がprocessorで始まる行の数を数えていました。
マッチした行数を数えるにはwcコマンドを使うまでもなく、grep -c でできるとは知りませんでした。
これでJOBSにCPUの個数が入っているので、それを使用して子プロセスでmakeコマンドを起動しています。
$(MAKE) -f Makefile.perf --no-print-directory -j$(JOBS) O=$(FULL_O) $(SET_DEBUG) $@
perfコマンドをソースからビルドする
linuxカーネルの計測ツールであるperfコマンドをソースからビルドしました。必要なライブラリを揃えるのにけっこう苦労したのでメモを残します。
linux 3.16.1
debian jessie armel
準備
$ sudo apt-get install \ flex bison python pkg-config libaudit-dev binutils-dev \ libelf-dev python-dev libpython-dev libperl-dev \ libslang2-dev libdw-dev libiberty-dev
armelにはlibunwind7-dev と libnuma-dev は無いので省きました。
また、GUIは使用しないのでgtkも入れませんでした。
ビルド
armのマシン上に、linux v3.16.1 のソースコードを展開して
$ cd tools/perf $ make BUILD: Doing 'make -j2' parallel build config/Makefile:350: No libunwind found. Please install libunwind-dev[el] >= 1.1 and/or set LIBUNWIND_DIR config/Makefile:426: GTK2 not found, disables GTK2 support. Please install gtk2-devel or libgtk2.0-dev config/Makefile:585: No numa.h found, disables 'perf bench numa mem' benchmark, please install numactl-devel/libnuma-devel/libnuma-dev Auto-detecting system features: ... dwarf: [ on ] ... glibc: [ on ] ... gtk2: [ OFF ] ... libaudit: [ on ] ... libbfd: [ on ] ... libelf: [ on ] ... libnuma: [ OFF ] ... libperl: [ on ] ... libpython: [ on ] ... libslang: [ on ] ... libunwind: [ OFF ] ... libdw-dwarf-unwind: [ on ] ... DWARF post unwind library: libdw GEN common-cmds.h ... (snip) ...
ライブラリが足りなくてauto detectがうまくいかないときは、config/feature-checks/ の下の検出用プログラムが何かビルドエラーになっていると思うので、そのディレクトリで単独でmakeすると何かわかります。
$ ./perf stat ls CREDITS builtin-diff.o builtin-record.c config Documentation builtin-evlist.c builtin-record.o design.txt MANIFEST builtin-evlist.o builtin-report.c lib ...(snip) ... builtin-buildid-list.o builtin-probe.c command-list.txt util builtin-diff.c builtin-probe.o common-cmds.h Performance counter stats for 'ls': 7.812500 task-clock (msec) # 0.420 CPUs utilized 28 context-switches # 0.004 M/sec 0 cpu-migrations # 0.000 K/sec 78 page-faults # 0.010 M/sec 7946598 cycles # 1.017 GHz 1677282 stalled-cycles-frontend # 21.11% frontend cycles idle 6520742 stalled-cycles-backend # 82.06% backend cycles idle 2128870 instructions # 0.27 insns per cycle # 3.06 stalled cycles per insn 233255 branches # 29.857 M/sec 90247 branch-misses # 38.69% of all branches 0.018604500 seconds time elapsed
clang/llvmをソースからビルドする
LLVM 3.5 がそろそろリリースされそうですが、それを待たずにソースコードからclang/llvmをビルドしてみました。
ビルドの方法は以下に書かれています。
Getting Started with the LLVM System — LLVM 9 documentation
本家のリポジトリはsvnなのですが、gitのミラーも用意されているのでそちらからソースを入手しました。
$ WORKDIR=$HOME/work $ mkdir $WORKDIR $ cd $WORKDIR $ git clone http://llvm.org/git/llvm.git $ cd llvm $ (cd tools/; git clone http://llvm.org/git/clang.git) $ (cd projects/; git clone http://llvm.org/git/compiler-rt.git)
release_35のブランチをcheckoutします。
$ git checkout -b work origin/release_35 $ (cd tools/clang; git checkout -b work origin/release_35) $ (cd projects/compiler-rt/; git checkout -b work origin/release_35)
ビルドは別のディレクトリで行います。
$ cd $WORKDIR $ mkdir obj $ cd obj $ ../llvm/configure
ビルドは時間がかかるので、nohupをつけて途中でログアウトしても大丈夫なようにしました。
$ nohup make ENABLE_OPTIMIZED=1 DISABLE_ASSERTIONS=1 -j4 &
インストール
$ sudo make ENABLE_OPTIMIZED=1 DISABLE_ASSERTIONS=1 install
$ which clang /usr/local/bin/clang $ clang --version clang version 3.5.0 (http://llvm.org/git/clang.git 3d96c02ac11c62ff959aed67163bef2c79012a83) (http://llvm.org/git/llvm.git a3313efbbeab739eacbba6faf4e941129576dbd2) Target: x86_64-unknown-linux-gnu Thread model: posix
クロスDockerのためのDockerfile
dockerhubにはx86, x86_64以外のアーキテクチャ用のコンテナもいくつかpushされています。これをqemuを利用したクロス環境のDockerとして動作させるためには、以下の準備が必要です。
(1) binfmt_misc でクロスで動かしたいアーキテクチャのELFファイルの設定がされていること
(2) コンテナ内の/usr/bin にstatic linkされたユーザーモードqemuのバイナリがコピーされていること
(1)はsudo apt-get install qemu-user-static を行ったときに自動的に行われます。
昨日の記事で使用したericvh/arm64-ubuntu-dev はすで(2)がなされた状態でdockerhubに置かれていたので、すぐにクロスで動かすことができました。
(1)だけで(2)がされていない状態では、docker runしたときに no such file or directoryのエラーになります。(1)で設定されたstatic linkされたユーザーモードqemuのバイナリがコンテナの名前空間で見つからないためです。
githubのarmel/debian:jessieを実行しようとすると正にこのエラーが発生します。
$ docker run -it armel/debian:jessie /bin/bash 2014/08/19 13:31:22 no such file or directory 2014/08/19 22:31:22 Error response from daemon: Cannot start container d9a83fa5578a44fd168bfc4d49a24a5a02e8cd88e6d5db46658e299582d61d56: no such file or directory
これを解決するには、qemu-arm-staticをコンテナ内の/usr/binにコピーします。
それを行うためのDockerfileは以下のようになります。
FROM armel/debian:jessie ADD ./qemu-arm-static /usr/bin/
RUNコマンドを追加するときには、必ず "ADD ./qemu-arm-static /usr/bin/"より後ろに追加してください。そうでないとそのコマンドの実行がno such file or directoryのエラーになります。
このDockerfileをカレントディレクトリに置いて以下を実行して新たなコンテナを作成します。
$ cp /usr/bin/qemu-arm-static . $ docker build --tag="koba/debian_armel" . Sending build context to Docker daemon 6.195 MB Sending build context to Docker daemon Step 0 : FROM armel/debian:jessie ---> 693f87a07cac Step 1 : ADD ./qemu-arm-static /usr/bin/ ---> Using cache ---> aaa2d71f8e2c Successfully built aaa2d71f8e2c
このコンテナには koba/debian_armel のタグをつけたので、以下のようにして実行できます。
$ docker run -it koba/debian_armel /bin/bash root@56400d6857a3:/# uname -m armv7l root@56400d6857a3:/#
Aarch64で遊ぶ最も手軽な方法
Aarch64(ARM64)の実機はまだまだ入手は困難ですが、qemuを使ってAarch64のユーザーランドを動かすことは可能です。そしてDockerを利用すると手軽にAarch64の環境を試すことができます。コンパイラも動きます。
準備
Ubuntu 14.04 (x86_64)を使用しています。
まずはDockerをインストールします。詳細はここを見る
次にqemuをインストールします。
$ sudo apt-get install qemu-user-static
起動
$ docker run -it ericvh/arm64-ubuntu-dev /bin/bash
これだけで、dockerhubからaarch64のルートファイルシステムをロードしてきて実行し、bashのプロンプトが出ます。
アーキテクチャの確認。
root@5031da283529:/# uname -m aarch64
gccも利用可能です。
root@5031da283529:/# gcc -v Using built-in specs. COLLECT_GCC=/usr/bin/gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/aarch64-linux-gnu/4.8/lto-wrapper Target: aarch64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.8.2-19ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-libmudflap --disable-libitm --disable-libsanitizer --disable-libquadmath --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-arm64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-arm64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-arm64 --with-arch-directory=arm64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-multiarch --disable-werror --enable-checking=release --build=aarch64-linux-gnu --host=aarch64-linux-gnu --target=aarch64-linux-gnu Thread model: posix gcc version 4.8.2 (Ubuntu/Linaro 4.8.2-19ubuntu1)
root@5031da283529:/root# vi hello.c root@5031da283529:/root# gcc hello.c root@5031da283529:/root# ./a.out Hello, world root@5031da283529:/root# file ./a.out ./a.out: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 3.7.0, BuildID[sha1]=a6ee525387725894f818f789acd9b0203adff657, not stripped
gdbもあるのですが、残念ながら以下のエラーでブレークポイントがうまくかかりませんでした。
qemu: Unsupported syscall: 117
Dockerをソースからビルドしてみた
昨日に続いてDockerの話。Dockerに関しては驚くことばかりです。検索するとほぼ一年前に盛り上がっていたようですね。周回遅れです。
DockerはGo言語で書かれていてソースコードがGithubで公開されています。
GitHub - moby/moby: Moby Project - a collaborative project for the container ecosystem to assemble container-based systems
このソースをとってきてビルドしてみました。
$ git clone https://github.com/dotcloud/docker $ git checkout -b work v1.0.1
ビルドの方法がすぐに見つけられなかったので、とりあえずMakefileがあることだしmakeコマンドを実行してみました。
$ make
え?!え?!何が起こった?
いきなりdockerが起動してコンテナをビルドし始めました。ubuntu 14.04を取ってきて、apt-getでツールチェインをインストールして、goのソースを取ってきてそれをビルドして ...
そのビルドログ
そして最後にはたったひとつの実行ファイルがごろんとできていました。それ以外の中間ファイルが全くないのが不思議な感じです。全てのビルドはコンテナの中で行われたからです。
$ ls -l bundles/1.0.1/binary/ total 17132 lrwxrwxrwx 1 root root 12 Jun 26 22:24 docker -> docker-1.0.1 -rwxr-xr-x 1 root root 17533452 Jun 26 22:24 docker-1.0.1 -rw-r--r-- 1 root root 47 Jun 26 22:24 docker-1.0.1.md5 -rw-r--r-- 1 root root 79 Jun 26 22:24 docker-1.0.1.sha256 $ file bundles/1.0.1/binary/* bundles/1.0.1/binary/docker: symbolic link to `docker-1.0.1' bundles/1.0.1/binary/docker-1.0.1: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=0xe58e81b17140670d7a3228ea83ee3861fb87cd0b, not stripped bundles/1.0.1/binary/docker-1.0.1.md5: ASCII text bundles/1.0.1/binary/docker-1.0.1.sha256: ASCII text
これはどうしたらいいのかな?無謀にも現在の/usr/bin/docker.ioに差し替えて動かしてみました。
$ sudo service docker.io stop $ sudo mv /usr/bin/docker.io /usr/bin/docker.io.org $ sudo cp bundles/1.0.1/binary/docker-1.0.1 /usr/bin/docker.io $ sudo service docker.io start
バージョンを確認。
$ docker version Client version: 1.0.1 Client API version: 1.12 Go version (client): go1.2.1 Git commit (client): 990021a Server version: 1.0.1 Server API version: 1.12 Go version (server): go1.2.1 Git commit (server): 990021a
動いているようです。docker run なども問題なく動きました。
試行錯誤してやってみましたが、その後でビルド方法のドキュメントを見つけました。
http://docs.docker.com/contributing/devenvironment/
DockerをビルドするのにまずDockerをインストールしろと書いてあります。
不思議な話ですが、これによって誰がビルドしてもOSとかツールチェインとかライブラリのバージョンの違いによるトラブルが発生することがありません。これからのOSSのソースリポジトリにはそのビルド環境構築のためのDockerfileがついてくるのが当たり前になるかもしれません。
ちなみに初回のビルドにはビルド用のコンテナの作成のためにそれなりの時間がかかりましたが、2回目以降はコンテナの作成がうまい具合にcacheされるのであっという間にビルドが完了します。
2回目のビルドのログ
apt-get install とかgit cloneとかの実行がなんでcacheできるのかとても不思議で魔法のようです。
なお、make cross とすると、linux/amd64の他に以下の7種類のdockerがビルドされます。
そのうちlinux/armのdockerを試してみよう。
Dockerでダウンロードしたファイルはどこに置かれるのか
昨日に続いてDockerをさわってみた話です。
docker run ... で自動的にルートファイルシステムがダウンロードされますが、それらのファイルはいったいどこに置かれたのでしょうか?
カレントディレクトリには何も新しいファイルはできていません。ダウンロードしたのはubuntuとbusyboxのルートファイルシステムなので、それらしい名前で新しくファイルができていないか探したのですが見つかりません。
それでもうまく動いているのですから気にしなくてもいいとも言えるのですが、私としてはわからないのは気持ちが悪い。少なくともギガバイトのサイズをダウンロードしたはずなのに、いったいどこに消えてしまったのか?
ようやく見つけたヒントがこれ。
コンテナの中のプロセスの/proc/self/mountinfo を見るとそれらしいディレクトリが見つかりました。
$ docker run -it busybox cat /proc/self/mountinfo 81 48 0:37 / / rw,relatime - aufs none rw,si=dd4c1e0e1d1a2e1c 82 81 0:39 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw 83 81 0:40 / /sys ro,relatime - sysfs sysfs ro 84 81 0:41 / /dev rw,nosuid - tmpfs tmpfs rw,mode=755 85 84 0:42 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k 86 84 0:43 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 87 81 8:1 /var/lib/docker/init/dockerinit-1.0.1-dev /.dockerinit ro,relatime - ext4 /dev/disk/by-uuid/29ad72a0-27da-42e3-9f5f-3a13990ab837 rw,errors=remount-ro,data=ordered 88 81 0:16 /resolvconf/resolv.conf /etc/resolv.conf ro,relatime - tmpfs tmpfs rw,size=404108k,mode=755 89 81 8:1 /var/lib/docker/containers/d37d6c8e7de990094775c91dc1832f9ed30d10a0a554557ccb487263282a59cd/hostname /etc/hostname ro,relatime - ext4 /dev/disk/by-uuid/29ad72a0-27da-42e3-9f5f-3a13990ab837 rw,errors=remount-ro,data=ordered 90 81 8:1 /var/lib/docker/containers/d37d6c8e7de990094775c91dc1832f9ed30d10a0a554557ccb487263282a59cd/hosts /etc/hosts ro,relatime - ext4 /dev/disk/by-uuid/29ad72a0-27da-42e3-9f5f-3a13990ab837 rw,errors=remount-ro,data=ordered 91 84 0:12 /5 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000 50 82 0:39 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw 52 82 0:39 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw 53 82 0:39 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw 54 82 0:39 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw 55 82 0:41 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,mode=755
/var/lib/dockerを調べてみます。
$ ls -l /var/lib/docker ls: cannot open directory /var/lib/docker: Permission denied $ ls -ld /var/lib/docker drwx------ 9 root root 4096 Jun 25 22:59 /var/lib/docker
このディレクトリはrootにしかアクセス権限がありません。
$ sudo bash [sudo] password for koba: # cd /var/lib/docker # ls aufs containers execdriver graph init linkgraph.db repositories-aufs vfs volumes # du -s -h * 3.6G aufs 4.0M containers 8.0K execdriver 808K graph 25M init 16K linkgraph.db 4.0K repositories-aufs 8.0K vfs 8.0K volumes #
見つけました。ギガバイトサイズのルートファイルシステムはaufsのフォーマットで格納されていました。
補足
先ほどの/proc/self/mountinfoを見ると、/.dockerinit, /etc/resolv.conf, /etc/hostname, /etc/hostsなどは単一のファイルがmountされています。
てっきりmountはディレクトリに対して行うものだとばかり思っていましたが、ファイル単位でmountもできるんですね。
追記(2014.0626): /var/lib/docker を移動する
/var/lib のディスクの容量にそんなに余裕がないと思ったので、別のディスクにある/proj の下に引っ越しました。
以下のようにしたらできました。
root権限のshellで
# mkdir -p /proj/var/lib # service docker stop # mv /var/lib/docker /proj/var/lib/ # cd /var/lib/ # mv docker docker.org # ln -s /proj/var/lib/dorcker . # service docker start
(Ubuntu 14.04の場合はサービス名をdocker.ioに変更。)