5、读取记录
功能:程序读取每条记录,并显示每条记录中的“名”
记录读取功能主要执行以下步骤:
1、打开文件
2、读取一条记录
3、若到文件末尾,则退出;否则计算“名”的字符数
4、将“名”写入STDOUT中
5、输出换行符到STDOUT中
6、返回,并读取另一条记录
(1)字数统计函数
由于每条记录中,“名”的长度不定,因此需要一个函数来统计写入的字符数。该程序存于count-chars.s
#目的:对记录中字符数进行统计,遇到空字符结束
#输入:字符串地址
#输出:计数值返回到%eax中
#变量: %ecx——字符计数
# %al——当前字符
# %edx——当前字符地址
.type count_chars, @function
.globl count_chars
.equ ST_STRING_START_ADDRESS, 8 #字符串开始地址
count_chars: pushl %ebp
movl %esp, %ebp
movl $0, %ecx #计数器从0开始
movl ST_STRING_START_ADDRESS(%ebp), %edx #数据的起始地址
count_loop_begin: movb (%edx), %al #获取当前字符,采用间接寻址方式
cmpb $0, %al #判断是否为空字符
je count_loop_end
incl %ecx
incl %edx
jmp count_loop_begin
count_loop_end: movl %ecx, %eax #结束循环,将统计的字数存入%eax中
popl %ebp
ret
(2)写一个换行符到STDOUT的函数
该程序存于文件write-newline.s文件中
.include "linux.s"
.globl write_newline
.type write_newline, @function
.section .data
newline:
.ascii "\n"
.section .text
.equ ST_FILEDES, 8
write_newline: pushl %ebp
movl %esp, %ebp
movl $SYS_WRITE, %eax
movl ST_FILEDES(%ebp), %ebx
movl $newline, %ecx
movl $1, %edx
int $LINUX_SYSCALL
movl %ebp, %esp
popl %ebp
ret
(3)主程序
该程序存放在文件read-records.s中。
.include "linux.s"
.include "record-def.s"
.section .data
file_name: .ascii "test.dat\0"
.section .bss
.lcomm record_buffer, RECORD_SIZE
.section .text
.globl _start #主程序
_start: #定义存储输入输出描述符的栈位置
#也可用一个.data段中的内存地址代替
.equ ST_INPUT_DESCIPTOR, -4
.equ ST_OUTPUT_DESCRIPTOR, -8
movl %esp, %ebp
subl $8, %esp
#打开文件
movl $SYS_OPEN, %eax
movl $file_name, %ebx
movl $0, %ecx #表示只读打开
movl $0666, %edx
int $LINUX_SYSCALL
movl %eax, ST_INPUT_DESCRIPTOR(%ebp) #保存输入文件描述符
movl $STDOUT, ST_OUTPUT_DESCRIPTOR(%ebp) #保存输出文件描述符,此处为“标准输出”
record_loop: pushl ST_INPUT_DESCRIPTOR(%ebp) #输入文件描述符
pushl $record_buffer #缓冲区地址指针
call read_record
addl $8, %esp
#比较读取的字节数和缓冲区大小
#如果不一致,说明达到文件尾部或出错
cmpl $RECORD_SIZE, %eax
jne finished_reading
#打印名,需先知道“名”的大小
pushl $RECORD_FIRSTNAME + record_buffer #该指令将两个常数相加,得到的结果为内存地址
#得到的结果将入栈。RECORD_FIRSTNAME记录从
#起始地址到名字字段之间的字节数
call count_chars
addl $4, %esp
movl %eax, %edx
movl ST_OUTPUT_DESCRIPTOR(%ebp), %ebx
movl $SYS_WRITE, %eax
movl $RECORD_FIRSTNAME + record_buffer, %ecx
int $LINUX_SYSCALL
jmp recor_loop
finish_reading: movl $SYS_EXIT, %eax
movl $0, %ebx
int $LINUX_SYSCALL
编译、链接、执行上面程序
as read-record.s -o read-record.o
as count-chars.x -o count-chars.o
as write-newline.s -o write-newline.o
as read-records.s -o read-records.o
ld read-record.o count-chars.o write-newline.o read-records.o -o read-records
6、修改记录
修改程序主要有以下步骤:
1、打开输入文件和输出文件
2、从输入文件读取记录
3、递增年龄
4、新纪录写入文件
将下面程序存入文件add-year.s中
.include "linux.s"
.include "record-def.s"
.section .data
input_file_name: .ascii "test.dat\0"
output_file_name: .ascii "testout.dat\0"
.section .bss
.lcomm record_buffer, RECORD_SIZE
#局部变量的栈偏移量
.equ ST_INPUT_DESCRIPTOR, -4
.equ ST_OUTPUT_DESCRIPTOR, -8
.section .text
.globl _start
_start: movl %esp, %ebp
subl $8, %esp
#打开输入文件
movl $SYS_OPEN, %eax
movl $input_file_name, %ebx
movl $0, %ecx
movl $0666, %edx
int $LINUX_SYSCALL
movl %eax, ST_INPUT_DESCRIPTOR(%ebp) #输入文件的文件描述符
#打开输出文件
movl %SYS_OPEN, %eax
movl $output_file_name, %ebx
movl $0101, %ecx
movl $0666, %edx
int $LINUX_SYSCALL
movl %eax,ST_OUTPUT_DESCRIPTOR(%ebp)
loop_begin: pushl ST_INPUT_DESCRIPTOR(%ebp)
pushl $record_buffer
call read_record
addl $8, %esp
#判断是否到达文件尾部,或出现读错误
cmpl $RECORD_SIZE, %eax
jne loop_end
#递增年龄
incl record_buffer + RECORD_AGE #注意此处为直接寻址,和上面的“pushl $a+b”形式区别
#写记录
pushl ST_OUTPUT_DESCRIPTOR(%ebp)
pushl $record_buffer
call write_record
addl $8, %esp
jmp loop_begin
loop_end: movl $SYS_EXIT, %eax
movl $0, %ebx
int $LINUX_SYSCALL
编译、链接、运行上面程序
as add-year.s -o add-years.o
ld add-year.o read-record.o write-record.o -o add-years
./add-years
7、让程序更加健壮
在上面add-years.s程序的基础上,我们添加下面这个程序。该程序打印错误消息并退出,其作为“被调函数”,由add-year.s程序调用。该函数存于文件:error-exit.s。为了使用该函数,需将错误信息的地址和错误代码入栈,然后进行调用。
.include "linux.s"
.equ ST_ERROR_CODE, 8
.equ ST_ERROR-MSG, 12
.globl .error_exit
.type error_exit, @function
error_exit: pushl %ebp
movl %esp, %ebp
#写错误代码
movl ST_ERROR_CODE(%ebp), %ecx
pushl %ecx
call count_chars #计算字符数
popl %ecx
movl %eax, %edx #字符数量
movl $STDERR, %ebx
movl $SYS_WRITE, %eax
int $LINUX_SYSCALL
#写错误信息
movl STD_ERROR_MSG(%ebp), %ecx
pushl %ecx
call count_chars
popl %ecx
movl %eax, %edx
movl $SYS_WRITE, %eax
movl $STDERR, %ebx
int $LINUX_SYSCALL
pushl $STDERR
call write_newlines
#退出
movl $SYS_EXIT, %eax
movl $1, %ebx
int $LINUX_SYSCALL
以上为处理出错时的函数。下面将给出add-years.s是如何使用该函数的。
前面的add-year.s在打开文件后并未进行检错——可能出现找不到文件等情况。所以下面程序对打开文件后的返回码进行检测,保证正确打开文件。
#打开供读取的文件
movl $SYS_OPEN, %eax
movl $input_file_name, $ebx
movl $0, %ecx
movl $0666, %edx
int $LINUX_SYSCALL
movl %eax, INPUT_DESCRIPTOR(%ebp) #将返回码存入栈中,供检测
#以下将对%eax存储的返回码进行检测,若为负值,则调用错误函数
cmpl $0, %eax
jl continue_processing #0
#发送错误消息
.section .data
no_open_file_code: .ascii "0001:\0"
no_open_file_msg: .ascii "cant open input file\0"
.section .text
pushl $no_open_file_msg
pushl $no_open_file_code
call error_exit
continue_processing:
#程序其余部分
编译、链接、运行程序:
as add-year.s -o add-year.o
as error-exit.s -o error-exit.o
ld add-year.o write-newline.o error-exit.o read-record.o write-record.o count-chars.o -o add-year