Linux下的扬声器汇编程序设计

本文作者:admin       点击: 2006-10-08 00:00
前言:
Linux是目前最常用的操作系统之一,同微软的Windows系列操作系统相比,它具有速度快、安全性、稳定性好、源代码开放且免费等优点。我国Linux的发展也处于蓬勃发展的局面,据权威预测,中国Linux人才的需求将突破120万。对于大多数只熟悉DOS/Windows程序设计的国内程序员来说,了解和掌握Linux下的程序设计大有裨益。这里介绍在Linux 下进行扬声器汇编程序设计。

AT&T 汇编指令

同DOS/Windows 下使用的Intel 风格的汇编语言不同,在Linux 系统中,更多的是采用AT&T 格式。两者的汇编指令名类似,但在语法格式上有着较大的不同。
1.在AT&T汇编中,寄存器名要加'%'前缀;而在Intel汇编中,寄存器名不需要加前缀。
2.在AT&T汇编中,用'$'前缀表示一个立即数;在Intel 汇编中,立即数不用任何前缀。 
3.在 AT&T 汇编格式中,目标操作数在源操作数的右边;而在 Intel 汇编中,顺序相反。指令举例如下:
AT&T 格式:addl $0x80, %eax                
 Intel 格式: add eax, 80h
4.在 AT&T 汇编格式中,操作数的字长由操作符的最后一个字母决定,后缀'b'、'w'、'l'分别表示操作数为字节(byte,8 bit)、字(word,16 bit)和长字(long,32 bit);而在 Intel 汇编格式中,操作数的字长是用 "byte ptr" 和 "word ptr" 等前缀来表示的。例如: 
AT&T格式: movb $0x32, %al      
Intel格式: mov al, byte ptr 32h 
5.在 AT&T 汇编格式中,绝对转移和调用指令(jump/call)的操作数前要加上'$'作为前缀,而在 Intel 格式中则不需要。 远程转移指令和远程子调用指令的操作码,在 AT&T 汇编格式中为 "ljump" 和 "lcall",而在 Intel 汇编格式中则为 "jmp far" 和 "call far",即: 
AT&T格式:ljump $section, $offset           
Intel格式:jmp far section:offset 
与之相应的远程返回指令则为: 
AT&T格式: lret $stack_adjust               
Intel格式:ret far stack_adjust 
6.在 AT&T 汇编格式中,内存操作数的寻址方式是:section:disp(base, index, scale) 而在 Intel 汇编格式中,内存操作数的寻址方式为: section:[base + index*scale + disp] 
7.由于 Linux 工作在保护模式下,用的是 32 位线性地址,所以在计算地址时不用考虑段基址和偏移量,其地址计算方法为:disp + base + index * scale。两种格式对比举例如下:
扬声器汇编程序开发

Linux 是一个运行在保护模式下的 32 位操作系统,采用 flat memory 模式,目前最常用到的是 ELF 格式的二进制代码。一个 ELF 格式的可执行程序通常划分为如下几个部分:.text、.data 和 .bss,其中 .text 是只读的代码区,.data 是可读可写的数据区,而 .bss 则是可读可写且没有初始化的数据区。代码区和数据区在 ELF 中统称为 section,根据实际需要你可以使用其它标准的 section,也可以添加自定义 section,但一个 ELF 可执行程序至少应该有一个 .text 部分。
扬声器发声程序主要是通过I/O端口的数据读写完成的,由于Linux 是一个运行在保护模式下的32 位操作系统,系统默认情况下不能直接对I/O端口进行读写。在程序进行I/O端口读写前,首先要请求Linux系统开放(允许读写)相应的I/O端口,同时还应该注意在程序结束前提请系统关闭开放的I/O端口。对I/O端口的开放/关闭操作可以使用Linux系统功能调用(0x80号中断)的101号功能。开发AT&T 汇编语言程序包括编辑源程序、汇编、连接、调试运行等步骤。

● 编辑汇编源程序
启动Linux下的文本编辑器,编辑输入AT&T 汇编语言源程序。这里介绍的扬声器汇编源程序如下,需要注意的是,在AT&T 汇编语言源程序中用“#”标记注释,也可以C语言一样用“/*   */”作为注释标识符。
data   #以下定义数据区数据
# frequency 存储的为1,2,3,4,5,6,7七个音符(中音)和高音1的频率,0为结束标志
frequency: .word  524,588,660,698,784,880,988,1048,0  
timeslice: .word 15000,15000,15000,15000,15000,15000,15000,45000
text    #以下定义代码区
此处的_start相当于C语言中的main()函数,定义为全局型
global _start 
_start:
#请求系统开放0x42、0x43端口
movl $101,%eax  #系统功能号
movl $0x42,%ebx #起始端口号
movl $0x02,%ecx #端口个数
movl $1,%edx    #允许用户使用
int $0x80       #系统功能调用中断
#请求系统开放0x61端口,允许用户程序读写
movl $101,%eax
movl $0x61,%ebx
movl $0x01,%ecx
movl $1,%edx
int $0x80
#获取频率、时长数据的起始地址
movl $frequency,%esi
movl $timeslice,%edi
load_data:
pushl %edi  
#取频率值,为0则结束
movw (%esi),%di
cmpw  $0,%di
je finished
#根据频率值求对应的计数值
movw $0x12,%dx
movw $0x3280,%ax
div %di
popl %edi
call sound_on   #转发声子程序
#调整指针寄存器的值
addl $2,%esi
addl $2,%edi
jmp load_data  #循环取下一组声音值
finished:  #准备结束程序
popl %edi
#关闭扬声器
inb $0x61,%al
andb $0xfc,%al
outb %al,$0x61
#关闭0x42、0x43端口
movl $101,%eax  #系统功能号
movl $0x42,%ebx #起始端口号
movl $0x02,%ecx #端口个数
movl $0,%edx    #允许用户使用
int $0x80       #系统功能调用中断
#关闭0x61端口,禁止用户程序读写
movl $101,%eax
movl $0x61,%ebx
movl $0x01,%ecx
movl $1,%edx
int $0x80
#1号系统功能调用,结束本程序
movl $0,%ebx #结束返回码
movl $1,%eax     
int  $0x80       
#主程序到此结束
sound_on:  #发声子程序开始
pushl %eax
#选择定时器2
movb $0xb6,%al
outb %al,$0x43
popl %eax
#向计时器2依次写入2个字节计数值
outb %al,$0x42
movb %ah,%al
outb %al,$0x42
inb $0x61,%al
#打开扬声器
orb $3,%al
outb %al,$0x61
#声音播放的延时
movw (%edi),%bx
wait1:
movl $39480,%ecx
delay:loop delay
dec %bx
jnz wait1
ret   #子程序返回
#扬声器汇编程序结束
源程序程序输入完毕,以纯文本格式保存。Linux 系统下的汇编语言源程序扩展名通常为“.s”,程序员也可以自行设定扩展名。本示例的汇编源程序文件名为speaker.s。

● 将源程序编译为目标代码
用 AT&T 格式编写的汇编程序源文件输入完毕,接下来的工作就要使用汇编器将源程序文件编译。汇编器(assembler)的作用是将用汇编语言编写的源程序转换成二进制形式的目标代码。Linux 平台的标准汇编器是 GAS,它是 GCC 所依赖的后台汇编工具,通常包含在 binutils 软件包中。GAS 使用标准的 AT&T 汇编语法,可以用来汇编用 AT&T 格式编写的汇编语言程序。在Linux 系统终端窗口中输入以下格式的命令:
[root@sunqd]$ as -o speaker.o speaker.s 
其中的斜体部分为输入的命令,此命令将当前文件夹(sunny)下的speaker.s汇编程序转换为二进制形式的目标代码文件名speaker.o。汇编器GAS能够发现汇编源程序中的语法错误,在屏幕上列出错误行号和相关的错误提示信息。

● 连接生成可执行程序
由汇编器产生的目标代码是不能直接在计算机上运行的,它必须经过连接器的处理才能生成可执行代码。连接器可以将多个目标代码文件连接成一个可执行代码文件,这样可以先将整个程序分成几个模块来单独开发,然后才将它们组合(连接)成一个应用程序。 Linux 使用 ld 作为标准的连接程序,它同样也包含在 binutils 软件包中。汇编语言源程序在成功通过 GAS 或 其它汇编器的编译并生成二进制目标代码后,就可以使用 ld 将其连接成可执行程序了。在Linux 系统终端窗口中输入以下格式的命令: 
[root@sunqd]$ ld -s -o speaker  speaker.o 
该命令将目标代码文件speaker.o连接为可执行程序speaker,如果发现错误,则连接程序ld将提示相关的错误信息;如果没有看到错误提示信息,则说明已经生成在Linux 系统中运行生成的可执行程序了。

● 运行可执行程序
若要运行生成的可执行程序,只需在在Linux 系统终端窗口中输入可执行文件名即可。运行我们的扬声器的示例程序,可以输入如下命令:
[root@sunqd]$ ./speaker
需要注意的是,扬声器程序需要访问计算机系统的I/O端口,所以需要切换为系统管理员(root)权限的用户才能正常运行该程序,否则系统将提示“Segmentation fault”出错信息。