(第28回)リアルタイム性について考える(その3:動的メモリ獲得を改良する)

2007/12/12

あなたは 人目のお客様です.

えーと,風邪を引いてしまいました.咳が止まらないです (で,会社を休んで連載を書いている). みなさんも風邪には気をつけて,うがいをマメにするようにしましょう.

で,今回はKOZOSの,というよりもOS一般のメモリ管理について考えてみよう.

前回の最後に説明したことなのだけど,現状のKOZOSでは内部のメモリ管理に malloc ライブラリをそのまま用いている.まあKOZOSはユーザランドで動作する 疑似OSなのでこんなことができているわけだが,まああたりまえだが普通のOSでは ただ内部で malloc() を呼び出せばメモリが得られる,なんてことはない.

UNIX の malloc() ライブラリは,sbrk() システムコールによってOSからページサイズ 単位(通常,4KB)で得られたメモリ領域を可変サイズで切り売りするものだ. 基本的に,OSは任意サイズでメモリを管理するようなことはまああまり無い (この理由は後述). なので固定サイズのメモリの内部を任意サイズでうまいこと分割して管理して くれるのが malloc() ライブラリだ.まあこのへんの話は K&R2 にもあるし, ほかにも探せばいろいろと出てくると思うので,そちらを参考にしてほしい.

で,OSの内部でも,内部の処理用に動的にメモリが欲しい場合は多々ある. KOZOSの内部でも,メッセージキューやタイマキューの管理のために, sendmsg(), thread_timer() などで動的にメモリを獲得している. で,現状では malloc() を呼んでしまっているのだが, 実際のOSではこんなふうに何も考えず内部で malloc() を使えることはなく, OSの内部で独自に動的メモリ管理を行わなければならない. で,とりあえず簡単に考えると,以下のような仕組みにすることを思いつく.

  1. static char membuf[1024*256];
    
    のようにして,巨大なメモリ空間を静的に確保する.
  2. 上記メモリ空間を(malloc() ライブラリで行っているように)内部を可変長で 細分化して管理する.
まあ何を言っているのかわかりにくいかもしれないが, つまりOSの内部に malloc() のような可変長メモリ管理を導入するというものだ.

で,これでいいかというと,まあ実際にこれでもOSは動作するだろう. しかし普通のOSでは,このような構造はまあ採用されないだろうと思う. というのはこのように固定領域の内部を可変長で管理するという方法だと, 内部をリンクリスト管理する必要性が出てくるため,メモリ獲得時のリンクリストの 検索が避けられなくなっている(工夫することで回数を減らすことはできるだろうが, 根本的に解決することはできない).で,こーいうのはOSの応答性能に如実に かかわってくる.動的メモリ獲得はOS内部のいろいろなところで利用されるため, その都度,メモリ獲得にかかる時間が予測できなくなってしまうわけだ.

これはまず,リアルタイムOSでは致命的だ. しかしリアルタイムでない汎用OSなどでも, 性能の問題になるのでまああまりよろしくない. ということでふつうはどういうふうに動的メモリ管理を行うかというと, まずは可変長という仕組みを廃止して,固定長で管理するのが普通だと思う.

固定長ならば配列として管理できるので,領域の使用/未使用をビットマップ管理 することができる.このため空き領域の検索処理を高速化したり, 書き方を工夫することでリアルタイム性を確保することができる. まあこのへんはそのうち機会があったら説明するが,ビットマップ管理されている ならば,CPUのビットサーチ命令を使ったり,ビットマップを半分ずつ分割して ゼロチェックしていく方法(Toppersで優先度キューの検索に使われている)などにより, 一定時間での空き領域検索が可能になるため, リアルタイム性を確保することができるのだ.

とくにOSの内部では,それほど巨大なメモリ領域が必要となる機会はそれほど 多くはないので,巨大メモリを確保している場所は限定できる場合が多い. なので,数KBくらいの固定長メモリ管理で十分に役目を果たせる場合が多い. 利用効率を考えるならば,数十バイト,数百バイト,数キロバイトの数種類の サイズの固定メモリ管理を用意し,必要に応じて利用者側で選択 (もしくは自動選択)するようにすればよい.

で,そーいう理由なのかどうか歴史的ないきさつはあまりよくは知らないのだけど, BSDでは mbuf という,固定サイズの動的メモリ管理機構をOS内部で持っている. 主にネットワーク転送のためにパケットを格納するのに利用されたりするが, 他にも固定サイズの動的メモリとして,様々な箇所で利用される. Linux だとまた別に何かあったと思う,たしか. こんなふうに,OSは内部に汎用的な固定サイズの動的メモリ獲得機構を 持っている場合が多い.

OSのカーネルソースを読むと,なぜメモリ獲得に malloc() を使っていないのか, なぜ固定長なのか,という疑問にぶちあたるのだけど,まあこーいう理由があるのだ.

で,今回はKOZOS内部のメモリ管理を設計しなおしてみよう.

ということで,以下のように設計しよう. たとえば extintr では,リードしたデータのメッセージ送信のために2KBの領域 (BUFFER_SIZE として定義されている)を kz_memalloc() によって獲得している. これは応答性のことを考えると,kz_memalloc() ではなく kz_kmalloc() を 利用するのが望ましい.このように,固定サイズだとはっきりとわかっているような 場合には kz_kmalloc() を使ったほうがよい.リアルタイム性が確保できるし, リアルタイム性が不要だとしても,とりあえず高速だからだ. しかし必要サイズが実行時までわからないような場合には,kz_memalloc() を 利用する,ということになる.

で,修正したのが以下.

(2009/04/10 ライセンスに関する文書として,KL-01とLICENSEを追加. 詳しくは第43回を参照)

以下に修正点について説明する. 今回はけっこういっぱい修正しているので注意してほしい.

まず,固定サイズの動的メモリ管理のために memory.h, memory.c を追加している. memory.h はまあたいして説明することは無いので説明は省略. memory.c は,まあ単なるC言語の関数なのだが,今回の目玉でもあるので 以下に詳細に説明しよう.

まずは memory.c の先頭部分.

#define MEMORY_AREA1_SIZE 128
#define MEMORY_AREA1_NUM  100
#define MEMORY_AREA2_SIZE 512
#define MEMORY_AREA2_NUM  50
#define MEMORY_AREA3_SIZE 2048
#define MEMORY_AREA3_NUM  20

static char memory_area_1[MEMORY_AREA1_SIZE * MEMORY_AREA1_NUM];
static char memory_area_2[MEMORY_AREA2_SIZE * MEMORY_AREA2_NUM];
static char memory_area_3[MEMORY_AREA3_SIZE * MEMORY_AREA3_NUM];
今回作成した固定サイズ動的メモリ管理ライブラリでは, 固定領域のサイズとして128バイト,512バイト,2キロバイトの3種類のサイズを 用意している. それぞれの獲得最大数は,とりあえず100個,50個,20個に設定してある. このためそれぞれのメモリプールのサイズは128×100バイト,512×50バイト, 2048×20バイトの固定領域を static に獲得している.

このように,メモリの動的管理のためにあらかじめ確保する巨大な空きメモリのことを 一般に「プール」とか「メモリプール」とか「フリープール」とか呼ぶ. メモリプールは物理メモリの空き部分である「ヒープ領域」から一定のサイズを 獲得している場合が多いのだが,KOZOSではとりあえず static なバッファとして memory.c の先頭で獲得している.

OS内部の固定サイズ動的メモリ管理機構は,このようにして確保したメモリプールを 固定サイズの配列に区画分けして,必要に応じて割り当てるという作業を行うことに なる.

次に,領域の管理用構造体の定義.

typedef struct _kzmem {
  struct _kzmem *next;
  int size;
  int dummy[2];
} kzmem;

typedef struct _kzmem_pool {
  char *area;
  int size;
  int num;
  kzmem *free;
} kzmem_pool;

static kzmem_pool pool[] = {
  { memory_area_1, MEMORY_AREA1_SIZE, MEMORY_AREA1_NUM, NULL },
  { memory_area_2, MEMORY_AREA2_SIZE, MEMORY_AREA2_NUM, NULL },
  { memory_area_3, MEMORY_AREA3_SIZE, MEMORY_AREA3_NUM, NULL },
};

#define MEMORY_AREA_NUM (sizeof(pool) / sizeof(*pool))
まあ構造体の使いかたについては,たいして難しくはないので memory.c を 読んでほしい. 注意として,メモリ領域の先頭には管理領域として必ず kzmem 構造体が付加されて いる.kzmem 構造体のサイズは16バイトなので, たとえば今回の設計では固定領域のサイズとして 128バイト,512バイト,2キロバイトの3種類のサイズが利用できるが, 128バイト領域が実際に格納できるサイズは 128 − 16 で112バイトということになる.

ちなみに kzmem 構造体は,ダミーのメンバを入れることでサイズを16バイトに 調整してある. これは,将来的に管理用に新しいメンバ (マジックナンバとか,メモリの種別とか,フラグとか)を追加したくなった際に, ダミーの部分を置き換えることで kzmem のサイズを変更せずに拡張できるように するためだ.まあサイズが変わったところで実害は無いのでここまで考えておく 必要は無いかもしれないが,いちおうあらかじめダミーを入れておくことにした.

次に,メモリ管理の初期化.

static int kzmem_init_pool(kzmem_pool *p)
{
  int i;
  kzmem *mp;
  kzmem **mpp;

  mp = (kzmem *)p->area;
  mpp = &p->free;
  for (i = 0; i < p->num; i++) {
    *mpp = mp;
    memset(mp, 0, sizeof(*mp));
    mp->size = p->size;
    mpp = &(mp->next);
    mp = (kzmem *)((char *)mp + p->size);
  }

  return 0;
}

int kzmem_init()
{
  int i;
  for (i = 0; i < MEMORY_AREA_NUM; i++) {
    kzmem_init_pool(&pool[i]);
  }
  return 0;
}
kzmem_init() を呼び出すと,3種類の固定メモリのメモリプールの初期化を行う. まあ具体的には kzmem_init_pool() を呼び出し,メモリプールを固定サイズに 分割してぜんぶフリーリストに繋げておく,ということを行う. kzmem_init() はKOZOSの起動時に読んでもらう必要がある. ちなみにここではループ処理を行っているためリアルタイム性を確保できないが, 起動時に1回呼ぶだけの初期化処理なのでまあ気にしないことにする. このへんはメモリ獲得のたびに徐々に切り取っていくようにするなどすれば, kzmem_init() で一気に初期化しないようにすることもできる. そのうち改良が必要かも.

次に,実際のメモリ獲得処理.

void *kzmem_alloc(int size)
{
  int i;
  kzmem *mp;
  kzmem_pool *p;

  for (i = 0; i < MEMORY_AREA_NUM; i++) {
    p = &pool[i];
    if (size <= p->size - sizeof(kzmem)) {
      if (p->free == NULL) {
	kz_sysdown();
      }
      mp = p->free;
      p->free = p->free->next;
      mp->next = NULL;
      return (char *)mp + sizeof(kzmem);
    }
  }

  kz_sysdown();
  return NULL;
}
kzmem_alloc()は,メモリ獲得用のライブラリ関数だ. 与えられたサイズに応じて,3種類の固定メモリの中から適切なサイズのものを返す. ちなみに最適サイズの検索のために
  for (i = 0; i < MEMORY_AREA_NUM; i++) ...
のようなループを行っているが,これはループ回数を固定で見積もれるため, リアルタイム性に関する問題は無い.

プールのサイズは有限なので,プールが不足するとメモリが獲得できないと いうことになる.この場合はどうするべきか? また実際の領域は3種類の固定サイズ(128バイト,512バイト,2キロバイト)の中から 適切なものが選択されるが,たとえば4キロバイトの領域を獲得しようとした場合には どうすべきか?

これについては kzmem_alloc() がNULLを返し,呼び出し元でNULLチェックして 判断する,という方法もある. しかし組み込みOSでは,システム全体を1単位として開発元でチューニングする ことができる.よって 「メモリ領域が足りなくなったらどうするか?」ではなく, 「メモリ領域が足りなくならないように,各アプリが必要とする領域数に合わせて 領域数を拡張しておく」 という設計が可能だ. なので,今回は足りなくなったときにはNULLを返すのではなく, システムダウンする設計にする.上述したように,領域が不足しているということは そもそも領域を拡張するようなチューニングが必要だ,ということなので, NULLを返してそれなりに動いてしまうのではなく,領域不足としてダウンしてくれた ほうが親切だ.4キロバイトなどのオーバーサイズの領域を獲得しようとした 場合についても,同様にシステムダウンする設計にする.

次に解放用のライブラリ関数.

void kzmem_free(void *mem)
{
  int i;
  kzmem *mp;
  kzmem_pool *p;

  mp = (kzmem *)((char *)mem - sizeof(kzmem));

  for (i = 0; i < MEMORY_AREA_NUM; i++) {
    p = &pool[i];
    if (mp->size == p->size) {
      mp->next = p->free;
      p->free = mp;
      return;
    }
  }

  kz_sysdown();
}
こちらは返された領域をフリーリストに繋ぎなおすだけだ.

注目なのは,未使用領域の獲得時にはキューの先頭から領域取得して, 領域の解放時にはキューの先頭に繋げるだけのため, ループによる検索などは行っていないことだ. このためメモリの獲得・解放はリアルタイム性を保証できる.

次に,他の部分の修正差分について説明しよう.

まず thread.h についてなのだけど,実はメッセージの管理用構造体が kz_membuf という名前になっていて,今回追加したメモリ管理と名前が混乱しやすい. なので kz_membuf を kz_msgbuf という本来の名前に変更する.

diff -ruN kozos27/thread.h kozos28/thread.h
--- kozos27/thread.h	Wed Dec 12 13:19:43 2007
+++ kozos28/thread.h	Wed Dec 12 15:00:49 2007
@@ -12,12 +12,12 @@
 #define PRI_NUM 32
 #define THREAD_NAME_SIZE 16
 
-typedef struct _kz_membuf {
-  struct _kz_membuf *next;
+typedef struct _kz_msgbuf {
+  struct _kz_msgbuf *next;
   int id;
   int size;
   char *p;
-} kz_membuf;
+} kz_msgbuf;
 
 typedef struct _kz_thread {
   struct _kz_thread *next;
@@ -35,8 +35,8 @@
   } syscall;
 
   struct {
-    kz_membuf *head;
-    kz_membuf *tail;
+    kz_msgbuf *head;
+    kz_msgbuf *tail;
   } messages;
 
   struct {
次に thread.c の修正が以下.
diff -ruN kozos27/thread.c kozos28/thread.c
--- kozos27/thread.c	Wed Dec 12 13:19:43 2007
+++ kozos28/thread.c	Wed Dec 12 15:00:50 2007
@@ -8,6 +8,7 @@
 
 #include "kozos.h"
 #include "syscall.h"
+#include "memory.h"
 #include "thread.h"
 #include "stublib.h"
 
@@ -164,7 +165,7 @@
 
 static void recvmsg()
 {
-  kz_membuf *mp;
+  kz_msgbuf *mp;
 
   mp = current->messages.head;
   current->messages.head = mp->next;
@@ -177,16 +178,16 @@
     *(current->syscall.param->un.recv.idp) = mp->id;
   if (current->syscall.param->un.recv.pp)
     *(current->syscall.param->un.recv.pp)  = mp->p;
-  free(mp);
+  kzmem_free(mp);
 }
 
 static void sendmsg(kz_thread *thp, int id, int size, char *p)
 {
-  kz_membuf *mp;
+  kz_msgbuf *mp;
 
   current = thp;
 
-  mp = (kz_membuf *)malloc(sizeof(*mp));
+  mp = (kz_msgbuf *)kzmem_alloc(sizeof(*mp));
   if (mp == NULL) {
     fprintf(stderr, "cannot allocate memory.\n");
     exit(1);
@@ -238,7 +239,7 @@
   int diffmsec;
   struct itimerval itm;
 
-  tmp = malloc(sizeof(*tmp));
+  tmp = kzmem_alloc(sizeof(*tmp));
   tmp->next = NULL;
   tmp->thp = current;
 
@@ -286,7 +287,7 @@
   sendmsg(timers->thp, 0, 0, NULL);
   tmp = timers;
   timers = timers->next;
-  free(tmp);
+  kzmem_free(tmp);
   if (timers) {
     gettimeofday(&alarm_tm, NULL);
     itm.it_interval.tv_sec  = 0;
@@ -324,6 +325,19 @@
   return 0;
 }
thread.h と同様に kz_membuf → kz_msgbuf の修正が各箇所に入っている. あとメッセージ管理とタイマ管理に malloc()/free() を利用していた部分があり, kzmem_alloc()/kzmem_free()に変更している. これらの部分では sizeof(kz_msgbuf) や sizeof(kz_timebuf)のサイズの領域が 獲得されている.つまり,構造体のサイズのメモリ領域が獲得されている. よってここで獲得される領域サイズは固定なので, kzmem_alloc()/kzmem_free() に移行してしまって問題は無い. これにより,メッセージ処理部分,タイマ処理部分(タイマ設定部分を除く)に対して リアルタイム性を持たせることができる.

次に,まあ kzmem_alloc()/kzmem_free() は本来はKOZOSのカーネル内部での 動的メモリ管理のためのライブラリなのだが, リアルタイム性のあるメモリ管理が行えるため, それをそのままサービスするためのシステムコールを追加する.

+static void *thread_kmalloc(int size)
+{
+  putcurrent();
+  return kzmem_alloc(size);
+}
+
+static int thread_kmfree(char *p)
+{
+  kzmem_free(p);
+  putcurrent();
+  return 0;
+}
+
 static void *thread_memalloc(int size)
 {
   putcurrent();
@@ -386,6 +400,12 @@
   case KZ_SYSCALL_TYPE_DEBUG:
     p->un.debug.ret = thread_debug(p->un.debug.sockt);
     break;
+  case KZ_SYSCALL_TYPE_KMALLOC:
+    p->un.kmalloc.ret = thread_kmalloc(p->un.kmalloc.size);
+    break;
+  case KZ_SYSCALL_TYPE_KMFREE:
+    p->un.kmfree.ret = thread_kmfree(p->un.kmfree.p);
+    break;
   case KZ_SYSCALL_TYPE_MEMALLOC:
     p->un.memalloc.ret = thread_memalloc(p->un.memalloc.size);
     break;
本来ならばカーネル用のメモリプールとユーザースレッド用(システムコール用)の メモリプールを別に管理すべきなような気もするが,面倒なのでまあいいとしよう.

こーいうように,カーネルのための資源とユーザーのための資源をごっちゃにして しまうと,ユーザースレッドがへんなことをしたらカーネル側にもそれが伝搬して システム全体が落ちる,ということの原因になり得るので,本来ならばきちんと 資源を分離すべきだと思う.

しかしまあ第25回の最後のほうで書いたように,組み込みOSは性善説に基づいて いるというか,ユーザースレッドがおかしなことをしたときには, 「そのスレッドはどうでもいいとして,全体としては落ちることなく動作する」 というのではなく,「そのユーザースレッドを正すべき」という考えだ.なので きちんと資源を分離することには,デバッグが楽になるという以上の意味は無い. (これが汎用OSならば,おかしなアプリが動いたときにOS全体がおかしくなることを 防止する,という意味があるのだが,組み込みOSではそうなったらそーいうアプリの バグを直すべきなので,そーいう意味で,デバッグが楽になる以上の意味は無い)

次に,KOZOSの起動部分に kzmem_init() の呼び出しを追加. さらにシステムダウン用のサービス関数として,kz_sysdown() を作成する.

@@ -512,6 +532,8 @@
 
 void kz_start(kz_func func, char *name, int pri, int argc, char *argv[])
 {
+  kzmem_init();
+
   sigemptyset(&block);
   sigaddset(&block, SIGSYS);
   sigaddset(&block, SIGHUP);
@@ -525,6 +547,11 @@
 
   /* ここには返ってこない */
   abort();
+}
+
+void kz_sysdown()
+{
+  kill(getpid(), SIGILL);
 }
 
 void kz_trap()
次に,システムコールの追加について. 今回は新規システムコールとして,リアルタイムな動的メモリ獲得のための kz_kmalloc()/kz_kmfree() が追加されている.
diff -ruN kozos27/syscall.c kozos28/syscall.c
--- kozos27/syscall.c	Wed Dec 12 13:19:43 2007
+++ kozos28/syscall.c	Wed Dec 12 14:58:54 2007
@@ -120,6 +120,22 @@
   return param.un.debug.ret;
 }
 
+void *kz_kmalloc(int size)
+{
+  kz_syscall_param_t param;
+  param.un.kmalloc.size = size;
+  kz_syscall(KZ_SYSCALL_TYPE_KMALLOC, ¶m);
+  return param.un.kmalloc.ret;
+}
+
+int kz_kmfree(void *p)
+{
+  kz_syscall_param_t param;
+  param.un.kmfree.p = p;
+  kz_syscall(KZ_SYSCALL_TYPE_KMFREE, ¶m);
+  return param.un.kmfree.ret;
+}
+
 void *kz_memalloc(int size)
 {
   kz_syscall_param_t param;
diff -ruN kozos27/syscall.h kozos28/syscall.h
--- kozos27/syscall.h	Wed Dec 12 13:19:43 2007
+++ kozos28/syscall.h	Wed Dec 12 14:51:32 2007
@@ -17,6 +17,8 @@
   KZ_SYSCALL_TYPE_PENDING,
   KZ_SYSCALL_TYPE_SETSIG,
   KZ_SYSCALL_TYPE_DEBUG,
+  KZ_SYSCALL_TYPE_KMALLOC,
+  KZ_SYSCALL_TYPE_KMFREE,
   KZ_SYSCALL_TYPE_MEMALLOC,
   KZ_SYSCALL_TYPE_MEMFREE,
 } kz_syscall_type_t;
@@ -77,6 +79,14 @@
       int sockt;
       int ret;
     } debug;
+    struct {
+      int size;
+      void *ret;
+    } kmalloc;
+    struct {
+      char *p;
+      int ret;
+    } kmfree;
     struct {
       int size;
       void *ret;
まあシステムコールの追加については連載のかなり初期のほうで説明しているので, ここでは詳しくは説明しない.

次に,各種スレッドについてちょっと修正. まずは extintr.c の修正について.

diff -ruN kozos27/extintr.c kozos28/extintr.c
--- kozos27/extintr.c	Wed Dec 12 13:19:43 2007
+++ kozos28/extintr.c	Wed Dec 12 15:10:44 2007
@@ -10,7 +10,7 @@
 #include "kozos.h"
 #include "thread.h"
 
-#define BUFFER_SIZE 2048
+#define BUFFER_SIZE 1024
 
 int extintr_id;
 
まずは外部割り込み発生時に,ソケットをリードして当該のスレッドにメッセージ送信 するためのバッファのサイズなのだが,今までは2KBになっていた. しかし今回,このメモリ獲得を kzmem_alloc() に移行する. memory.c で管理している固定メモリ領域の最大サイズは 2キロバイトなので,これはギリギリ入りそうに思えるのだが, 実はこの2キロバイトというサイズは管理領域である kzmem 構造体のサイズも 含まれているため,実際に利用できるのは 2048 − 16 = 2032 バイトとなる. よって BUFFER_SIZE を1KBに縮小している.まあ BUFFER_SIZE を縮小したところで, 足りなければ数回に渡って read() されるだけなので実害は無い.

あとついでに,memory.c での固定メモリ領域の最大サイズを2048バイト (実質は2032バイト)にしている理由なのだが,将来的に ethernet フレームを 扱う際に,フレーム全体(最大で約1.5KB)が格納できるサイズにしておきたかったから.

次に,extintr.c 内部で kz_memalloc()/kz_memfree() によってメモリ獲得していた 部分を,kz_kmalloc()/kz_kmfree() に移行する. これは外部割り込みの処理に,リアルタイム性を持たせたかったため.

@@ -96,12 +96,12 @@
       FD_CLR(fd, &readfds);
       ibp = *ibpp;
       *ibpp = (*ibpp)->next;
-      kz_memfree(ibp);
+      kz_kmfree(ibp);
       return -1;
     }
   }
 
-  ibp = kz_memalloc(sizeof(*ibp));
+  ibp = kz_kmalloc(sizeof(*ibp));
   ibp->next = NULL;
   ibp->fd = fd;
   ibp->id = id;
@@ -154,7 +154,7 @@
 		kz_send(ibp->id, s, NULL);
 	      break;
 	    case INTR_TYPE_READ:
-	      buffer = kz_memalloc(BUFFER_SIZE);
+	      buffer = kz_kmalloc(BUFFER_SIZE);
 	      size = read(ibp->fd, buffer, BUFFER_SIZE);
 	      if (size >= 0)
 		kz_send(ibp->id, size, buffer);
extintr はソケットの監視を行い,受信データが届いた際には read() して メッセージによって該当のスレッドに送信する. この際利用されるデータ格納のバッファが kz_kmalloc() で獲得されるように 修正されたので,ソケットの監視に extintr を利用しているスレッドは, kz_memalloc() でなく kz_kmalloc() によって獲得されたメモリ領域を受けることに なる.以下はそのための各スレッドの修正.
diff -ruN kozos27/httpd.c kozos28/httpd.c
--- kozos27/httpd.c	Wed Dec 12 13:19:43 2007
+++ kozos28/httpd.c	Wed Dec 12 14:56:28 2007
@@ -37,7 +37,7 @@
       break;
     }
     memcpy(buffer + len, p, size);
-    kz_memfree(p);
+    kz_kmfree(p);
     len += size;
     buffer[len] = '\0';
   } while (strchr(buffer, '\n') == NULL);
diff -ruN kozos27/telnetd.c kozos28/telnetd.c
--- kozos27/telnetd.c	Wed Dec 12 13:19:43 2007
+++ kozos28/telnetd.c	Wed Dec 12 14:57:04 2007
@@ -43,7 +43,7 @@
 	|| memchr(p, 0xff, size) /* Ctrl-C対応 */
 	) break;
     memcpy(buffer + len, p, size);
-    kz_memfree(p);
+    kz_kmfree(p);
     len += size;
     buffer[len] = '\0';
 
diff -ruN kozos27/stubd.c kozos28/stubd.c
--- kozos27/stubd.c	Wed Dec 12 13:19:43 2007
+++ kozos28/stubd.c	Wed Dec 12 14:56:52 2007
@@ -81,7 +81,7 @@
 	fprintf(stderr, "\'%c\'", p[i]);
       }
     }
-    kz_memfree(p);
+    kz_kmfree(p);
   }
 
   close(s);
httpd, telnetd, stubd で受信したバッファを kz_memfree() でなく kz_kmfree() で 解放するように修正した.

次に,コンソールへのログ出力のために outlog スレッドというのがいるが, これも kz_memalloc() によって獲得した領域を利用していたため,kz_kmalloc() に 移行する.これにより,出力できるログの長さに上限ができることになるが, まあ2000文字以上のログを出すこともあまり無いと思われるので気にしない. outlog を利用している clock スレッドにも,同様の修正を入れる.

diff -ruN kozos27/outlog.c kozos28/outlog.c
--- kozos27/outlog.c	Wed Dec 12 13:19:43 2007
+++ kozos28/outlog.c	Wed Dec 12 14:56:38 2007
@@ -11,6 +11,6 @@
   while (1) {
     kz_recv(NULL, &p);
     fprintf(stderr, "%s", p);
-    kz_memfree(p);
+    kz_kmfree(p);
   }
 }
diff -ruN kozos27/clock.c kozos28/clock.c
--- kozos27/clock.c	Wed Dec 12 13:19:43 2007
+++ kozos28/clock.c	Wed Dec 12 14:56:14 2007
@@ -17,7 +17,7 @@
     kz_recv(NULL, NULL);
 
     t = time(NULL);
-    p = kz_memalloc(128);
+    p = kz_kmalloc(128);
     strcpy(p, ctime(&t));
     kz_send(outlog_id, 0, p);
   }
あとは Makefile とか kozos.h とかにちょこちょこと対応が入っているが, まあたいした修正ではないのでここでは説明しない. ちなみに今回は,全体的な動作確認のため,main.c を第23回のものに戻している (ので diff.txt を見ると main.c にいっぱい修正が入っていることになっている) ので注意してほしい.

で,make して動作確認した.まあ確認結果は面倒なので省くが, gdbからの接続,continueによる動作開始,時刻表示,telnet接続, telnetでのコマンド実行,telnetでのブレーク,ブレークからのcontinueによる 動作再開と,ひととおり動作を確認できた.まあ問題は無いようだ.

ちなみに今回の修正でKOZOS内部での malloc()/free() 呼び出しを kzmem_alloc()/kzmem_free() に移行してリアルタイム性を持たせたが, 部分的に malloc()/free() が残っている箇所がある. kz_start() による起動時のスタック作成と, kz_run() によるスレッド作成時のスタック作成部分だ. これらは,スタックにはわりと大きめの領域が必要となるという理由で, あえてそのままにしてある. まあ kz_start() は起動時の1回だけなのでいいとして,kz_run() は 起動後もスレッド作成のたびに頻繁に呼ばれる(そしてスレッドの作成は, 例えば telnetd ならば接続要求を受け付けるたびに行われる)ので, これはたぶん問題だろう.これについては,またあとで考えることにしよう.

今回はメモリ管理についてだいぶ修正した.動的なメモリ管理のサービスを 内部で持っていないOSは,相当簡略化されたものを除けば,まあほとんど 無いと思われる.動的なメモリ管理に関しては他にもいろいろと資料があると 思われるのでそちらも参照してほしいのだが,OS内部で利用するには独自の流儀と いうか, 単なるライブラリ関数に求められるものとは要求されるものが違ってくるので 注意が必要だ.逆のいいかたをすると,OS内部で利用するためには何が必要なのかを 理解できれば,なぜ mbuf のようなメモリ管理が実装されているのかが自ずと わかってくるものだ.


メールは kozos(アットマーク)kozos.jp まで