8. メモリを実行時に確保する

前の章ではファイルから1バイトを読みこんで1バイトを別のファイルに 書き出していましたが,直感的に「なんか忙しそう」な感じがしませんか?

一度に読みこんで一気に書き出すとすると早くなりそうな気がします.

さて,ファイルをすべて読み込むためには読みこんでおく記憶領域(メモリ) を用意しなければなりません.どれだけ用意しておくかは読みこむファイルの 大きさに依存します.どんな大きさのファイルを読むかは実行するまでわかり ません.そこで実行するたびに読みこむファイルサイズを求めて,ぴったりと 納まるメモリを確保してみましょう.

Linux ではメモリを確保するシステムコールは brk です.brk の機能は 単にメモリの最後尾を増減することです.C の malloc や free のように確保 や解放を自由にすることはできません. malloc や free ではライブラリ中で brk で確保した領域をより高レベルで管理しています.

ここでは単に1つのファイルのサイズを取得して,そのファイルが納まる領域 を確保するだけです. エディタなどを作成する場合には一定量のメモリを 複数に分けてメモリ確保や解放を行うことになるでしょう.

サンプルプログラム (memtest.asm) の流れを示します.

  1. コマンドライン引数のファイル名を取得
  2. ファイルオープン
    int sys_open(const char * filename, int flags, int mode)
  3. ファイルサイズを取得
    off_t sys_lseek(unsigned int fd,off_t offset,unsigned int origin)
  4. メモリ取得前の brk 表示
    unsigned long sys_brk(unsigned long brk)
  5. メモリ取得し,メモリ取得後の brk 表示
    unsigned long sys_brk(unsigned long brk)
  6. ファイル先頭にシーク
    off_t sys_lseek(unsigned int fd,off_t offset,unsigned int origin)
  7. ファイルを読みこみ
    ssize_t sys_read(unsigned int fd, char * buf, size_t count)
  8. 読みこんだファイルに関する処理 (ここでは表示のみ)
  9. メモリ解放
    unsigned long sys_brk(unsigned long brk)
  10. プログラム終了
; --------------------------------------------------------------------------
; メモリの動的確保とファイルの読み込み
; アセンブラによるファイル操作の実験
;   memtest.asm      6/ 6/2000
;     Copyright (C) 2000 Jun Mizutani  <[email protected]>
; --------------------------------------------------------------------------

%assign SEEK_SET       0       ; Seek from beginning of file.
%assign SEEK_CUR       1       ; Seek from current position.
%assign SEEK_END       2       ; Seek from end of file.

; --------------------------------------------------------------------------
section .bss

r_buf          resd   1               ; バッファ先頭アドレス保存
buf_size       resd   1               ; ファイルサイズ
fd             resd   1               ; fd
work           resd   1
; --------------------------------------------------------------------------
section .text
global _start

%include "fcntl.inc"
%include "stdio.inc"

_start:
        ; コマンドライン引数のファイル名を取得
               pop     eax              ; 引数の数 argc
               pop     ebx              ; コマンド名取得
               dec     eax              ; 実際の引数の数
               jnz     .nx
               jmp     noarg_err        ; if no args jmp noarg_err
           .nx
               pop     ebx              ; コマンドライン引数:ファイル名
               or      ebx, ebx         ; check null pointer
               jnz     .nx1
               jmp     noarg_err        ; 終了
           .nx1

        ; ファイルオープン
        ;    ebx にファイル名文字列のアドレス
               mov    eax, SYS_open     ; システムコール番号
               mov    ecx, O_RDONLY     ; 第2引数 flag
               mov    edx, 0            ; 第3引数 mode
               int    0x80
               test   eax,eax           ; eax <- fd
               js     near open_err
               mov    [fd], eax         ; save fd

        ; ファイルサイズを取得
               mov    ebx, eax          ; 第1引数 : fd
               mov    eax, SYS_lseek    ; システムコール番号
               xor    ecx, ecx          ; 第2引数 : offset = 0
               mov    edx, SEEK_END     ; 第3引数 : origin
               int    0x80
               mov    [buf_size], eax   ; save file_size
               push   eax
               mov    eax, filesize_msg
               call   OutAsciiZ
               pop    eax
               mov    ecx, 10
               call   PrintRight
               call   NewLine

        ; brk 現在値 (メモリ取得前) 表示
               xor    ebx, ebx          ; new_brk=0,小さい値を渡して現在値を得る
               mov    eax, SYS_brk      ; brk
               int    0x80
               mov    [r_buf], eax      ; brk 現在値(ファイル先頭アドレス)
               mov    [work], eax
               call   disp_brk

        ; メモリを取得   (メモリ取得後の brk 表示)
               mov    ebx, [buf_size]   ; ebx にファイルサイズ
               inc    ebx
               add    ebx, eax          ; ebx = brk + ファイルサイズ
               ; sub    ebx, 1000         ; 故意に少ないメモリを確保したら ?
               mov    eax, SYS_brk      ; メモリ確保
               int    0x80
               call   disp_brk

        ; 取得したメモリ量を表示
               or     eax, eax          ; エラーチェック
               jz     near brk_err      ; エラー処理
               push   eax
               mov    eax, alloc_msg
               call   OutAsciiZ
               pop    eax
               sub    eax, [work]       ; 取得したメモリ量を表示
               mov    ecx, 10
               call   PrintRight
               call   NewLine

        ; ファイル先頭にシーク
               mov    ebx, [fd]         ; 第1引数 : fd
               mov    eax, SYS_lseek    ; システムコール番号
               xor    ecx, ecx          ; 第2引数 : offset=0
               xor    edx, edx          ; 第3引数 : origin=0
               int    0x80

        ; ファイルを読みこみ
               mov    eax, SYS_read     ; システムコール番号
               mov    ebx, [fd]         ; 第1引数
               mov    ecx, [r_buf]      ; 第2引数
               mov    edx, [buf_size]   ; 第3引数
               int    0x80
               test   eax,eax           ; エラーチェック
               js     read_err          ;
               mov    eax, [r_buf]
               add    eax, [buf_size]
               mov    byte [eax + 1], 0 ; ファイル最終 + 1 に 0 を格納

        ; 読みこんだファイルに関する処理 (ここでは表示のみ)
               mov    eax, [r_buf]      ;
               call   OutAsciiZ         ; 64kbytes 以内を表示
               call   NewLine

        ; メモリを解放
               mov    ebx, [r_buf]      ; ebx に元のメモリ最終
               mov    eax, SYS_brk      ; メモリ解放
               int    0x80
               call   disp_brk          ; メモリ解放後の brk 値表示

               jmp    done              ; 終了

open_err:
               mov    eax, e_open_msg
               jmp    exit
brk_err:
               mov    eax, e_brk_msg
               jmp    exit
read_err:
               mov    eax, e_read_msg
               jmp    exit
noarg_err:
               mov    eax, e_noarg_msg
               jmp    exit
done:
               mov    eax, done_msg
exit:
               call   OutAsciiZ
               call   NewLine
               call   Exit              ; プログラム終了

; brk の表示 10進と16進
disp_brk:
               push   ecx
               push   edx
               push   eax               ; brk値退避
               mov    eax, brk_msg
               call   OutAsciiZ
               pop    eax               ; brk値復帰
               mov    ecx, 10           ; 10桁指定
               call   PrintRight        ; 右詰め数値出力
               push   eax
               mov    eax, " "
               call   OutChar           ; スペース表示
               pop    eax
               call   PrintHex8         ; 16進8桁
               call   NewLine
               pop    edx
               pop    ecx
               ret

filesize_msg   db     "File size     : ", 0
brk_msg        db     "brk pointer   : ", 0
alloc_msg      db     "Mem Allocated : ", 0
e_open_msg     db     "Can't open such file.", 10, 0
e_brk_msg      db     "Can't allocate memory.", 10, 0
e_read_msg     db     "Can't read such file.", 10, 0
e_noarg_msg    db     "Filename required.", 10, 0
done_msg       db     "DONE..", 10, 0
; --------------------------------------------------------------------------

アセンブルして実行すると:

jm:~/ex_asm$ ./asm memtest
jm:~/ex_asm$ ./malloc README.EUC
File size     :         72
brk pointer   :  134517896 08049488
brk pointer   :  134517969 080494D1
Mem Allocated :         73
このファイルの使い方は https://www.mztn.org/ を参照して下さい.

brk pointer   :  134517896 08049488
DONE..

別に面白くないプログラムになっていますが,エディタを作成しようと すると必須の処理になります. またインタプリタのような言語処理系 を作成する場合もソースファイルをメモリに読みこんでおく処理が必要 です.

このプログラムの遊び方は,わざと確保したメモリより多くのメモリを 使ってみることです. 確保したメモリより 1 バイトでも多く使うと セグメンテイションフォルトになるのでしょうか? 試してみると (もちろん正しくない処理です) ... 上記のコード中で以下のコメントとなっている部分で色々な値を減算し てみてください.

  ; sub    ebx, 1000         ; 故意に少ないメモリを確保したら ?

カーネルがプロセスにどのようにメモリを確保するのか試してみましょう.