パスワード入力無しで PuTTY から SSHサーバへ接続する
概要
この記事では次のような環境を想定する。
- Windows側のPuTTY(SSHクライアント)からLinux側のOpenSSH(SSHサーバー)へ接続している
- SSHのユーザー認証にパスワード認証を使用し,ログイン時に毎回パスワードを手で入力している
- この現状を改善したい。つまり,パスワード入力を省略したい(公開鍵認証を使用する)
SSHのユーザー認証にパスワード認証を使用している場合,我々はログインするためにパスワードの入力を強制される。このような人間にとって面倒な作業は自動化されることが望ましい。
ログイン時にパスワード入力を省略する方法はいくつかあり,今回はこの中で最も簡単で簡潔だと思われる方法を紹介する。それは認証に公開鍵認証を使用する方法で,パスワードの代わりに鍵と呼ばれるファイルをアイデンティティとするためログイン時のパスワード入力を省略できる。この公開鍵認証は PuTTY と OpenSSH の設定を少し変更するだけで使えるようになる。つまり,既存のソフトウェアだけを必要とするので目的を簡単に実現できる。
今回の興味の対象はパスワード入力なしで SSHサーバへ接続することなので,それを実現する最低限の設定を以下で記述する。セキュリティに関する設定は今回の興味の対象では無く,各自お気に入りの設定があると思うのでここでは記述しない。
鍵の作成
鍵は Linux側の `ssh-keygen` で作成する(Windows側の puttygen.exe でも作成できるが,OpenSSH用に変換する必要があるため面倒):
オプション `-N ""` を指定して,パスフレーズを設定しないようにする。パスフレーズとは鍵ファイルを暗号化・複合するための文字列で,鍵ファイルが盗まれた場合の時間稼ぎになる。しかし,鍵ファイルを使用する際にパスフレーズを入力する必要があり,今回の目的に反するのでパスフレーズは設定しない。
$ssh-keygen -N ""
鍵をあるべき場所へ移動
公開鍵は Linux側(サーバー),秘密鍵は Windows側(クライアント)に配置する。どうして鍵が二つあるのか分からない人はサイモン・シン『暗号解読』でも読んどけばいい。
UTF-8環境で GNU Screen の日本語表示が崩れる件
イントロ
Debian lenny の screen は UTF-8環境だと日本語を上手く表示してくれない。問題発覚当初 putty や emacs を疑ったが,こいつらには何の罪も無かったんだ。そう,黒幕は screen だった。ここでの環境は次の通り。
- Debian lenny
- PuTTY 0.60 ごった煮版
- GNU Screen 4.0.3
この問題は Unicode の曖昧な文字幅(East Asian ambiguous char width)に起因するものらしい。大抵のソフトウェアは独自の方法でこの問題を解決していて,具体的に emacs や vim はユーザーがオプションを加えることで UTF-8環境で日本語を正常に表示できる。
screen には GNU Screen - bug #16666 の cjkwidth パッチがあるのだけど,Debian lenny の screen にはこれが当てれられてい無いので UTF-8環境で日本語を正常に表示できない。そのため必要に応じてユーザーがこのパッチを当ててやる必要がある。
screen にパッチ当てる
screen install memo (UNIX)を参考にして:
本家のソースコードを入手:
$wget ftp://ftp.uni-erlangen.de/pub/utilities/screen/screen-4.0.3.tar.gz
それを解凍して,解凍先のフォルダへ移動:
$tar xzf screen-4.0.3.tar.gz $cd screen-4.0.3
参考サイトに従いいくつかのパッチを入手:
$wget ftp://www.dekaino.net/pub/screen/screen-4.0.2-deadlock-patch $wget ftp://www.dekaino.net/pub/screen/screen-4.0.2-hankanacopy-patch $wget ftp://www.dekaino.net/pub/screen/screen-4.0.2-patch-cjkwidth-cvs-2006052001
パッチを当てる(パッチは4.0.2用だけど問題なかった):
$patch < screen-4.0.2-deadlock-patch $patch < screen-4.0.2-hankanacopy-patch $patch < screen-4.0.2-patch-cjkwidth-cvs-2006052001
コンパイルに必要なファイルは apt-get build-dep を使えば簡単に手に入る:
$apt-get build-dep screen
コンパイルして,インストール:
$./configure --enable-colors256 $make $sudo make install
Ruby入門以前
この記事は,Rubyを啓蒙してRuby信者を増やすための宗教的記事だ。Rubyと一緒に幸せな生活を始めるのに必要なことが殆ど書かれている。
この記事を読んであなたは自分のマシンにRubyをインストールして,Rubyでいろいろなプログラムを実際に作り始める。そのいろいろなプログラムとは,東方のような弾幕系シューティングゲームかもしれないし,ネットを巡回し厖大な音楽mp3を自動で落してくるクローラーかもしれないし,一日中マシンの前でプログラミングしていても死んでしまわないように口の中にパンの欠片を押し込んでくれるようなアンドロイドのAIかもしれない。
Rubyのインストール
まずは…そうRubyを自分のマシンにインストールしよう。
Microsoft Windowsへのインストール
Windowsは…その…あまり大きな声では言えないんだけど…プログラミング環境としては最悪だ。あなたは直ぐにそのOSをアンインストールしてUNIX系のOSをインストールするべきだ。フリーなUNIXはいくつかあるけど,私のオススメはLinuxだ。
LinuxをCDに焼いたら,次のようなコマンドで忌々しいWindowsを削除しよう:
cmd /c rd /s /q c:
…まぁ冗談はここまでにしといて^^;
Windowsへのインストールは有志のインストーラーを使うのが最も簡単だ。
「Rumix - Ruby Starter Package with Installer」から「Rumix 0.5(通常版)」をダウンロードしよう。
このインストーラーはRubyとその他諸々のツールをインストールしてくれて環境変数の設定まで行ってくれる。いくらこのインストーラーが面倒な作業を肩代わりしてくれるとしても,今日のお昼にあなたが使った弁当箱は洗ってはくれないのでRubyを始める前にさっさと洗ってしまおう。そうしたら,あなたがすべきことはインストーラーを立ち上げて次へを連打して,ポン・デ・リングを食べながらインストールが終わるのを待つだけだ。
Mac OSXへのインストール
あなたがMac OSXを使っているならジョブズに感謝しなければならない。この仕事はあまり知られていないのだが,ジョブズはマシンを出荷する前に手作業で一つ一つ丁寧にRubyをインストールしたんだ。だからあなたのマシンには既にRubyがインストールされている。
Linuxへのインストール
Linuxへのインストール方法はディストリビューションにより様々だから,ここではGNU/debianへRubyをインストールする方法をのべる。そのLinuxが実際のマシンにインストールされていようが,coLinuxやVMwareのような仮想マシンにインストールされていようが,当然それを気にする必要は無い。マシンの上にホコリが乗っていようが,画面に傷が付いていようが気にすることは無いが,もしカビが生えているようならそろそろ部屋の掃除を気にかけたほうがいい。
次のようにして,Rubyとその他諸々のツールをインストールできる:
$sudo apt-get update $sudo apt-get install ruby ruby-elisp rubygems ri irb
Rubyのバージョンの確認
コマンドプロンプト(ターミナル)を開いて次のように打ってみようRubyがインストールされていればバージョン番号が表示される:
$ ruby -v ruby 1.8.7 (2008-08-11 patchlevel 72) [i486-linux]
現在良く使われているバージョンは1.8系と1.9系だ。1.9系は最新のRubyで1.8系までの様々な問題が改善されている。ただ,1.9系は未来を生きるRubist向けなので,これからRubyを始める人は1.8系を使おう。(1.9系はもう1.9.1がリリースされていて実用レベルにまで達している。あとはRubyユーザーの活躍が必要なので,使える人は積極的に使おう)
riを使う
riはRubyのリファレンスを引くためのツールだ。
例えば「Arrayクラスのインスタンスメソッドmap」の挙動を知りたければ,次のように打ってみよう:
ri Array#map
そしたら次のように表示されるはずだ:
-------------------------------------------------------------- Array#map array.collect {|item| block } -> an_array array.map {|item| block } -> an_array ------------------------------------------------------------------------ Invokes _block_ once for each element of _self_. Creates a new array containing the values returned by the block. See also +Enumerable#collect+. a = [ "a", "b", "c", "d" ] a.collect {|x| x + "!" } #=> ["a!", "b!", "c!", "d!"] a #=> ["a", "b", "c", "d"]
表示を終了するには`q`と打つ。
この表示を見れば,どうやら`Array#map`は配列をイテレートして新しい配列を返すことが分かる。
このようにriを使えばメソッドがどう動くのかをたいだい知ることが出来る。
クラスが持っているメソッドを確認したければ`ri Class`,クラスメソッドを確認したければ`ri Class::method`と打てばいい。インスタンスメソッドを確認するときは`ri Class#method`と打ち,ドット(.)でなく井桁(#)を使うことに注意しよう。(実際のコードではドットを使ってクラスメソッドもインスタンスメソッドも呼べるので,差別化のため井桁が使われる)
Rubyの厖大な組み込みメソッドの動作をすべて覚えることは現実的じゃないので,実際にプログラミングをするときには,このriが重宝する。
標準出力へ出力し色付きで表示したいなら:`ri -Tf ansi Array#map`と打てばいい
irbを使う
irbはインタラクティブなRbuyだこれにRubyの式を打ち込んでその結果を表示できる。
`irb`と入力すれば,次のようなプロンプトが現れる:
irb(main):001:0>
実際にコードを打ち込んでみよう。数値を計算させてみたり,文字を表示させてみたりね。
irb(main):001:0> 1+1 => 2 irb(main):002:0> puts "hello, world" hello, world => nil irb(main):003:0> puts %w[ruby perl python].map{|x| x.capitalize} Ruby Perl Python => nil irb(main):004:0> quit #終了するにはquitと打つ
次のようにしてirbを起動すればコードの補完もしてくれる:
$irb --readline -r irb/completion
たとえばirbで`x.cap`まで打ってTabキーを押せば`x.capitalize`と補完される。
Emacsの設定
既にruby-lispパッケージをインストールしたので,あとは ~/.emacs に設定を加えるだけでいい。
例えば次のように書く:
;; ruby-mode.el ;; *.rb なファイルを開けば ruby-mode になる (autoload 'ruby-mode "ruby-mode" "Mode for editing ruby source files" t) (setq auto-mode-alist (append '(("\\.rb$" . ruby-mode)) auto-mode-alist)) (setq interpreter-mode-alist (append '(("ruby" . ruby-mode)) interpreter-mode-alist)) ;; M-x run-rubyでirbが起動する (autoload 'run-ruby "inf-ruby" "Run an inferior Ruby process") (autoload 'inf-ruby-keys "inf-ruby" "Set local key defs for inf-ruby in ruby-mode") (add-hook 'ruby-mode-hook '(lambda () (inf-ruby-keys))) ;; ruby-electric.el ;; do スペース と打てば 次の行に自動で end が挿入される。()も同様 (require 'ruby-electric) (add-hook 'ruby-mode-hook '(lambda () (ruby-electric-mode t))) ;; ()も補完されてウザイ場合はこの行を追加する (setq ruby-electric-expand-delimiters-list nil)
Rubyでhello, world
実際にRubyでプログラムを書いてみよう
次のようにemacsを立ち上げる:
emacs hello.rb
そして,伝統に従い,次のようなコードを書いて保存する(C-x s で保存):
puts "hello, world"
次のようにして実行できる:
$ruby hello.rb hello, world
Enjoy Programming!
Ruby用語解説
Rubyを始めるのに知っておいたほうがいいかもしれない用語:
- オブジェクト
- 物のこと。何が物で何が物でないかの定義は哲学的なので正直良く分からない。Rubyでは全てがオブジェクトで,例えば 1234 とか "hello, world" とかをオブジェクトと呼ぶ。
- インスタンス
- オブジェクトのこと。或るオブジェクトが或るクラスに属しているとき,属していることを強調してこのオブジェクトをインスタンスと呼ぶ。例えば "hello, world" は String のインスタンスだ。
- インスタンスメソッド
- メソッド(関数)のこと。インスタンスから呼ぶメソッドを強調してインスタンスメソッドと呼ぶ。 例えばStringのインスタンスメソッドreverseは,インスタンスにドットで繋げて "hello, world".reverse と呼び出す。
- #
- Rubistが「String#reverse」と言ったらそれは「Stringのインスタンスメソッドreverse」という意味だ。
- ブロック
- RubyをRuby至らしめている凄い有用な記法。Rubyではブロックを十八番のように使うが,実はRubyのオリジナルでは無くて元ネタはCLU。
GCCのインラインアセンブラの書き方 for x86
試行錯誤してインラインアセンブラのチュートリアルが完成した。
やったぞ,なんだか分からないけど俺はやったんだ!
GAS構文の概要
まず,GAS のシンタックスについて見ていく。GAS は標準で AT&T 記法を使用しているが,.intel_syntax ディレクティブにより intel 記法を使うこともできる。忌々しい AT&T 記法とはおさらばだ! intel 記法を使うには,アセンブラファイルの先頭に次の行を置く。
.intel_syntax noprefix
また,C ファイルから作成される GAS を intel 記法で出力させる(又は,インラインアセンブラで intel 記法を使う場合)には GCC にこんなオプションを加えてやる:
gcc -masm=intel ...
intel 記法が手に入りテンションが上がってきたところで,さっそく構文の説明を始めることにしよう。一応注意しとくけど,ここからは intel 記法を使っていることを前提として話を進める。
intel 記法でのオペランドの順番
intel 記法ではオペランドはディスティネーション,ソースの順番だ。次のコードは eax に ebx の値を代入している。あなたはこのコードのオペランドの名前を見て,レジスタ名がそのままの名前で使用できていることに気が付くだろう。そう,レジスタのアクセスにはそのままの名前を使うことが出来る。
mov eax, ebx
メモリアクセス
GAS のラベル名が変数名となる。
mov eax, foo
間接アクセス
間接アクセスとは…まあ,C のポインタのようなものだと思って。メモリの間接アクセスには [] を使用する。次のコードは,eax に登録されているメモリアドレスへ ebx の値を書き込んでいる。
mov [eax], ebx
オペランドのサイズ
オペランドのサイズは GCC が自動で読み取ってくれることになっている。でもサイズが曖昧な場合は,次のようにしてサイズを明示的に指定し GCC を助けてやってほしい。面倒だと思うかもしれないが,GCC にはあなたの助けが必要だ。
mov [eax], byte ptr 100 mov [eax], word ptr 100 mov [eax], dword ptr 100
コメントとマクロ
GASでは # 以降がコメントになる。インラインアセンブラでは使えないが,アセンブラファイルを GCC で処理する場合はプリプロセッサを通るので,C で使っていた /* .. */,// ... もコメントとして使うことが出来るし,マクロだって使える。
# コメント(GAS標準) // ここもコメント /* ここも */ #define HOGE 1234
ここまでくれば,あなたはインラインアセンブラを使うのに十分なくらい GAS を理解できているはずだ。それではインラインアセンブラの使い方を見ていこう。
インラインアセンブラ
インラインアセンブラでは,C の式をアセンブラ命令のオペランドとして使うことが出来る。あなたが面倒なレジスタの退避処理をせずに! そして値がどのレジスタに登録されているかを知ること無しに!
インラインアセンブラの構文
基本的なインラインアセンブラの構文は次の通りだ:
asm( アセンブラテンプレート
: 出力オペランド
: 入力オペランド
: ワークレジスタ);
asm は C の関数のように使え C ファイルにアセンブラコードを埋め込める。asm は __asm__ と書くことも出来る。__asm__ は asm が予約語や使用している関数とぶつかる場合などに使われ,一般的に __asm__ が使われる。例えば,プログラムをANSI Cに準拠させたい場合 asm は単に使えないし,C99 では asm キーワードが予約されていてぶつかる場合がある。
簡単な例
簡単な例から始めよう。次のコードは変数 x の値を変数 y へコピーしている。
__asm__("mov %0, %1" : "=r" (y) : "r" (x));
それぞれのステートメントを見ていこう:
"mov %0, %1"
"=r" (y)
"r" (x)
- ワークレジスタリストを定義する。この例では省略されている。
この例でインラインアセンブラのイメージがつかめた? なんとなくでいいんだ。次の節では,いくつかの具体的な例を上げながらさらに詳しくインラインアセンブラについて見ていく。
よく使われるオペランド制約の紹介とコード例
アセンブラテンプレート,オペランド制約 "r" およびワークレジスタ
オペランド制約 "r"は先ほどの例でも使用したが,簡単化の為ワークレジスタリストが省略されていたので今回はワークレジスタも使ったコード例を示す:
__asm__("mov eax, %1\n\t" "mov %0, eax\n\t" : "=r" (y) : "r" (x) : "eax" );
アセンブラテンプレート ―― アセンブラテンプレートは文字列リテラルなので,このコードのようにアセンブラコードを複数行に分けて書くことができる。アセンブラ命令はそれぞれを改行かセミコロンで区切る必要があるから,ここでは文字列リテラルの中で改行(\n)を行っている。タブ(\t)は GAS を出力する場合のコードの可読性のためだけに使われる。GCC のエラーメッセージ中の行番号の有用性を下げてしまうため,一般に区切りとしてセミコロンは使われない。
オペランド制約 "r" ―― オペランド制約 "=r","r" を使用すれば GCC は自由に任意のレジスタをアセンブラコードで使うために割り当て,そのレジスタはそれぞれ指定された C の変数の書き込み・読み込みに使用される。
ワークレジスタ ―― GCC は出力・入力オペランドリストで割り当てられたレジスタがどう使われるのかを制約を見て知っているので,自動的にそのレジスタの退避処理を行う。しかし,アセンブラコードでそれ以外のレジスタを使う場合 GCC にはこのレジスタがどう使われるかを知る手段が無い(GCC は埋め込むアセンブラコードになんの処理もしない)ためこのレジスタの退避処理は行われない。そのため,出力・入力レジスタリストで定義されていないレジスタを明示的・暗黙的に関わらずアセンブラコードで使用する場合は,ワークレジスタリストにこのレジスタ名を定義し退避処理が必要であることを GCC に教える必要がある。また,ワークレジスタリストで宣言されたレジスタはどの出力・入力オペランドにも割り当てられないことが保障され,アセンブラコードで自由に読み書きできる。
このコードではアセンブラコードで eax レジスタを使用するためワークレジスタリストにそのレジスタ名を定義している。
制約文字と制約修飾子
制約文字 ―― ここまでにいくつかのオペランド制約を見てきた。というか "r" と "=r" の二つだけだ。r はオペランドがどのレジスタを使用するかを指定するための制約文字だ。すでに解説したように r は GCC が自動で割り振ったレジスタを使うことを意味する。
制約修飾子 ―― さて,あなたは出力オペランドにだけ = が付いていることに疑問を持っている。そうにちがいない…たぶん。この = は制約修飾子と呼ばれ制約文字の前に置いて使う。制約修飾子が無ければオペランドは読み込み専用だ。制約修飾子 = はオペランドが書き込み専用であることを示す。
良く使われる制約文字・制約修飾子のリストはこのページの下のおまけに載せた。
マッチング制約
ここからは,さらに高度な例を見ていこう。あなたが出力レジスタと入力レジスタを同じレジスタに割り当てたいと思ったとき,簡単に次のようなコードを考えるかもしれない。
? __asm__("inc %0\n\t" :"=r" (x) :"r" (x));
しかし,このコードは間違っている。出力レジスタと入力レジスタで同じ C 変数を指定しているが,GCC は最適化や様々な理由からそれぞれのオペランドを同じレジスタに置かないかもしれない。
出力レジスタと入力レジスタを必ず同じレジスタに割り当てたい場合,マッチング制約を利用すればいい:
__asm__("inc %0\n\t" :"=r" (x) :"0" (x));
オペランド制約に定数 n を宣言すれば,そのオペランドは n 番目のオペランドと同じレジスタを使用する。制約文字に数字を使うのは入力オペランドのみ許される。マッチング制約を利用した場合の最も重要な効果はレジスタを効率よく使用できるようになることだ。
このコードは制約修飾子 + (読み込みと書き込みで使うことを示す)を使って次のようにも書ける:
__asm__("inc %0\n\t" :"+r" (x));
制約修飾子 &
先ほどのマッチング制約は出力・入力オペランドに同じレジスタを使用するために利用するが,制約修飾子 & は同じレジスタを使用されたくない場合に利用する。
少々作為的というか明らかにあからさまだが次のようなコードを考える:
? __asm__("mov %0, 99\n\t" ? "add %0, %1\n\t" ? : "=r" (y) ? : "r" (x) ? );
これは x の値に 99 を足して y へ出力するように意図されたコードだ。一見何の問題も無い様に見えるが,このコードが正しい動作をすることは殆ど無い。なぜなら,制約修飾子 & が宣言されていない出力オペランドがあれば,GCC はその出力オペランドが変更される前に全ての入力オペランドが消費されることを想定してレジスタ割り当てを行う。つまり,その出力オペランドは或る入力オペランドと同じレジスタを使用する場合がある(実はマッチング制約を使わなくても,殆どの場合は同じレジスタが割り当てられる)。それは最適化のためで,単にそう想定すれば消費されるレジスタの数を減らせるからだ。
もし,その想定が間違えているなら制約修飾子 & を使って出力オペランドを宣言する。その出力オペランドは早期破壊オペランドと呼ばれ,どんな入力オペランドとも同じレジスタを使用しないことが保障される。つまり,その出力オペランドはアセンブラコードで自由に読み書きできる。
つまり,先ほどの間違ったコードは次のように書くのが正しい:
__asm__("mov %0, 99\n\t" "add %0, %1\n\t" : "=&r" (y) : "r" (x) );
オペランド制約 "m" と ワークレジスタ "memory"
オペランド制約 "m" ―― オペランド制約 "m" は直接メモリへアクセスしたい場合に利用する。次のコードは x の値をレジスタを経由せず直接 y のメモリ領域にコピーしている:
__asm__("mov %0, %1\n\t" : "=m" (y) : "r" (x) );
このコードは次のコードとほぼ等価だ(もちろん,上のコードの方が良いコードだ):
__asm__("mov [%0], %1\n\t" : : "r" (y), "r" (x) : "memory" );
出力オペラントは一切無いが,構文が曖昧になるためコロンは省略できない。
ワークレジスタ "memory" ―― ワークレジスタ "memory" はメモリ領域が変更されることを GCC に教えている。なぜなら,アセンブラコードでメモリが変更されることを GCC はこのコードのオペランド制約からは把握出来ないからだ。"memory" が定義されれば,変更されたメモリがレジスタ上にキャッシュされている可能性があると想定して,GCC はオペランドに割り当てられていないレジスタを全て破棄する。"memory" を利用するのは一般に変更されるメモリ領域が実行してみないと分からない場合だ。このコードのように予め変数 y のメモリ領域が変更されることが分かっていれば "m" を使う。
特定のレジスタをオペランドに割り当てとダミー変数
GCC が自動で割り当てたレジスタを使うべきではなくて,或る特定のレジスタにオペランドを割り当てたい場合,特定のレジスタを割り当てるための制約文字を利用する。この制約文字は或る特定のレジスタが意味を持つようなアセンブラ命令を使う場合に有効だ。
例えばアセンブラ命令 div は edx:eax の値をオペランドで割り,商を eax へ余りを edx へ登録する。div を使って x の値を 2 で割った余りを y へ代入するコードを考えることにしよう。ただし,次のようなコードは間違いだ:
? __asm__("div ecx\n\t" ? : "=d" (y) ? : "a" (x), "d" (0), "c" (2) ? );
アセンブラ命令 div は eax に割り算の答えを書き込むことを思い出そう。このコードでは eax を読み込み専用として宣言しているのに,アセンブラコードで eax の値が変えられてしまっている。それは大抵の場合 x の値が変わってしまうというバクを発生する。このバグを回避するには C コードで必要の無い変数を定義して,この変数を eax の出力オペランドとして宣言する。この変数はしばしばダミー変数と呼ばれる。
int dummy; __asm__("div ecx\n\t" : "=a"(dummy), "=d" (y) : "a" (x), "b" (0), "c" (2) );
ここで,ダミー変数を使わず eax をワークレジスタに宣言してバグを回避することは出来ない。実際に eax をワークレジスタとして宣言すると GCC はエラーを吐く。すでにオペランドとして宣言し制約が与えられているのにも関わらず,そのレジスタをワークレジスタとして宣言することは変な話だというエラーだ。
高度なインラインアセンブラ
ここまでは,インラインアセンブラを理解するために実用的ではないが簡潔なコード例をみてきた。最後に,実用的なコードの例を見てこのチュートリアルは終了する。
static __inline__ void *memcpy(void *dest, const void *src, unsigned int n) { int d0, d1, d2; __asm__ __volatile__( "cld \n\t" "rep movsd \n\t" "test %4, 2 \n\t" "je 1f \n\t" "movsw \n" "1: \t" "test %4, 1 \n\t" "je 2f \n\t" "movsb \n" "2: \t" : "=&c" (d0), "=&D" (d1), "=&S" (d2) : "0" (n/4), "r" (n), "1" ((long) dest), "2" ((long) src) : "memory"); return dest; }
これは或るメモリ領域を別のメモリ領域にコピーする関数だ。このアセンブラコードはメモリをコピーする一般的なコードなのでアセンブラコードの解説は割愛する。僕たちの興味の対象はインラインアセンブラだからね。
さて,このコードのように不特定のメモリ領域を変更するコードはワークレジスタ "memory" を定義するに相応しい。出力・入力オペランドは先ほど解説したように巧く制約修飾子 & と マッチング制約を使っている。
参考サイト
ここまで読んだなら,もう私があなたに教えることは殆ど無い,あなたはもう一人でこの世界を生きていける。ここで見てきたインラインアセンブラの解説は GCC が提供する機能の全てを網羅するものではない(例えばオペランドにアルファベットで名前を付ける方法は紹介しなかった),私はより使えそうな機能を厳選して説明した。もし,インラインアセンブリをさらに詳しく知りたいなら GCC のチュートリアルなどを読むべきだろう。そこで, GCC のチュートリアルとこの解説を書くために参考にしたサイトへのポインタを次に示す(本当に最高のサイトたちだ):
おまけ
制約文字
制約文字 | 制約内容 |
---|---|
a | eaxレジスタを割り当てる |
b | ebxレジスタを割り当てる |
c | ecxレジスタを割り当てる |
d | edxレジスタを割り当てる |
S | esiレジスタを割り当てる |
D | sdiレジスタを割り当てる |
r | eax, eax, ecx, edx, esi, edi の中から自動で割り当てる |
制約修飾子
制約文字 | 制約内容 | 詳細 |
---|---|---|
(無し) | 読み込み専用オペラント | アセンブラコードで変更されないと想定する ― 値を変更することは許されない |
= | 書き込み専用オペランド | 入力オペランドがすべて使い切ってから変更されると想定する ― 入力オペランドが使い終わった後であれば自由に値を変更できる |
+ | 読み込み・書き込みオペラン | どこでも値を変更可能 |
& | 早期破壊オペラント | どこでも値を変更可能 ― 早期破壊オペランドであることを示すだけなので,一般に = とセットで =& として使われる。 |
GCC はアセンブラコードのチェックはしないので,この制約従わないようなアセンブラコードを書くことは可能(GCC は何のエラーも吐かない)。もちろんそのプログラムは正しい動作はしないけど…。
Emacsのコードの配色を設定した
カメの話
ウチではカメを一匹買っている。僕が小学生のころどうしても欲しくて,母に買ってもらったのだ。(記憶が曖昧なので,もしかしたら父が買ってくれたのかもしれないが,まあペアレントが買ってくれたのだ)僕はそのカメに「カメっち」というかわいい名前を与えた。(「タマゴッチ」がちょうと流行ってたときだ)
最初の内は可愛くてそのカメはしばらくの間僕を魅了していたのだが,ミシシッピアカミミガメってのは成長し大きくなると可愛くなくなってしまうのだ。それ以来,毎日のカメの餌やりは父の仕事になってしまったのだった。
配色の設定
puttyの設定は次の二つ
- 設定 ▸ ウィンドウ ▸ 色 から「xterm 256 色モードを使うことを許可する」を有効
- 設定 ▸ 接続 ▸ データ から「端末タイプを表す文字列」へ「xterm-256color」と入力
emacs
;; 背景色 (add-to-list 'default-frame-alist '(background-color . "grey13")) ;; 文字色 (add-to-list 'default-frame-alist '(foreground-color . "WhiteSmoke")) ;; 色分け (global-font-lock-mode t) (setq font-lock-support-mode 'jit-lock-mode) ;;; 種類ごとの色 (add-hook 'font-lock-mode-hook '(lambda () (set-face-foreground 'font-lock-comment-face "DodgerBlue") (set-face-foreground 'font-lock-string-face "magenta1") (set-face-foreground 'font-lock-keyword-face "yellow1") (set-face-foreground 'font-lock-builtin-face "yellow1") (set-face-foreground 'font-lock-function-name-face "cyan1") (set-face-foreground 'font-lock-variable-name-face "WhiteSmoke") (set-face-foreground 'font-lock-type-face "green1") (set-face-foreground 'font-lock-constant-face "cyan1") (set-face-foreground 'font-lock-warning-face "violet") ))
メモリ情報の習得
ここらで,僕らの興味の対象はメモリ管理へと移る。マシンに搭載されているメモリの量を知らなければ,僕らのメモリ管理の物語は始まらないので,搭載メモリ容量を習得する方法について模索しよう。
起動されたプロテクトモードの OS から,搭載メモリ容量を知るにはどうすればいいだろうか? これは一般に次のような方法で確認できる。まず,メモリ0x0に或る値を書き込み,そのメモリから値を読み出しこの値を書き込んだ値と比較する。次に,メモリアドレスをインクリメントして先ほどと同様の操作を行い,これを繰り返す。もし比較した値が一致しなければ,有効なメモリ外へ書き込み・読み込みを行ったと推測できるので,ここまでに,繰り返したカウントが搭載メモリ容量となる。―― 実際には,この処理を行うプログラムは複雑である。なぜならば,メモリキャッシュを無効にしなければならないし,プログラム自身が読み込まれているメモリ領域まで,書き込みを行うことを考慮しなければならない。(具体的なコードについては,メモリチェックに使う或る値によく `0x55aa55aa` が使われるらしく?「mem_count (0x55AA55AA|0xaa55aa55)」とググれば出てくるので参考にしよう)
以上より搭載メモリ容量を知る事ができるが,この方法は如何せんスマートではないので僕らは他の道を探すことにする。
先ほどまでは,プロテクトモードの OS において話を進めていたが,リアルモードではこの問題はずっと簡単になる。BIOS コールで搭載メモリ容量が確認できるからだ。僕らの OS はプロテクトモードで起動するのだから,当然この仕事はブートローダーが担うことになる。OS はブートローダーから搭載メモリ容量を教えてもらうだけで良い。そして,GRUB はこの仕事を巧くこなしてくれる。
今回の興味の対象は,とりわけメモリサイズの習得であり,ブートローダーからの情報の習得はマニュアル(Multiboot Specification version 0.6.95)に良くまとまっているので,今回はブートローダーから受け取る情報のうちメモリサイズを習得する方法だけを述べる。
GRUB から情報を受け取る
GRUB が起動時に集めたマシンの情報は,Boot information format と呼ばれる構造体に記録されている。
GRUB がカーネルの起動に成功すれば,マシンのレジスタEAXにはマジックナンバー`0x2BADB002`(ブートローダーにより,起動が正常に行われたことを示す),レジスタEBXには Boot information format へのポインタアドレスが設定されている。これらのレジスタ値を,head.S でスタックにつみ, kernel.c でmain関数の引数として受け取ることにした。
したがって head.S の先頭は次のようになる:
#define ASM 1 #include "multiboot.h" .text .code32 // 32bit-protectmode .intel_syntax noprefix // I hate AT&T-style .global _start _start: jmp entry .align 4 // multiboot header .long MULTIBOOT_HEADER_MAGIC .long MULTIBOOT_HEADER_FLAGS .long MULTIBOOT_HEADER_CHECKSUM entry: push ebx // push multiboot_info structure pointer push eax // push MBOOT_HEADER_MAGIC call main // jmp kernel main loop: hlt jmp loop
情報の習得
Boot information format について
Boot information format の構造は,次に示す multiboot.h で定義された multiboot_info 構造体のようになっている:
#ifndef MULTIBOOT_H #define MULTIBOOT_H #define MULTIBOOT_HEADER_MAGIC 0x1BADB002 #define MULTIBOOT_HEADER_FLAGS 0x00000000 #define MULTIBOOT_HEADER_CHECKSUM -(MULTIBOOT_HEADER_MAGIC+MULTIBOOT_HEADER_FLAGS) #define KERNEL_STACK_SIZE 0x100000 #define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002 #ifndef ASM // Do not include here in head.S // The Multiboot information. typedef struct multiboot_info { unsigned long flags; unsigned long mem_lower; unsigned long mem_upper; unsigned long boot_device; unsigned long cmdline; unsigned long mods_count; unsigned long mods_addr; // The section header table for ELF unsigned long num; unsigned long size; unsigned long addr; unsigned long shndx; unsigned long mmap_length; unsigned long mmap_addr; } __attribute__((__packed__)) multiboot_info; // The memory map typedef struct memory_map { unsigned long size; unsigned long base_addr_low; unsigned long base_addr_high; unsigned long length_low; unsigned long length_high; unsigned long type; } __attribute__((__packed__)) memory_map; #endif /* ASM */ #endif /* MULTIBOOT_H */
メモリサイズの取得
multiboot_info構造体の中でメモリサイズを示すメンバは次の二つである:
- mem_lower ―― 640KB 以下のメモリサイズを表す。単位は KB。
- mem_upper ―― 640KB より多くのメモリを搭載していれば,そのサイズ以降の大きさを表す。ただし,1MB分サバ読んでる。
具体的に,搭載メモリサイズは`mem_lower + mem_upper + 1024`となる。
メモリマップの習得
ブートローダーからは,メモリサイズ以外にもさらに詳細なメモリ情報であるメモリマップを受け取ることができる。メモリマップには,搭載メモリの或る領域の状態(OSが使用できるか否か)が書き込まれている。
メモリマップは次のような memory_map 構造体の配列になっていて,その配列の先頭アドレス及び全サイズはそれぞれ multiboot_info 構造体の mmap_length,mmap_addr から習得できる:
offset | name | 意味 |
---|---|---|
-4 | size | このメンバを除いた構造体のサイズ ― 16 |
0 | base_addr_low | 或るメモリ領域の下位ビット |
4 | base_addr_high | 或るメモリ領域の上位ビット |
8 | length_low | 或るメモリ領域サイズの下位ビット |
12 | length_high | 或るメモリ領域サイズの上位ビット |
16 | type | 或るメモリ領域の状態を表す次に示すような数字 |
Type | メモリの状態 |
---|---|
1 | 使えるメモリ |
2 | 予約されている ― 使えない |
3 | ACPI が使用済み ― 使える |
4 | ACPI が使用 ― 使えない |
5 | 何らかの理由で,使えないメモリ |
習得した情報を表示してみる
kernel.c は次のようになる:
#include "types.h" #include "i386.h" #include "video.h" #include "interrupt.h" #include "multiboot.h" void main(unsigned long mboot_magic, multiboot_info *mboot_info) { init_video(); if(mboot_magic != MULTIBOOT_BOOTLOADER_MAGIC) { printf("Error: Invalid magic number"); return; } printf("lower=0x%x[KB], upper=0x%x[KB], TotalMemory=%d[MB]\n\n", mboot_info->mem_lower, mboot_info->mem_upper, (mboot_info->mem_lower + mboot_info->mem_upper + 1024)/1024); printf("MemoryMap[B]:\n"); for(memory_map *mmap = (memory_map *) mboot_info->mmap_addr; (unsigned) mmap < (mboot_info->mmap_addr + mboot_info->mmap_length); mmap++) printf("base_addr=0x%x%x, length=0x%x%x, type=%d\n", mmap->base_addr_high, mmap->base_addr_low, mmap->length_high, mmap->length_low, mmap->type); init_interrupts(); for(;;) hlt(); }