な毎か

なんて事ない毎日がかけがえないんだよなぁ…

x86(i386)向けのOS作成日記 - メッセージ1

2018.1.13

年明けは仙台とTDC(Tokyo Dome City)でアイカツライブ。特筆すべきは仙台の紳士淑女では紳士の中で最前だったこと、そして満足度が高かったこと。So Beautiful Storyは泣かせに来ていた。仙台は牛タン、そして寿司が美味かった。TDCではソレイユの3人が出てきて、Good Morning My Dream→Trap of Love→ショコラショー・タイムfull→カレンダーガール(!!)。これを見たら泣かざる(カレンダーガールは無理だし、やらないと思っていた)を得ないし、アイカツが終わったことを実感せざるを得なかった。あとは荒野の軌跡で歌詞飛びがあったのがポイントだろうか。アイカツのライブでは明らかな失敗は初めて経験した。

最初の作業は多摩近くで。まずやるのはメッセージキューの設計/実装。

メッセージモジュールの設計

構造体

まず、メッセージの構造体Messageは次のようにする:

#define MAX_MESSAGE_STRING_LENGTH 128 /* 適宜 */

/* メッセージ構造体 */
struct Message {
    uint32_t type; /* メッセージを識別する番号 */
    /* メッセージ本体           */
    union {
        uint32_t   uint32;
        char       string[MAX_MESSAGE_STRING_LENGTH];
    } u;
};

typeでメッセージを識別する番号を入れ、共用体uでメッセージ内容を入れる。メッセージの領域管理を簡単にするため、メッセージ本体にはポインタを使用していない。 Linuxとかだと構造体を置き換えることで多様なメッセージを使用可能にしているみたいだが、そうしない(構造体のメンバ並びかえが怖いため)。 また、この構造体はヘッダに公開する。

これらのメッセージをまとめるメッセージ・キューの構造体は次:

#define MAX_NUM_MESSAGES_PAR_CUE    128    /* 適宜 */
#define NUM_MESSAGE_CUES           16 /* 適宜 */

/* メッセージのリスト(今は線形リスト。後に拡張するかも) */
struct MessageList {
    struct Message     message;    /* メッセージ */
    struct MassageList*    next;       /* リストの次の要素 */
};

/* メッセージキュー */
struct MessageCue {
    uint32_t           cue_id;         /* メッセージキューのID  */
    struct MessageList*    msg_list;       /* メッセージリストの先頭 */
    uint32_t           size;           /* リストのサイズ */
    uint32_t           free;           /* 空きサイズ */
    uint32_t           flags;          /* 内部状態フラグ */
    struct MessageList meg_pool[MAX_NUM_MESSAGES_PAR_CUE]; /* メッセージリストの領域 */
};

/* システム(OS)向けにはキューリストは固定長の配列で持つ */
static struct MessageCue st_system_message_cue[NUM_MESSAGE_CUES];

メッセージのリストMessageListは配列ではなく線形リストとする。メッセージリストの途中から変更する可能性があるため、メッセージは普通のキュー(FIFO)にはならないからである。

メッセージキューにはIDを設けておく。(優先すべきメッセージとそうでないものを区別するため)

API

ユースケースを考え、一般的な使用順序でいくと...

キューの新規作成
int32_t MessageCue_Create(uint32_t cue_id, uint32_t cue_size);

キューIDとメッセージリストのサイズを指定してキューを新規作成する。キューIDの重複があったり、キューサイズが大きければ失敗。

メッセージの送受信
int32_t Message_SendMsg(uint32_t cue_id, struct Message* message);
int32_t Message_ReceiveMsg(uint32_t cue_id, uint32_t msg_type, struct Message* message);
int32_t Message_ReceiveMsgNonBlock(uint32_t cue_id, uint32_t msg_type, struct Message* message);

指定したキューIDのキューに対してメッセージを送受信する。 メッセージはメッセージの種類とその先頭ポインタ、そしてメッセージ構造体自体のサイズを渡す。 受信側ではmsg_typeの送信を待ち、キューから一番古いmsg_typeを持つメッセージを取り出す。 Message_ReceiveMsgはブロックして指定したタイプのメッセージの到着を待つ。Message_ReceiveMsgNonBlockは現在のキューに指定したタイプのメッセージがなければ即時に終了してエラーを返す。

2018.2.17

多摩近くで再開。いろんなことが起こりすぎて作業着手できなかった。

今一番心にあるのはアイカツ歌唱担当の卒業。これにより、少なくとも初代アイカツが非可逆的に失われることになる。アイカツについては別の記事にまとめておく。

仕事面でも案件を回しきれない日々が続いた。家に帰ってもピアノと絵に手が付かない毎日。理論勉強もあまり進まない。というか、色々なことに手を出しすぎだ。タスク一覧を挙げてみる:

  • 仕事:継続するか?やりたいこと出来ているか怪しい。耐えてるだけでは。
  • ピアノ:週2でやれれば良い方。。。上達が薄い気がするし、作曲に移れない
  • 絵:一ヶ月やれてない。模写だけでも良いからやりたい。
  • OS自作:この日記を書くまで止まってた。
  • その他の趣味プログラミング:圧縮(アーカイバLZMAプロトタイピング)やりたいけどできてない。
  • アイカツ:稼働終了に伴い平日プレイ回数増加中
  • 理論:群論(見える群論)、超関数(これなら分かる工学部の数学)について見ているだけ…まとめたい

メッセージを思い出しながらコーディングしてみる。 外部仕様自体は2018.1.13で決めてあったので、気軽にコーディングした。

2018.2.18

武道館公演まで時間がない。

アイカツのCOMPLETE CD-BOXを取り込みながらメッセージモジュールのテストを書く。

書いていて思ったがノンブロッキングな受信関数は関数で分けるべきでは無いのでは?受信と行っても様々なモード(例:キューから取り除かない、見るだけとか存在確認だけ等)があるので、モード引数をつけるべきでは。

また失敗時のエラー原因をマクロ定数化すべきと思う。

実装を再検討し、完成次第ここに書こう。

2018.2.21

アイカツを見ながらテストケースを追加し、仕様を固めた。(精神が限界。アイカツが本当に終わろうとしている。)

メッセージ

メッセージ構造体は公開する。ユーザはこの構造体にメッセージを詰め、APIの引数に渡す。

/* メッセージ構造体 */
struct Message {
    uint32_t type; /* メッセージを識別する番号 */
    /* メッセージ本体 */
    union {
        uint32_t   uint32;
        char       string[MAX_MESSAGE_STRING_LENGTH];
    } u;
};

メッセージキュー

メッセージを束ねるメッセージキューは次の構造体。

/* メッセージのリスト(今は線形リスト。後に拡張するかも) */
struct MessageList {
  uint8_t               flags;      /* メッセージ状態 */
  struct Message       message;    /* メッセージ */
  struct MessageList    *next;     /* リストの次の要素 */    
};

/* メッセージキュー */
struct MessageCue {
    uint32_t           cue_id;         /* メッセージキューのID  */
    struct MessageList *msg_list;      /* メッセージリストの先頭 */
    uint32_t           size;           /* リストのサイズ */
    uint32_t           free;           /* 空きサイズ */
    uint32_t           flags;          /* 内部状態フラグ */
    struct MessageList msg_pool[MAX_NUM_MESSAGES_PAR_CUE]; /* メッセージリストの領域 */
};

メッセージリストの配列は、静的に持つ。(もしかしたら動的にアロケートできるかも。。。簡単にするために固定長配列。)

キューはグローバルな静的領域に確保する。

/* システム(OS)向けにはキューリストは固定長の配列で持つ */
static struct MessageCue st_system_message_cue[NUM_MESSAGE_CUES];

メッセージキューモジュールの初期化

全てのメッセージキューを無効値で埋める。この後にメッセージは送信可能になる。

/* メッセージキューモジュール初期化 */
void MessageCue_Initialize(void)
{
  uint32_t i_cue, i_msg;
  struct MessageCue *p_msgcue;

  for (i_cue = 0; i_cue < NUM_MESSAGE_CUES; i_cue++) {
    p_msgcue           = &st_system_message_cue[i_cue];
    p_msgcue->cue_id   = INVALID_CUE_ID;
    p_msgcue->flags    = 0;
    p_msgcue->msg_list = NULL;
    for (i_msg = 0; i_msg < MAX_NUM_MESSAGES_PAR_CUE; i_msg++) {
      p_msgcue->msg_pool[i_msg].next = NULL;
    }
  }
  
}

メッセージキューの作成

静的領域から取得するが、ワーク領域を渡して動的確保しても良いかも。 (動的確保するメリットが薄いが…)

/* メッセージキューの作成 */
int32_t MessageCue_Create(uint32_t cue_id, uint32_t cue_size)
{
  uint32_t i_cue, i_msg;
  struct MessageCue *p_msgcue;

  /* 最大メッセージ数を越えていたらエラー */
  if (cue_size > MAX_NUM_MESSAGES_PAR_CUE) {
    return -1;
  }

  /* 無効なキューID */
  if (cue_id == INVALID_CUE_ID) {
    return -2;
  }

  /* 指定したキューIDは既に使われていないかチェック */
  for (i_cue = 0; i_cue < NUM_MESSAGE_CUES; i_cue++) {
    if ((st_system_message_cue[i_cue].flags & MESSAGE_CUE_FLAGS_INITIALIZED)
        && (cue_id == st_system_message_cue[i_cue].cue_id)) {
      return -3;
    }
  }

  /* 使用可能なキューがないか探索 */
  for (i_cue = 0; i_cue < NUM_MESSAGE_CUES; i_cue++) {
    if (!(st_system_message_cue[i_cue].flags & MESSAGE_CUE_FLAGS_INITIALIZED)) {
      break;
    }
  }

  /* キューが全て使用済みだった */
  if (i_cue >= NUM_MESSAGE_CUES) {
    return -4;
  }

  /* 適宜初期値をセット */
  p_msgcue           = &st_system_message_cue[i_cue];
  p_msgcue->cue_id   = cue_id;
  p_msgcue->size     = cue_size;
  p_msgcue->free     = cue_size;
  p_msgcue->msg_list = NULL;
  for (i_msg = 0; i_msg < MAX_NUM_MESSAGES_PAR_CUE; i_msg++) {
    p_msgcue->msg_pool[i_msg].flags = 0;
    p_msgcue->msg_pool[i_msg].next  = NULL;
  }

  /* 初期化フラグを立て、無事に処理を終了 */
  st_system_message_cue[i_cue].flags |= MESSAGE_CUE_FLAGS_INITIALIZED;

  return 0;
}

メッセージキューの破棄

指定したキューIDを持つメッセージキューを削除する。

int32_t MessageCue_Destroy(uint32_t cue_id)
{
  uint32_t i_cue;
  struct MessageCue *p_msgcue;

  /* 無効なキューID */
  if (cue_id == INVALID_CUE_ID) {
    return -1;
  }

  /* 指定したキューIDを持ち、初期化済みのメッセージキューを探索 */
  for (i_cue = 0; i_cue < NUM_MESSAGE_CUES; i_cue++) {
    p_msgcue = &st_system_message_cue[i_cue];
    if ((p_msgcue->cue_id == cue_id)
        && (p_msgcue->flags & MESSAGE_CUE_FLAGS_INITIALIZED)) {
      break;
    }
  }

  /* 探索に失敗した */
  if (i_cue >= NUM_MESSAGE_CUES) {
    return -2;
  }

  /* 無効値にクリア */
  p_msgcue->cue_id   = INVALID_CUE_ID;
  p_msgcue->msg_list = NULL;
  p_msgcue->size     = 0;
  p_msgcue->free     = 0;

  /* フラグを落とす */
  p_msgcue->flags &= ~MESSAGE_CUE_FLAGS_INITIALIZED;

  return 0;
}

メッセージの送信

メッセージを指定したキューIDのメッセージキューのリスト末尾に追加する。

第二引数がポインタである必然はない…。構造体の実体でも全く問題ない。 (もしメッセージを抽象化するならポインタにするべし。)

もう一点気になるのは、エラーコードは内部的でもいいので決めておくべきか…。

/* メッセージを送信 */
int32_t Message_SendMsg(uint32_t cue_id, const struct Message* message)
{
  uint32_t i_msg;
  struct MessageCue  *p_msgcue;
  struct MessageList *p_msg, *p_newmsg;
  
  /* 無効なキューID */
  if (cue_id == INVALID_CUE_ID) {
    return -1;
  }

  /* メッセージキューの検索 */
  if (messagecue_searchcue(st_system_message_cue, cue_id, &p_msgcue) != 0) {
    return -2;
  }
  
  /* メッセージキューが初期化済みではない */
  if (!(p_msgcue->flags & MESSAGE_CUE_FLAGS_INITIALIZED)) {
    return -3;
  }

  /* メッセージキューが一杯 */
  if (p_msgcue->free == 0) {
    return -4;
  }

  /* 空いているメッセージを探索 */
  for (i_msg = 0; i_msg < p_msgcue->size; i_msg++) {
    if (!(p_msgcue->msg_pool[i_msg].flags & MESSAGE_FLAGS_USED)) {
      p_newmsg = &p_msgcue->msg_pool[i_msg];
      break;
    }
  }

  /* 指定したキューIDが見つからない */
  if (i_msg >= p_msgcue->size) {
    return -5;
  }

  /* メッセージの新規作成 */
  p_newmsg->flags   |= MESSAGE_FLAGS_USED;
  p_newmsg->message  = *message; /* 構造体コピー */
  p_newmsg->next     = NULL;

  /* メッセージをキュー(リスト末尾)に追加 */
  p_msg = p_msgcue->msg_list;
  if (p_msg == NULL) {
    /* 初回時は先頭に配置 */
    p_msgcue->msg_list = p_newmsg;
  } else {
    for (p_msg = p_msgcue->msg_list;
         p_msg->next != NULL;
         p_msg = p_msg->next) { ; }
    p_msg->next = p_newmsg;
  }
  
  p_msgcue->free--;

  /* 受信待ち状態に遷移 */
  p_newmsg->flags |= MESSAGE_FLAGS_WAITFORRECIEVE;

  return 0;
  
}

メッセージの受信

フラグrecvflagsに従ってキューからメッセージをキュー先頭から取り出す。 フラグにはキューから取り出さずコピーするMESSAGE_RECEIVE_FLAGS_COPYと、メッセージをブロックせずに受信するMESSAGE_RECEIVE_FLAGS_NOWAITがある。

MESSAGE_ERROR_NOMESSAGEだけは外部公開してある。(ただしその値が-4...-1あたりに再定義したいが、内部コードが汚くなる)

/* メッセージ受信 */
int32_t Message_ReceiveMsg(uint32_t cue_id, uint32_t msg_type, struct Message* message, uint32_t recvflags)
{
  struct MessageCue *p_msgcue;
  
  /* 無効なキューID */
  if (cue_id == INVALID_CUE_ID) {
    return -1;
  }

  /* メッセージキューの検索 */
  if (messagecue_searchcue(st_system_message_cue, cue_id, &p_msgcue) != 0) {
    return -2;
  }

  /* メッセージキューが初期化済みではない */
  if (!(p_msgcue->flags & MESSAGE_CUE_FLAGS_INITIALIZED)) {
    return -3;
  }

  /* メッセージの受信待機(待たない時は1回で終了) */
  do {
    if (message_searchfromcue(p_msgcue, msg_type, message, recvflags & MESSAGE_RECEIVE_FLAGS_COPY) == 0) {
      return 0;
    }
  } while (!(recvflags & MESSAGE_RECEIVE_FLAGS_NOWAIT));

  /* メッセージが見つからない */
  return MESSAGE_ERROR_NOMESSAGE;

}

懸念

  • 送受信の排他を行っていない。 ミューテックスセマフォを用意していないから、用意のしようがない。OS自作ではどうやって排他を実現していたっけ…