あるごリズム備忘録

学んだことの記録と日記

DIPとモックによるテストの手習い

はじめに

最近ちょこちょこテストを書くようになった。その中で、うまいテストの書き方がなかなか見つからないことも多い。ここで言う「うまい」というのは、テストしたい対象の外側に対して依存がなく(少なく)なるような書き方のことを指している。

うまく書けない例としては、データベースが絡むパッケージに関するテストが挙げられる。この「データベースが絡む」の範囲が、必要以上に広がってしまうせいで、本来はデータベースをあまり考えたくないパッケージのテストを書くにしても、データベース関係の処理を書く必要が出たりする。理想的には、外部環境に依存しない形でパッケージごとのテストを書きたい。もちろん、実際のデータベースを使わなければ、稼働時とは異なる状態での動作になる。しかし、外部環境を考えないテストであっても、対象パッケージのロジック自体の確認にはなるし、本来はそれが対象パッケージへのテストとしてはある意味正しいような気もする。

そんなことを考えている状態で、書籍『Clean Archtecture -達人に学ぶソフトウェアの構造と設計-』を年始に読んで、問題を解決する方法が見つかった(かもしれない)。書籍全体としては、まだ理解しきれていない部分も多いが、後述する「依存関係逆転の原則」に関しては納得できたつもりなので、ここにまとめておく。

依存関係逆転の原則(DIP: Dependency Inversion Principle)

クリーンアーキテクチャの基本的な考えとして、「変化しにくいもの(内部)に変化しやすいもの(外部)を依存させるべき。」という考えがある。すごく単純化して例を挙げると、変化しにくいものと言うのはビジネスのルールとかアプリケーションのロジックの部分で、変化しやすいものと言うのはデータベースとフレームワークとかの外部要因など。

つまり、データベース特有の処理などに、アプリケーションのロジックが引っ張られてはだめということ。データベースのよう外部要因に関する部分は、いつでも取り替えることができて、アプリケーションのロジックのコードを変更はしたくない。このような理想的な状態を、DIPによって生み出すことができる。

アプリケーションを何も考えず単純に書いた場合、図1のようになる。アプリケーションのロジックが書かれた「Service Package」から、データベースに関する処理が書かれた「Data Package」に対して参照がある。参照があるというのは、コード上での依存が存在することを意味する。

f:id:fcimsb55yn23:20190105151531p:plain
図1シンプルな構成

この依存関係を逆転させたい。つまり、変化しやすい「Data Package」が変化しにくい「Service Package」に対して依存するようにしたい。これを実現するために、DIPでは図2のようにインターフェースを利用する。「Service Package」側でインターフェースを定義して、パッケージ内のコード上ではそのインターフェースを使った処理を書く。そして、「Data Package」では「Service Package」のインターフェースを実装する。このような設計にすることで、パッケージという単位で見ると、処理の流れはそのままだが、依存関係は「インターフェース実装」という形で逆転している。

f:id:fcimsb55yn23:20190105151531p:plain
図2DIPを用いた構成

適用例

前述のパッケージ構成を再現した、Goによるサンプルコードを載せる。パッケージの構成は以下のようになっている。

└── simple
    ├── data
    │   └── user_service.go
    └── service
        ├── user_service.go
        └── user_service_test.go

└── dip
    ├── data
    │   └── user_service.go
    └── service
        ├── user_service.go
        └── user_service_test.go

シンプルな構成(simple)

simple package

DIPを用いた構成(dip

dip package

DIPを使って構成された方は、「service」パッケージに関するテストが、外側に依存しない形でかけていることがわかる。このテストで検証しているのは、「service」パッケージのロジックそのものだけになっている。実際には裏でデータベースが起動しているような外側の構造については、モデル化されたモックを実装している( UserRepositoryMock )。

参考

サンタクロース問題(スレッド・アクター・チャンネル)

はじめに

この記事は Treasure Advent Calendar 2018 23日目の記事です。

今回はクリスマスにちなんで、並列プログラミングの古典的なトピックである「サンタクロース問題」を取り上げます。この問題を、並列処理を記述するための一般的なモデルであるスレッド(Java)、アクター(Java, Akka)、そしてチャンネル(Go)を使ってそれぞれどのようになるか試しに書いてみます。

各言語、ライブラリについて、「らしい書き方」ができていない部分も多いかと思います。特に、ビジーループを使ってしまっている部分はもっと適切な方法がありそうです。「もっとこう書いたほうが良さそう。」などあれば、教えていただけるとありがたいです。

サンタクロース問題

サンタクロースの家には、1人のサンタクロースとそれぞれ複数のトナカイと妖精が住んでいます。いま、サンタクロースは家で寝ていますが、トナカイと妖精は外へ出かけていいます。サンタクロースは、状況に合わせて次の行動をとります。

  • 妖精が3人集まった場合
    • 起床して新しいおもちゃの開発をする。
    • 終わったら解散してまた眠る。
  • トナカイが9匹集まった場合
    • 起床してプレゼントを届けに出かける。
    • 終わったら解散してまた眠る。

今回は妖精は9人、トナカイは27匹存在し、それぞれ1回のみ働くとします。すべての妖精とトナカイが仕事をしたら終了です。

実装

ここでは、各実装の概要のみを説明します。それぞれの実装の詳細は以下のリンク先に置いてあります。

スレッド(Java

妖精とトナカイをそれぞれThreadを継承したクラスとして定義し、フィールドとしてサンタのオブジェクトをもたせるようにしました。

コード(一部抜粋)

public class Santa {
    /* ... */

    public void addElf(){
        synchronized (this){
            if(this.state == 0 && this.elfCount < elfMax) this.elfCount++;
            if(this.elfCount == elfMax) develop();
        }
    }

    /* ... */

    private void develop(){
        this.state = 1;
        System.out.println("Santa starts to develop.");
    }

    /* ... */
}


public class Elf extends Thread {
    final private String name;
    
    /* ... */

    @Override
    public void run() {
        while(!this.working){
            synchronized (this.santa){
                if(this.santa.getState() == 0){
                    System.out.println(this.name + " start to wait.");
                    this.santa.addElf();
                    this.working = true;
                }
            }
        }
        while(this.working){
            synchronized (this.santa){
                if(this.santa.getState() == 1){
                    this.santa.remElf();
                    this.working = false;
                    System.out.println(this.name + " finished working.");
                }
            }
        }
    }
}

環境:Java8

アクター(Java, Akka)

アクターはそれぞれが処理主体で、個別にメールボックス(受信メッセージを貯めておくキュー)を持っています。異なるアクター同士のメッセージのやり取りによって全体の処理が進んでいきます。アクター上では、常に1個以下のメッセージがシングルスレッドで処理されるので、ロックなどの排他制御の記述が基本的には登場しません。Akkaにおいては、アクターに受信したメッセージの振る舞いを記述することによって、アクターを定義します。他のアクターへのメッセージは、ActorRef 型のメッセージ用の参照を介して送信され、直接アクターオブジェクトの状態をいじるようなことはできなくなっています。

サンタと妖精、トナカイをそれぞれアクターとして定義しました。外出先からサンタの元に帰ってくる行為や、仕事の完了通知などは、それぞれ個別のメッセージとして定義しています。

コード(一部抜粋)

public class Santa extends AbstractActor {

    /* ... */

    @Override
    public Receive createReceive() {
        return receiveBuilder()
                .match(Messages.AddElfMsg.class, this::addElfMsg)
                .match(Messages.AddReindeerMsg.class, this::addReindeerMsg)
                .match(Messages.StoppedElfMsg.class, this::stoppedElf)
                .match(Messages.StoppedReindeerMsg.class, this::stoppedReindeer)
                .build();
    }

    private void addElfMsg(Messages.AddElfMsg msg){
        this.elfSet.add(msg.ref);
        if(this.elfSet.size() == elfMax){
            develop();
        }
    }

    /* ... */

    private void develop(){
        System.out.println("Santa starts to develop.");
        for (ActorRef ref : this.elfSet) {
            ref.tell(new Messages.FinishWorkingMsg(), this.getSelf());
        }
        this.elfSet = new HashSet<>();
        System.out.println("Santa starts to sleep..");
    }

    /* ... */

    private void stoppedElf(Messages.StoppedElfMsg msg){
        this.stoppedElfCount++;
        terminateSystem();
    }

    /* ... */
}


public class Elf extends AbstractActor {
    final private String name;
    final private ActorRef santaRef; 

    /* ... */

    @Override
    public Receive createReceive() {
        return receiveBuilder()
                .match(Messages.StartWorkingMsg.class, this::addElf)
                .match(Messages.FinishWorkingMsg.class, this::finishWorking)
                .build();
    }

    private void addElf(Messages.StartWorkingMsg msg){
        System.out.println(this.name + " starts to work.");
        this.santaRef.tell(new Messages.AddElfMsg(this.getSelf()), this.getSelf());
    }

    private void finishWorking(Messages.FinishWorkingMsg msg){
        System.out.println(this.name + " has finished working.");
        this.santaRef.tell(new Messages.StoppedElfMsg(), this.getSelf());
        this.context().system().stop(this.getSelf());
    }
}

// 各メッセージはクラスとして定義
public class Messages {
    public static class AddElfMsg {
        public ActorRef ref;

        public AddElfMsg(ActorRef ref){
            this.ref = ref;
        }
    }

    /* ... */
}

環境:Java8, Akka 2.5.19

チャンネル(Go)

サンタと妖精、トナカイをそれぞれ関数として定義してgroutineを起動させました。それぞれやり取りについては、チャンネルを介して行っています。アクターと同様に、チャンネルへの書き込みはメッセージと見ることができそうです。ただ、アクターの場合との違いとしては、送信先の存在を直接知っているのではなく、チャンネルという共通のメッセージ置き場を介して、受信側も能動的にメッセージを取りに行っている(ように見える)という点でしょうか。

コード(一部抜粋)

type Streams struct {
    ElfChan           chan int
    ReindeerChan      chan int
    NotifElfChan      chan int
    NotifReindeerChan chan int
}

func main() {
    wg := sync.WaitGroup{}

    streams := Streams{
        ElfChan:           make(chan int),
        ReindeerChan:      make(chan int),
        NotifElfChan:      make(chan int),
        NotifReindeerChan: make(chan int),
    }

    wg.Add(1)
    go santa(&wg, streams)

    for i := 0; i < elfSize; i++ {
        wg.Add(1)
        go elf(&wg, i, streams.ElfChan, streams.NotifElfChan)
    }

        /* ... */

    wg.Wait()
}

func santa(wg *sync.WaitGroup, streams Streams) {
    defer wg.Done()

    elfCount := 0
    reindeerCount := 0

        /* ... */

    for {
        select {
        case t := <-streams.ElfChan:
            elfCount += t
        case t := <-streams.ReindeerChan:
            reindeerCount += t
        default:
            fmt.Println("Santa starts to sleep.")
        }

        if elfCount >= 3 {
            fmt.Println("Santa starts to develop.")
            for i := 0; i < 3; i++ {
                streams.NotifElfChan <- 1
            }
            elfCount = 0
            developCount++
        }

                /* ... */

        if developCount == elfSize/3 && shipCount == reindeerSize/9 {
            break
        }
    }
}

func elf(wg *sync.WaitGroup, num int, elfChan chan int, Notif chan int) {
    defer wg.Done()
    fmt.Println("start elf-", num)

    elfChan <- 1

    <-Notif

    fmt.Println("end elf-", num)
}

環境:go 1.10.1

最後に

アクターを使った書き方が、物理的な世界とのイメージとの対応という意味で、問題を一番自然に記述できた気がします。「メッセージを投げたらあとは知らない」といった雰囲気で、サンタと妖精、トナカイがそれぞれ完全に独立した存在として記述できると感じました。共有するロックやチャンネルが登場しないことが大きいかもしれません。

普通のプログラミングにおける「FizzBuzz」のようなものとして、新しい並列プログラミングのスタイルを試すときに「サンタクロース問題」も使えそうです。

参考

AtCoder Beginner Contest 115 D問題

問題:Christmas

本番では解くことができなかった。しかし、解法がとてもシンプルかつ、再帰関数の実装に関して教育的な問題だと感じた。

解法

  • 各レベルiの「層の総数」と「含まれるパティの数」は入力に関係なく、事前に計算できる。
  • レベルiのバーガーにはレベル(i-1)のバーガーが2個含まれる再帰的な構造を利用して、再帰関数を実装する。引数は、現在考えているレベルiと下から食べる層の最高位置xとする。
  • レベルiのバーガーは、5つの部分に分かれている。それぞれ下側から、次のように呼称する。
    1. 最下層バン
    2. レベル(i-1)バーガー(下)
    3. 中間パティ
    4. レベル(i-1)バーガー(上)
    5. 最上層バン
  • 位置xがバーガー中の5つの内どの部分かによって処理が分岐する。
    1. 最下層バン1枚のみ食べる。(ただしレベル0のときはパティ1枚)
    2. レベル(i-1)バーガー(下)の中で再帰的に考える。
    3. 最下層バン、 レベル(i-1)バーガー(下)、中間パティをちょうど食べる。
    4. 最下層バン、 レベル(i-1)バーガー(下)、中間パティを食べ、レベル(i-1)バーガー(上)の中で再帰的に考える。
    5. 最下層バン、 レベル(i-1)バーガー(下)、中間パティを食べ、レベル(i-1)バーガー(上)、最情報バンをちょうど食べる。
long long N, X;
long long s[55];   // s[i]: レベルiのバーガーの層の総数
long long p[55];   // p[i]: レベルiのバーガーに含まれるパティの総数

long long f(long long i, long long x){   //レベルn, 下からx
    if(x == 1){ //ケース1
        return i == 0 ? 1 : 0;
    }else if(1 < x && x <= s[i-1] + 1){ //ケース2
        return f(i-1, x-1);
    }else if(x == s[i-1] + 2){  //ケース3
        return p[i-1] + 1;
    }else if(s[i-1] + 2 < x && x <= 2*s[i-1] + 2){  //ケース4
        return p[i-1] + 1 + f(i-1, x-(s[i-1]+2));
    }else{  //x == 2*s[n-1] + 3(ケース5)
        return p[i];
    }
}

int main() {
    cin>>N>>X;

    // 各レベルの層の総数と含まれるパティの数を計算
    s[0] = 1;
    p[0] = 1;
    REP(i,1,N+1) s[i] = 2*s[i-1] + 3;
    REP(i,1,N+1) p[i] = 2*p[i-1] + 1;

    cout<<f(N, X)<<endl;

    return 0;
}

コンテスト中の思考(失敗)

  • レベルが決まれば、パティの数が求められることとその式については気づいていた。
  • レベルiにおけるバーガーの層の総数を式で表すことは思いつかなかった。
  • iを増やしながら実験をして、何かルールがないか探していた。
  • 反省:再帰構造が見え見えだったので、それを利用してもっと愚直な実装を考えてい見るべきだった。

Treasure2018の記録

はじめに

 VOYAGE GROUPの夏のインターンTreasure2018に参加して来ました。Treasureを通して自分には経験をアウトプットすることがもっと必要だと思ったので、まずはインターンでの経験を書き残すため、1年以上前から更新が止まっているこのブログを復活させてみようと思います。インターンの内容自体は、他の人が結構書いているので、個人的な部分をピンポイントで書きます。

参加動機

 はじめに、自分は地方の大学で情報系を専攻している修士1年の学生です。博士課程に進む予定はなく就職するつもりです。ただし、仕事でもプログラミングによるものづくりやコンピュータ・サイエンスに関わりたいという思いはあるものの、その就職先として、大きく分けてもメーカーやSIer、Web等と進む方向を決めきれずにいました。特にWeb業界を進路の選択肢として本気で意識しだしたのはここ半年程度です。それまでは、メーカーやSIerがメインで、Webについては具体的なイメージが湧いてませんでした。また、業務内容や企業文化など、新卒として自分が働くときどのようなものが相性がいいのかがイメージできていなかったため、夏のインターンシップでは、大手のSIerベンチャーっぽいWeb系の企業のある意味正反対(のように見える)の組織を自分の目で見て、その経験を進路を考えるための材料にしようという狙いがありました。もちろん、インターンでスキルを上げたいという思いもありましたが、モチベーションとしてはこちらのほうが大きかったように思います。
 Web系のインターン先としてVOYAGE GROUPのTreasureに応募した理由は大きく3つです。
 1つ目は、技術力的な成長です。これまである程度プログラミング経験があり、動くものを作ることはできます。ただ、いつまでたっても「とりあえず動く」というレベルを脱することができていないような気がしていました。今考えると何を学ぶべきかわからないという状態だったのかもしれません。この一年くらいは技術的な成長の実感を感じることも少なく、そのような停滞を突破するきっかけを探していました。過去の参加者の話などから、Treasureならばそのきっかけになるのではないかと思っていました。
 2つ目は、インターンに一緒に参加する同世代の優秀な学生の人たちと話して、彼らの考えを聞きたいと思っていたからです。これは地方に住んでいる学生にありがちなことだと思いますが、プログラムを書いている同世代の人間が周りにおらず、技術的な話題や将来について真剣に話す機会が極端に少ないです。都心の企業でお金を受け取ってコードを書いている学生たちは自分にとっては、雲の上のような存在で、実際のところ彼らがどんなんことを考えているのかは、想像するしかありませんでした。
 最後の理由は、VOYAGE GROUPの中の人達と話してみたかったということです。会社が配信しているポットキャスト「ajitofm」を以前から聞いていおり、「この人たちと話してみたい」という憧れのような気持ちと、なんとなく「この人たちなら自分の質問にも真剣に答えてくれそう」という思いがありました。
 

講義内容

 インターン期間の最初の3分の2は座学中心の講義で、残りがチーム開発です。
 Treasure参加前の自分の経験としては、競プロやiOS、Unity、 組込みなどが中心で、Web開発の経験は簡単なAPIサーバ(mainファイル1つのみで完結)程度だったので、講義で学ぶ内容はどれも新鮮で学びの多いものでした。具体的には、Golang、TDD、フロント(React)、APIの設計、セキュリティ、データベース(モデリング)、チーム開発手法(アジャイル)、アイデアの出し方、等です。どの講義も非常に濃い内容でまだ消化しきれていないくらいなのですが、現状で特に印象にの残っているものを2つ挙げます。(強調しておきたいのは、他の講義で学んだことが少ないという意味でないということです。どの講義も学んだことが多すぎて、それらをまとまった文章を書く自信がないので、ほんの一部書きやすい印象的なエピソードを抽出しています。)
 まず、フロントエンドの講義。自分はWebのフロントエンドどころか、jQueryも触ったことがありませんでした。「HTMLとCSS、JSは読めなくはない」くらいの事前知識でした。そんな状態だったので、講義を受け終わったあとは10数年くらい未来にタイムスリップしたような感覚でした。フロントエンドには興味があまりなかったのですが、フロント側のstateの管理などが案外面白そうで興味が湧いてきたのと同時に、やはり見た目が変わるインパクトが楽しく勉強してみたくなりました。
 次に、API設計の講義について。ここで得た知識は、後半のチーム開発で最も生かされた内容かもしれません。過去に自分がPHPやGoで作った簡単なAPIは、エンドポイントのURIは操作自体の名称を使用して、ほぼ全てGETメソッドでやっていました。具体的には「GET /api/addUser」、「GET /api/deleteUser」などです。本当にひどいですね。このへんが、まさに先に述べた「とりあえず動く」状態というのが最もよく現れている部分です。講義で学んだような、リソースを中心にそれに対する操作ベースで考えるAPI設計は、多くのAPIが必要な後半の開発でも非常に見通しが良くなり、開発しやすかったです。
 すべての講義を通して、講師やTAの方のサポートが圧倒的で、こちらが驚くくらい丁寧に質問に答えてもらえました。そのおかげで、自分の知識が薄い部分についても、ついて行けなくなるようなことはなく、理解しながら講義を聞くことができました。

チーム開発

 自分たちが作るサービスを考えるパートは、途中まではわりと順調に進み、初日の夕方には作るサービスが一つ決まりました。アイデアが一旦決まってみんなで食事に行ったあと、どうしても先程までの自分たちのアイデアが既存サービスと同じに見えてしまい、それを正直にチームメンバーに言いました。ほとんど決まったものに対していまさらちゃぶ台を返すようなことをやるのかどうかはかなり悩みましたが、ここで言わないと途中で作る意味を見失ってしまいそうな気がしたので伝えました。その後、予定の時間をかなり延長してアイデアを再考しました。結局、アイデアの中心的な部分が大きく変わることはなかったのですが、この議論を通して納得感を持って開発をスタートすることができました。
 開発パートでは、最初期でフロントエンド担当のメンバーが見た目のモックを作ってくれました。完成イメージが明確になることで、作るモチベーションが上がったのと同時に、実装のイメージも具体的になって設計もすぐに考え始めることができました。過去のチーム開発でデザインができる人がいた経験はほとんどなかったので、このような開発初期段階でのスピード感は初体験でした。
 また反省点としては、APIの変更を曖昧にしたまま進めることがあったことです。最初の大まかな設計が決まった段階で、APIの仕様を決めてwikiにまとめてからフロントエンドとバックエンドに別れて開発をスタートしました。開発の途中で、実装面の問題からAPIの仕様が微妙に変更になることが多々ありました。その場合、口頭で伝えて実装してしまってからwikiを修正するよなことが多かったです。このような流れでやっていると、wikiにかかれていることと実際に動いている実装が異なっている場面が多くなり、そのズレが蓄積してコードが機能しなくなり修正に余計な時間を取られることが何度かありました。おそらく、多少時間を使っても、全員の共通意識としてのwikiをしっかり修正してから実装するという手順を守る方が、全体としての開発スピードは落ちなかったような気がします。
 個人的にはとてもいいチームメンバーに恵まれました。前半の期間で話したことがある人もない人もいましたが、どちらもチーム開発での関わりの中で、技術的な部分ではもちろん、人間的に尊敬したいと思える部分が数多く発見できました。真似できる部分は真似して、自分の行動方針に取り入れて行きたいと思います。

終わりに

 3週間のインターンは本当にあっという間でした。毎日の密度が非常に濃く、新しく学んだことを必死に理解しようとしていたら終わっていました。内容が本当に盛りだくさんだったので、消化しきれていないことも多いです。正直、「圧倒的成長」できているのかはわからないですし、実際そのようになれるかは、この消化不良感に目を背けずに自分で学びを進める必要があると思います。これまでの自分だと、この圧倒的成長した「感覚」の満足感で止まりがちだった気がしますが、Treasureの期間で一緒だったみんなの技術へ取り組む姿勢を見ていると、とても満足できなくなりました。
 成長の実感があまりないと書きましたが、いまの時点でひとつだけ成長を実感していることがあります。Treasure直前に、軽く「Real World HTTP」という本に目を通していました。正直そのときは、目から入る情報がそのまま頭を抜けていく感じでした。しかしTreasure終了後に同じ本の目次を見てみると、景色が変わっていました。各項目がインターン中に解決しきれなかった疑問に答えてくれているような内容に感じられ、読むときの吸収率が桁違いになっていました。同様にTreasureでの経験をきかっけに吸収しやすくなっていることがまだまだありそうなので、そのあたりを突破口に進んで行きたいです。

組み込みOS(第7回)

ステップ7の内容

割り込み処理を実装する。前回までのシリアル入力の受取りはビジー・ループによって行われていたが、今回はシリアル割り込み処理によるものに変更する。

割り込み入力ピン

部割り込みに対しては、ピンへの入力によって割り込みハンドラを呼び出す。
ピンの数が外部からの割り込み出力の種類より多い場合はそれぞれのコントローラにピンを割り当てればよい。しかし、ピンが1つしかない場合は、複数のコントローラの出力のORをピンへの入力とし、各コントローラの持つ割り込み関連のレジスタを、ハンドラ内で調べて処理を変更する。

割り込みコントローラ

複数の出力をOR論理で結合してピンに伝えるが、それぞれの割り込みの優先度などの制御ができる。

割り込み処理の流れ

割り込みが発生すると、プログラムカウンタの値を、割り込みベクタに登録してある割り込みハンドラへのアドレスに書き換える。
このとき、もとのプログラムカウンタの値は自動的にスタックや専用レジスタに保存される。(保存先はCPUによってことなる。)同時に、モード・レジスタの値も保存され、自動的にモード・レジスタの設定は割り込み禁止に変更される。
割り込みハンドラの最後では、プログラム・カウンタとモード・レジスタの状態を復帰させる処理を行う。

H8の仕様

CCR

H8では、モード・レジスタの機能をもつCCRというレジスタがある。CCRはモード・レジスタの役割の他にも、フラグ・レジスタの役割も担っており、各桁のビットがそれぞれの役割を果たしている。

PCとCCRの保存先

割り込みが発生した時の、プログラム・カウンタとCCRの保存先は、スタック・ポインタ(PC)のER7レジスタになる。ER7の指す先のアドレスの上位1バイトにCCR、下位3バイトにPCの値が格納される。

割り込み復帰方法

プログラム・カウンタとCCRの値を復旧させるrte命令を、割り込みハンドラの最後で呼び出す。

インライン・アセンブラ

C援護のプログラムの中で、アセンブラを記述るためのもの。
今回は、ブートローダのプログラム中でCCRを操作するために使用する。


12ステップで作る組込みOS自作入門

12ステップで作る組込みOS自作入門

組み込みOS(第6回)

ステップ6の内容

前回ステップで、転送したELF形式ファイルを解析し、セグメント情報を読み取り、とりあえず内容を表示することだけはできた。今回のステップでは、読み取った情報をもとに、ファイル内容ををRAM上にコピーし、エントリーポイントに処理を渡すプログラムを実装する。

エントリ・ポイント

実行を開始するアドレスのこと。ELFヘッダの中には、エントリ・ポイントを保持するために割り当てられた領域がある。

関数へのポインタ

ELF形式ファイルから読み取ったエントリポイントのアドレスは、関数へのポインタとして変数に持っておく。関数へのポインタ f は次のように宣言できるらしい。

[返り値の型] (*f)( [引数] );

コード中に次のように書くと、f にポインタが渡されている関数が呼び出される。

f([引数]);

疑問点

runコマンドの動作

ブート・ローダのrunコマンドは、1回目は必ず失敗する。2回目はうまくいく。
理由はわからなかった。
アドレスを表示させてみたところ、一度目のコマンド実行時は、エントリーポイントのアドレスが0になっているが、2度目は正常にアドレスが入っている。しかし、1回目と2回目のコマンド実行の間に何か処理が入っているわけではないので、原因がわからない。

OS側のmain関数

OSのコマンド”exit”で、os側のmain関数はreturn されるが、その後の処理がどこに渡されるかがわからない。書籍のままのコードだとexitにあとフリーズする。これはおそらくリンカ・スクリプトの無限ループのため、この部分を丸々削除するとブート・ローダに処理が戻るはずだと考えた。しかし実際は、1回目のexitコマンドでは再びOSが起動し、2回目はブート・ローダに処理が戻った。原因が特定できなかった。


12ステップで作る組込みOS自作入門

12ステップで作る組込みOS自作入門

組み込みOS(第5回)

ステップ5の内容

ステップ4までで、とりあえずファイルの転送まではできるようになった。ここからは、転送したELF形式ファイルをRAM上に展開できるようにする。ステップ5では、展開はせず、ELFの解析プログラムまでを作成する。

マジック・ナンバ

ELF形式ファイルの先頭にある16バイトの領域に配置されている数値データのこと。様々な識別情報が書かれており、特に最初の4バイトは、「0x7f 0x45 0x4c 0x46」となっていて、これによってELF形式であることが判別できる。

「セクションと」と「セグメント」

セクション

リンカが、複数のオブジェクト・ファイルをリンクして一つの実行形式ファイルにする際に使用する。それぞれのオブジェクト・ファイルのセクションに従って、同じ種類のデータは、実行形式ファイルにおいて、同じデータ領域(.textとか)にまとめられる。

セグメント

ローダは、実行形式ファイルの内容を、書かれているセグメント情報にしたがって、セグメント単位でメモリ上に展開する。

疑問点

リンカについて

リンカ・スクリプト(ld.scr)は、リンカそのものではなく、リンカへの指示が書かれたファイルであることを理解できた。しかし、そうなるとリンカ本体はどれになるのかまだ理解できてない。


12ステップで作る組込みOS自作入門

12ステップで作る組込みOS自作入門