UNIXv1における制御文字を用いたclearコマンドの作り方
はじめに
本記事は「日本陰キャ協会 Advent Calendar 2022」における12日目の記事になります。
目次
動作確認に関して
今回の方法はManjaroLinuxにおけるkonsoleにて動作確認を行いました。また、制御文字に依存しているため、全ての端末で動作するとは限りません。
UNIXv1とは
UNIXv1とは1971年に公開されたUNIXの1st Editionとなります。また、特徴としてマルチタスキング、マルチユーザー、階層型ファイルシステム持っており、現代のOSにつながる要素を持っています。
制御文字とは?
制御文字とは、文字コードで決められた周辺機器の制御などに用いる特殊な文字のことです。今回利用するのは、ターミナルを制御するエスケープシーケンスとなります。
clearコマンドとは?
端末の画面をクリアにするコマンドになります。
処理の流れ
処理の流れは以下のようになります。
last1120cを用いた実装
下記コードが全体のソースコードになります。
/* clear command */ main(){ extern clear, rcur; clear(); rcur(); return; } /* clear:screen clear */ clear(){ extern esc; esc("2J"); } /* rcursor: reset curosor */ rcur(){ extern esc; esc("H"); } /* esc */ /* code:escape sequences code */ esc(code) char code[]; { extern printf; printf("%c[%s", 033, code); }
esc関数
エスケープシーケンスを発行する処理を行う関数です。033を1バイトの8進数として表示をし、文字列として続くcodeを出力します。
/* esc */ /* code:escape sequences code */ esc(code) char code[]; { extern printf; printf("%c[%s", 033, code); }
clear関数
画面をクリアするエスケープシーケンスを出力する処理を行う関数です。"\033[2J"を出力させます。
/* clear:screen clear */ clear(){ extern esc; esc("2J"); }
rcur関数
カーソル位置を0行目の0文字目に移動する処理を行う関数です。 "\033[H"を出力させます。
/* rcursor: reset curosor */ rcur(){ extern esc; esc("H"); }
使ってみる
おやなにかの木が...。
見ていると心が傷んでくるので消しておきましょう。
これで心の安寧は保たれました。
まとめ
クリスマスは消えました!!!
PS)
消しそこねた人は「クリスマス?なにそれ美味しいの?」を聞きながら当日を過ごしましょう。
参考文献
The Restoration of Early UNIX Artifacts
https://www.usenix.org/legacy/event/usenix09/tech/full_papers/toomey/toomey.pdf:
FreeBSD Manual Pages - clear
https://www.freebsd.org/cgi/man.cgi?query=clear&apropos=0&sektion=0&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html
プログラミング色々
http://7ujm.net/etc/esc.html
UNIXv1の操作方法に関して
はじめに
本記事では、初期UNIXであるResearch Unix version1(以降UNIXv1)を操作するための基本的な知識、操作方法および注意点を記載する。
前回の記事
もくじ
UNIXv1のディレクトリ構造
図1ではUNIXv1におけるディレクトリ構造を図に起こしたものである。tmpディレクトリの中身は最初から存在するutmpとedコマンドで利用されるetm* を記載している。使用するプログラムによっては他のtmpファイルが生成されることもある。
プロセス
図2は自作したpsコマンドの出力結果である。ここではSLOTが16個までしかないと疑問を感じると思う。これは、UNIXv1が16個までのプロセスしか管理できないためそのような表記となっている。また、基本的にSLOT1はinitに、SLOT2~10は端末、システムコンソールに利用されているため、残り6個のSLOTがユーザーによって利用できるプロセスとなる。
シェルの操作方法
ログイン
rootユーザーログインをしたい場合はlogin:の際にrootと入力、ユーザーログインをしたい場合はlogin:の後にユーザー名を入力する。
ユーザーken
root
ログイン可能なデフォルトのユーザーは以下となる。
ken jack
ログアウト
Ctrl-d(EOF)を押すことでログアウトができる。
ディレクトリの移動
chdirコマンドを利用して移動する。
@ chdir ..
最後が/(スラッシュ)で終わるとエラーとなる。
@ chdir ../ Bad directory
ディレクトリ内部のリスト
lsコマンドを利用することで取得ができる。
@ ls as2 getty glob init msh passwd std0 suftab uids @
ls -alもサポートされている
@ ls -al total 34 104 sdrwr- 2 root 110 Jan 1 00:00:00 . 41 sdrwr- 7 root 110 Jan 1 00:00:00 .. 106 lxrwr- 1 bin 5778 Jan 1 00:00:00 as2 105 sxrwr- 1 bin 446 Jan 1 00:00:00 getty 107 sxrwr- 1 sys 2662 Jan 1 00:00:00 glob 108 sxrwr- 1 sys 1192 Jan 1 00:00:00 init 109 sxrwr- 1 sys 186 Jan 1 00:00:00 msh 110 s-rw-- 1 sys 299 Jan 1 00:00:00 passwd 111 s-rwr- 1 root 512 Jan 1 00:00:00 std0 112 s-rwr- 1 bin 2082 Jan 1 00:00:00 suftab 113 s-rwr- 1 sys 96 Jan 1 00:00:00 uids @
プロンプト
rootユーザーの場合#、userユーザーの場合@がプロンプトとして表示がされる。
ユーザーken
root
逐次処理
コマンドの逐次処理は;(セミコロン)を利用してできる。
@ ls; ls
同時実行
&を利用して同時実行を行わせることができる。
@ ls & ls
バックグラウンドプロセス
入力の最後を&にしてコマンドを実行した場合、バックグラウンドプロセスとして稼働させることができる。
@ ls &
注意してほしいのが、バックグラウンドプロセスを無限ループで稼働させた場合、バックグラウンドプロセスの数によってはシステムがハングアップするということである。
リダイレクト
現在におけるリダイレクトもある。ファイル名の先頭2文字を特殊扱いすることで動作することに注意。
標準出力
@ ls >file
標準入力
@ cat <file
文字列
"(ダブルクォーテーション)、'(シングルクォーテーション)によって表現ができる。
@ echo "hello"
文字の削除
文字の削除は#を使い入力を行う。画面上では#として入力されているように見えるが、システム内部では1文字削除されたものとして扱われている。もし、#を入力したい場合は\#とエスケープシーケンスを利用して入力する。
@ echo "hello#####unix" unix
行の削除
行単位で削除する場合は@を利用する。これが入力された場合、行頭から@が入力された箇所までの入力が無効化される。@を入力したい場合は\@とエスケープシーケンスを利用する。
@ echo "hello"@echo "unix" unix
割込み
無限ループプログラムなどを稼働させた場合に中止させたい場合、ASCII DELを入力することで中止することができる。現代のキーボードではBackSpaceやDelキーを入力することでASCII DELの送信ができる。実行している方法は割とトリッキーな方法であり、おもしろい。
正規表現
初期の単純な正規表現として?、 * が存在する。
?は一文字マッチとなる
@ ls ma?i.s maki.s @
*はワイルドカードとなる。
@ ls *.a bilib.a filib.a liba.a libb.a libc.a libf.a @
その他注意事項
扱えるプロセスの制限
プロセスは端末のプロセスを合わせて16個までしか実行可能状態にできない。もし、それ以上のプロセスを立ち上げようとした場合
@ ls Try again
という警告が出力され、実行することができない。端末、initのプロセスは1~10までのプロセスとして常に利用しているため、残りの6個が扱えるということになる。
動かしているカーネルイメージおよびバイナリに関して
今回動かしているUNIXv1のイメージは純粋なUNIXv1ではない。コマンドにはUNIXv2のバイナリが利用されており、それを利用するためにUNIXv1にはパッチが施されている。また、C言語を動作させるためにユーザー空間の拡張が行われている。
マニュアルをダウンロード
下記の場所に公開がされている。
UNIXv1
https://www.tuhs.org/Archive/Distributions/Research/Dennis_v1/UNIX_ProgrammersManual_Nov71.pdf
もし、OCR化された文書が欲しい場合、以下の場所に公開されている。各章に分かれているのはpdfuniteコマンド等で連結すれば便利である。
UNIXv2
実行される基本コマンドはUNIXv2のものであるため、コマンドなどはこちらを参照した方が良い。
https://www.tuhs.org/Archive/Distributions/Research/Dennis_v2/unix_2nd_edition_manual.pdf
最後に
次回は基本的なコマンドの利用について記事にする予定である。
参考
- 図の作成にはFlowchart Maker & Online Diagram Softwareを利用
- GitHub - dspinellis/unix-history-repo at Research-V2 : UNIXv2におけるソースコード
- Unix Manual, first edition : UNIXv1のユーザーマニュアル
次回
未公開
UNIXv1のエミュレーション環境の構築
はじめに
本記事では、初期UNIXであるResearch Unix version1(以降UNIXv1)をPDP-11/20のエミュレーションが可能なsimhを利用し環境構築する手法を説明する。
目次
説明する環境に関して
今回環境構築をするにあたってUbuntu 20.04.4 LTS x86_64を用いて環境構築を行った。
作業ディレクトリを用意
$ mkdir UNIXv1 $ cd UNIXv1
simhのビルド
simhをソースコードからビルドする。
ビルドに必要なパッケージの準備
$ sudo apt install build-essential $ sudo apt install libpcap-dev $ sudo apt install bridge-utils $ sudo apt install uml-utilities
simhの準備
まず、エミュレータであるsimhをビルドする。simhはgithubにおけるリポジトリ(https://github.com/simh/simh)で開発がされている。よって、このリポジトリを任意のディレクトリにダウンロードする。また、この記事を書いた時simhのmasterはversion4である。
$ wget https://github.com/simh/simh/archive/refs/heads/master.zip -O simh.zip $ unzip simh.zip
simhのビルド
simhディレクトリ内に入り、ビルドを行う。
$ cd simh-master $ make pdp11
ビルドされた実行ファイルはsimh-master/BIN/pdp11として存在する。
イメージの準備
イメージのダウンロード
UNIXv1ディレクトリに戻り、イメージ関連の圧縮ファイルがダウンロードする。
$ wget https://www.tuhs.org/Archive/Distributions/Research/Dennis_v1/images-20080625.tgz -O V1image.tgz $ mkdir V1image; tar -xzvf V1image.tgz -C V1image --strip-components 1
ディレクトリの構成が以下のようになっているかを確認する。
UNIXv1 ├── V1image ├── V1image.tgz ├── images-20080625 ├── pdp11 ├── simh-master └── simh.zip
この際、simh.zipは削除しても大丈夫だが、イメージ内の環境を壊してしまった場合やリセットしたい場合のためにV1image.tgzは保持しておくことをお勧めする。
UNIXv1の起動
simh.cfgを起動スクリプトとして渡し、UNIXv1を起動する。
$ cd V1image $ ../simh-master/BIN/pdp11 simh.cfg
以下のような表示がされた場合UNIXv1の起動には成功している。
初期セットアップ
rootでログインを行う。ここからは、UNIXv1内での作業に移る。以降はバックスペースが効かないため注意すること。もし間違えた場合、@を行末に追加しEnterを押すことで入力を無効化することができる。
login: root root # ls -@ #
セットアップスクリプトと実行
tmpディレクトリに移動する
chdir tmp
cat コマンドを利用し、set.shを作成する。chdir /で始まる内容を入力するにはクリップボードを使用すれば良い。また、入力後に改行してからCtrl-dを押せば確定する。
# cat >set.sh chdir / chmod 017 tmp ls -al chdir /usr chown 6 ken ls -al chdir /usr/src/lib as crt0.s; mv a.out crt0.o mv crt0.o /usr/lib/crt0.o #
set.shを実行する。
# sh set.sh
set.shでやっていること
セットアップスクリプトで実行していることを説明する。
tmpディレクトリの権限書き換え
一般ユーザーでもedコマンド等を利用できるようにtmpディレクトリの権限を変更する。rootユーザーでtmpディレクトリの権限を変更する。
# chdir / # chmod 017 tmp
ls -alを使って権限が変更されたかを確認する。
# ls -al ... 114 sdrwrw 2 root 120 Jan 1 00:00:00 tmp ...
/usr/kenの所有者の変更
kenディレクトリ内でユーザーkenによるリダイレクトを利用できるようにする。そのために/usr/kenの所有者をrootからkenに変更する。こちらでも、rootユーザーで所有者を変更する。
# chdir /usr # chown 6 ken
ls -alを使い確認
# ls -al ... 57 sdrwr- 2 ken 70 Jan 1 00:00:00 ken ...
C言語の実行起動ルーチンの準備
/usr/src/lib/crt.sをビルドし適切な場所に配置する必要がある。まず、/usr/src/libに移動する。
# chdir /usr/src/lib
次に、ビルドをする。
# as crt0.s; mv a.out crt0.o
最後に、アセンブルしたファイルを/usr/libに配置する。
# mv crt0.o /usr/lib/crt0.o
C言語が動作するか確認
次の作業をするためにCtrl-dを押しログアウトをする。すると、プロンプトがでてくるため、そこにkenと入力しユーザーkenでログインする。その後、catコマンドを利用しソースファイルを作成する。
@ cat >hello.c
このコマンドを入力した時点で入力待ちになるため、以下のコードを入力する。
@ cat >hello.c main(){ extern printf; printf("hello UNIXv1\n"); return; }
入力後は改行をし、Ctrl-dを押すことで確定する。その後、コンパイルをする。
@ cc hello.c
この際、コンパイルできていればセットアップは完了である。実行ファイルであるa.outファイルが生成されるので実行する。
@ a.out hello UNIXv1
停止のさせ方
Ctrl-eを押すことでsimhのコマンドモードに移行する。
# Simulation stopped, PC: 007332 (MOV (SP)+,25244) sim>
ここでquitコマンドを入力することで終了する。
# Simulation stopped, PC: 007332 (MOV (SP)+,25244) sim> quit Goodbye %SIM-INFO: RF: writing buffer to file: rf0.dsk
最後に
今回は、UNIXv1をエミュレータで実行するための環境構築に関して解説した。他にもdockerイメージなども公開されているため調べてみてほしい。次回は、UNIXv1の操作方法に関しての記事を予定している。
次回
未公開
自作OSに関する記事のリンク
はじめに
もうちょい整理してとかあったらおしえてね。
目次
ブートプロトコル
multiboot2
bootboot
Stivale1 Stivale2
SMBIOS
Wiki
UEFIだとGUIDで開けるため、アンカーをメモリ上が探す必要はない。
仕様書
2.xと3.xには違いがあるので注意 www.dmtf.org
実装に関する参考資料
LibGetSmbiosString関数を見るといいかも。あとは、仕様書でdouble nullに関して調べる。
いろんなOS
GitHubとかで実装を探してる時に見つけたやつ
learn-os
EggOS
Go言語のx86カーネル(gccgoじゃないっぽい?珍しい)
TomatOS
skiftOS
ビルド時の注意
Minoca OS
CurrOS
luakernel
FlorenceOS
Zig言語で書かれてるっぽい
MITTOS64
Suckless Operating System
現在作っているブートローダに関して
Table of Contents
はじめに
メリークリスマス!適当に入れたらクリスマスイブ担当となった猫(1010)です。ちなみにクリスマスはアドベントカレンダーを書いて、夕方にセキュリティキャンプのチームとRust入門をやり、深夜にVRChatをやるといった感じですね。充実しているんだかしてないんだか…。
仕切り直して、最近の近況といきます。今年は色々変化があった年でした。しかし、もう終わりとなると感慨深いものがあります。11月ごろから、セキュリティ・キャンプ全国大会2020onlineにYトラックOS自作ゼミとして参加をしてきました。同じような趣味の方にであうことができて非常に楽しい経験だったことを覚えています。また、セキュリティキャンプを通してブートローダを作成していました。セキュリティ・キャンプ中には完成はしなかったのですが(計画性の破綻)、期間中に作るためのイメージはついたのでチョコチョコ作業をしております。
どのようなブートローダを目指しているのか?
- おしゃれ
- できる限りの情報をじっくり確認できる
- ELF形式のカーネルをブート
これらを達成できるといいなぁと考えながら作成をしています。
特にできる限りの情報をじっくり確認できるという点に関しては、以前からブートローダがスッ…と終わってしまい。ブートローダー何やっとるん?といった感覚になっていたというのが理由としてあります。要はLinuxのブート時のログのように表示できれば面白そうってことですね。ですが、Linuxのログも表示が早いため、なんか悲しいです。まぁログファイルが残るといえば早くてもいいだろうという感覚やブート時に遅かったらダルいと考えると理解できます。ですが、自作のブートローダーくらいじっくり見たいのです!
あとは、開発するにあたりはじめはフルスクラッチで書こうとしていました。ですが、ヘッダーファイルを書くのが結構大変ということもあり、現在ではEDK2を使っています。
現在の見た目
このような感じで、Linux風のログを表示します。変わっている点としては、ELFヘッダの内容を一部表示させている点ですね。現状としては普通の自作のブートローダでも出力している内容に近いですが、色を変更したりしてわかりやすくすることと情報の量を増やしていこうと考えています。また、ロゴに関してはアスキージェネレータで生成したものをPrint()で愚直に表示をさせています。
ちなみに、動画バージョンは以下に
現状デチ...(最後Satll()入れるの忘れたな?) pic.twitter.com/iWIAyC90uk
— 猫(1010) (@Wagahaiha_toto) 2020年12月24日
動作に関して
『どのようなブートローダを目指しているのか?』でも述べましたが、表示をじっくり見ることができるように画面出力の合間にStall()を利用して時間差をつけています。また、'''Kernel boot(press RET)'''の箇所では、Retrunを押すと処理が始まるようにしています。
どのようにしたか?
それぞれの調整はほんとに愚直にやりました。
表示の遅延に関して
以下のようにしています
SystemTable->BootServices->Stall(n);
nの箇所をマイクロ秒として仕込んであげることで遅延を起こすことができます。自分は、500000にしています。
statusのみの色を変えたことに関して
SetAttribute()を利用して色を変えていました。
SystemTable->ConOut->SetAttribute(SystemTable->ConOut, EFI_LIGHTRED);
上記コード例では、ライトレッドの色合い色合いにしています。このEFI_LIGHTREDはありがたいことにEDK2側で定義されており
edk2/MdePkg/Include/Protocol/SimpleTextOut.h
にて定義されています。git grepコマンド等で探せば早いかと思います。
ロゴの表示に関して
これは以下のURL先のASCIIジェネレータを利用して生成したものをPrint()で出力させています。
partorjk
これを使って以下のように、出力させると表示できました。
Print(L" __ __ __ ______ ______ \n"); Print(L"/ | _ / | / | / \\ / \\ \n"); Print(L"$$ | / \\ $$ | _______ ______ _$$ |_ /$$$$$$ |/$$$$$$ |\n"); Print(L"$$ |/$ \\$$ | / | / \\ / $$ | $$ | $$ |$$ \\__$$/ \n"); Print(L"$$ /$$$ $$ |/$$$$$$$/ $$$$$$ |$$$$$$/ $$ | $$ |$$ \\ \n"); Print(L"$$ $$/$$ $$ |$$ | / $$ | $$ | __ $$ | $$ | $$$$$$ |\n"); Print(L"$$$$/ $$$$ |$$ \\_____ /$$$$$$$ | $$ |/ |$$ \\__$$ |/ \\__$$ |\n"); Print(L"$$$/ $$$ |$$ |$$ $$ | $$ $$/ $$ $$/ $$ $$/ \n"); Print(L"$$/ $$/ $$$$$$$/ $$$$$$$/ $$$$/ $$$$$$/ $$$$$$/ \n"); Print(L" \n"); Print(L" \n"); Print(L" \n");
ここで注意するべきなのはエスケープシーケンスです。そこだけを一つずつ修正すると良い感じとなります。
妙に引っかかった箇所
ウォッチドッグタイマの無効化忘れ
Qemuを放っておくと、5分ごとに再起動を起こしており不思議だったのですが、ウォッチドッグタイマを無 効化することで解決しました。
SystemTable->BootServices->SetWatchdogTimer(0, 0, 0, NULL);
今後に関して
今後は選択画面を実装したいと考えています。Clearscreenとprintを高速で繰り返すことで実装することをイメージしており、最初はUEFI shellに行ける選択肢とカーネルブートの選択肢か、ダミーの選択肢を作成するかで迷い中…。あとは、memorymapの取得まで実装が完了しているので、ExitBootServices()周辺を実装してあげれば動くかなと考えています。
書き終えて
気づいた人は少ない、もしくはいないかもしれないですが、いつものブログの書き方とは少し体裁が異なっています。その理由は、emacsのorg-modeで作成したorgファイルをmark-downに変換した後にはてなブログに貼っつけ、微調整をしたからなんですよね。もしかして、私はemacsに盆栽をするようにカスタマイズしまくることが好きなのかな、とか思いながらアドベントカレンダーを書いていました。