シェルを作ろう(1)コマンドを実行する。

さて、今回は予告通り入力を受け取って、コマンドを実行するところまで試してみたいと思います。

使用する関数、システムコール

  • printf
  • fgets
  • strcpy
  • strtok
  • calloc/malloc
  • execve

入力を受け取る(fgets)

さて、Cで入力(文字列)を受け取りたい時どんな関数を使おうと思うでしょうか?
多くの人はscanfを思い浮かべるかと思います、この関数も使い方によってはとても便利なのですが、 シェルのコマンドでは基本的にプロンプトが表示された行に入力された文字列を一行丸ごと読みとりたいので、scanfだと少し不便です。 (ここではscanfが本質ではないですが、scanfを使うと空白文字が挟まるごとに文字列を格納するための空間が新しく必要になりますよね?)

というわけで今回は一行ごとに入力を受け取るfgetsという関数を使いたいと思います。
以下プログラム例

#include <stdio.h>

#define SIZE 128

int main(void){
    char buf[SIZE] = "";

    fgets(buf,SIZE,stdin);
    printf("%s",buf);

    return 0;
}

実行例

$gcc -Wall -std=c99 test_fgets.c -o test_fgets
$./test_fgets
   hello world!
   hello world!
$

上記のようにfgets関数を使うと改行が現れるまで、空白' ‘やタブ’\t',改行文字'\n'も合わせて一つの空間に読み込んでくれます。 (改行文字も一緒に読み込んでくれるので,printfで出力する時に\nがなくても改行してくれます。)

プログラムの実行について

さて、次はstrtokの説明です…と言いたいところですが、その前にstrtokを使う理由を説明したいと思います。 例えばlsコマンドなどを使う時,

$ls -la

実はこのようにコマンドを実行する時、入力したコマンドやオプションは空白文字ごとに分割され、main関数の第二引数に渡されて (ちなみにmain関数の第一オプションにはコマンドとオプションの合計の個数 (コマンドライン引数の個数)が渡されます。) それをプログラムが解釈して実行されています。

例えば上の例ようにコマンドを入力すると int main(int argc,char *argv[])main関数の引数を設定すると argcには2 argv[0]には"ls"という文字列(が確保されている場所へのポインタ)、 argv[1]には"-la"という文字列(が確保されている場所へのポインタ) argv[2]にはNULLが格納されます。

このように、コマンドを実行するために受け取った文字列を空白文字区切りで配列に格納するために便利なのがstrtokという関数です。

文字列を分割する。(strtok)

strtokのプログラム例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

define SIZE 128

int main(void){
    char commands[128] = "";
    char *tmp = (char *)NULL;
    
    printf("$");
    fgets(commands,SIZE,stdin);
    
    tmp = strtok(commands," \t\n");
    
    printf("%s\n",tmp);
    
    while((tmp = strtok(NULL," \t\n"))!= NULL){
        printf("%s\n",tmp);
    }
    return 0;
}

実行例

$gcc -Wall -std=c99 test_strtok.c -o test_strtok
$./strtok
    this is a pen
this
is
a
pen
$

このように strtokを使うと、一つの文字列を指定した文字で簡単に分割できます。

さて次はいよいよコマンドの実行です。

コマンドを実行する(execve)

さて、コマンドを実行するためにはexecveを使用するのですが、このシステムコールには3つの引数があります。 まず第一引数は実行するコマンドのパス名 これで指定したコマンドがあればそれを実行し(厳密には単に実行するわけではない)、なければ-1を返して通過します。 第二引数にはargvのような順でコマンドライン引数に相当する文字列集合を渡します。 第3引数にはmain関数第3引数(一般にenvpと表記される。)に渡される環境を渡します。
これはコマンドライン引数に現れないパスなどの値が格納されています。 さてそれでは以下にプログラム名を示します。
実際に試してみましょう!

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


#define BUFSIZE 128
#define ARGNUM 32

int main(int argc, char *argv[], char *envp[]){

    char buf[BUFSIZE];
    char *p;
    char *arg[ARGNUM];
    int i = 0;    

    fgets(buf,BUFSIZE,stdin);

    p = strtok(buf," \t\n");
    
    arg[i] = calloc(strlen(p),sizeof(char));
    strcpy(arg[i],p);
    //printf("%s\n",arg[i++]);

    while((p = strtok(NULL," \n\t")) != NULL){
    
        arg[i] = calloc(strlen(p),sizeof(char));
        strcpy(arg[i],p);
        //printf("%s\n",arg[i++]);
    }
    
    arg[i] = NULL;
    execve(arg[0],arg,envp);
    printf("failed execve\n");
    return 0;
}

(本当はfreeをして解放するべきですがうっかり時間がないので割愛)

実行例

$ ./test_execve 
/bin/ls -a
.   test_execve    test_fgets    test_strtok
..  test_execve.c  test_fgets.c  test_strtok.c
$./test_execve
hello
failded execve
$

注 この際、まだパス展開の機能がないのでプログラムはフルパスで指定してやる必要があります。

うまく実行できましたね! 短い説明ですが、とりあえず今回はこれで終わりとさせていただきます。

シェルを作ろう(0)導入編

先日、某理学部情報科学システムプログラミング実験の課題の一つとしてシェル(コマンドラインシェル)を作りました。 (制作期間2週間、OCamlインタプリタの作成と並行だったのでえぐかった…) 授業の課題要件を超えて色々と実装したので、せっかくなので記事にします!

環境

シェルといったらCLI,CLIといったらLinux
ということで、今回は以下の様なLinux環境上向けにシェルを作りました。

  • OS: Ubuntu14.04
  • コンパイラ: gcc4.8(マイナーバージョンは忘れたのであとで修正します。)

もしかしたらMacCygwinでも動くかもしれませんが自身がないです。 というのも今回はLinuxシステムコールの嵐!とまでは行かないまでも、多くのシステムコールを使っているので 互換性は保証されません。

ソースコード

とりあえず、作成したソースコードは以下にあるので、気になった方はぜひご覧ください。
https://github.com/progrunner17/my_shell

次回予告

シェルってなんだっけ?ってなった人こんな画面みたことないですか?
f:id:gonta42:20170530231727p:plain
この様にターミナル上で入力を受け取ってコマンドを実行したり色々してくれるプログラムです。*1

という訳で次回は以下のことをやりたいと思います!

  • プロンプトの表示*2
  • 入力の処理
  • コマンドの実行

その次はおそらくforkを使ったプロセスの複製について書きます。
(SECCAMPの応募では時間がなくて全然書けなかったのでぶっちゃけこれを先に書きたい…)

とりあえず導入編はこれで終わり、次回以降実際にシェルの作成の説明をしていこうと思います!
今回こそしっかり最後まで書き上げることを目標にします笑

注釈

*1:GUIシェルなどを考えるとこの定義とは全く異なります。
気になった人は以下リンクなどでより詳しい定義を確認してください。
シェル - Wikipedia

*2: 上記写真でbash-3.2$となっている部分。

VHDLの基本的な論理演算と型(VHDL入門その1)

VHDLの基本的な論理演算

構文 意味
A<=B AにBを代入
A and B AとBの論理積
A or B AとBの論理和
A xor B AとBの排他的論理和
not A Aの否定
A nand B not ( A and B )
A nor B not ( A or B)
A xnor B not (A xor B)

VHDLとデータ型

VHDLでは様々なデータ型が用意されているが、標準で用意されているものとライブラリ(パッケージ)を読み込む必要があるものがあるので、以下にその一覧をまとめる。
なお、VHDLでは文字列以外で大文字小文字の区別がない。

標準で用意されている型

データ型 内容
integer 32ビット符号付き整数
real 浮動小数
bit ’0’/‘1’
bit_vector bitのベクトル型
boolean true/false
character ASCII文字
time 時間の物理タイプ*1
severity level メッセージタイプ*2
natural(positive) integerのサブセット*3
string 文字列

IEEE.STD_LOGIC_1164で定義される型

データ型 内容
std_logic ’0’や’1’、不定値’X’など
std_logic_vector std_logicのベクトル型

std_logicについて

EEE.STD_LOGIC_ARITH

データ型 内容
signed 符号付き整数
unsigned 符号なし整数

IEEE.STD_LOGIC_ARITHは複数あるらしい? 参考サイト *4

STD.TEXTIOパッケージで定義される型

データ型 内容
line
text
side
width

STDはいまのところ使う予定がないので保留…

GHDLでパッケージstd_logic_arith, std_logic_signed, std_logic_unsigned, std_logic_textio. を使うときは—ieee=synopsysのオプションをつける必要がある

*1:fs、ps、ns、us、ms、sec、min、hr

*2:NOTE、WARNING、ERROR、FAILURE

*3:nturalは0以上の整数、positiveは1以上の整数

*4:std_logic_arith_syn.vhd

VHDLを試してみた。

この記事はIS17er Advent Calendar 2016 - Adventar23日目の記事として書かれました。

VHDLとは?

VHDL(Very High Speed Integrated Circuit Hardware Description Language)は米国防総省で作られたハードウェア記述言語の一種で、現在はIEEEによって標準化されている。
Adaを参考に作られた言語なので知っていると少し学習しやすいかもしれない...らしい。
(GHDLドキュメント http://ghdl.readthedocs.io/en/latest/Introduction.htmlより)
IS2017erにとってはハードウェア構成法のレポートで推奨されている言語なので、とりあえず動かしてみたときの記録を書く。

開発環境(GHDL/GTKWave)

GHDLというオープンソースVHDLシミュレータと、GHDL推奨の波形表示ソフトGTKWaveを用いる。
導入は下記サイトを参考にした。 lv4.hateblo.jp

VHDLの基本構成

VHDLは基本的に、ライブラリ宣言部、エンティティ宣言部、アーキテクチャ宣言部の3つの部分から構成される。
全加算器を例に見てみよう。
adder.vhdl

-- ライブラリ宣言
library IEEE ;
use IEEE.STD_LOGIC_1164.ALL ;

--エンティティ宣言部
entity adder is
  port (i0, i1 : in bit; ci : in bit; s : out bit; co : out bit);
end adder;

--アーキテクチャ宣言部
architecture rtl of adder is
begin
   s <= i0 xor i1 xor ci;
   co <= (i0 and i1) or (i0 and ci) or (i1 and ci);
end rtl;

ちなみに、このコードはほぼGHDLのドキュメントhttp://ghdl.readthedocs.io/en/latest/Starting_with_GHDL.htmlのコピー。
ghdlで日本語のコメントが弾かれたので、実際はコメントを消去して保存した。

ライブラリ宣言

C言語でいうincliude文のようなもの。
libraryで使用するライブラリを指定し、useでそのライブラリ中のパッケージ(アイテム)を指定する。 このサンプルコードでは説明のため宣言したが、実際にはライブラリは使用していない。
(#include<stdlib.h>したのにprintfしか使ってない感覚。)

エンティティ宣言

C言語での関数プロトタイプ宣言のようなもの。
エンティティ名、変数名、型、モード(in,out等)を指定する。

アーキテクチャ宣言

C言語での実際の関数記述部分に近い。
内部信号(ローカル変数)を使う際はisとbegeinの間に宣言する。

テストベンチ

上のコードでは単に全加算器を定義しただけなので、実際に動かすためにテストコード(テストベンチと呼ぶことが多いっぽい)
adder_tb.vhdl

--  A testbench has no ports.
entity adder_tb is
end adder_tb;

architecture behav of adder_tb is
   --  Declaration of the component that will be instantiated.
   component adder
     port (i0, i1 : in bit; ci : in bit; s : out bit; co : out bit);
   end component;

   --  Specifies which entity is bound with the component.
   for adder_0: adder use entity work.adder;
   signal i0, i1, ci, s, co : bit;
begin
   --  Component instantiation.
   adder_0: adder port map (i0 => i0, i1 => i1, ci => ci,
                            s => s, co => co);

   --  This process does the real job.
   process
      type pattern_type is record
         --  The inputs of the adder.
         i0, i1, ci : bit;
         --  The expected outputs of the adder.
         s, co : bit;
      end record;
      --  The patterns to apply.
      type pattern_array is array (natural range <>) of pattern_type;
      constant patterns : pattern_array :=
        (('0', '0', '0', '0', '0'),
         ('0', '0', '1', '1', '0'),
         ('0', '1', '0', '1', '0'),
         ('0', '1', '1', '0', '1'),
         ('1', '0', '0', '1', '0'),
         ('1', '0', '1', '0', '1'),
         ('1', '1', '0', '0', '1'),
         ('1', '1', '1', '1', '1'));
   begin
      --  Check each pattern.
      for i in patterns'range loop
         --  Set the inputs.
         i0 <= patterns(i).i0;
         i1 <= patterns(i).i1;
         ci <= patterns(i).ci;
         --  Wait for the results.
         wait for 1 ns;
         --  Check the outputs.
         assert s = patterns(i).s
            report "bad sum value" severity error;
         assert co = patterns(i).co
            report "bad carry out value" severity error;
      end loop;
      assert false report "end of test" severity note;
      --  Wait forever; this will finish the simulation.
      wait;
   end process;
end behav;

adder.vhdl同様 http://ghdl.readthedocs.io/en/latest/Starting_with_GHDL.htmlから引用。

シミュレーションしてみる

GCCでCのコードをコンパイルするのと同様に、GHDLでVHDLのコードを analysis、elaborateして実行ファイルを生成する。

$ ghdl -a adder.vhdl
$ ghdl -a adder_tb.vhdl
$ ghdl -e adder_tb

オプション-r で実行する際に、--vcd=で波形データを出力できる

$ ghdl -r adder_tb --vcd=adder_tb.vcd

生成されたファイルはgtkwaveで参照できる

$ gtkwave adder_tb.vcd

gtkwaveのウィンドウが表示されたら、 左下のciとかioとかをメインの黒い部分にドラッグアンドドロップして左上の一番左の虫眼鏡のアイコンで縮尺を合わせると以下の様に表示された。 f:id:gonta42:20161223170430p:plain

時間がないのでとりあえずここで終了

VHDLの基本的な論理演算と型(VHDL入門その1) - 情報系学生ランナーの備忘録

参考サイト

初めてでも使えるVHDL文法ガイド ―― 文法ガイド編|Tech Village (テックビレッジ) / CQ出版株式会社

初めてでも使えるVHDL文法ガイド ―― 記述スタイル編|Tech Village (テックビレッジ) / CQ出版株式会社

GHDL on OS X - Handwriting

Starting with GHDL — GHDL 0.33 documentation

sublimetextでcを書く(情報科学基礎実験を乗り越えるために)

この記事はIS17er Advent Calendar 2016 - Adventarの8日目の記事として書かれました。

情報科学基礎実験もとうとうアセンブリに突入しニーズが低いとは思いますが、きっと誰かの役に立つと信じてsublimetextでc言語の開発環境?を整えた話を書きます。(僕自身はそろそろvimに乗り換えるつもりなのですが...笑)

Sublimetextとは?

www.sublimetext.com

"恋に落ちるエディタ"というキャッチフレーズとともに近年人気のエディタらしい (AtomとかBracketsとか競合に押されている感じもあるが...)

授業ではEmacsが推奨されているがsublimetextの方が気楽に使えると思う。

sublimetextの良さや導入方法については時間がないので下記サイトに丸投げ。

techacademy.jp

オススメのパッケージ

僕がsublimetextでCやアセンブリを書く際に使っていパッケージを紹介します。

  • ConvertToUTF8
  • BracketHighliter
  • TrailingSpaces
  • C Improved
  • Clang-Complete
  • x​86 and x​86_64 Assembly

ConvertToUTF8

様々な文字コードをUTF8に変換して使うためのパッケージ ありがたみを感じることはないけど多分ないと文字化けなどで困る

BracketHighliter

カーソルの外側の括弧がわかりやすいようにハイライトをつけるパッケージ f:id:gonta42:20161208235342p:plain
↑行番号の隣と{}の下にハイライトが付いている  

TrailingSpace

行末の空白をハイライトしてくれるパッケージ   f:id:gonta42:20161208235504p:plain  

C Improved

カラースキームが少し良くなる   f:id:gonta42:20161208235551p:plain 
C Improved - Packages - Package Control

Clang-Complete

+sでセーブすると文法ミスを指摘してくれる他、 Cの各種関数の自動補完とかもしてくれる。  

f:id:gonta42:20161208235752p:plain   f:id:gonta42:20161208235756p:plain  

コンパイルせずにエラーがわかるし、エラー箇所を直接ハイライトしてくれるから結構助けられてる。 

x​86 and x​86_64 Assembly

アセンブリをハイライトしてくれる
f:id:gonta42:20161209132737p:plain
x86 and x86_64 Assembly - Packages - Package Control

C言語用ビルドシステム

sublimetextでは+bソースコードをsublimetextのコンソール上でコンパイル及び実行してくれる... はずが、デフォルトではC言語用の設定がなかったので自分で書いた。
f:id:gonta42:20161209003108p:plain 上のようにTools>Build System> New Build Systemで新しい設定ファイルを作成。
今回はC言語用なので C.sublime-buildという名前で保存

{
    "selector":"sourse.c",
    "cmd":["gcc","-Wall","$file","-o","${file/c/out/}"],

    "variants":[
        {
        "name":"実行",
        "cmd":"${file/c/out/}"
        },
        {
        "name":"debag",
        "cmd":["gcc","-Wall","-g","$file","-o","${file/c/out/}"]
        },
        {
        "name":"アセンブリ出力",
        "cmd":["gcc","-Wall","-S","$file","-o","${file/c/s/}"]
        },
        {
        "name":"コンパイルのみ",
        "cmd":["gcc","-Wall","-c","$file","-o","${file/c/o/}"]
        }
    ]
}

以上のように(JSON形式で)設定を書くと+bソースコードコンパイルしてくれる
(例えばhello.cを書いている時は$ gcc -Wall hello.c -o hello.outを実行してくれる)
実行したりアセンブリを出力したかったりする時は+shift+bから他のコマンドを選択して実行できるようにした。
f:id:gonta42:20161209134717p:plain

それぞれの設定の意味は察してください笑
(詳しい説明は下記のサイトとかを見るといいかもしれない)
Build Systems — Sublime Text Unofficial Documentation

なんだかグダグダになってしまったのでそのうち真面目に書き直すつもりですが、解決方法、アドバイス等あれば教えていただけるとありがたいです。
最後まで読んでくれた方々ありがとうございました!!

ログインシェルの変更(Mac,zsh)

macの標準のシェルはbashだが、zshの方が何かと便利らしいのでとりあえず導入。

zshの導入

zshも標準でインストールされているものがあるが、バージョンが古かったので先日導入したbrewを使ってzshをインストール

brew install zsh

確かこれでインストールしたはず。(zshのインストール自体は少し前にしたので記憶が曖昧)

$ which -a zsh
/bin/zsh
/usr/local/bin/zsh

後者がbrewでインストールした方だろうということでディレクトリを覗いてみると、zshzsh-5.2と2つのコマンドがあったのでバージョンを調べてみた。

$ /usr/local/bin/zsh --version
zsh 5.2 (x86_64-apple-darwin15.6.0)
$ /usr/local/bin/zsh-5.2 --version
zsh 5.2 (x86_64-apple-darwin15.6.0)

知らなかったけど、どうやら両方エイリアスで、本体はbrewでインストールしたソフトはusr/local/Cellar/以下にあるらしい。 ビールの保管場所がセラーってなんか良い...なんて思いつつ次に行くことにします。

ログインシェルの変更

ログインシェルに変更できるシェルは/etc/shellsに記載されているシェルらしいので確認

$ cat /etc/shells
# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.

/bin/bash
/bin/csh
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh

今回はbrewでインストールしたzshを使いたいので末尾に/usr/local/bin/zshを追加

最後にログインシェルを変更するためにchsh -s /usr/local/bin/zshを実行 (多分chshはchange shell の略)

ターミナルを再起動しログインシェルが変更されているか確認

$ echo $SHELL
/usr/local/bin/zsh

どうやら正しく変更できたらしい。

参考サイト

【Mac】ログインシェルの変更方法について (chsh, chpass) - TASK NOTES

UNIXコマンド - chsh (Linux/FreeBSD/Solaris)

パッケージマネージャーの導入

Macの入手から数日...

中間試験やら走っていたりやら、Macに触りたくても触れない生活が続いた末にようやく少し遊ぶ余裕ができたので、少し環境構築を進めることができたので投稿。

 

Windowsと異なりせっかく標準でターミナル環境が整っていることだしソフトウェアの管理もパッケージマネージャーでやることにした。

今までyumとかpacmanとかは少しかじったことはあったけど、Macに関しては全く前提知識がなく、MacportsとかFinkとか複数選択肢があることに若干驚きつつ、学科でなんとなく聞いたり、日本語の情報が多そうなのでHomebrew/caskを導入することにした。

 

Xcodeのインストール

brewのインストールに関わらずMacでの開発環境の構築にはXcodeをインストールすべきということで導入。

なんとなくでインストールしたので記録もほとんどないが、4GB以上あってダウンロードに結構時間がかかった気がする。

 

 

Homebrewのインストール

googleで検索かけたらトップに日本語公式サイトが出てきたのでページを開く。

Homebrew — macOS 用パッケージマネージャー

f:id:gonta42:20161109010613p:plain

 

指示通りスクリプトをターミナルに貼り付けて実行

参考サイト

MacにHomebrewを導入する方法&使い方まとめ | vdeep

の記述通り

$ brew doctor

を実行

Please note that these warnings are just used to help the Homebrew maintainers

with debugging if you file an issue. If everything you use Homebrew for is

working fine: please don't worry and just ignore them. Thanks!

 

Warning: You have Xcode 8 installed without the CLT;

this causes certain builds to fail on OS X El Capitan (10.11).

Please install the CLT via:

  sudo xcode-select --install

と帰ってきた。

どうやらXcodeのCLT(Command Line Tools)がインストールされていないようなので

 

$ sudo xcode-select --install

でインストール

その後下記を実行

$ brew doctor

Your system is ready to brew.

 

とりあえずうまくインストールできたらしい。

使い慣れない環境だとこんな小さなことにも一喜一憂してしまう。

学科のアドベントカレンダーに投稿できそうなネタはしばらく書けそうもないです。

まぁそこらへんはプロにお任せするとして少しずつブログの投稿を稼いで行きます。