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 <mizutani.jun@nifty.ne.jp>
; --------------------------------------------------------------------------
%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 ; 故意に少ないメモリを確保したら ?
カーネルがプロセスにどのようにメモリを確保するのか試してみましょう.