【汇编语言】17-1-手写字符串输入shell程序


公粽号「专注Linux」,专注Linux内核开发

本系列将讲解《汇编语言》一书,本节讲解**【汇编语言】17-1-手写字符串输入shell程序**。

本期源码,欢迎star:
https://github.com/ZYKWLJ/assembly4-homework


本节速览
1.整个shell程序如何判断输入字符的类别的?
2.我们从键盘写入的字符放在了内存缓冲区的那一块?
3.字符栈怎么实现的?
4.BIOS的电传打字输出什么意思?
5.程序有使用直接定址表吗?为什么可以使用?使用的优势是?
6.画出整个程序的执行架构图?

1.字符串输入shell程序介绍

我们进入dosbox后,都会有类似于C:\>这样的图标,提示你输入命令,并且输入的字符可以删除。

shell程序要实现的样子

综上,我们要实现的shell程序,具有以下功能:

我们要实现的shell程序,具有的功能


2.程序分析

我们编写的子程序,参数如下:

我们要实现的shell程序的参数

1.字符的输入和删除

维护一个字符栈,栈中的所有字符,从栈底到栈顶,组成一个字符串。支持压栈出栈

2.回车后,字符串输入结束

输入回车符后,可以在字符串中加入0表示字符串结束

3.在输入的同时需要显示这个字符串

每次有新的字符输入和删除一个字符的时候,都应该重新显示字符串栈底到栈顶,显示所有的字符。

4.程序处理过程

调用 int 16h 读取键盘输入;

如果是字符,进入字符栈,显示字符栈中的所有字符;继续执行①;

如果是退格键,从字符栈中弹出一个字符,显示字符栈中的所有字符;继续执行①;

如果是 Enter 键,向字符栈中压入 0,返回。


从程序的处理过程中可以看出,字符栈的入栈出栈显示栈中的内容,是需要在多处使用的功能,我们应该将它们写为子程序

子程序的参数表以及使用直接定址表的思想

3.字符栈子程序

有上述分析,字符栈子程序如下:

;字符栈子程序实现
;程序:字符栈的入栈、出栈和显示。
;参数说明:

;(ah)=功能号,0表示入栈,1表示出栈,2表示显示:ds:si指向字符栈空间;
;对于0号功能:(al)=入栈字符;
;对于1号功能:(al)=返回的字符;
;对于2号功能:显示字符串,其中(dh)=行、(dl)=列

charstack: 
	jmp short charstart		;这是子程序 ` 标准写法 `,在数据定义前面写一条跳转指令,实现数据定义与首指令跳转和执行!

table 	dw charpush, charpop, charshow	
						;对应的入栈、出栈、显示

top 		dw 0                    	;栈顶指针

charstart: 
	push bx				;子程序标准用法,保存自己会用到的寄存器
	push dx
	push di
	push es
	push ax

	cmp ah, 2                 ; 判断功能号是否大于2
	ja sret                     	; 若大于2则直接返回

	mov bl, ah			;ah存放的是功能号,注意一定是要bl,因为bx是基址寄存器,可以直接写在[]里面的,即[bx]。【ax:累加器、bx基址寄存器、cx计数寄存器、dx数据寄存器】
	mov bh, 0
	add bx, bx                 	; 计算功能号对应的偏移量,因为定址表是字大小,而我们的地址是字节。
	jmp word ptr table[bx]      ; 跳转到对应功能的子程序。这里使用了直接定制表的思想。

; 入栈子程序
charpush: 
	mov bx, top			;栈顶指针
	mov [si][bx], al            	; 将al中的字符存入栈中
	inc top                     		; 栈顶指针加1
	jmp sret

; 出栈子程序
charpop: 
	cmp top, 0        	 ; 判断栈是否为空
	je sret                     	 ; 栈空则返回
	dec top                     ; 栈顶指针减1
	mov bx, top
	mov al, [si][bx]           ; 将栈顶字符存入al中
	jmp sret

; 显示子程序
charshow: 
	mov bx, 0b800h    	; 显存段地址
	mov es, bx
	mov al, 160
	mov dh, 0
	mul dh                      ; 计算行偏移(每行160字节),这里得到行显示的字节,ax里面是这个值
	mov di, ax			;这是何故?将ax里面的行偏移字节转存到di中
	add dl, dl			;这是列偏移,要加两次,因为每列两字节
	mov dh, 0			;这里又是干嘛呢?
	add di, dx			;加上了列偏移,现在di中存的就是字节显示的屏幕的位置的全部的字节偏移了 

	mov bx, 0

charshowes: 
	cmp bx, top     			; 判断是否显示完所有字符
	jne noempty			;还有字符,就显示!
	mov byte ptr es:[di], ' '   	; 栈空则显示空格
	jmp sret

noempty: 					;字符显示代码
	mov al, [si][bx]   		; 取出字符
	mov es:[di], al             	; 显示字符
	mov byte ptr es:[di+2], ' ' ; 清除下一个位置的残留字符
	inc bx
	add di, 2                   	; 移动到下一个显示位置
	jmp charshowes

sret: 
	pop es                		; 恢复寄存器
	pop di
	pop dx
	pop bx
	ret

字符栈的动态访问图示:

字符栈的动态访问图示


4.总代码

伪代码流程:

getstr:
	push ax

getstrs:
	mov ah,0
	int 16h			;使用int 16读取键盘缓冲区的数据,该功能的编号为 0
		
	cmp al 20h		;这里说明不是字符,这个值是32,对应的
	jb nochar		;不是字符,查看是不是删除或者enter
	mov ah, 0		;是字符,先入栈
	call charstack		;调用0号功能,字符入栈
	mov ah, 2		;再显示
	call charstack 	;调用2号功能,显示栈中的字符
	jmp getstrs		;继续循环等待输入

nochar:
	cmp ah, 0eh
	je backspace
	cmp ah, 1ch
	je enter
	jmp getstrs
	
backspace:
	mov ah, 1
	call charstack	;字符出栈
	mov ah, 2
	call charstack 	;显示栈中的字符(因为不管入栈出栈,都要显示字符!)
	jmp getstrs	;继续读取
	
enter:			;输入结束,开启下一段输出,继续显示prompt
	mov al, 0
	mov ah, 0
	call charstack	;0入栈
	mov ah, 2
	call charstack 	;显示栈中的字符
	pop ax
	ret  

有了上述子程序,我们方便实现多了,不过,另外一个要注意的问题是,显示栈中字符的时候,要注意清除屏幕上上一次显示的内容。(这真的很容易忘记!)


可运行源码如下

assume cs:code,ds:data

data segment
    prompt db 'LinLin>','$'
    buffer db 1000 dup(0)
    top    dw 0        ; 将top移到数据段
data ends

code segment

start:
    mov ax, data
    mov ds, ax
    mov si, offset buffer  ; 使用offset明确指定buffer位置

getstr:
; 每次输入字符前,都要先打印prompt `linlin`
prompt_display:
    mov dx, offset prompt  ; 显示提示符
    mov ah, 9
    int 21h

getstrs:
    mov ah, 0
    int 16h         ; 使用int 16读取键盘缓冲区的数据
    
    cmp al, 20h     ; 对键入的字符进行判断,看是不是可视化字符?
    jb nochar       ; 不是字符,查看是不是删除或者enter
    
    ; 是字符,先入栈
    mov ah, 0
    call charstack
    ; 再显示
    mov ah, 2
    call charstack
    jmp getstrs     ; 继续循环等待输入


;==不是字符的情况处理==
nochar:
    cmp ah, 0eh     ; 退格键
    je backspace
    cmp ah, 1ch     ; 回车键
    je enter_key
    jmp getstrs     ; 其他键忽略,继续读取

backspace:
    mov ah, 1
    call charstack  ; 字符出栈
    mov ah, 2
    call charstack  ; 显示栈中的字符
    jmp getstrs     ; 继续读取


enter_key:          ; 输入结束
    ; 检查退出条件 - 如果输入的是单个字符'q'
    cmp top, 1
    jne not_quit
    mov bx, 0
    mov al, [si+bx]     ; 获取栈中第一个字符
    cmp al, 'q'
    je do_quit
    cmp al, 'Q'         ; 也检查大写Q
    je do_quit

not_quit:
    ; 获取当前光标位置
    mov ah, 3
    mov bh, 0
    int 10h
    ; dh = 行号, dl = 列号
    
    ; 换到下一行
    inc dh          ; 行号+1
    mov dl, 0       ; 列号归0
    mov ah, 2
    int 10h         ; 先将光标移到新行的行首
    
    ; 检查是否需要屏幕滚动(如果到达底部)
    cmp dh, 25      ; 通常80x25文本模式
    jb no_scroll
    ; 调用滚动功能
    mov ah, 6       ; 向上滚动
    mov al, 1       ; 滚动1行
    mov bh, 7       ; 属性
    mov ch, 0       ; 左上角行
    mov cl, 0       ; 左上角列  
    mov dh, 24      ; 右下角行
    mov dl, 79      ; 右下角列
    int 10h
    mov dh, 24      ; 设置到新底部行
    
no_scroll:
    ; 重置栈指针
    mov top, 0
    
    ; 先将光标移到行首,再显示提示符
    mov ah, 2
    mov bh, 0
    mov dl, 0
    int 10h
    
    jmp getstr      ; 重新开始输入

do_quit:
    ; 返回DOS
    mov ax, 4c00h
    int 21h


; 字符栈子程序实现
charstack: 
    jmp short charstart
  
;直接定址表表项内容(存放着3个字符栈的操作)
table   dw charpush, charpop, charshow

charstart: 
    push bx
    push dx
    push di
    push es
    push ax

    cmp ah, 2
    ja sret

    mov bl, ah
    mov bh, 0
    add bx, bx
    jmp word ptr table[bx]

; 入栈子程序
charpush: 
    cmp top, 1000   ; 防止缓冲区溢出
    jae push_ret
    mov bx, top
    mov [si+bx], al
    inc top
push_ret:
    jmp sret

; 出栈子程序
charpop: 
    cmp top, 0
    je sret
    dec top
    mov bx, top
    mov al, [si+bx]
    jmp sret

; 显示子程序
charshow:
    ; 保存当前光标位置
    mov ah, 3
    mov bh, 0
    int 10h
    push dx         ; 保存光标位置
    
    ; 移动到提示符后的位置开始显示
    mov ah, 2
    mov bh, 0
    mov dl, 7       ; "LinLin>" 长度是7,所以从第7列开始
    int 10h
    
    ; 显示所有栈中的字符
    mov bx, 0
    cmp bx, top
    je show_cursor  ; 如果栈空,只显示光标
    
display_loop:
    cmp bx, top
    jae display_done
    
    mov al, [si+bx]
    mov ah, 0Eh     ; BIOS 电传打字输出。模拟老式电传打字机(Teletype)的字符输出方式,每输出一个字符后,光标自动向右移动一格,当光标到达行尾时,自动移动到下一行行首。当光标到达屏幕底部时,整个屏幕向上滚动一行,输出字符时保持当前颜色和属性设置。
    int 10h
    inc bx
    jmp display_loop

display_done:
    ; 显示闪烁光标(下划线)
    mov al, ' '
    mov ah, 0Eh
    int 10h

show_cursor:
    ; 恢复光标位置到输入末尾
    pop dx
    mov ah, 2
    mov bh, 0
    ; 计算新的列位置:7 + 字符数
    mov dl, 7
    add dl, byte ptr top
    int 10h
    
sret: 
    pop ax
    pop es
    pop di
    pop dx
    pop bx
    ret
    
code ends
end start 

结果如下:

建议移步公粽号查看完整的视频。

从这里我们其实可以看出,使用汇编编写程序,我们最主要的是明确要开辟那几块内存?要操作哪一块内存,明确操作逻辑,各个寄存器分工,如果充分利用BIOS已有程序等等。



本节回顾
1.整个shell程序如何判断输入字符的类别的?
2.我们从键盘写入的字符放在了内存缓冲区的那一块?
3.字符栈怎么实现的?
4.BIOS的电传打字输出什么意思?
5.程序有使用直接定址表吗?为什么可以使用?使用的优势是?
6.画出整个程序的执行架构图

Linux102系列

本系列将精讲Linux0.11内核中的每一个文件,共计会发布100+文章。


0.一些辅助文件

😉【Linux102】1-Makefile

😉【Linux102】2-Makefile.header

😉【Linux102】3-system.map


1.内核引导启动程序

😉【Linux102】4-bootsect.s

😉【Linux102】5-setup.s

😉【Linux102】6-head.s

😉【Linux102-D】/boot


2.内核初始化过程

😉【Linux102】7-main.c


3.进程调度与系统调用

😉【Linux102】8-kernel/asm.s

😉【Linux102】9-kernel/traps.c

😉【Linux102】10-kernel/printk.c

😉【Linux102】11-kernel/vsprintf.c

😉【Linux102】12-include/stdarg.h

😉【Linux102】13-kernel/mktime.c

😉【Linux102】14-kernel/system_call.s

😉【Linux102】15-include/linux/sched.h

😉【Linux102】16-kernel/sched.c

😉【Linux102】17-kernel/signal.c

😉【Linux102】18-include/signal.h

😉【Linux102】19-include/sys/types.h

😉【Linux102】20-include/linux/kernel.h

😉【Linux102】21-include/asm/segment.h

😉【Linux102】22-include/linux/head.h

😉【Linux102】23-include/linux/mm.h

😉【Linux102】24-include/linux/fs.h

😉【Linux102】25-include/errno.h

😉【Linux102】26-include/sys/wait.h

😉【Linux102】27-include/inux/tty.h

😉【Linux102】28-include/termios.h

😉【Linux102】29-kernel/panic.c

😉【Linux102】30-include/sys/times.h

😉【Linux102】31-include/sys/utsname.h

😉【Linux102】32-include/stddef.h

😉【Linux102】33-include/linux/sys.h

😉【Linux102】34-kernel/sys.c

😉【Linux102】35-kernel/fork.c

😉【Linux102】36-include/asm/system.h

😉【Linux102】37-kernel/exit.c

😉【Linux102】38-include/linux/fdreg.h

😉【Linux102】39-include/asm/io.h


4.输入输出系统--块设备驱动程序

😉【Linux102】40-kernel/blk_drv/blk.h

😉【Linux102】41-kernel/blk_drv/hd.c

😉【Linux102】42-include/linux/config.h

😉【Linux102】43-include/linux/hdreg.h

😉【Linux102】45-kernel/blk_drv/ramdisk.c

😉【Linux102】46-include/asm/memory.h

😉【Linux102】47-include/string.h

😉【Linux102】48-kernel/blk_drv/floppy.c


5.输入输出系统——字符设备驱动程序

😉【Linux102】49-kernel/chr_drv/keyboard.S

😉【Linux102】50-kernel/chr_drv/console.c

😉【Linux102】51-kernel/chr_drv/serial.c

😉【Linux102】52-kernel/chr_drv/rs_io.s

😉【Linux102】53-kernel/chr_drv/tty_io.c

😉【Linux102】54-include/ctype.h

😉【Linux102】55-lib/ctype.c

😉【Linux102】56-kernel/chr_drv/tty_ioctl.c


6.文件系统

😉【Linux102】58-fs/buffer.c

😉【Linux102】59-fs/bitmap.c

😉【Linux102】60-fs/inode.c

😉【Linux102】61-fs/super.c

😉【Linux102】63-fs/file_table.c

😉【Linux102】64-fs/block_dev.c

😉【Linux102】90-include/fcntl.h

😉【Linux102】75-include/a.out.h

😉【Linux102】65-fs/file_dev.c

😉【Linux102】66-fs/pipe.c

😉【Linux102】67-fs/char_dev.c

😉【Linux102】68-fs/read_write.c

😉【Linux102】69-fs/truncate.c

😉【Linux102】70-fs/open.c

😉【Linux102】71-fs/exec.c

😉【Linux102】72-fs/stat.c

😉【Linux102】73-fs/fcntl.c

😉【Linux102】74-fs/ioctl.c


7.内存管理模块

😉【Linux102】77-mm/memory.c

😉【Linux102】78-mm/page.s


8.内核库文件

😉【Linux102】95-lib/dup.c

😉【Linux102】88-lib/write.c

😉【Linux102】87-lib/wait.c

😉【Linux102】86-lib/string.c

😉【Linux102】85-lib/setsid.c

😉【Linux102】84-lib/open.c

😉【Linux102】81-lib/errno.c

😉【Linux102】80-lib/close.c

😉【Linux102】79-lib/_exit.c

😉【Linux102】55-lib/ctype.c


Linux内核精讲系列

和Linux内核102系列不同,本系列将会从全局描绘Linux内核的各个模块,而非逐行源码分析,适合想对Linux系统有宏观了解的家人阅读。

😉【Linux】学习Linux前必备的知识点

😉【Linux】linux0.11操作系统概述

😉【Linux】Linux内核对进程的内存抽象

😉【Linux】Linux概述1-linux对物理内存的使用

😉【Linux】软件从写下到运行的全部流程

😉【Linux】CPU的三寻址:实模式、保护模式、长模式

😉【Linux】实模式与保护模式的寻址, 这次讲明白了!

😉【Linux】linux0.11的源码目录架构

😉【Linux】Makefile机制及基础详解

😉【Linux】编译并运行Linux0.11

😉【Linux】“进进内网文”—Linux的内核结构全貌

😉【Linux】linux的中断机制

😉【Linux】linux进程描述


汇编语言

本系列将带领大家从0开始循序渐进学习汇编语言,直至完全掌握这门底层语言。同时给出学习平台DOSBox的使用教程。

😉【汇编语言】1—基础硬件知识

😉【汇编语言】2—寄存器基础知识

😉【汇编语言】3-寄存器与内存的交互

😉【汇编语言】4-第一个完整的汇编程序

😉【汇编语言】5-[BX]和loop指令

😉【汇编语言】6-包含多个段的程序

😉【汇编语言】7-灵活的5大寻址方式

😉【汇编语言】8-1-数据的位置

😉【汇编语言】8-2-数据的长度

😉【汇编语言】8-数据处理的两个基本问题(位置与长度)

😉【汇编习题】9-在屏幕正中间显示不同颜色文字

😉【汇编语言】9-转移指令的本质

😉【汇编语言】10-ret和call指令与子程序

😉【汇编语言】11-标志寄存器

😉【汇编作业】11-任意字串中的字母转化为大写

😉【汇编练习】12-验证ROM和其他内存区域的OS的接管情况

😉【汇编语言】12-内中断+中断程序实战

😉【汇编语言】12-1-debug单步调试的原理

😉【汇编语言】12-2-入栈指令屏蔽相应中断

😉【DOSBox】1-debug

😉【DOSBox】2-debug可执行文件

😉【DOSBox】3-完整开发流程


C语言

本系列将直击C语言的本质基础,流利处理出各个易错、实用的实战点,并非从零开始学习C。

😉【C语言】GDB详解大全

😉【C语言】C Token(C89 C99 C11)

😉【C语言】指针基础

😉【C语言】数组基础

😉【C语言】结构体对齐

😉【C语言】华为C语言进阶测试

😉【C语言】触发刷新到磁盘的方式总结

😉【C语言】C语言文件操作的mode详解

😉【C语言】C语言文件知识全讲解

😉【C语言】从extern到头文件包含的最优实践

😉【C语言】C语言的关键字与重载机制

😉【C语言】长字符串的2种处理方式

😉【C语言】C语言嵌入汇编程序

😉【C语言】指针数组 VS 数组指针 原来这么简单!

😉【C语言】深浅拷贝、传参、赋值 本质剖析

😉【C语言】find-in-linux递归搜索文件名函数

😉【C陷阱与缺陷】-1-词法陷阱

😉【C陷阱与缺陷】-2-语法陷阱

😉【C陷阱与缺陷】-3-语义陷阱


关于小希

😉嘿嘿嘿,我是小希,专注Linux内核领域,同时讲解C语言汇编等知识。

我的微信:C_Linux_Cloud,期待与您学习交流!

加微信请备注哦


小希的座右铭:别看简单,简单也是难。别看难,难也是简单。我的文章都是讲述简单的知识,如果你喜欢这种风格:

不妨关注、评论、转发,让更多朋友看到哦~~~🙈

下一期想看什么?在评论区留言吧!我们下期见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值