8. メモリを実行時に確保する
前の章ではファイルから1バイトを読みこんで1バイトを別のファイルに 書き出していましたが,直感的に「なんか忙しそう」な感じがしませんか?
一度に読みこんで一気に書き出すとすると早くなりそうな気がします.
さて,ファイルをすべて読み込むためには読みこんでおく記憶領域(メモリ) を用意しなければなりません.どれだけ用意しておくかは読みこむファイルの 大きさに依存します.どんな大きさのファイルを読むかは実行するまでわかり ません.そこで実行するたびに読みこむファイルサイズを求めて,ぴったりと 納まるメモリを確保してみましょう.
Linux ではメモリを確保するシステムコールは brk です.brk の機能は 単にメモリの最後尾を増減することです.C の malloc や free のように確保 や解放を自由にすることはできません. malloc や free ではライブラリ中で brk で確保した領域をより高レベルで管理しています.
ここでは単に1つのファイルのサイズを取得して,そのファイルが納まる領域 を確保するだけです. エディタなどを作成する場合には一定量のメモリを 複数に分けてメモリ確保や解放を行うことになるでしょう.
サンプルプログラム (memtest.asm) の流れを示します.
- コマンドライン引数のファイル名を取得
- ファイルオープン
int sys_open(const char * filename, int flags, int mode)
- ファイルサイズを取得
off_t sys_lseek(unsigned int fd,off_t offset,unsigned int origin)
- メモリ取得前の brk 表示
unsigned long sys_brk(unsigned long brk)
- メモリ取得し,メモリ取得後の brk 表示
unsigned long sys_brk(unsigned long brk)
- ファイル先頭にシーク
off_t sys_lseek(unsigned int fd,off_t offset,unsigned int origin)
- ファイルを読みこみ
ssize_t sys_read(unsigned int fd, char * buf, size_t count)
- 読みこんだファイルに関する処理 (ここでは表示のみ)
- メモリ解放
unsigned long sys_brk(unsigned long brk)
- プログラム終了
; -------------------------------------------------------------------------- ; メモリの動的確保とファイルの読み込み ; アセンブラによるファイル操作の実験 ; 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 ; 故意に少ないメモリを確保したら ?
カーネルがプロセスにどのようにメモリを確保するのか試してみましょう.