MSE12 版 (精华区)
发信人: scarsty (小蝶), 信区: MSE12
标 题: 留给2004--linux下的硬件实验
发信站: BBS 听涛站 (Fri Dec 31 23:52:48 2004), 转信
这是真的吧…………
Linux 下的硬件实验
孙铁昱
清华大学材料科学与工程系
目次
一、Linux 简介和硬件实验环境
包括Linux 的一些简单介绍。完成我们的实验不需要有很深的Linux 知识,但至少要
知道我
们下面介绍的一些东西。
二、汇编语言程序设计
在这里讨论了实现汇编语言简单程序设计的一些必备的知识,主要是两种环境下汇编
程序设
计的不同,并选择了三个比较典型且有一定难度的程序在Linux 下重写。
三、输入/输出设备实验
恐怕端口操作是与DOS 下差别最小的地方。
四、中断技术
很遗憾,我们找到的大多数资料呈现给我们的现实是:Linux 下用汇编语言进行中断
设计是
基本不可能实现的。
五、总结
一、LINUX 简介和硬件实验环境
1-1 基本命令
在Linux 下我们完全可以使用已经非常成熟的XWindows 用户界面进行日常的操作,
比如文件编辑,增删等。
但要进行硬件实验,我们至少应该了解以下几个命令。注意Linux 是区分大小写的,
以下几个命令都是小
写。
(1)ls
类似DOS 中的dir 命令,事实上Linux 下也有dir 命令,但ls 可以用不同颜色区分
不同属性的文件。
(2)cd
类似DOS 中的cd 命令,注意斜杠方向与DOS 是相反的,并且不能省略命令与参数之
间的空格。
(3)rpm
用来安装软件的程序,下面将介绍的nasm 需要用它安装。
安装nasm 的rpm 软件包使用如下的指令:
rpm -ivh nasm-0.98-8.i386.rpm
其中ivh 可以用Uvh 代替,其实Uvh 才是系统建议的参数。
由于讨论的重点不在这里,更为详尽的说明请参阅Linux 的帮助文档或专业书籍。
1-2 安装软件
前面提到的rpm 可以进行rpm 包的安装,但有些软件是采用源代码方式进行分发,这
时就要自行编译,我
们下面提到的ald 就是如此。这类软件一般都有安装的说明文档,但大多数软件的安
装不外以下三步:
./CONFIGURE
./make
./make INSTALL
(具体的大小写请参考帮助文档和软件本身)
1-3 内存分配
Linux 使用的是CPU 的保护模式。在保护模式下,32 根地址线有效,系统采用分段
与分页相结合的方法管
理4GB 的物理存储器。
保护方式下分段模式与实模式下分段的方式不同。在实模式下段寄存器存放一个基址
(尽管事实上要乘以
10h,但我们仍然可以这样认为),我们访问存储器时需要提供一个偏移量,基址加
上偏移量就可以得到物
理地址。这时存在着一个隐患,就是对数据段的越界访问有可能把代码段冲掉(不知
大家是否还记得那个
程序),当然我们可以设法避免这样,但隐患已经存在。
在保护模式下处理器在段间强行分界,此时段寄存器存放一个段描述符的选择符,描
述符中包括段的基址、
大小、类型、访问权限和优先级。将段基址与偏移量相加就得到线性地址。适当应用
段描述符中的东西应
当可以避免一些悲剧的发生。
线性地址并不是物理地址,因为4GB 的地址空间内存根本不够,所以还有页管理机制
。将4GB 的地址空间
分为若干页,一部分存放在内存中,一部分存放在磁盘中。当访问线性地址时,处理
器就使用分页部件将
线性地址转为物理地址,如果要要访问的页不在内存中,就从磁盘中取出这个页,再
执行应用程序。
下图描述了分段地址、线性地址、分页地址之间的关系:
我们有理由相信我们在Linux 程序设计中,使用的是线性地址。如果我们用ALD 中的
d(相当于DEBUG 中
的u)和e(相当于DEBUG 中的d)查看EIP 指向的地址的内容,我们发现结果是一样
的;但在DOS 下用D
和U 一般得不到相同结果,因为这两个段寄存器指向不同的段。
所以在Linux 下我们可以将整个存储器看成同一个段,这样段的概念就变得非常模糊
了。
我们将使用32 位程序设计,它与16 位程序设计并没有太大不同,我们从前设计的多
数算法还是有用的,
只是地址的表示要修改一下。
1-4 NASM
NASM 是Linux 下的汇编工具,其实汇编工具还有很多,但NASM 的语法与DOS 下的汇
编工具(MASM,FASM
等)是最为接近的。
下表列出了MASM 与NASM 语法上主要的不同。如果需要详细的说明,请查阅帮助手册
。
项目 MASM NASM
段说明 seg_name segment
seg_name ends
有明确的起始和结束标记
section .data //数据段
section .text //代码段
无明确的结束标记
程序结束 ends label 不需要
段寄存器指定 assume 不需要
变量引用 变量有明确的类型,对变量名操作相当于对
变量的内容操作,要求相对宽松
变量的类型无实际意义,变量名只代表地
址!在使用变量时必须用寄存器或伪说明指
定类型,即使直接使用变量的地址也需说明
dword 属性!
子程序 proc_name proc
proc_name endp
只需在子程序前加一标记,使用call 指令
调用此标记名即可
1-5 ALD
ALD 相当于DOS 下的debug。几个重要的命令如下:
r:运行程序
n:按步运行程序,可以通过子过程
s:按步运行程序
e:查看内存内容
d:反汇编
q:退出
register:查看各寄存器
help:命令帮助
1-6 汇编语言的上机过程
使用文本编辑器编辑程序源文件,可以使用vim 或XWindows 下的编辑器。
编辑好后,在命令行下执行以下命令:
nasm -f elf asmfile
ld -s -o execfile objfile
以上命令随系统不同可能会有一些区别。
asmfile、execfile、objfile 分别是源文件名,可执行文件名,目标文件名。
其中asmfile 的扩展名通常是asm,objfile 的扩展名是o,execfile 一般可以没有
扩展名(Linux 区分文
件的标准不是扩展名,而是文件属性)。
执行程序,并使用ald 调试,直至程序完成所需功能。
Windows 下其实已经有了很成熟的图形界面的汇编工具(比如陈文尧用BCB 编写的未
来汇编),用起来很方
便。在Linux 下我用的一个办法是在XWindows 下打开一个终端,并在另一个窗口中
用KWrite(一个文本
编辑软件),编写程序,写的差不多就保存一下,然后在终端中输入这些命令编译一
下。也算是一个半图形
界面的解决办法。
1-7 Hello World!
无论新出现了任何编译环境,程序员总要编写一个“Hello World!”程序,我们也不
免这个俗。
下面是“Hello World!”程序的源代码,这将成为我们硬件实验的开始。
section .data
msg db 'Hello World!',10,13
section .text
global _start
_start:
mov eax,4
mov ebx,0
mov ecx,msg
mov edx,14
int 80h
mov eax,1
mov ebx,0
int 80h
在进入实际的编程之前,请再次阅读上面列出的表格!
二、汇编语言程序设计
2-1 Linux 与DOS 下的汇编的主要区别
谢天谢地有nasm 这个工具,这使得我们有可能在原来DOS 程序的基础上作较少的修
改就可以使之运行在
Linux 下。
在第一部分我们已经说明了汇编语言的上机过程,与DOS 下大同小异。下面我们将简
要分析汇编语言本身,
找出二者最大的不同。
汇编语言中出现最多的是机器指令,比如mov,int 等。这些语句与指令系统有关,
只要我们使用相同的
CPU,这部分的区别完全可以忽略。
汇编语言中的另一部分是伪代码说明,这部分是我们必须注意的,比如NASM 就没有
ptr 这个关键字,在第
一部分NASM 的列表中也有介绍,我们修改起来也并不困难。另外NASM 中变量类型只
成为了一个摆设,所
以后面的程序均使用了db 定义变量。
最为主要的区别在以下两点:
(1)32 位的程序设计。
DOS 下也有32 位的程序设计,不过我们这里仍然要强调这一点。在Linux 下我们将
不得不使用32 位程序
设计,比如使用一个dword 量表示地址,不再需要强调段的定义等。最为重要的是:
我们将在地址的修正
上花费大量的时间。
(2)系统功能调用。
显然int 21h 将不再有效。在Linux 下,我们使用int 80h 代替它,比较糟糕的是以
前我们使用的入口和
出口等参数将不再相同。我们必须将这些使用系统功能调用的程序段全部重写以适应
新的系统。我们将在
下面的三个程序中介绍一些主要的系统功能。
在这一部分我们选择了三个具有代表性的程序进行重写。但是由于我们的水平有限,
不得不去掉了一些功
能。
2-2 读时钟
原题目即实验三的2 题。
我们但稍微做了一些简化,去掉了设置时钟的功能,仅仅显示当前时间。
这个程序包含了一些简单的系统调用,其中主要的就是利用int 0x80 实现了DOS 中
的9 号系统功能。
这个程序包含了比较详细的注释,简述了Linux 汇编的特点。
程序和注释如下:
section .data ;程序中数据段的定义
time db 0,0,0,0 ;用来取时间的整数
time_h db 0 ;时
time_m db 0 ;分
time_s db 0 ;秒
time_c db "00:00:00",10,13 ;输出用的字符串
str1 db "Current time is : "
section .text ;程序中不需要段结束标志,这里是代码段开始标志
global _start
_start:
mov eax,13 ;13 号功能,取系统时间,置于ebx 所指示的地址中
mov ebx,time ;变量名指的是地址,这点是c 语言的习惯
int 0x80
mov eax,[time] ;必须加中括弧!
xor edx,edx
mov ebx,86400 ;时钟由一个64 位的整数表示,每秒递增一次
div ebx ;时钟里包含日期,且起点并非某一天的零点
mov eax,edx ;日期算法未破译
xor edx,edx
mov ebx,3600
div ebx
mov [time_h],al
mov eax,edx
xor edx,edx
mov ebx,60
div ebx
mov [time_m],al
mov [time_s],dl
add byte [time_h],8 ;小时数必需加8,应是起点不是零点所致
cmp byte [time_h],24
jb kk
sub byte [time_h],24
kk:
mov al,[time_h] ;从这里开始是100d 以下的二进制数转10 进制程序
xor ah,ah ;除10分别取商和余数
mov bl,10
div bl
add [time_c],al ;以下是将10 进制数转为字符
add [time_c+1],ah
mov al,[time_m]
xor ah,ah
mov bl,10
div bl
add [time_c+3],al
add [time_c+4],ah
mov al,[time_s]
xor ah,ah
mov bl,10
div bl
add [time_c+6],al
add [time_c+7],ah
mov eax,4 ;4号功能,输出一个字符串
mov ebx,0 ;ebx的意义将在后面讨论
mov ecx,str1 ;取得地址
mov edx,18 ;取得长度
int 0x80
mov eax,4
mov ebx,0
mov ecx,time_c
mov edx,10
int 0x80
mov eax,1 ;1号功能,返回系统
mov ebx,0
int 0x80
2-3 十进制加法和乘法计算
第二个程序选择了难度较大的十进制运算,即实验四任务二2,但这里仍然进行了一
些简化,去掉了屏幕
控制的部分。
这个程序大量使用了子过程和系统功能3 和4,对应于DOS 中的10 和9。
主要的不同之处在注释中有注明。
section .data
num1in db 10,10,10,10,10
num2in db 10,10,10,10,10
num1 db 0,0
num2 db 0,0
num3 db 0,0,0,0
num4 db 0,0,0,0
num3d db 0,0,0,0,0,0,0,0
num4d db 0,0,0,0,0,0,0,0
str1 db 'Please input the 1st number:'
str2 db 'Please input the 2nd number:'
str3 db 'Invalid number!Input again!',10 ;以上三个字符串长度均为28
str4 db 0 ;输出单个字符的缓冲区
str5 db 'sum = '
str6 db 'mul = '
str7 db 10,13 ;换行
section .text
global _start
global _input
global _conv
global _convx
global _comp
global _output ;所有的子过程声明(可以省略)
global _list ;子过程并没有明显的标记,入口有标记,结束时有返回即可
_start:
push dword str1 ;尽管变量名代表的是地址,但仍须注明类型!
push dword num1in
call _input ;输入子程
push dword num1in
push dword num1
call _conv ;转换子程
push dword str2
push dword num2in
call _input
push dword num2in
push dword num2
call _conv
call _comp ;计算子程
push dword num3
push dword num3d
call _convx ;转10进制子程
push dword num4
push dword num4d
call _convx
call _output ;输出子程
mov eax,1
mov ebx,0
int 128
_input:
mov ebp,esp ;利用栈传递数据的典型方法
kk:
mov eax,4
mov ebx,0
mov ecx,[ebp+8]
mov edx,28
int 128
mov eax,3 ;3号系统功能,读入字符
mov ebx,0 ;ebx的意义将在后面讨论
mov ecx,[ebp+4] ;写入内存的地址
mov edx,5 ;读入的长度,如果键盘缓冲区里的字符数太多,就不管了
int 128 ;未从键盘缓冲区读入的字符将成为下次输入的内容
mov ecx,4 ;这部分检验数据的合法性
mov ebx,[ebp+4]
next: mov al,[ebx]
cmp al,10 ;输入时会引入一个10,所以10 也是合法的
jz cc
cmp al,39h
ja inv
cmp al,30h
jb inv
cc: inc ebx
loop next
jmp exit
inv: mov eax,4
mov ebx,0
mov ecx,str3
mov edx,28
int 128
jmp kk ;输入错误的处理
exit: ret 8 ;由于压入两个dword 值,所以是8
_conv:
mov ebp,esp
mov ebx,[ebp+8]
mov ecx,4
mov si,10
mov ax,0
cnext: cmp byte [ebx],10
jz cexit
mul si
and byte [ebx],0fh
mov dh,0
mov dl,[ebx]
add ax,dx
inc ebx
loop cnext ;十进制字符串转二进制的典型方法,只是地址是32 位
cexit: mov ebx,[ebp+4]
mov [ebx],ax
ret 8
_comp:
mov ax,[num1]
add ax,[num2]
mov [num3],ax
mov word [num3+2],0 ;置和结果的高8 位为零,这样可以简化输出部分
mov ax,[num1]
mul word [num2]
mov [num4],ax
mov [num4+2],dx
ret
_convx:
mov ebp,esp
mov ebx,[ebp+8]
mov eax,[ebx]
xor edx,edx
mov ecx,8
mov esi,10
mov ebx,[ebp+4]
add ebx,7
nextxl: div esi ;这里使用32 位运算,使用16 位需先将10000 以上的4 位分离
mov [ebx],dl
xor edx,edx
dec ebx
loop nextxl
ret 8
_output:
push dword num3d
mov eax,4
mov ebx,0
mov ecx,str5
mov edx,6
int 128
call _list
mov eax,4
mov ebx,0
mov ecx,str7
mov edx,2
int 128
push dword num4d
mov eax,4
mov ebx,0
mov ecx,str6
mov edx,6
int 128
call _list
mov eax,4
mov ebx,0
mov ecx,str7
mov edx,2
int 128
ret
_list:
mov ebp,esp
mov ebx,[ebp+4]
mov ecx,8
mov dh,0
nextl: mov dl,[ebx]
add dh,dl ;dh将所有准备输出的位加起来,这样可以去掉无用的0
cmp dh,0 ;dh不为0即输出这一位
jz ee
or dl,30h
mov [str4],dl
push ebx ;由于系统功能调用需要eax,ebx,ecx,首先保存现场
push ecx
push edx
mov eax,4
mov ebx,0
mov ecx,str4
mov edx,1
int 128
pop edx
pop ecx
pop ebx
ee: inc ebx
loop nextl
cmp dh,0
jz zero
jmp ex
zero: mov byte [str4],'0' ;如结果为0,还需多输出一个0
mov eax,4
mov ebx,0
mov ecx,str4
mov edx,1
int 128
ex: ret 4
2-4 显示文件内容
这部分内容用到如下系统功能:
5 号功能
入口
eax:5
ebx:文件名的字符串,以0 结尾
ecx:打开方式,参数不明
edx:打开权限?
出口
eax:文件柄,如打开失败返回一个负数
3 号功能的补充
入口
eax:3
ebx:文件柄,没这个文件柄就读键盘
ecx:字符串起始地址
edx:预计读入的字符数
出口
eax:实际读入的字符数
section .data
name db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
handl db 0,0,0,0
buf db 0
str1 db 'Please input file name:'
str2 db 'Fail to open file!',10,13,10,13
str3 db 'The content of this file:',10,13,10,13
str4 db 10,13,10,13
section .text
global _start
_start:
mov eax,4
mov ebx,0
mov ecx,str1
mov edx,23
int 80h
mov eax,3
mov ebx,0
mov ecx,name
mov edx,20
int 80h
mov eax,4
mov ebx,0
mov ecx,str4
mov edx,2
int 80h
mov ecx,20 ;对输入的文件名的处理,将换行符改为0
mov ebx,name
next: cmp byte [ebx],10
jnz kk
mov byte [ebx],0
kk: inc ebx
loop next
mov eax,5 ;5号功能,打开文件
mov ebx,name ;文件名
mov ecx,0 ;打开方式
mov edx,0
int 80h
cmp eax,0 ;打开是否成功
jl fail
mov [handl],eax
mov eax,4
mov ebx,0
mov ecx,str3
mov edx,29
int 80h
rnext: mov eax,3 ;用3号功能读文件
mov ebx,[handl] ;文件柄
mov ecx,buf ;缓冲区
mov edx,1 ;读入长度
int 80h
cmp edx,eax ;如果实际读入长度与预计读入长度不同,就关闭文件并结束
jnz close
mov eax,4 ;4号功能,显示刚才读入的字符
mov ebx,0
mov ecx,buf
mov edx,1
int 80h
jmp rnext
close: mov eax,4
mov ebx,1
mov ecx,str4
mov edx,4
int 80h
mov eax,6 ;6号功能,关闭文件
mov ebx,[handl] ;文件柄
int 80h
jmp exit
fail: mov eax,4 ;打开文件失败的提示
mov ebx,0
mov ecx,str2
mov edx,22
int 80h
exit: mov eax,1
mov ebx,0
int 80h
2-5 汇编语言程序设计总结
很少有机会,我们会直接面对汇编语言。
在Windows 下,我们有太多的开发工具可以选择,比如与系统关系最为密切的VC,号
称开发工具之王的
Delphi 等。甚至Linux 下也有Delphi,不过换了一个名字叫Kylix。
但是,有时我们的程序出错,屏幕上出现的是那些汇编指令的时候,我们才会意识到
:这些漂亮的脸面之
下,其实是那些机器代码。
确实,无论是简洁的C,严谨的Pascal,还是通俗的BASIC,它们的背后,都是汇编指
令,是那些机器代
码。
从DOS 移向Linux,甚至连电脑都没有换,我们所作的事情已经够多的了。
汇编语言的代码是极为繁冗的,但是我们也获得了极高的效率。仅仅输出一行文字,
QB 给了我们一个2k
多的exe 文件,而不到2k 的汇编语言编译出来的exe 文件已经可以实现比较复杂的
功能了。
程序设计的核心是数据结构和算法,稍为复杂一点的数据结构和算法在汇编语言中都
成为非常困难的事,
更何况我们还要考虑数据进制的转换。
但是,我们因此获得了一些从前不曾有过的功能,比如直接写屏的显示方法和最为简
单的读时钟方法。
今后,我们还会有机会写很多程序,不过很可能不再是汇编。
三、输入/输出设备实验
3-1 Linux 下的端口读写规则
Linux 使用的是保护方式,端口作为重要的输入输出部件,被操作系统严严实实地保
护起来。但是Linux
下的端口操作其实就像一张窗户纸,一捅就破。
当然,不是什么“人”都有这个能力。进行端口操作时,为避免不必要的麻烦,建议
使用root 登陆,至少
拥有root 的权限。
直接操作端口是会出错的,以下两个函数可以使我们获得对端口的操作权限:
ioperm
原型:
int ioperm(unsigned long from, unsigned long num, int turn_on);
参数:
from: 起始端口值
num: 指定端口的个数
turn_on: 打开端口的参数
描述:
这个函数可以使root 获得对端口的读写权限,例如ioperm(0x20,5,1)可以使你获得
从0x20 开始的5 个端
口的操作权力。其中第三个参数为1 表示打开,为0 表示关闭。这个函数只对0-0x3ff
的端口有效。
iopl
原型:
int iopl(int level);
参数:
level: 新的端口读取权限设定,取值0-3
描述:
改变所有端口的读取权限,例如iopl(3)会使我们获得对所有端口的全部权限。
返回值:
成功返回0,失败返回-1。
这两个函数其实是解释了保护方式下端口权限的设定:在FLAG 中有一个全局的设置
,并且在系统中还有一
个对端口权限的列表。
我们可以使用int 80h 直接调用这两个函数,它们的调用号为101 和110。
下面的实验中,我们将给出几个从简单到复杂的例子,它们都是建立在端口操作基础
上的。
其实我们只需把从前在DOS 下写的很好的程序加上上面两个函数调用就可以了。当然
,别忘了返回指令也
要换一下。
3-2 8255A 接口
下面的程序完成用开关控制灯的亮灭,当全部开关关掉时灯全灭,之后程序自动停止
运行。
PA0-PA7 接输入设备(开关),PB0-PB7 接输出设备,CS 接片选译码的200-207
,共19 根线。
section .text
global _start
_start:
mov eax,110
mov ebx,3
int 80h ;调用iopl获得对所有端口的所有权限
mov dx,0c803h
mov al,90h ;控制字
out dx,al
kk: mov dx,0c800h
in al,dx ;从A读
mov dx,0c801h
out dx,al ;输出到B
cmp al,0ffh ;结束条件
jz ee
jmp kk
ee: mov eax,1
mov ebx,0
int 80h ;返回
这个程序我是从写好的DOS 下的程序copy 过来的,只改动了开头和结尾部分。
这里的端口是c803h,远大于3ffh,所以只能用iopl。
一般认为为安全起见,能用ioperm 就不用iopl,但是问题其实也不大。DOS 下开放
了全部端口,不是也没
出什么问题吗?
3-3 数模转换和模数转换
数模转换的程序设计比较简单,因为算法流程中只是用到了端口的输出。所以改写程
序时并不用更改很多
地方。下面我们列出一个综合了数模/模数转换的实验,就是正弦波形交给模数转换
器之后,再交给数模转
换器进行输出。
前面数据段的变量表可以按照实际的接线情况修改。
section .data
portadc dw 0c800h
portdac dw 0c810h
port8255a dw 0c818h
section .text
global _start
_start:
mov eax,110
mov ebx,3
int 80h
mov dx,[port8255a]
add dx,3
mov al,90h ;8255A 的控制字
out dx,al
pp: call adcpro
jmp pp
exit: mov eax,1
mov ebx,0
int 80h ;返回
adcpro:
mov dx,[portadc]
out dx,al ;送开始转换指令(al 是什么都可以)
mov dx,[port8255a]
t1: in al,dx
test al,1
jz t1 ;查询是否转换结束
mov dx,[portadc]
in al,dx ;读入转换结果
mov dx,[portdac]
out dx,al ;送结果到DAC
mov dx,[portdac]
inc dx
out dx,al ;两次锁存
ret
这个程序的编写过程中我们偷了一点懒,就是没有写退出的语句,而是利用Linux 自
身的Ctrl+C 这个组
合键跳出(改写键盘中断非常的困难,这一点后面还有论述)。
3-4 小键盘
其实在明白了端口的读写规则之后,基本上我们已经能够解决几乎所有的端口实验。
下面我们尝试进行一
个综合实验。
section .data
i db 100
x db 0,0
xx db 0
letter db 'CDEFBA9845673210WXYSRPMG'
section .text
global _start
_start:
mov eax,110
mov ebx,3
int 80h ;开启端口
pp: call main
jmp pp ;不断调用子程
mov eax,1
mov ebx,0
int 80h ;退出
trans:
xor ebx,ebx
and word [x],7ffh
cmp word [x],7ffh
jz exit ;无键按下就跳过后面的部分
mov al,[x]
not al
mov cx,7
tra1: rol al,1
jc tra2
loop tra1
tra2: mov bx,cx ;通过移位记录B 端口的0 的位置
mov al,[x+1]
or al,0f8h
not al ;取反,便于译码
dec al
cmp al,03h
jnz tra3
dec al ;2对应100,1 对应010,0 对应001
tra3: xor ah,ah
mov cl,3
rol ax,cl ;A端口获得的译码结果(0、1、2)乘以8
add bx,ax ;加上B端口的译码结果
mov dl,[letter+ebx] ;获得一个唯一对应的字符,地址用32 位
mov [xx],dl
mov eax,4
mov ebx,0
mov ecx,xx
mov edx,1 ;输出
exit: ret
delay:
push si
push di
mov si,0f000h
de1: nop
mov di,800h
de2: nop
dec di
jnz de2
dec si
jnz de1
pop di
pop si
ret
main:
call delay
mov dx,0c803h
mov al,90h
out dx,al
mov dx,0c801h
mov al,0h
out dx,al
mov dx,0c800h
in al,dx
mov [x],al ;A端口获得的值
mov dx,0c803h
mov al,82h
out dx,al
mov dx,0c800h
mov al,0h
out dx,al
mov dx,0c801h
in al,dx
mov [x+1],al ;B端口获得的值
call trans ;译码
int 80h
ret
四、中断技术
Linux 的中断技术概论
“Linux 是一个运行在保护模式下的共享库的环境,意味着没有中断服务。”
上面这句话摘自互联网上的一个论坛,当然这句话是不对的,如果没有中断,设备驱
动程序怎么办?这句
话的作者也很快否定了这个命题。
但是我们在试图探讨Linux 下的中断服务的时候,确实遇到了很多困难。由于Linux
本身是用C 写的,所
以C 才是Linux 下的标准编程语言。虽说汇编与C 相比有很多优点(?),但是确实
汇编的更加深入的应用
几乎是找不到的。
事实上,我读过CAICHONG 师兄的报告,并且耗费了一些时间找了很多资料,认为在
Linux 下用汇编语言进
行中断设计是不现实的(即使我们很清楚无论什么语言写出的程序最后都要变成汇编
代码)。这一点似乎与
保护方式或者Win32 编程还不一样。
如果想添加新的系统功能,似乎是要编译内核,但是即使最简单的编译也有如天书,
初学者(我也是初学
者)几乎不可能掌握。
CAICHONG 给出的报告的水平是很高的,虽然程序本身有一点小错误(一个函数没有
声明)。
在算法或者端口实验中我可以设计出好一点的算法或者将端口实验扩展丰富,但是这
里我无法比他做的更
好。
以下内容引自互联网上的一篇文章。由于函数用到的数据结构非常复杂,用汇编语言
是几乎不可能实现的。
request_irq()、free_irq()
这是驱动程序申请中断和释放中断的调用。在include/linux/sched.h 里声明。
request_irq()调用的定义:
int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
unsigned long irqflags,
const char * devname,
void *dev_id);
irq 是要申请的硬件中断号。在Intel 平台,范围0--15。handler 是向系统登记的
中断处理函
数。这是一个回调函数,中断发生时,系统调用这个函数,传入的参数包括硬件中断
号,device
id,寄存器值。dev_id 就是下面的request_irq 时传递给系统的参数dev_id。irqflags
是中断
处理的一些属性。比较重要的有SA_INTERRUPT,标明中断处理程序是快速处理程序(
设置
SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT)。快速处理程序被调用时屏
蔽所有中
断。慢速处理程序不屏蔽。还有一个SA_SHIRQ 属性,设置了以后运行多个设备共享
中断。dev_id
在中断共享时会用到。一般设置为这个设备的device 结构本身或者NULL。中断处理
程序可以用
dev_id 找到相应的控制这个中断的设备,或者用irq2dev_map 找到中断对应的设备
。void
free_irq(unsigned int irq,void *dev_id);
五、总结
这次完全不同的Linux 之旅终于结束了。
在Linux 下进行汇编语言编程时,我的感觉是NASM 的要求要比MASM 严格得多,但是
功能要在MASM 之上。
其实DOS 下还有TASM,用它可以生成功能更为强大的代码。
另外一个感觉是Linux 似乎缺少一个统一的标准,这一点是自由软件作者各人的习惯
不同,如果Linux 不
在这方面进行努力的话,是不可能与微软对抗很久的。
CAICHONG 在他的报告中数次提到微软,都把英文拼成Micro$oft,看来大家对微软的
成见都很深啊。其实
我并不反感微软,从1993 年开始学习DOS 时,我几乎使用过微软此后出品过的全部
操作系统(第一次见到
Win95 的确被震惊了),微软的确提供给我们一个非常华丽且易用的平台。
其实我也想过中国计算机业的现状。我国大陆的电子芯片制造能力很弱,前些日子的
“龙芯”其实并不能
让我们兴奋多久,毕竟Intel 已经快要启用0.09 微米工艺了。我国台湾省的芯片产
业倒是比较兴旺,拥有
华硕、威盛这样的大厂。现在大陆的联想电脑也向生产芯片组方面迈进,这倒是非常
喜人的现象。
中国的软件业还是很弱的,从前看过一个故事,说找了几个中国和印度的程序员开发
一个系统,中国的程
序员抱着《数据结构》去编程,印度的程序员仅仅写了简单的数组。采用好的数据结
构虽然可以增大效率,
但是在当时的计算机硬件条件下,速度已经不是问题,使用简单数据结构使得代码的
可读性和维护性非常
好,可靠性也很高。而且即使编写同样的程序,中国程序员的代码的清晰程度也差了
很多。
中国的好的软件几乎都是些共享软件,多数是由个人程序员编写的,像微软的那些软
件(Windows、Visual
Studio、Office)都不是几个人就能完成的,他们的合作能力非常的强。中国程序员
的合作能力远远及不
上他们,每个人都有自己的一套习惯,凑在一起,看懂别人的代码就很困难。
Linux 也是缺少一个统一的标准,所以我前面的一个时钟的程序,拿到另外一台电脑
上就会出问题。这种
问题是写程序的人意料不到的。但是等到有了一个标准之后,恐怕Linux 就不再是开
放源代码的了(那也
必然是Linux 走向绝路的时候)。
我查阅一些汇编的资料的时候,发现网络上的程序有一些统一的习惯,比如使用异或
指令清除一个寄存器,
在调用系统功能的时候直接改写ax 等等。我想汇编本身就是一个很难读懂的语言,
需要一些约定俗成的规
则。
总结絮絮叨叨写了这么多,好像与这次实验没什么关系。其实我也不知总结该写些什
么,我觉得实验的难
度并不大,只是两个系统之间频繁的切换使人想到了很多东西。
这次实验的很多资料来自CAICHONG 的整理,在此对他提供的资料和朱小梅老师的指
导表示感谢!
【 在 scarsty (小蝶) 的大作中提到: 】
: 第一次听说weyl是教授讲的,也许他是20世纪最伟大的数学家。
: 对前辈的崇敬是不需语言形容的。
: 外尔
: 张奠宙
: (华东师范大学)
: 外尔,H(Weyl,Hermann)1885年11月9日生于德国的埃尔姆斯霍恩;1955年12月
: 8日卒于瑞士苏黎世.数学,数学物理.
: 外尔出生在邻近汉堡的一个小镇上.父亲路德维希(Ludwig)是银行家,母亲安娜
: (Anna)在家里照料孩子.外尔在乡镇上度过了少年时代,并在阿尔托纳的一所文法中
: 学读书.虽说乡下的孩子往往比较闭塞,见识不广,但外尔在中学时已读过Ⅰ.康德
: (Kant)的《纯粹理性批判》(Critique of Puve Reason,1781).他回忆说:“这书
: ...................
--
lalala……
狂风暴雪……
※ 来源:·BBS 听涛站 tingtao.net·[FROM: 219.224.174.174]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:4.091毫秒