めのん@ひとりプログラミング同好会

このブログはすべてフィクションであり、実在の人物、団体とは一切関係ありません

CSVを変換するだけの仕事

こんばんは、めのんです!

今日の日中にはCSVを読み込むプログラムをC++の練習がてら作ってみました。

これまでCSVのパースは.NETのTextFieldParserクラスを使ってC#で実装していました。 でも、できることならC#で実装したいと思って今回挑戦してみました。

実をいうとCSVを読み込んで別の形式のCSVに出力するような仕事の依頼が結構来ます。 そんなに利益になる仕事ではありませんし、お断りすることもできるんですけど、必要としていただけるのはありがたいことなので低価格でお引き受けしています。

でも、どうせやるならそれをネタに新しい技術を実務経験を得たいと思いますよね。 そういうこともあって、今回C++CSVのパーサーを作ったんです。

私は法人化していますが実質フリーランスです。 客先常駐ならある程度収入は安定しますが、私の場合はそうもいきません。 ですので、できることはひとつでも増やしておきたいのです。

これからフリーランスを目指す方にはぜひ知っておいてほしいですね。 自分のスキルがどんなに高くても、それで仕事が得られるわけではないということを。

今回はなんか愚痴っぽくなってしまいましたので、早めに切り上げることにします。

明日はOpenSiv3Dの話題を取り上げようと思いますので、どうぞご期待ください。

それでは!!

C++の練習を兼ねてCSVパーサーを書いてみました!

こんにちは、めのんです!

平日はZigの話題が中心で、週末は主にOpenSiv3Dの話題だったんですけど、今回はちょっと違うことに挑戦してみました。

Twitterでほえほえ@LWPさんが「(CSVを)ベタなコードで解析してほしい」とおっしゃっていましたので、さっそく私も挑戦してみました。 使ったプログラミング言語C++です。 Zigはちょっとまだ無理です💦

まずはCSVの規格の確認からですね。 これはRFC 4180が相当すると思います。

tools.ietf.org

RFC 4180そのままではASCIIしか対応できないと思いましたので、Excelで作成したCSVを読めるように拡張しました。 というか判定がザルなだけなんですけどね。

あと、Excel for Macが出力したCSVは改行文字がCRだということですので、そちらにも対応したつもりです。

では、私の書いたコードを貼ることにします。

#include "menonfled/csv.hpp"
#include <utility>
#include <stdexcept>

namespace menonfled
{
  namespace
  {
    /// TEXTDATAの読み込み
    /// @param[in]  is  入力ストリーム
    /// @return TEXTDATAであればその文字を、それ以外であればナル文字を返す。
    char get_textdata(std::istream& is)
    {
      char c;

      if (is.get(c))
      {
        if (c < 0x20 || c == 0x7f || c == ',' || c == '"')
          is.unget();
        else
          return c;
      }
      return '\0';
    }

    /// non-escapedの読み込み
    /// @param[in]  is  入力ストリーム
    /// @param[in]  c   先頭の1文字
    /// @return 読み込んだフィールドを返す。
    std::string get_nonescaped(std::istream& is, char c)
    {
      std::string str;

      do
      {
        str.push_back(c);
        c = get_textdata(is);
      } while (c);
      return std::move(str);
    }

    /// escapedの読み込み
    /// @param[in]  is  入力ストリーム
    /// @return 読み込んだフィールドを返す。
    /// 先頭の二重引用符はスキップ済み
    std::string get_escaped(std::istream& is)
    {
      std::string str;
      char c;

      while (is.get(c))
      {
        if (c == '"')
        {
          char c2;
          if (!is.get(c2) || c2 != '"')
            break;
        }
        str.push_back(c);
      }
      return std::move(str);
    }

    /// fieldの読み込み
    /// @param[in]  is    入力ストリーム
    /// @param[out] field 入力したフィールドの格納先
    /// @return EOFに達した場合はfalseを、それ以外はtrueを返す。
    bool get_field(std::istream& is, std::string& field)
    {
      std::string str;
      char c;
      bool escaped = false;

      if (!is.get(c))
        return false;

      switch (c)
      {
      case '\r':
      case '\n':
      case ',':
        is.unget();
        field.clear();
        break;
      case '"':
        field = get_escaped(is);
        break;
      default:
        field = get_nonescaped(is, c);
      }
      return true;
    }
  }

  /// CSVの1レコードを読み込む
  /// @param[in]  is    入力ストリーム
  /// @return 読み込んだレコードを文字列の列として返す。
  std::vector<std::string> get_csv_record(std::istream& is)
  {
    std::vector<std::string> record;

    while (is)
    {
      std::string field;
      char c;

      if (!get_field(is, field))
        break;
      record.push_back(field);

      if (!is.get(c))
        break;
      if (c == '\r')
      {
        if (is.get(c) && c != '\n')
          is.unget();
        break;
      }
      if (c == '\n')
        break;
    }
    if (record.size() == 1 && record.front().empty())
      record.clear();
    return std::move(record);
  }
}

いろいろ試行錯誤しながら書いたのでキレイなコードとはいいにくいのですが、恥を忍んで貼ってみました。 こういうのもたまにはいいですね!

それでは!!

Zig版ASP3カーネルのcan_actとter_tsk

こんばんは、めのんです!

今夜は違う話題にしようかとも考えたんですけど、これといったネタがなかったのでいつものようにZig版ASP3カーネルです。

前回はact_tskを見ましたので、今回はある意味それと対になるcan_actを見ていくことにします。 定義はact_tskと同じでkernel/task_manage.zigにあります。

///
///  タスク起動要求のキャンセル[NGKI1138]
///
pub fn can_act(tskid: ID) ItronError!c_uint {
    var p_tcb: *TCB = undefined;
    var retval: c_uint = undefined;

    traceLog("canActEnter", .{ tskid });
    errdefer |err| traceLog("canActLeave", .{ err });
    try checkContextTaskUnlock();               //[NGKI1139][NGKI1140]
    if (tskid == TSK_SELF) {
        p_tcb = p_runtsk.?;                     //[NGKI1146]
    }
    else {
        p_tcb = try checkAndGetTCB(tskid);      //[NGKI1141]
    }
    {
        target_impl.lockCpu();
        defer target_impl.unlockCpu();

        retval = p_tcb.flags.actque;            //[NGKI1144]
        p_tcb.flags.actque = 0;                 //[NGKI1144]
    }
    traceLog("canActLeave", .{ retval });
    return retval;
}

can_actというサービスコールは、タスクを休止状態にするんではなくてact_tskをキャンセルするためのものです。 act_tskのキューイング数が1しかないASP3カーネルではあまり使いどころはないかもしれませんね。 でもまったく無意味かというとそんなことはなくて、休止状態のタスクに対して呼び出したときなんかは意味があります。

ざっと見た感じではcan_actの内容はZigを学ぶ上では見所はとくになさそうですね。

続いてter_tskも見ていきます。 定義はkernel/task_term.zigにあります。

///
///  タスクの強制終了[NGKI1170]
///
pub fn ter_tsk(tskid : ID) ItronError!void {
    traceLog("terTskEnter", .{ tskid });
    errdefer |err| traceLog("terTskLeave", .{ err });
    try checkContextTaskUnlock();               //[NGKI1171][NGKI1172]
    const p_tcb = try checkAndGetTCB(tskid);    //[NGKI1173]
    try checkIllegalUse(p_tcb != p_runtsk);     //[NGKI1176]
    {
        target_impl.lockCpu();
        defer target_impl.unlockCpu();

        if (isDormant(p_tcb.tstat)) {           //[NGKI1177]
            return ItronError.ObjectStateError;
        }
        else {
            task_terminate(p_tcb);              //[NGKI3450]
            if (p_runtsk != p_schedtsk) {
                target_impl.dispatch();
            }
        }
    }
    traceLog("terTskLeave", .{ null });
}

ter_tskはタスクを休止状態に遷移させるサービスコールです。

カーネルを読み解く上では結構重要なサービスコールなんですが、Zigを学ぶという観点では見所はとくになさそうです。

振り返ってみるとact_tskがひとつの山だったのかもしれませんね。 act_tskに表れたZigの特徴を押さえておけば、あとは同じような言語仕様しか登場しません。

次回は待ち状態に遷移させるサービスコールを何かひとつ見ていきたいと思います。 slp_tskかdly_tskを考えていますが、予定は変わるかもしれませんよ。

それでは!!

Zig版ASP3カーネルのact_tsk

こんにちは、めのんです!

昨夜は久々に違う話題にしましたけど、ここのところずっとZig版TOPPERS/ASP3カーネルの話題を取り上げています。

今回はact_tskというサービスコールを見ていきたいと思います。 念のためact_tskについて少しだけ解説しておきますね。

act_tskはμITRONからあるサービスコールでタスクを起動するためのものです。 正確にいうとタスクを休止状態から実行可能状態に遷移させます。

静的APIのCRE_TSKでTA_ACTを指定したタスクはact_tskを呼ばなくても起動しますが、それ以外のタスクや一度ter_tskで休止状態にしたタスクはact_tskを呼ぶまで起動しません。 act_tskの呼び出しはキューイングできるので、キューイングされている状態でcan_tskやter_tskを呼べばキューが1つずつ減っていくんだったと思います(ちょっとうろ覚えです)。 といっても、ASP3カーネルではキューイングできるのは1つまでなので、あまりキューイングという感じはしません。

では、早速act_tskの定義を見てみましょう。 kernel/task_manage.zigに定義があります。

///
///  タスクの起動[NGKI3529]
///
pub fn act_tsk(tskid: ID) ItronError!void {
    var p_tcb: *TCB = undefined;

    traceLog("actTskEnter", .{ tskid });
    errdefer |err| traceLog("actTskLeave", .{ err });
    try checkContextUnlock();                   //[NGKI1114]
    if (tskid == TSK_SELF and !target_impl.senseContext()) {
        p_tcb = p_runtsk.?;                     //[NGKI1121]
    }
    else {
        p_tcb = try checkAndGetTCB(tskid);      //[NGKI1115]
    }
    {
        target_impl.lockCpu();
        defer target_impl.unlockCpu();

        if (isDormant(p_tcb.tstat)) {
            make_active(p_tcb);                 //[NGKI1118]
            requestTaskDispatch();
        }
        else if ((p_tcb.p_tinib.tskatr & TA_NOACTQUE) != 0
                     or p_tcb.flags.actque == TMAX_ACTCNT) {
            return ItronError.QueueingOverflow; //[NGKI3528]
        }
        else {
            p_tcb.flags.actque += 1;            //[NGKI3527]
        }
    }
    traceLog("actTskLeave", .{ null });
}

今回は見所がたくさんありますね。

まずはerrdeferです。 関数の中でtry演算子を使うと、エラーが発生したときにただちに関数からリターンするんですけど、そのときに呼び出される処理を登録するためのものです。 今回はtraceLogを呼び出すようになっています。

次に、

        p_tcb = p_runtsk.?;                     //[NGKI1121]

この部分ですが、うしろに .? を付けることでp_runtskがnullだったときには何もしないことを意味しています。

先ほどはerrdeferが登場しましたが、今度はdeferが登場しています。 これはエラーが発生したかどうかに関係なく、現在のスコープから抜ける際に呼び出す処理を登録しています。 tryブロックにfinallyを使える言語は多いですが、そんな感じでしょうね。

μITRONやC版ASP3カーネルのact_tskの返却値型はERですけど、このact_tsk関数はItronErrorとvoidのエラー共用体になっています。 Zig版ASP3カーネルのサービスコールは、エラーが発生せず真偽値を返すものをのぞけば、全部エラー共用体を返すようになっています。

C版と同じシグニチャを持つ関数も用意されているようで、kernel/c_api.zigに定義があります。

// act_tskのC言語API
export fn act_tsk(tskid: ID) ER {
    return callService(task_manage.act_tsk(tskid));
}

callService関数は、エラーが発生しなければE_OKにするなどの変換を行うためのものなので、サラッと流してもいいと思います。

というわけで今回はact_tskを見てみました。 関連するサービスコールということで、次回はcan_actを見ていきたいと思います。 余力があればter_tskも見ていきたいですね。

それでは!!

ハローワークも悪くないと思いますよ!

こんばんは、めのんです!

ここのところずっとZig版ASP3カーネルの話題ばっかりでしたので、たまには違い話もしてみようかなと思って今回は投稿しています。

昨年の終わりぐらいからだんだん景気が悪くなってきていて、年が明けると新型コロナウイルス感染症の影響もあってさらに悪化しています。 これから学校を卒業して就職される方、仕事を失って転職される方にとっては本当に大変な時期だと思います。

転職を考えているけれどまだ仕事を失っていない方は、できる限り今の仕事を続けることを考えた方がいいと思います。 すでに仕事を失ってしまった方については、生活もあるので何か仕事をしないといけないと思います。 アルバイトを見つけるのも今の時期は楽ではないと思いますので、それなら何とか頑張って定職を見つけてほしいと思います。

仕事を探すにあたって、みなさんどういう方法で探されているのでしょうか? 求人サイト? 就職/転職エージェント? ハローワーク?コネ?

いろいろ方法はあると思います。 でも、仕事を探す方法によって採用のハードルが変わってくることをご存じでしょうか? とくに実務未経験からITエンジニアを目指されている方、未経験ではないけれどまだまだ経験年数が浅い方の場合は、最近はとくに採用が厳しくなっていますので、よくよく考えないとうまくいかないと思います。

仕事を探す方法の中でもっともお勧めできないのがエージェントを使う方法です。 エージェントを使うと採用側の企業は35%程度の手数料をエージェントに払う必要が出てきます。 自分が手数料を払うわけではないので問題ないと考えている方もいらっしゃるようですが、とんでもない間違いです。

たとえば、ある会社がエージェント経由であなたを年収300万円で採用するとしましょう。 そうすると、採用した会社は300万円×35%=105万円を手数料としてエージェントに払わなければなりません。 それに加えて保険料の会社負担分なんかが約2割ほどかかります。 合計すると、最初の1年間であなたにかけるコストは約465万円ということになります。

実務未経験のあなたがそれだけの価値を採用してくれた会社に提供できるでしょうか? もしこれがエージェント経由ではなく直接応募だった場合、360万円で済むことになります。 その差105万円、比率にすれば約30%、あなたを採用するハードルが上がるということです。

そういう観点からすると、企業のウェブサイトに求人情報が出ていれば、そこから直接応募するのが一番ハードルが低いということになりそうです。 もちろん企業ごとに個別の事情はありますから例外はありますよ。 でも、おおむねその傾向があるのは間違いないと思います。

でも、直接応募するためには求人を出している企業を探し出さないといけませんし、それはそれで大変ですよね。

これは私の考えなんですけど、ハローワークを使って仕事を探すのも悪くないんじゃないかと思うんです。 私は関西なので、さっきハローワークのウェブサイトで、大阪、京都、兵庫の求人情報を検索してみました。

検索条件としては地域のほかに、「ソフトウェア開発技術者」、「経験不問」、「学歴不問」を設定しました。 年齢は設定していません。

結果、ヒットしたのは256件ありました。

ソフトウェア開発技術者」といってもいろいろです。 Webプログラマーの募集もあれば組込みプログラマーの募集もありますし、SAPの開発者の募集もありました。

ヒットした会社が実際のところどんな会社かはふたを開けてみないとわかりませんが、ざっと見た感じではブラックっぽいところばかりではないと思いました。 ハローワークに出ている求人なんかブラック企業ばかりだとか、そういう先入観を持って調べもしないのは損だと思いますよ。

1年以上前の売り手市場ならいくらでも求人がありましたが、今は先入観で選択肢を狭めている余裕はないと思います。 わざわざハローワークまで足を運ばなくてもウェブサイトで検索できますから、ぜひ一度試してみてください。

あとそれともうひとつ。

比較的景気がいい時期でも、未経験者の転職先を見つけるにはハローワークは穴場だという話を聞いたことがあります。 今のこのご時世に求人を出しているような会社ですから、まともな企業の割合も上がってきているんじゃないかな? とも思います。

ぜひ参考にしてみてください。

それでは!!

Zig版ASP3カーネルのPrioBitmap

こんにちは、めのんです!

前回はタスクに関する本当にこまごました内容をざっと見ていきました。 今回はその中で登場したレディキューサーチのためのビットマップに使用していたPrioBitmap関数を見ていくことにします。

まずは前回のおさらいで、レディキューサーチのためのビットマップの定義から見ていきましょう。 定義はkernel/task.zigにあります。

///
///  レディキューサーチのためのビットマップ
///
///  レディキューのサーチを効率よく行うために,優先度ごとのタスクキュー
///  にタスクが入っているかどうかを示すビットマップを用意している.ビッ
///  トマップを使うことで,メモリアクセスの回数を減らすことができるが,
///  ビット操作命令が充実していないプロセッサで,優先度の段階数が少な
///  い場合には,ビットマップ操作のオーバーヘッドのために,逆に効率が
///  落ちる可能性もある.
///
var ready_primap: prio_bitmap.PrioBitmap(TNUM_TPRI) = undefined;

このPrioBitmap関数の定義はlibrary/prio_bitmap.zigにあります。

/// 優先度ビットマップ
pub fn PrioBitmap(comptime level: comptime_int) type {
    if (level <= 1) {
        @compileError("priority level must be larger than 1.");
    }
    else if (level <= 32) {
        // 32レベル以下の場合は1段のビットマップで実装
        return OneLevelBitmap(level);
    }
    else if (level <= 1024) {
        // 1024レベル以下の場合は2段のビットマップで実装
        return TwoLevelBitmap(level);
    }
    else {
        @compileError("unsuppored priority levels.");
    }
}

PrioBitmap関数の内容を見ると、32レベル以下の場合と1024レベル以下の場合で別の関数を呼び出していますね。 C版では16ビット符合無し整数を使ったサーチしかやっていませんでしたので、Zig版はこのあたりかなりパワーアップしたんだと思います。

32レベル以下の場合、私の想像では32ビット整数をMSBからかLSBからかわかりませんが探索して最初に1が立っているビットを調べるんだと思います。 Zigの場合は最大65535ビット整数まで扱えるのですが、32レベルを超える場合はC版と同じように2段階に分けたんだと思います。

それでは、32レベル以下に対応したOneLevelBitmap関数から見ていきますね。

/// 1段のビットマップでの実装
fn OneLevelBitmap(comptime level: comptime_int) type {
    const Prio = PrioType(level);
    const Bitmap = BitmapType(level);

    return struct {
        bitmap: Bitmap,

        /// 優先度ビットマップの初期化
        pub fn initialize(p_self: *@This()) void {
            p_self.bitmap = 0;
        }

        /// 優先度ビットマップのセット
        pub fn set(p_self: *@This(), prio: Prio) void {
            assert(prio < level);
            p_self.bitmap |= (@as(Bitmap, 1) << prio);
        }

        /// 優先度ビットマップのクリア
        pub fn clear(p_self: *@This(), prio: Prio) void {
            assert(prio < level);
            p_self.bitmap &= ~(@as(Bitmap, 1) << prio);
        }

        /// 優先度ビットマップがセットされているかのチェック
        pub fn isSet(self: @This(), prio: Prio) bool {
            assert(prio < level);
            return (self.bitmap & (@as(Bitmap, 1) << prio)) != 0;
        }

        /// 優先度ビットマップが空かのチェック
        pub fn isEmpty(self: @This()) bool {
            return self.bitmap == 0;
        }

        /// 優先度ビットマップのサーチ
        pub fn search(self: @This()) Prio {
            assert(self.bitmap != 0);
            return @intCast(Prio, @ctz(Bitmap, self.bitmap));
        }

        /// 優先度ビットマップの整合性検査
        pub fn bitCheck(self: @This()) bool {
            return (self.bitmap & ~@as(Bitmap, (1 << level) - 1)) == 0;
        }
    };
}

C版ではそれぞれ個別の関数が用意されていましたが、Zig版では関連する関数をまとめた構造体を組み立ててそれを返しています。

それぞれを関数を見ていくとビルトイン関数がたくさん使われていますね。 せっかくなのでひとつひとつ見ていくことにします。

まずは@This関数です。 この関数を呼び出したときのもっとも内側の構造体、共用体、列挙型の型を返します。 他の言語ではthisというとオブジェクトのポインタや参照になるんですけど、Zigでは型を返すんですね。

次は@as関数です。 これは型変換を行うビルトイン関数のようです。 @as(Bitmap, 1) と書けば、1をBitmap型に型変換するという意味だと理解しました。

そして今回の目玉となる@ctz関数です。 これはLSBから連続した0のビット数を数えるための関数です。 いいかえると最初に1が現れるビット位置を返すということになります。 ちなみにMSBから調べるのは@clz関数になります。

@ctz関数や@clz関数のようなものはアセンブリ言語の命令として備わっているCPUはそれなりにあるんですけどCにはないので、Cの場合はインラインアセンブラを使わないといけなかったんですよ。 Zigにはビルトイン関数として備わっているの便利です。 でも、CPUのアーキテクチャによってはソフトウェアでエミュレーションすることになるので決して早いとはいえないのでしょうね。

ビルトイン関数以外は取り立てて見るべきポイントはないのでこれぐらいにしておきます。

TwoLevelBitmap関数も念のため貼っておきますが、複雑にはなりましたがZigという言語を学ぶ上で見るべきポイントはとくにないようです。

/// 2段のビットマップでの実装
fn TwoLevelBitmap(comptime level: comptime_int) type {
    const Prio = PrioType(level);
    const upper_level = (level + 31) / 32;
    const UpperPrio = PrioType(upper_level);
    const LowerPrio = PrioType(32);

    return struct {
        upper_bitmap: OneLevelBitmap(upper_level),
        lower_bitmap: [upper_level]OneLevelBitmap(32),

        /// 優先度ビットマップの初期化
        pub fn initialize(p_self: *@This()) void {
            p_self.upper_bitmap.initialize();
            for (p_self.lower_bitmap) |*bitmap| {
                bitmap.bitmap = 0;
            }
        }

        /// 優先度ビットマップのセット
        pub fn set(p_self: *@This(), prio: Prio) void {
            assert(prio < level);
            p_self.lower_bitmap[prio / 32].set(@intCast(LowerPrio, prio % 32));
            p_self.upper_bitmap.set(@intCast(UpperPrio, prio / 32));
        }

        /// 優先度ビットマップのクリア
        pub fn clear(p_self: *@This(), prio: Prio) void {
            assert(prio < level);
            p_self.lower_bitmap[prio / 32].clear(@intCast(LowerPrio,
                                                          prio % 32));
            if (p_self.lower_bitmap[prio / 32].bitmap == 0) {
                p_self.upper_bitmap.clear(@intCast(UpperPrio, prio / 32));
            }
        }

        /// 優先度ビットマップが空かのチェック
        pub fn isEmpty(self: @This()) bool {
            return self.upper_bitmap.isEmpty();
        }

        /// 優先度ビットマップのサーチ
        pub fn search(self: @This()) Prio {
            const upper_prio: Prio = self.upper_bitmap.search();
            return upper_prio * 32 + self.lower_bitmap[upper_prio].search();
        }

        /// 優先度ビットマップの整合性検査
        pub fn bitCheck(self: @This()) bool {
            if (!self.upper_bitmap.bitCheck()) {
                return false;
            }
            if (self.lower_bitmap[(level - 1) / 32].bitmap
                    & ~@as(BitmapType(32),
                           (1 << ((level - 1) % 32 + 1)) - 1) != 0) {
                return false;
            }

            // upper_bitmapとlower_bitmapの整合性の検査
            for (self.lower_bitmap) |*bitmap, upper_prio| {
                if (bitmap.bitmap == 0) {
                    if (self.upper_bitmap.isSet(@intCast(UpperPrio,
                                                         upper_prio))) {
                        return false;
                    }
                }
                else {
                    if (!self.upper_bitmap.isSet(@intCast(UpperPrio,
                                                          upper_prio))) {
                        return false;
                    }
                }
            }
            return true;
        }
    };
}

それでは今回はこれぐらいにしておきます。 次回は何にするかまだ決めていませんので、今夜までに考えておきます。

それでは!!

Zig版ASP3カーネルのタスクに関するこまごま

こんばんは、めのんです!

前回予告したとおり、今回はタスクに関するこまごました内容を見ていくことにします。

最初は前回も登場したレディキューから見ていくことにしましょう。

///
///  レディキュー
///
///  レディキューは,実行できる状態のタスクを管理するためのキューであ
///  る.実行状態のタスクも管理しているため,レディ(実行可能)キュー
///  という名称は正確ではないが,レディキューという名称が定着している
///  ため,この名称で呼ぶことにする.
///
///  レディキューは,優先度ごとのタスクキューで構成されている.タスク
///  のTCBは,該当する優先度のキューに登録される.
///
pub var ready_queue: [TNUM_TPRI]queue.Queue = undefined;

コメントにもあるように、レディキューには実行可能状態のタスクと実行状態のタスクが登録されています。 タスク優先度ごとの配列になっていて、ぞれぞれ別のキューを持っています。 タスク優先度が高い(値が小さい)レディキューから順に探索して、最初に見つかったタスクが実行状態になります。

この探索を高速に行うための工夫がされていています。 それがサーチ用のビットマップです。

///
///  レディキューサーチのためのビットマップ
///
///  レディキューのサーチを効率よく行うために,優先度ごとのタスクキュー
///  にタスクが入っているかどうかを示すビットマップを用意している.ビッ
///  トマップを使うことで,メモリアクセスの回数を減らすことができるが,
///  ビット操作命令が充実していないプロセッサで,優先度の段階数が少な
///  い場合には,ビットマップ操作のオーバーヘッドのために,逆に効率が
///  落ちる可能性もある.
///
var ready_primap: prio_bitmap.PrioBitmap(TNUM_TPRI) = undefined;

ビットマップというと画像を思い浮かべる方がいらっしゃるかもしれませんが、ここでいうビットマップは画像ではありません。 これについては次回以降に深掘りできたらと考えています。

レディキューを探索して見つけたタスクはp_schedtskに格納されます。

///
///  実行すべきタスク
///
///  実行すべきタスクのTCBを指すポインタ.実行できるタスクがない場合は
///  nullにする.
///
///  p_runtskは,通常はp_schedtskと一致しているが,非タスクコンテキス
///  ト実行中は,一致しているとは限らない.割込み優先度マスク全解除で
///  ない状態の間とディスパッチ禁止状態の間(すなわち,dspflgがfalseで
///  ある間)は,p_schedtskを更新しない.
///
pub var p_schedtsk: ?*TCB = undefined;

コメントにもあるdspflgは次のように定義されています。

///
///  タスクディスパッチ可能状態
///
///  割込み優先度マスク全解除状態であり,ディスパッチ許可状態である
///  (ディスパッチ禁止状態でない)ことを示すフラグ.ディスパッチ保留
///  状態でないことは,タスクコンテキスト実行中で,CPUロック状態でなく,
///  dspflgがtrueであることで判別することができる.
///
pub var dspflg: bool = undefined;

そして、ディスパッチ許可状態を表すフラグの定義も見てみましょう。

///
///  ディスパッチ許可状態
///
///  ディスパッチ許可状態であることを示すフラグ.
///
pub var enadsp: bool = undefined;

ディスパッチ許可状態というのは、ena_dspやdis_dspといったサービスコールで制御します。

実際にディスパッチが行われると、実行状態のタスクはp_runtskに格納されます。

///
///  実行状態のタスク
///
///  実行状態のタスク(=プロセッサがコンテキストを持っているタスク)
///  のTCBを指すポインタ.実行状態のタスクがない場合はnullにする.
///
///  サービスコールの処理中で,自タスク(サービスコールを呼び出したタ
///  スク)に関する情報を参照する場合はp_runtskを使う.p_runtskを書き
///  換えるのは,ディスパッチャ(と初期化処理)のみである.
///
pub var p_runtsk: ?*TCB = undefined;

カーネルを一通り読むためには今回見たような要素の理解は不可欠です。 Zigを勉強するという意味では、とくに見所はないんですけどね。

今回はほとんどソースコードをペタペタ引用して貼っただけに近くなってしまいました。 次回はもうちょっと中身のある内容にできたらいいなと思います!

それでは!!