(OS-p emacs)

Table of Contents

(OS-p emacs)

lisp風に書くと (OS-p emacs)、scheme風なら (OS? emacs)、自然言語風ならば、 emacsはOSですか?

前回emacsの全貌に迫ろうとして調べ初めたら、ふと思たんだ。んで、識者に 聞いてみる。まずはOSの定義からだな。

Q: OS(Operating System)とは何ですか? 要件を5個挙げて、3000文字で説明して ください。

A: OS(Operating System、オペレーティングシステム)は、コンピューターシステムの基盤となるソフトウェアです。コンピューターのハードウェア資源を効率的に管理し、ユーザーとアプリケーションソフトウェアがハードウェアを操作できるようにします。以下にOSの要件を5つ挙げて詳しく説明します。

  1. リソース管理: OSは、コンピューターのリソースを効率的に管理します。これには、CPU(中央処理装置)、メモリ、ディスクストレージ、ネットワークインターフェースなどが含まれます。リソース管理の主な目的は、複数のプロセスやタスクが同時に実行される際にリソースの競合を防ぎ、公平なアクセスを確保することです。
  2. ユーザーインタフェース提供: OSは、ユーザーとコンピューターシステムとの間でコミュニケーションを可能にするインタフェースを提供します。これには、コマンドラインインタフェース(CLI)、グラフィカルユーザーインタフェース(GUI)、タッチスクリーンなどが含まれます。ユーザーはこのインタフェースを通じて、コンピューターを操作し、アプリケーションを実行します。
  3. プロセス管理: OSは、実行中のプロセスを管理し、調整します。これには、プロセスの作成、削除、優先順位の設定、プロセス間の通信などが含まれます。また、マルチタスキングをサポートし、複数のプロセスが同時に実行されるようにします。
  4. ファイルシステム: OSは、データの永続的な保存とアクセスを可能にするファイルシステムを提供します。ファイルシステムはデータを整理し、ユーザーやアプリケーションがファイルを管理・アクセスできるようにします。この役割は、ファイルやディレクトリの作成、削除、移動、検索などを含みます。
  5. セキュリティと保護: OSは、システム全体のセキュリティを確保し、ユーザーとデータを保護するための機能を提供します。これには、アクセス制御、認証、暗号化、マルウェア対策などが含まれます。また、ユーザー間のデータの隔離やシステムの安定性を保つための機能も提供されます。

これらの要件によって、OSはコンピューターシステム全体を管理し、ユーザーとアプリケーションソフトウェアが効率的かつ安全に動作できる環境を提供します。

Q: emacsは、その挙動や構成方法から、上記の機能を満たしているように思えま す。貴方の意見を3000文字で提示してください。

A: Emacsは、テキストエディターとして広く知られていますが、それだけでなく、自己記述的であり、ほぼオペレーティングシステムのような機能を持っていると言えます。Emacsは非常に柔軟であり、ユーザーがその挙動や構成をカスタマイズできるため、OSのような機能を満たしていると言える面があります。

まず、Emacsはリソース管理において非常に柔軟で効率的です。Emacsは、様々なプラグインやモードを備えており、それぞれの機能やタスクに特化した設定が可能です。ユーザーは、プロセス間の切り替えや複数の作業を同時に行うための仮想デスクトップのような機能を、Emacsのウィンドウとバッファを用いて実現できます。

また、Emacsはユーザーインタフェースにおいても柔軟性があります。EmacsのGUIバージョンでは、マウスを使って操作することも可能ですが、特に強力なのはキーボードを中心とした操作方法です。これにより、ユーザーは高度な操作を素早く実行できます。加えて、Emacs Lispと呼ばれる言語を用いて、ユーザー自身が新たな機能やインタフェースを作成することもできます。

プロセス管理についても、Emacsは非常に優れた機能を提供しています。複数のバッファやウィンドウを同時に操作できるため、複数のタスクを同時に進行させることが可能です。さらに、Emacsはプロセス間でのデータの共有や連携も容易に行えるため、ユーザーは異なる作業やプロジェクトを効率的に切り替えることができます。

ファイルシステムについても、Emacsは非常に豊富な機能を提供しています。Emacsはバッファという仮想的なエディタ領域を持っており、複数のファイルやテキストを同時に扱うことができます。さらに、バッファやファイルの管理、検索、変更、保存などを行うための多くの組み込み機能が用意されています。

最後に、セキュリティと保護の面においても、Emacsはユーザーに柔軟性を提供します。Emacsは外部からのエディタの拡張やカスタマイズを可能とするため、ユーザーは自身のセキュリティ設定やプライバシー設定を調整することができます。また、Emacs Lispを使ったカスタム機能やパッケージもセキュリティ上のリスクを避けることができます。

Emacsは単なるテキストエディターを超えた存在であり、その柔軟性と拡張性によって、OSのような機能を提供しています。ただし、厳密なOSとしての機能をすべて備えているわけではなく、リソース管理やハードウェア制御などのOS固有の機能は持ち合わせていません。しかし、Emacsの柔軟性とカスタマイズ性は、ユーザーに非常に高度な操作と環境の構築を可能にし、その点でOSのような役割を果たしていると言えます。

Q: viやvimは、いかがでしょうか?

A: viやVimは、Emacsと同じくテキストエディターの範疇に入りますが、OSの機能を持つというよりは、テキスト編集と操作に特化しています。これらのエディターは、効率的なテキストの編集や操作を提供することに重点を置いています。

viやVimは、コマンドラインインターフェースをベースにしており、キーボードを使用して操作を行います。初めて使う人にとっては学習コストが高いですが、慣れると非常に高速で効率的な操作が可能となります。

これらのエディターは、ファイルの編集・保存・検索・置換などの基本的なテキスト処理機能を提供すると同時に、強力なマクロやカスタマイズ機能を持っています。Vimの場合、プラグインシステムやスクリプト言語であるVim scriptを利用して機能を拡張することができます。

一方で、viやVimはOSの機能を直接提供するものではありません。OS全体のリソース管理やプロセス管理、ファイルシステムの操作、セキュリティ管理などの重要な機能は提供しません。これらのエディターは、あくまでテキストの編集や操作に特化しており、OSとしての役割を果たすことはありません。

つまり、viやVimは非常に強力で効率的なテキストエディターであり、テキストの操作や編集において高度な機能を提供しますが、OSとしての広範な機能を持つわけではありません。それぞれのユーザーが好みや必要性に応じて、Emacsや他のOSに対応した機能を持つソフトウェアと組み合わせて使用することが多いです。

こうやって進めていけば、幾らでも紙面を埋められる。けど、コピペ野郎と見 做されそうなんで、これぐらいにしとく。

丸裸にする

前回は、calcを取っ掛かりにしたけど、それじゃ未知な部分が多すぎるんで、 nadd.elってファイルに下記を作成。factの乗算部分を加算にしたものだ。 これぐらい単純だと、追跡し易いだろう。

(defun nadd (n)
  (if (= n 0)
      0
    (+ n (nadd (- n 1)))))

(nadd 2)

debug-on-entry nadd して、実行する。

Debugger entered--returning value: 0
  nadd(0)
  (+ n (nadd (- n 1)))
  (if (= n 0) 0 (+ n (nadd (- n 1))))
  (lambda (n) (if (= n 0) 0 (+ n (nadd (- n 1)))))(1)
  apply((lambda (n) (if (= n 0) 0 (+ n (nadd (- n 1))))) 1)
 * nadd(1)
  (+ n (nadd (- n 1)))
  (if (= n 0) 0 (+ n (nadd (- n 1))))
  (lambda (n) (if (= n 0) 0 (+ n (nadd (- n 1)))))(2)
  apply((lambda (n) (if (= n 0) 0 (+ n (nadd (- n 1))))) 2)
 * nadd(2)
  eval((nadd 2) nil)
  elisp--eval-last-sexp(nil)
  eval-last-sexp(nil)
  funcall-interactively(eval-last-sexp nil)
  call-interactively(eval-last-sexp nil nil)
  command-execute(eval-last-sexp)

これ、再帰のベース・ケースが満足して、帰り道に差しかかった所のスナップ ショットだ。どんどん再帰する時は、冒頭部分が、Debugger entered–entering a function: になってる。

注目はスタックの一番底の部分、command-execute(eval-last-sexp) かな。 起動の始点に相当するんだな。calcの時は、この部分が、 command-execute(calc-algebraic-entry) だった。

(nadd 2000) とかやると、スタックを使い果たして、例のエラーになる。

Debugger entered--Lisp error: (excessive-lisp-nesting 1601)
  (nadd (- n 1))
  (+ n (nadd (- n 1)))
  (if (= n 0) 0 (+ n (nadd (- n 1))))
  nadd(1470)

emacsには、インクリメンタル・サーチ(C-s isearch-forward)が備わっている。 一部の人は、検索酔いするので嫌われているけど、慣れれば便利。こいつの呼 出を辿ってみると

Debugger entered--entering a function:
 * isearch-forward(nil 1)
  funcall-interactively(isearch-forward nil 1)
  call-interactively(isearch-forward nil nil)
  command-execute(isearch-forward)

やはり、起点は、command-executeになってる。これがshell相当なんだな。

doc

闇雲にやっても効率が悪いので、少し資料の収集をしる。

22 コマンドループ

E.7 Emacsプリミティブの記述

このあたりかなあ。歴史が有るだけに、資料が日本語になってて有り難い。翻 訳物をおしげなく提供されてる各位に感謝です。 それから、下記のgdb支援も便利そうなので、勉強しれ

emacs-29.1/src/.gdbinit

byte compile

資料探しをしてる最中に面白いのに遭遇したので、確認しとく。いわゆるバイ ト・コンパイルの威力を誇示する例。

 *** Welcome to IELM ***  Type (describe-mode) or press C-h m for help.
ELISP> (defun silly-loop (n)
  "ループをN回繰り返し実行して時間を秒でリターンする"
  (let ((t1 (float-time)))
    (while (> (setq n (1- n)) 0))
    (- (float-time) t1)))
silly-loop
ELISP> (silly-loop 50000000)
4.57642126083374
ELISP> (byte-compile 'silly-loop)
#f(compiled-function
   (n)
   "ループをN回繰り返し実行して時間を秒でリターンする" #<bytecode -0x1917fa9030f3db4a>)

ELISP> (silly-loop 50000000)
0.766021728515625

コンパイルしていないと4.5秒かかる演算が、コンパイルすると0.8秒未満にな りましたよって事だ。これオイラーの所では、最速な、ArchLinux on VMWare での結果だ。馬鹿げたループ、計測の為に作為をもって作られたものなんで、 通常使用の場合これだけのパフォーマンスを常に発揮できるとは限らない。

Debugger entered--entering a function:
 * silly-loop(1000)
  eval((silly-loop 1000) t)
  ielm-eval-input(#("(silly-loop 1000)" 0 17 (fontified t)) nil)
  ielm-send-input(nil)
  ielm-return()
  funcall-interactively(ielm-return)
  call-interactively(ielm-return nil nil)
  command-execute(ielm-return)

小さい数のループだと、コンパイルしたコードより、素の方が速い。バイトコー ド実行の為に仮想マシンを用意するオーバーヘッドが災いしてるのかな。同 種の問題は、rubyのjitにも有るのかな。ああ、あちらは実マシンか。

両方共、evalから呼出されているから、その部分を点検すれば、何かヒントが 得られるかな?

bytecode実行の部分を 探し切れず。方針を変えて、前の方に出てきた文字列の #f(compiled-function を頼りに、こんなの見付けた。

DEFUN ("byte-code-function-p", Fbyte_code_function_p, Sbyte_code_function_p,
       1, 1, 0,
       doc: /* Return t if OBJECT is a byte-compiled function object.  */)
  (Lisp_Object object)
{
  if (COMPILEDP (object))
    return Qt;
  return Qnil;
}

それが縁結びになって、

#0  Fbyte_code_function_p (object=XIL(0x811d5ccb)) at data.c:500
#1  0x1ab97985 in funcall_subr (subr=0x3a9f1fc0 <Sbyte_code_function_p>,
    numargs=1, args=0x429e732c) at eval.c:3034
#2  0x1abe1975 in exec_byte_code (fun=<optimized out>,
    args_template=<optimized out>, nargs=<optimized out>, args=<optimized out>)
    at bytecode.c:809
#3  0x1abe09c5 in Fbyte_code (bytestr=<optimized out>, vector=XIL(0x77fc21fd),
    maxdepth=make_fixnum(9)) at bytecode.c:329
#4  0x1ab91972 in eval_sub (form=XIL(0x811d6ab3)) at eval.c:2504
#5  0x1abc128d in readevalloop (readcharfun=<optimized out>,
    infile0=<optimized out>, sourcename=XIL(0x77fcfcd4),
    printflag=<optimized out>, unibyte=XIL(0), readfun=XIL(0), start=XIL(0),
    end=<optimized out>) at lread.c:2350
#6  0x1abbf827 in Fload (file=<optimized out>, noerror=XIL(0),
    nomessage=XIL(0x18), nosuffix=XIL(0), must_suffix=<optimized out>)
    at lread.c:1588
#7  0x1abc141e in save_match_data_load (file=XIL(0x62b6b02c), noerror=XIL(0),
    nomessage=XIL(0x18), nosuffix=XIL(0), must_suffix=XIL(0x18))
    at lread.c:1637
#8  0x1ab96380 in load_with_autoload_queue (file=XIL(0x62b6b02c),
    noerror=XIL(0), nomessage=XIL(0x18), nosuffix=XIL(0),
    must_suffix=XIL(0x18)) at eval.c:2287
#9  0x1ab94274 in Fautoload_do_load (fundef=XIL(0x62b6b00b),
    funname=XIL(0x27e84110), macro_only=XIL(0)) at eval.c:2333
#10 0x1ab975ac in funcall_general (fun=<optimized out>, numargs=2,
    args=0x429e70f0) at eval.c:2960
     :
#27 0x1ab93d3e in Ffuncall (nargs=2, args=0xcf7c3bc0) at eval.c:2995
#28 0x1aafddab in call1 (arg1=XIL(0x811d5ccb), fn=<optimized out>)
    at ./../lwlib/../src/lisp.h:3248
#29 command_loop_1 () at keyboard.c:1503
#30 0x1ab94b26 in internal_condition_case (bfun=0x1aafd780 <command_loop_1>,
    handlers=XIL(0x48), hfun=0x1aafee00 <cmd_error>) at eval.c:1474
#31 0x1aafd76a in command_loop_2 (handlers=XIL(0x48)) at keyboard.c:1133
#32 0x1ab943a4 in internal_catch (tag=XIL(0x6d80),
    func=0x1aafd730 <command_loop_2>, arg=XIL(0x48)) at eval.c:1197
#33 0x1aafcfa1 in command_loop () at keyboard.c:1111
#34 0x1aafce34 in recursive_edit_1 () at keyboard.c:720
#35 0x1aafd13f in Frecursive_edit () at keyboard.c:803
#36 0x1aafc084 in main (argc=<optimized out>, argv=0xcf7c3da4) at emacs.c:2529

これ、普通に関数を登録しようとした時の挙動。いやになる程、深い所で活動 してるな。言語明瞭、意味不明瞭の典型例。まあ、30年誓くの集積を、ポッと 理解しようとは、身の程知らずもはなはだしい。

exec func

普通の関数を実行する時は、どんな風になる? 超簡単な関数を定義してから、 実行。調べた資料によると、C言語側での相当な関数は、Ftanに(機械的と言う か慣習で)なる。よって、そこにBPを置く。

(defun  tt (n)
  (tan n))

(tt 1)
#0  Ftan (arg=make_fixnum(1)) at floatfns.c:144
#1  0x00567f1e in eval_sub (form=XIL(0x21a44543)) at eval.c:2449
#2  0x0056d93b in Fprogn (body=XIL(0)) at eval.c:436
#3  funcall_lambda (fun=fun@entry=XIL(0x21a445b3), nargs=<optimized out>,
    arg_vector=0xffbfd940) at eval.c:3233
#4  0x0056c480 in apply_lambda (fun=XIL(0x1), fun@entry=XIL(0x21a445b3),
    args=make_fixnum(1), count=208) at eval.c:3103
#5  0x00567efc in eval_sub (form=XIL(0x21a59113)) at ./lisp.h:1350
#6  0x0056c2a8 in Feval (form=XIL(0x21a59113), lexical=XIL(0)) at eval.c:2361
#7  0x0056d475 in funcall_subr (subr=0x89bd88 <Seval>, numargs=2,
    args=0x213d33f8) at eval.c:3059
#8  0x005ac4dd in exec_byte_code (fun=<optimized out>,
    args_template=<optimized out>, nargs=<optimized out>, args=<optimized out>)
    at bytecode.c:809
#9  0x0056d981 in fetch_and_exec_byte_code (fun=XIL(0x20cf4bdd),
    args_template=257, nargs=1, args=<optimized out>) at eval.c:3081
#10 funcall_lambda (fun=XIL(0x20cf4bdd), nargs=1, arg_vector=0xffbfe118)
    at eval.c:3153
#11 0x0056d39f in funcall_general (fun=make_fixnum(1), numargs=1,
    args=0xffbfe118) at ./lisp.h:1350
 :
#31 0x004ea8a5 in Frecursive_edit () at keyboard.c:803
#32 0x004e96b1 in main (argc=<optimized out>, argv=0xffbfeb24) at emacs.c:2529

Lisp Backtrace:
"tan" (0xffbfd8bc)
"tt" (0xffbfd940)
"eval" (0x213d33f8)
"elisp--eval-last-sexp" (0x213d33b8)
"eval-last-sexp" (0xffbfe118)
"funcall-interactively" (0xffbfe114)
"call-interactively" (0x213d3370)
"command-execute" (0xffbfe90c)

Lisp Backtrace ってのは、サービス精神の発露なのかしら。これはこれで、 有り難いな。もう一度btすると、

#0  FIXNUMP (x=make_fixnum(1)) at ./lisp.h:1290
#1  INTEGERP (x=make_fixnum(1)) at ./lisp.h:2802
#2  NUMBERP (x=make_fixnum(1)) at ./lisp.h:3029
#3  CHECK_NUMBER (x=make_fixnum(1)) at ./lisp.h:3151
#4  extract_float (num=make_fixnum(1)) at floatfns.c:78
#5  Ftan (arg=make_fixnum(1)) at floatfns.c:144
#6  0x00567f1e in eval_sub (form=XIL(0x21a44543)) at eval.c:2449
:

更に展開されてた。もとのFtanって、どんな定義p。

(gdb) l
139
140     DEFUN ("tan", Ftan, Stan, 1, 1, 0,
141            doc: /* Return the tangent of ARG.  */)
142       (Lisp_Object arg)
143     {
144       double d = extract_float (arg);
145       d = tan (d);
146       return make_float (d);
147     }

Lisp界の引数をC言語界のデータに変換。それを使ってtanを計算。結果をLisp 界に戻す為、浮動小数点に変換してる。なんだか、昔憧れてたrubyの拡張方法 を思い出すな。

そんな感傷に浸っていないで、表題の通り、OS的に解釈してみる。

emacsのコアはC言語で記述されている。OSで言う所のカーネルだな。沢山付属 してくる *.el は、ユーザーランド側だ。libcとかの基本部分に相当する物と か、散々やってきたcalcの様なアプリとか、viもどきとかな。

複数のファイルを同時に開いて、その間をスイスイと移動しながら編集できる のも、プロセスの切り替えに相当するだろう。あるプロセスはruby(のファイ ルを)扱い、別なプロセスでは、pythonと対話してるとか。。。

三角関数の計算装置とかは直接扱えないので、emacsOSに依頼しちゃう。Ftan なんてのは、ドライバーだな。ドライバーと言っても、トップ・ハーフになる かな。ボトム・ハーフは、強いて言えば、libm。

(gdb) bt 3
#0  _libm_tan (x=1) at /usr/src/lib/libm/src/s_tan.c:55
#1  0x16d1599a in Ftan (arg=0x6) at floatfns.c:145
#2  0x16d0c738 in eval_sub (form=0x446b3d03) at eval.c:2449
(More stack frames follow...)
(gdb) l
50      {
51              double y[2],z=0.0;
52              int32_t n, ix;
53
54          /* High word of x. */
55              GET_HIGH_WORD(ix,x);
56
57          /* |x| ~< pi/4 */
58              ix &= 0x7fffffff;
59              if(ix <= 0x3fe921fb) return __kernel_tan(x,z,1);

ありゃま、SunMicroの遺産とそっくりなのが出てきたわい(先月のfdlibm)。

tips

gdbでの観察は、動いているemacsにアタッチしてるんだけど、contした後は、 C-c が無視される。よって、確実にgdbに再入するには、b Ftan しとくのが良 い。(tt 1) すれば、必ずgdbに突入してくれるからね。(FreeBSDだけの挙動か な)

btして、フレームを上下しながら、ソースを見るんだけど、何かじっくりと見 られない。 btの結果を、bt-log.c とかやって保存しとく。後でそのファイルを開いて、 M-. で、関数を閲覧するのが良い。

debug-on-entry した時の Backtrace も同様に、back-log.c とかして保存す る。後は、calcFunc-gpu(3 4 5) なんてなってる場合(下線が引いてある場合)、 そこでRETを叩けば、ソースが閲覧できる。それ以外の場合は、任意の場所に カーソルを合わせて、M-. すれば良い。例えば

(and (or (consp (car a)) (fboundp (car a)) (and (not (featurep 'calc-ext)) ..

フィーチャーp って何だ? 最後がpって事はyes/no で回答できる質問って事 は、lispの慣習で分かるんだけど。

fns.c

DEFUN ("featurep", Ffeaturep, Sfeaturep, 1, 2, 0,
       doc: /* Return t if FEATURE is present in this Emacs.

Use this to conditionalize execution of lisp code based on the
presence or absence of Emacs or environment extensions.
Use `provide' to declare that a feature is available.  This function
looks at the value of the variable `features'.  The optional argument
SUBFEATURE can be used to check a specific subfeature of FEATURE.  */)
  (Lisp_Object feature, Lisp_Object subfeature)
    :

コードは読まなくても、docだけを見るだけで有用。自然にTAG jumpが使えて 便利であります。

gdb出来る様にemacsを準備しておくのが理想だろうけど、それじゃチト荷が重 い気がする。本物のカーネルをgdbするのにqemuを用意して、それから、、、っ てな、負担がいやだ。

で、ArchLinuxでは、emacsのソースだけを用意した。これで、*.c も *.el も、 上記の方法で、ソースを容易に参照できるだろう。

emacsにはpi相当として、float-pi が用意されてる事を知った。どこで、どん な風に定義されてる?

(defun ss (n)
  (sin (* n (/ float-pi 180))))

(ss 30)

こんな簡単な、ドライバー・コードを用意。勿論 float-piを明示した ものね。debug-on-entry ss してから、(ss 30) C-x C-e

Debugger entered--returning value: 0.017453292519943295
  /(3.141592653589793 180)
 * (* n (/ float-pi 180))
 * (sin (* n (/ float-pi 180)))
 * (lambda (n) (sin (* n (/ float-pi 180))))(30)
 * apply((lambda (n) (sin (* n (/ float-pi 180)))) 30)
 * ss(30)
  eval((ss 30) nil)
   :

float-piが字面に表われるまで、 d で、ステップ実行。後は、M-. で、定義 箇所にご案内。

float-sup.el.gz

;; Provide an easy hook to tell if we are running with floats or not.
;; Define pi and e via math-lib calls (much less prone to killer typos).
(defconst float-pi (* 4 (atan 1)) "The value of Pi (3.1415926...).")
(with-suppressed-warnings ((lexical pi))
  (defconst pi float-pi
    "Obsolete since Emacs-23.3.  Use `float-pi' instead."))
(make-obsolete-variable 'pi 'float-pi "23.3")
(internal-make-var-non-special 'pi)

(defconst float-e (exp 1) "The value of e (2.7182818...).")

もう、これで十分すぎる。 最後は、簡単な電卓もどき。いわゆるインタラクティブな奴ね。

(defun sd (deg)
  (interactive "nSin with degree: ")
  (message "Sin(%d) is %.15f"
           deg
           (sin (* deg (/ float-pi 180)))))

M-x sd して起動。 mini-buffer上で、対話する。

Sin with degree: 30
Sin(30) is 0.500000000000000

関数として(sd 45)を評価すると、"Sin(45) is 0.707106781186547" が、mini-bufferに 表示される。

messageって、printf なのね。interactiveは、scanf似だな。ってか、mini-bufferって、emacsに用意された、 stdin/stdoutなのか。

最後はクドクなっちゃったけど、これで今年の更新は終了です。来年も4649。

今年最後のリンク

ArchLinux/wiki Emacs

日本のEmacsユーザーのためのハブサイト

Emacs Wiki


This year's Index

Home