2012-02-15

Ubuntuにログインしたときに出るパッケージ情報の謎

私は結構長い間 Debian を使ってきて最近 Ubuntu に移行した。Ubuntu は Debian を元にしている。大きな変更も GUI まわりを中心にたくさんあるが、細かいこしゃくな仕掛けも色々用意している。そういうものの中で最初に気づいたのが、ssh でリモートログインしたときにでるパッケージの更新情報だ。↓こういうの。

Welcome to Ubuntu 11.04 (GNU/Linux 2.6.38-13-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

52 packages can be updated.
24 updates are security updates.

これを見たときに2つの疑問が生じた。一つは、Ubuntu は Debian と違って apt-get update を定期的に行っている(そうでないと上のメッセージは出せない)が、それはどこで、という点で、もう一つは、このメッセージはいったいどういう仕掛けで出しているのか、という点だった。

一つ目の点。apt-get update の自動実行は /etc/cron.daily/apt で設定されている。このシェルスクリプトが apt の設定を読んで APT::Periodic::Update-Package-Lists を調べてそれが非 0 なら n 日に一回 apt-get update を実行するようになっている。

デフォルトではファイル /etc/apt/apt.conf.d/10periodic に APT::Periodic::Update-Package-Lists "1"; と設定され 1 日に 1 回 apt-get update が実行されるようになっている。

二つ目の点。起点は /etc/init/mounted-varrun.conf で、これは upstart により起動時に実行される。このファイルから実行される /etc/update-motd.d/90-updates-available が /usr/lib/update-notifier/update-motd-updates-available を実行し、そのファイルが /usr/lib/update-notifier/apt-check --human-readable を実行してその出力が /var/run/motd に書き込まれて、それがログイン時に表示される。

...と調べていくうちに、この機能はパッケージ update-notifier により提供されていることに気付いた。これは Debian にもあるので、Ubuntu のこしゃくな仕掛けというには当たらない。こしゃくなパッケージ選択とはいえようか。

...Debian で試してみたところ、update-notifier をインストールしただけでは表示されなかった。面倒なのでこれ以上は調べない。

Perl の use

以前の記事「Pythonのimport」に続き Perl の use について調べてみた。

use はファイルを実行する

Perl における

use Module;

は厳密に以下と同等である。

BEGIN {
    require Module;
    Module->import();
}

BEGIN ブロックはファイルの実行に当たって最初に(書いてある順に)実行され、その後は二度と実行されない。if の中に書いてあっても必ず実行される。

require Module は Module に .pm を付加したファイル名のファイルを探してそのファイルを実行する。すでに実行済みの場合は実行しない。

ファイルはディレクトリの配列 @INC から探される。

Module をファイル名に変換する際に、:: は / に変換される。これによって、モジュールファイルを階層化ディレクトリに格納することが可能となる。

Module->import() については後述する。

パッケージ

use の動作は以上のような簡単なもので、これだけでは「モジュールをインポート」する事に使えそうに無い。実際、Module.py に sub f が定義してあるだけのとき、use Module したソースファイルからは f() としてしか参照できず、現在の名前空間を汚さずにファイルを読み込むというインポートの役目を果たさない。

use を実用的に使うためには Perl のパッケージを使う必要がある。パッケージは my によって宣言された変数(レキシカル変数)以外の名前が属する名前空間である。名前を定義する(sub によるサブルーチン定義を含む)と「現在の」名前空間(カレントパッケージ)に登録される。カレントパッケージは package Package によって切り替えることができる。カレントパッケージに属さない名前は Package::Name という形式で参照できる。デフォルトの名前空間には main:: により明示的にアクセスできる。

sub f { "main::f()"; }
f();            # => "main::f()"
{
    package Package;
    sub f { "Package::f()"; }
    f();        # => "Package::f()"
    main::f();  # => "main::f()"
}
f();            # => "main::f()"
Package::f();   #=> "Package::f()"

Perl では、use Module されるファイルの先頭に明示的に package Module を書くことで、モジュールのインポートを実現している。

Module.py が

package Module;
sub f() { ... }

のとき、use Module したソースファイルから Module::f() で呼び出すことができる。

my による変数以外は全てカレントパッケージに登録されるので、そのパッケージの外側からも全てのエントリにアクセスできる。つまり情報隠蔽はなされない。

クラスとしてのパッケージ

Perl にはオブジェクト指向をサポートするしくみとしてクラスという概念が用意されている。クラスに対しては ->(矢印演算子)によるメソッド呼び出しが可能である。Perl においてはクラスは単にパッケージのことで、Class->method() によりパッケージ Class のサブルーチン method()、すなわち Class::method() が呼び出される。ただし、このとき -> の左側の値(この場合には Class)が第 1 引数として呼び出される。

(一方「オブジェクト」も単なる Perl の値を bless() したものである。bless() された値はカレントパッケージと結びつけられ、矢印演算子によるメソッド呼び出しは、結びつけられたパッケージのサブルーチン呼び出しになる。これが Perl のオブジェクト指向サポートである。)

import による定義のコピー

use Module は require Module の後、Module->import() というメソッド呼び出しを行う。

必要なら、Module::import() (普通は Module.py の sub import)に定義のコピーを実装できる。つまり、Perl では定義のコピーを示唆する import という名前を使用しているものの、その用途は自由で、実際に定義のコピーを行う際も自分で明示的にする必要があるのだ。

use Module LIST という形式を用いて import 時の細かい指定を実現できる。この形式では Module->import(LIST) という呼び出しが行われる。

定義のコピーは型グロブなど Perl 言語の深い部分を使う必要があるので、あらかじめ用意された Exporter モジュールを用いて行うのが普通である。

シンボルテーブル

Perl のパッケージの実態はハッシュ様のオブジェクトで、名前をキーとして値が引ける表となっている。このハッシュ様オブジェクトには実際に言語からアクセスすることができ、%Module:: というハッシュとして見えるようになっている。このハッシュを操作することで定義のコピーなどを行うことができる。

階層化されたパッケージ

パッケージ名に :: を用いることで、モジュールファイルを階層化ディレクトリに配置できるが、Perl 言語としてはモジュールが階層化されているわけではない。例えば Red::Blue というパッケージは Red というパッケージとも Blue というパッケージとも関係が無い。

(ただし Perl の実装は Red という名前空間が Blue という名前を保持し、その値が Blue 名前空間を持つようになっている。だが言語仕様としてはこの事実は利用されない。)

References

man perlfunc に use, require, import の説明がある。

man perlmod にパッケージ、シンボルテーブルの説明がある。

man perlobj にクラス、メソッド呼び出しの説明がある。

プログラミング Perl」の 10 章にパッケージ、シンボルテーブルの説明がある。

マスタリング Perl」の 10 章にパッケージ、シンボルテーブルの説明がある。

2012-02-14

Ubuntu 11.04のCUDA開発環境を4.1にアップグレードする

CUDA Toolkit 4.1 がリリースされた(CUDA Toolkit 4.1 | NVIDIA Developer Zone)。Septième Sens: Ubuntu 11.04にCUDA開発環境を導入する では Ubuntu 10.10 用の Toolkit を 11.04 にインストールするのに必要な若干の工夫を説明したが、今回は 11.04 用のインストーラが配布されている。

4.0 から 4.1 のアップグレードは、特に何をする必要も無く上書きでインストーラを走らせればよい。前回の記事で X Updates の PPA を用いて nVidia 用のドライバを最新に保っているので、Toolkit と SDK をインストールすればよいだけである。

$ sudo sh cudatoolkit_4.1.28_linux_64_ubuntu11.04.run

インストールするディレクトリを尋ねられるので /usr/local/cuda を前回同様指定すると、「以前のバージョンをアンインストールするか」と言われて yes と答えると後は勝手にやってくれる。

今回のバージョンでは gcc 4.5 でも問題ないので、前回行った工夫はもはや必要ない(この工夫はアンインストールの際に取り去られているので何もしなくていい)。

次に SDK をインストールする。

$ sh gpucomputingsdk_4.1.28_linux.run

この展開先は前回とは別にしたほうが無難。展開先のディレクトリに行きビルドしてみる。

$ cd C
$ make

前回指摘した SDK のバグが修正されていないので、やはり LPATH の指定が必要である。

$ LPATH=/usr/lib64/nvidia-current make

ところで、4.0 にはあった、cudatools_4.0.17_linux_64.run という配布物が今回はないのだがあれは何だったろうか、思い出せない。

2012-02-13

UbuntuでKVM仮想マシンを作成する

Linux でホストされた Pukiwiki や Drupal でメモや日誌などを記録することをよくするが、これらのデータはテキストファイルや HTML ファイルに比べて可搬性が無いという問題点がある。

OS やシステムのアップグレードでソフトウェアが動作しなくなることもあるし、取っておいたバックアップから復元するのも非常に大変である。そもそもソフトウェアのインストールやセットアップのやり方を忘れていたり、やり方をメモしておいてもやたら面倒だったり、ソフトウェアの最新バージョンでは動作しなかったり、個人的なカスタマイズを行っていたりするからだ。

この種のソフトウェアでデータを記録する場合には仮想マシンでホストするべきであった。そうすれば仮想マシンを持ち運ぶだけでデータの復元は簡単にできる。バックアップの方法に悩まなくてもすむ。この記事では Ubuntu で KVM 仮想マシンを作成し、それを持ち運ぶ方法を説明する。

libvirt を用いた KVM 仮想マシンの作成

libvirt (libvirt: The virtualization API) は KVM や Xen といった仮想マシンのバックエンド(ハイパーバイザと呼ばれる)を用いた仮想マシンの作成・管理の共通 API を提供するライブラリである。libvirt を用いると同一のコマンドやアプリケーションで異なったハイパーバイザによる仮想マシンを操作できる。

まずマシン環境が KVM 仮想化をサポートしているかをチェックする必要がある。

$ sudo aptitude install cpu-checker
$ sudo kvm-ok

これで KVM acceleration can be used と表示されればよい。表示されない場合は BIOS をチェックして VT-x を有効にしなければならない(VT-d は必須では無い)。/dev/kvm が存在しないという表示が出るかもしれないが、これは kvm をインストールすれば作成されるので気にしなくてよい。

ここでは Ubuntu で libvirt を用いて KVM 仮想マシン (Debian GNU/Linux) を作成する方法を説明する。まずインストール:

$ sudo aptitude install kvm virtinst

これにより用意されるグループ libvirtd に加えられて virsh などによる仮想マシンの特権的操作が可能になる。グループを有効にするためにいったんログアウトしてログインする必要がある。万一グループに加えられてなければ、adduser yourname libvirtd を実行してグループに自分を加える必要がある。

作成:

$ cd working-dir
$ curl -LO 'http://ftp.jaist.ac.jp/pub/Linux/Debian-CD/current/i386/iso-cd/debian-6.0.4-i386-netinst.iso'
$ qemu-img create -f qcow2 example.img 16G
$ sudo virt-install --name=example --ram=1024 --vcpus=2    \
                    --cdrom=debian-6.0.4-i386-netinst.iso  \
                    --os-variant=virtio26
                    --disk='path=example.img,format=qcow2' \
                    --graphics='type=vnc,listen=0.0.0.0'

これにより仮想マシンが作成されてブートしインストーラが起動する。インストールを続行するには Ubuntu ホストに VNC で接続すればよい。

なお、作成された仮想マシンの設定は /etc/libvirt/qemu/example.xml というファイルに保存される。

(不明なこと: 上記でテキストで無い GUI ベースのインストーラを用いると VNC 画面でマウスとマウスポインタがずれるという不具合が生じる。これは上記 XML ファイルを編集して仮想マシンを「タブレット」として設定すれば防げるらしいが、インストーラをいったん中止して設定ファイルを書き換え、インストーラを再び起動する方法がよくわからない。)

libvirt により管理されている KVM 仮想マシンの操作

仮想マシンの強制終了

$ virsh destroy example

仮想マシンの起動

$ virsh start example

仮想マシンの登録破棄

$ virsh undefine example

仮想マシンのリストアップ

$ virsh list --all

libvirt により管理されている KVM 仮想マシンの持ち運び

仮想マシン example の構成要素はイメージファイルと設定 XML ファイルの2つのみである。

$ cd working-dir
$ ls
example.img
$ virsh dumpxml > example.xml
$ rsync -avzh example.img example.xml your.target.host:target-working-dir

持ち運び先での復元方法:

$ ssh your.target.host
$ cd target-working-dir
$ ls
example.img example.xml
$ vi example.xml
( <source file="..."/> の行を編集し、正しいイメージファイルのパスを記述する )
$ virsh define example.xml

これにより、/etc/libvirt/qemu/example.xml が作成される。あとは virsh を用いて起動や管理を行えばよい。

libvirt により管理されている KVM 仮想マシンの複製

同一ホストにおける仮想マシンの複製は持ち運びと同様であるが、uuid と名前を変更する必要があるだろう。

$ ssh your.target.host
$ cd working-dir
$ ls
example.img
$ cp example.img branch.img
$ virsh dumpxml example > branch.xml
$ vi branch.xml
( <name>example</name> の行を編集し、名前を変更する )
( <uuid>...</name> の行を編集し、uuid を変更する。uuid は uuidgen で生成できる )
( <source file="..."/> の行を編集し、正しいイメージファイルのパスを記述する )
$ virsh define branch.xml

(2012/5/18 追記) 仮想マシンをブリッジに接続して外部に見えるようにする方法について記事を書いた: Septième Sens: Ubuntu で KVM 仮想マシンをブリッジ接続する

(2015/1/10 追記) aptitude install kvm でインストールできないケースがある。aptitude install qemu-kvm の方が適切だろう: Septième Sens: Ubuntu で Vagrant 最新版を使って KVM を起動する

2012-02-07

Debian と Ubuntu の sudo の違い

Debian (6.0.4 squeeze) と Ubuntu (11.04 natty) で sudo vi としたときの挙動が異なる。Debian では sudo を呼び出したユーザのホームディレクトリの .vimrc が呼び出されていないようだ。

$ sudo sh -c 'echo $HOME'

とすると、Debian では /root が、Ubuntu では /home/kimura が表示されることから、sudo による環境変数の引き継ぎの挙動が原因だろう。

Debian でも Ubuntu でも /etc/sudoers はほぼ同じで、

Defaults env_reset

によって環境変数のほとんどが引き継がれないように設定されている。一部の環境変数は引き継がれるが、それは以下のコマンドを実行することで表示できる。

$ sudo sudo -V

これによると Ubuntu では HOME を引き継ぐように設定されている。これは Ubuntu 独特の設定で、Bug #760140 in sudo (Ubuntu): “HOME environmental variable no long preserved with sudo by default” によって sudo へのパッチとして変更されている。この変更の動機はバグレポートをざっと読んだがよくわからない。

ちなみに、HOME を引き継がないようになったのは sudo の upstream の最近の変更による。

Ubuntu にあわせるなら、以下の行を Debian の /etc/sudoers に足せばよい。

Defaults env_keep += "HOME"
Defaults env_reset

2012-02-01

Pythonのimport

Python の import について調べてみた。第一感よりかなり複雑だ。みかけの整然さの裏にひそむドロドロは Python ではよくあること。

import はモジュールを生成する

import 文はモジュールを生成する命令である。モジュールは辞書で実装されたオブジェクトで、属性参照 m.x が m.__dict__["x"] として評価されるオブジェクトである。m.x や m.y に関数やクラスを代入することで名前空間を作成できる(x や y という名前が呼び側の名前空間を侵犯せず m. という名前の元にまとめられているという意味)。m.x や m.y に代入される関数やクラスは、Python ソースファイル(.py)に記述されている。import は .py ファイル(モジュールファイルと呼ぼう)をモジュールオブジェクトに変換する命令といえる。

import m は、空のモジュール M を生成し、ファイル m.py を実行する。その際トップレベルの代入はモジュール M の属性参照への代入として実行される(たとえば def f(): により M.f に関数オブジェクトが代入される)。最後に import を呼び出したスコープにおけるローカルな名前空間で m にモジュール M を代入する。

m.py に f() が定義されていた場合、import m を記述したスコープからは m.f() で呼び出すことができる。

モジュールファイル m.py はどこに置かれたファイルなのだろうか?import で指定されたファイルは sys.path に登録されたディレクトリから探されるので、sys.path にあるディレクトリのどこかということになる。

モジュールの階層化

上で説明した単純な import だと、モジュールファイルの名前空間が枯渇する恐れがある。また、複数のファイルからなる大規模なライブラリも作りにくい。そこでファイルを階層的に配置できる方法が提供されている。これをパッケージと呼ぶ。

パッケージは __init__.py ファイルを含むディレクトリである。

import p.m は、空のモジュール P を生成し、ファイル p/__init__.py を実行する。その際トップレベルの代入はモジュール P の属性参照への代入として実行される(たとえば def f(): により P.f に関数オブジェクトが代入される)。空のモジュール M を生成し、ファイル p/m.py を実行する。その際トップレベルの代入はモジュール M の属性参照への代入として実行される(たとえば def f(): により M.f に関数オブジェクトが代入される)。P.m に M を代入し、最後に import を呼び出したスコープにおけるローカルな名前空間で p にモジュール P を代入する。

p/m.py に f() が定義されていた場合、import p.m を記述したスコープからは p.m.f() として呼び出すことができる。

モジュールファイルとしてのパッケージ

import で指定するモジュール名に対応するのがモジュールファイルである必要はなく、パッケージでもよい。その場合、最終的に実行されるファイルは __init__.py のみとなる。import p は  p/__init__.py をモジュールに変換する(そしてその他のファイルは実行しない)。

ネストしたパッケージ

パッケージの中にパッケージを入れてネストさせる場合、末端のモジュールにいたる全てのディレクトリに __init__.py が必要である。

as によるモジュールの別名

import p.m as m と指定すると、import を呼び出したスコープのローカルな名前空間で p に P を代入するのではなく m に M (つまり P.m) が代入される。長い階層化名を持つモジュールを短い名前でアクセスするために用いる。

p/m.py に f() が定義されていた場合、import p.m as m を記述したスコープからは m.f() として呼び出すことができる。

from による定義のコピー

from p.m import f とすると、import を呼び出したスコープのローカルな名前空間で p に P を代入するのではなく f に M.f (つまり P.m.f)が代入される。

p/m.py に f() が定義されていた場合、from p.m import f を記述したスコープからは f() として呼び出すことができる。

from によるモジュールの生成

from p import m は、p に対応するパッケージディレクトリからモジュールファイル m.py を読むのにも使える。from p imprt m は、空のモジュール P を生成し、ファイル p/__init__.py を実行する。そして空のモジュール M を生成し、ファイル p/m.py を実行する。最後に import を呼び出したスコープにおけるローカルな名前空間で m にモジュール M を代入する。

p/m.py に f() が定義されていた場合、from p import m を記述したスコープからは m.f() として呼び出すことができる。

p/__init__.py に m の定義があった場合、p/m.py は実行されず、import を呼び出したスコープで m は P.m を指すことになる。

この形式の from import で階層が記述できるのは  from 部分だけである。つまり from p.q import m とは書けるが、from p import q.m とは書けない。

この形式の from import の仕様はどこに記述してあるのだろうか?見当たらない。

この形式の from import を用いると1つのパッケージから複数のモジュールを読み込むことを簡単に記述できる。from p.q.r import m1, m2, m3 のように。しかしどうも存在意義があまり感じられない。

import * による全ての指定

from p.m import * により、モジュールファイル m.py に存在する「全ての」定義をローカルな名前空間での同名の変数に代入できる。

「全て」というのは以下を意味する。m.py に __all__ という変数に文字列のリストが代入されていた場合、その文字列の名前の定義だけが「全て」となる。そうでない場合、_ で始まらない定義が「全て」となる。

なお、__all__ にしても _ で始まる名前にしても import * のときにだけ効果があるものであって、個別に具体的な名前で import したり属性参照するのを防ぐことはできない。そういう意味で Python のモジュールには名前を隠蔽する手段は存在しない。

import * によるモジュールの生成

from p import * で、p がパッケージの場合、__all__ に指定された文字列の名前に対応するモジュールファイルが全てモジュールに変換される。そして import の呼び出し側で、その文字列の名前を持つ変数に代入される。

from p import * で、p/__init__.py に all = [ "a", "b" ] とあった場合、p/a.py と p/b.py がモジュールに変換され、それぞれ a, b という名前で参照できるようになる。

正確を期すために

import によるモジュールファイルの実行は1回しか行われない。2度目以降の同じモジュールの import ではすでに生成されたモジュールオブジェクトが用いられる。

上で説明したようなことは Python の仕様には書いてない。仕様では、m.p のような記述が与えられたときにそれをロードしてモジュールオブジェクトを作る関数をどのように探すかなどについて記述してある。import メカニズムに割り込んでカスタマイズ可能なようになっているのだ。上の説明は C Python 組み込みのデフォルトのローダの挙動について説明している。デフォルトのローダの仕様がどこに記述してあるかは不明だ。

import は .py ファイルだけでなく他のファイルを実行することもできる(もちろん上述の loader をカスタマイズすれば何でも実行できるわけだが)。