标签:
【计算机科学技术】
谢晓啸
湖北省沙市中学
【关键词】:
编译原理,操作系统内核实现,算法与数据结构,算法优化
1.引论
1.1研究内容
1.2研究目的
1.3研究提要
正文
2.1研究方法
2.2编译器部分
2.2.1从计算器程序中得到的编译器制作启示
2.2.2在编译器中其它具体代码的实现
2.2.3编译器中栈的高级应用
2.2.3编译器中树的高级应用
2.2.4编译器与有限状态机
2.3操作系统内核部分
2.3.1操作系统与底层编码
2.3.2朴素的自然编码方法
2.3.3操作系统内核的磁盘管理
2.3.4操作系统对硬件在数据结构上的操作
2.3.5基于51单片机的“实时系统”
3.研究总结与反思
3.1研究目标完成情况及反思
3.2感想与计划
4.参考文献
1.1研究内容:
本研究的课题是“基于编译器和操作系统内核的算法设计与实现”(AACOS:AACOS is Algorithms‘ design and implementation based on Compiler and Operating System kernel )。
1.2研究目的
通过对典型的编译器和操作系统内核的算法设计与实现进行分析,从其中总结它们高效的原因进而更好地促进实际开发与应用;
研制一个更好的词法分析器;
对部分非编译器和操作系统程序做出改进。
1.3研究提要
编译器和操作系统是计算机软件体系中对于性能要求十分苛刻的软件。
编译程序是现代计算机体系中基本的组成部分,绝大多数计算机系统都配有多个高级语言编译系统。在编译器中,不仅要能对于给定的程序设计语言生成目标代码,还要能够针对指定的平台生成最优代码,有些高级语言编译器比如GCC甚至针对不同的性能要求提供了不同的优化级别。而有些解释型的程序设计语言还需要一个提供高效运行的解决方案,比如Java的JVM是用来跨平台的高效运行Java字节码。还有一些软件为了提高自身的扩展能力往往会集成领域专用语言(DSL,Domain-Specific Language),比如Emacs内集成的Emacs Lisp这种Lisp方言,以及Microsoft Office的VBA。
而对于通用的操作系统,更是要能够及时响应来自硬件和程序的消息,对于现代的多用户多任务实时操作系统更是需要根据实际需求快速调度系统资源,管理进程和消息队列,这样的操作系统才是健全的。另外还有一些运用于特殊领域的比如航空航天、军事、工业流程控制上的操作系统必须要能够应对各种苛刻的条件,比如极小的内存、极低的运算能力以及强电磁场或高辐射可能带来的程序错误问题,因此这类操作系统还必须具备处理错误、占用资源低的特点。
上面所提到的编译器和操作系统(内核)的高效的运行都离不开高效的算法和数据结构的运用,只有针对特定的问题选择甚至设计合适的算法与数据结构才能满足这一需求。
在过去的二十年里,处理器的速度梦幻般地提速,随着“内存胜金”年代的一去不复返,现代的很多程序员越来越不注重程序的算法优化了,典型的就是当今用于便携式设备上的操作系统,如Android上的程序很多没有经过认真优化就发布了,这种为了赶进度而不关心用户的行为是不负责任的!如果民用级的应用程序都能够实现像编译器与操作系统内核那样高效的代码,那么计算机资源将会大大得到节省。几十年前,我们要优化代码是因为那时的计算机的运算速度低下,现在本研究再次谈到这样高效的算法设计与实现,是因为在如今摩尔定律遇到瓶颈而量子计算机与生物芯片还未普及的情况下,用高效的代码来提高事务处理。如果硬件不能提速,就应当考虑用算法来解决。本研究正是为了更好地实现对民用的应用程序的优化而了解编译器和操作系统内核的算法实现及设计,从而对现有的计算机工业提供新思路。
2.1研究方法
主要通过学习大量已有文献资料和源码以及实际编制程序并进行测试来研究“基于编译器和操作系统内核的算法设计与实现”(以下简称AACOS)。由于用到的文献资料众多,难以逐一指明哪种方法是出自哪篇资料的,因此统一在本论文末“参考文献”中列举出来。编译器主要研究的是Tiny C(编译型),crowbar(解释型,语法分析树),Diskam(解释型,字节码),操作系统主要研究的是Linux(内核版本2.6.6),但是也涉及到了Minix、BSD、MS-DOS、Win32、Win64,众所周知,后三个操作系统的源码是不向个人开放的,因此本研究也只是能从普通开发者的角度来谈Microsoft的产品,另外为了实现在引论中提到的“在特殊场合使用的操作系统”,还研究了给予Intel 8051内核的51单片机操作系统。
虽然研究的课题是“基于编译器和操作系统内核的算法设计与实现”,但是仍然会提到一些人工智能、数学方法、自然语言处理和硬件设计,这是因为这些领域在很多方面都有值得学习的方面。
研究中所提到的软件几乎都在机器上的运行过,研究中主要用到的软硬件条件是:
一台装有Ubuntu 14.04 LTS和Windows XP Professional SP3(中文版)的PC,采用的是AMD E-350D APU和2G内存;
单片机采用的是两片STC 89C52RC PDIP,分别外接ATMEL268 EPPROM和11.0592MHz晶振;
所用的编译程序,GCC用于C语言部分(在Windows上用MinGW作为替代),NASM和MASM5.0、MASM6.1用于汇编语言,但是在解释GCC的编译时也会用到gas;
反汇编软件,Linux下主要使用NASM自带的disasm,在Windows下则使用OllyDBG(在win32下)和DEBUG(在16位下);
用到的自动词法分析器生成工具是免费的flex,用到的自动解析器生成工具是bison;
单片机用的软件是Keil vision4(编译环境),STC-ISP4(下载软件),串口调试助手v1;
虚拟机Bochs 和Virtual Machine Station。
2.2编译器部分
2.2.1编译器实际应用概述
无论是基于超级计算机的长期天气预测,还是基于嵌入式计算机的作业控制,这些应用都依赖于计算机程序。然而,软件本身只是一个抽象层的产物,它的目的是和实际的硬件层进行交流。当代的所有这些软件都离不开编译器工具的转换。编译器本身只是一个程序,但是和一般的应用程序不同——一般的应用程序致力于将底层和数据代码隔离,然而好的编译器却应当努力将数据和硬件联系起来。
不同于自然语言,计算机语言是计算机特定的指令集合,因此它要求是精准的、是无歧义的、是简洁的,并且还要有足够的表现力。自然语言允许二义性的存在,但是程序设计语言需要努力的避免二义性,二义的语义无语义。然而随着一些基于上下文的程序设计语言的出现,编译器也应当能够根据上下文来处理特定的语言,比如在C++中:
std::vector<double> variable
std::vector<double>::iterator p=variable.begin()
可以被重写为:
std::vector<double> variable
auto p=variable.begin()
分析一下,这里的auto是C++11的变量类型自动推断功能,特别适合于模板和类的编程,因为通过这种方法可以方便有效的实现复杂类型变量的的声明,实际上在vector(GCC 4.8.1)中有这样的定义:
……
typedef typename _Base::iterator _Base_iterator;
typedef typename _Base::const_iterator _Base_const_iterator;
……
起先这种强大的功能并不是用于自动推断变量类型的,而是编译器用来判断类型兼容的,比如下面的声明:
int (*arr)[100];
int *(*p)[100];
int **p;
很显然第三个指针是类型不兼容的,这样的问题可以这样来解决:
auto p=arr;
把原本适用于编译器差错的功能提供给程序员,的确很方便。
编译器的目标是翻译针对人类的程序设计语言,并非针对机器的机器语言或者汇编语言,这意味着编译器要能够处理足够“自然的语言”,这也正是很多实验性的编译器只是一个从源到源的转换器(通常是由其他语言前端转换成C语言)。
广义的来说,凡是具有自然语言处理处理特色的程序都可以算的上“编译器”。比如PostScript、LexTeX这些排版程序,它们把如何将文档印刷的格式、布局作为输入,产生相应的文件作为输出——从一种格式转换到另一种格式,这就具有了编译器的某些特点了。然而严格的说,这不是编译器(Compiler)而是解释器(Interpreter)类似的比如Perl、Scheme、Python都只是依赖于解释器。当然Java通过字节码这一中间形式来让代码更紧凑,而字节码又依赖于JVM(Java Virtual Machine),许多JVM实现都包括JIT运行时解释器。还有广为流传的HTML和最火的HTML5,都是这样的运行模式。
无论是编译器还是解释器,都会做一些相同的工作,一是对程序合法性的分析,二是根据句意生成机器指令。
现代的编译器是庞大且复杂的,主流编译器的代码量都可以达到数十万行,这是为了应对日渐复杂的编程人物。在这编译器的开发过程中,自始至终的设计与决策都十分重要,编译器的设计实现是计算机工业半个多世纪以来关注的重心。
一个好的编译器就是计算机科学的缩影,编译器的每一步都用到了这个世纪以来最优秀杰出的科学家研究的成果:词法分析个语义分析涉及到有限自动机和下推自动机,指令的选择涉及到动态规划,寄存器的分配涉及到贪心算法,数据流的分析运用到不动点算法,错误代码的消除以及代码的优化涉及到图算法,表的调度涉及到启发式搜索技术……除了操作系统以外,很少软件能够同时汇集同时、同步、动态的特性。
2.2.2从计算器程序中得到的编译器制作启示
编译器编译高级语言,第一步要做的是将源代码分割为若干个语法几号,然后再从记号构建语法树并进一步“提纯”为抽象语法树,接着按照语法对语法树经行语义分析,最后再生成代码。然而,这就有一个问题,在语法分析阶段编译器是无法知道诸如数据类型等信息,然而记号的划分与语法树的构建却都是给予语法本身的,这倒并不是“鸡与蛋”的问题,只是在词法分析和语法分析阶段一定要讲究通用性。比如针对下面的代码:
if(a>b)
printf(“Poor A !\n”);
else
printf(“Lukily B!\n”);
如果单单是处理这一个分支判断语句实在是太简单了,下面就是一个解决方法:
scanf(“%s”,str_line);
if(strbrk(str_line,”if”))
{
printf(“if\n\n\n”);
puts(str_line+2);
while(gets(str_line)&&!strcmp(str_line,”else”))
{
puts(st_line);
}
puts(“\n\n\n”)
pusts(“else\n\n\n”);
while(gets(str_line))
{
puts(str_line);
}
}
这个过程并不通用,通用的将代码分成记号应该是下面这样的:
if
(
a
>
c
)
{
printf
(
“Poor A !\n”
)
;
}
else
{
printf
(
“Lukily B!\n”
)
;
}
它的语法分析树应当是:
显然如果直接采用上面的方法,还是不够的。直接以常见的计算器程序为例。
程序清单:
calc_2_2_1_1.c
#include<stdio.h>
#include<string.h>
#include<ctype.h>
#define MAXN 255
int push(int*,int,char*,char*);//压栈操作
int pop(int*,char*);//弹栈操作
int pri(char*,char*,int,int);//判断优先级
int main(void)
{
char s[MAXN],t[MAXN],symbol[MAXN];
int number[MAXN];
int i=0,p=0,j,k,m;
gets(s);
strcpy(t,”(”);
strcat(t,s);
strcat(s,“)”);
while(i<=strlen(s))
{
while(s[i]==’(’)
{
push(&p,i,symbol,s);
i++;
}
j=i;
do
{
i++;
}
while(!isdigit(s[i]));
m=0;
for(k=j;k<i-j+1;k++)
{
t[m++]=s[k];
}
t[k+1]=’\0’;
number[p]=atoi(t);
do
{
if(s[i]==’)’)
{
while(symbol!=’(’)
{
pop(&p,symbol);
}
p--;
number[p]=number[p+1];
}
else
{
while(pri(s,symbol,i,p))
{
pop(&p,symbol);
}
push(&p,i,symbol,s);
}
}
while(i>strlen(s)||s[i-1]!=’)’);
}
printf(“\n%d”,number[0]);
getchar();
}
int push(int *p,int i,char *symbol,char *s)
{
(*p)--;
symbol[p]=s[i];
return 1;
}
int pop(int *p,char *symbol)
{
(*p)--;
switch(symbol[p+1])
{
case ‘+’:number[*p]+=number[*p+1];break;
case ‘-’:number[*p]-=number[*p+1];break;
case ‘*’:number[*p]*=number[*p+1];break;
case ‘/’:number[*p]/=number[*p+1];break;
}
return 1;
}
int pri(char *s,char *symbol,int i,int p)
{
if((s[i]==’+’)||(s[i]==’-’)&&symbol[p]!=’(’)
return 1;
if((s[i]==’*’)||(s[i]==’/’)&&(symbol[p]==’*’)||(symbol[p]==’/’))
return 0;
}
这种用栈来描述的方法已经算比较简洁的了,更具体的来说:
程序清单ex2_2_1_2.c
#include<stdio.h>
#include<stdlib.h>
#define SIZE 50
typedef int DATA;
#include "SeqStack.h"
int IsOperator(char c) //检查字符是否为运算符
{
switch(c)
{
case‘+‘:
case‘-‘:
case‘*‘:
case‘/‘:
case‘(‘:
case‘)‘:
case‘=‘:
return 1;
break;
default:
return 0;
break;
}
}
int PRI(char oper1,char oper2) //判断两个运算符的优先级
// oper1>oper2返回1
//oper1<oper2返回-1
//oper1=oper2返回0
{
int pri;
switch(oper2) //判断运算符优先级
{
case ‘+‘:
case ‘-‘:
if(oper1==‘(‘||oper1==‘=‘) //为左括号或表达式开始符号
pri=-1; //返回小于
else
pri=1;
break;
case ‘*‘:
case ‘/‘:
if(oper1==‘*‘||oper1==‘/‘||oper1==‘)‘)
pri=1;
else
pri=-1;
break;
case ‘(‘:
if(oper1==‘)‘) //右括号右侧不能马上出现左括号
{
printf("语法错误!\n");
exit(0);
}else
pri=-1;
break;
case‘)‘:
if(oper1==‘(‘)
pri=0;
else if (oper1==‘=‘)
{
printf("括号不匹配!\n");
exit(0);
}else
pri=1;
break;
case ‘=‘ :
if(oper1==‘(‘)
{
printf("括号不匹配!\n");
exit(0);
}else if(oper1==‘=‘)
pri=0;
else
pri=1;
break;
}
return pri;
}
int Calc(int a,int oper,int b) //计算两个操作数的结果
{
switch(oper)
{
case‘+‘:return a+b;
case‘-‘:return a-b;
case‘*‘:return a*b;
case‘/‘:
if(b!=0)
return a/b;
else
{
printf("除0溢出!\n");
exit(0);
}
}
}
int CalcExp(char exp[]) //表达式计算函数
{
SeqStack *StackOper,*StackData;
int i=0,flag=0;
DATA a,b,c,q,x,t,oper;
StackOper=SeqStackInit(); //初始化两个栈
StackData=SeqStackInit();
q=0;
x=‘=‘;
SeqStackPush(StackOper,x); //首先将等号(=)进入操作符栈
x=SeqStackPeek(StackOper); //获取操作符栈的首元素
c=exp[i++];
while(c!=‘=‘ || x!=‘=‘)
{
if(IsOperator(c)) //若输入的是运算符
{
if(flag){
SeqStackPush(StackData,q);//将操作数入栈
q=0;
flag=0;
}
switch(PRI(x,c)) //判断运算符的优先级
{
case -1:
SeqStackPush(StackOper,c);//运算符进栈
c=exp[i++];
break;
case 0:
c=SeqStackPop(StackOper); //运算符出栈 (抛弃)
c=exp[i++];
break;
case 1:
oper=SeqStackPop(StackOper); //运算符出栈
b=SeqStackPop(StackData);//两个操作数出栈
a=SeqStackPop(StackData);
t=Calc(a,oper,b);
SeqStackPush(StackData,t);//将运算结果入栈
break;
}
}else if(c>=‘0‘&&c<=‘9‘) //若输入字符在0~9之间
{
c-=‘0‘;
q=q*10+c;
c=exp[i++];
flag=1;
}
else
{
printf("输入错误!\n");
getch();
exit(0);
}
x=SeqStackPeek(StackOper);//获取栈顶的运算符
}
q=SeqStackPop(StackData);
SeqStackFree(StackOper); //释放栈所占用的空间
SeqStackFree(StackData);
return q; //出栈,返回结果
}
int main()
{
int c;
char exp[80];
printf("请输入要计算的表达式(以=结束):");
scanf("%s",exp);
printf("%s%d\n",exp,CalcExp(exp));
getch();
return 0;
}
程序清单 SeqStack.h
typedef struct stack
{
DATA data[SIZE+1]; //数据元素
int top; //栈顶
}SeqStack;
SeqStack *SeqStackInit()
{
SeqStack *p;
if(p=(SeqStack *)malloc(sizeof(SeqStack))) //申请栈内存
{
p->top=0; //设置栈顶为0
return p;//返回指向栈的指针
}
return NULL;
}
int SeqStackIsEmpty(SeqStack *s) //判断栈是否为空
{
return(s->top==0);
}
void SeqStackFree(SeqStack *s) //释放栈所占用空间
{
if(s)
free(s);
}
void SeqStackClear(SeqStack *s) //清空栈
{
s->top=0;
}
int SeqStackIsFull(SeqStack *s) //判断栈是否已满
{
return(s->top==SIZE);
}
int SeqStackPush(SeqStack *s,DATA data) //入栈操作
{
if((s->top+1)>SIZE)
{
printf("栈溢出!\n");
return 0;
}
s->data[++s->top]=data;//将元素入栈
return 1;
}
DATA SeqStackPop(SeqStack *s) //出栈操作
{
if(s->top==0)
{
printf("栈为空!");
exit(0);
}
return (s->data[s->top--]);
}
DATA SeqStackPeek(SeqStack *s) //读栈顶数据
{
if(s->top==0)
{
printf("栈为空!");
exit(0);
}
return (s->data[s->top]);
}
上面这个用非常复杂的栈(主要指实现方法)来实现四则运算的程序是摘自一本算法书。即使是这样,它也不一定比第一个好。首先,它无法实现实数运算,其次它的方法不具备通用性。
更为通用的方法是用类似于编译器的记号分割法。
程序清单:ex2_2_1_lex.l
%{
#include <stdio.h>
#include "y.tab.h"
int
yywrap(void)
{
return 1;
}
%}
%%
"+" return ADD;
"-" return SUB;
"*" return MUL;
"/" return DIV;
"\n" return CR;
([1-9][0-9]*)|0|([0-9]+\.[0-9]*) {
double temp;
sscanf(yytext, "%lf", &temp);
yylval.double_value = temp;
return DOUBLE_LITERAL;
}
[ \t] ;
. {
fprintf(stderr, "lexical error.\n");
exit(1);
}
%%
程序清单:ex2_2_1_yacc.y
%{
#include <stdio.h>
#include <stdlib.h>
#define YYDEBUG 1
%}
%union {
int int_value;
double double_value;
}
%token <double_value> DOUBLE_LITERAL
%token ADD SUB MUL DIV CR
%type <double_value> expression term primary_expression
%%
line_list
: line
| line_list line
;
line
: expression CR
{
printf(">>%lf\n", $1);
}
expression
: term
| expression ADD term
{
$$ = $1 + $3;
}
| expression SUB term
{
$$ = $1 - $3;
}
;
term
: primary_expression
| term MUL primary_expression
{
$$ = $1 * $3;
}
| term DIV primary_expression
{
$$ = $1 / $3;
}
;
primary_expression
: DOUBLE_LITERAL
;
%%
int
yyerror(char const *str)
{
extern char *yytext;
fprintf(stderr, "parser error near %s\n", yytext);
return 0;
}
int main(void)
{
extern int yyparse(void);
extern FILE *yyin;
yyin = stdin;
if (yyparse()) {
fprintf(stderr, "Error ! Error ! Error !\n");
exit(1);
}
}
编译过程:
yacc -dv mycalc.y
lex mycalc.l
cc -o mycalc y.tab.c lex.yy.c
这种做法的好处之一就是它的通用性更强,比如上面的程序中不能处理表达式中的括号,那么只需要修改lex(flex)文件如下:
%{
#include <stdio.h>
#include "y.tab.h"
int
yywrap(void)
{
return 1;
}
%}
%%
"+" return ADD;
"-" return SUB;
"*" return MUL;
"/" return DIV;
"(" return LP;
")" return RP;
"\n" return CR;
([1-9][0-9]*)|0|([0-9]+\.[0-9]*) {
double temp;
sscanf(yytext, "%lf", &temp);
yylval.double_value = temp;
return DOUBLE_LITERAL;
}
[ \t] ;
. {
fprintf(stderr, "lexical error.\n");
exit(1);
}
%%
这样的做法不一定会时的运行效率更低,因为它对表达式的划分和常数值的提取更快捷,而且采用内置的正则表达式也使得实数的运算成为可能。
再比如这样一个模拟编译器拼数的程序:
程序清单:ex2_2_1_3.c
#include<stdio.h>
int main()
{
char char;
float result=0,scale;
scanf(“%c”,&ch);
printf(“%c”,ch);
while(ch>=’0’&&ch<=’9’)
{
result=result*10+ch-’0’;
scanf(“%c”,&ch);
printf(“%c”,ch);
}
if(ch==’.’)
{
scale=1;
scanf(“%c”,&ch);
printf(“%c”,&ch);
while(ch>=’0’&&ch<=’9’)
{
result=result*10+ch-’0’;
scale*=10;
}
result/=scale;
}
printf(“%f”,result);
}
这样的思路下去一个词法分析器并不是不可能的。
程序清单:token.h
#ifndef TOKEN_H_INCLUDED
#define TOKEN_H_INCLUDED
typedef enum {
BAD_TOKEN,
NUMBER_TOKEN,
ADD_OPERATOR_TOKEN,
SUB_OPERATOR_TOKEN,
MUL_OPERATOR_TOKEN,
DIV_OPERATOR_TOKEN,
LEFT_PAREN_TOKEN,
RIGHT_PAREN_TOKEN,
END_OF_LINE_TOKEN
} TokenKind;
#define MAX_TOKEN_SIZE (100)
typedef struct {
TokenKind kind;
double value;
char str[MAX_TOKEN_SIZE];
} Token;
void set_line(char *line);
void get_token(Token *token);
#endif /* TOKEN_H_INCLUDED */
程序清单:lexicalanalyzer.c
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include "token.h"
static char *st_line;
static int st_line_pos;
typedef enum {
INITIAL_STATUS,
IN_INT_PART_STATUS,
DOT_STATUS,
IN_FRAC_PART_STATUS
} LexerStatus;
void
get_token(Token *token)
{
int out_pos = 0;
LexerStatus status = INITIAL_STATUS;
char current_char;
token->kind = BAD_TOKEN;
while (st_line[st_line_pos] != ‘\0‘) {
current_char = st_line[st_line_pos];
if ((status == IN_INT_PART_STATUS || status == IN_FRAC_PART_STATUS)
&& !isdigit(current_char) && current_char != ‘.‘) {
token->kind = NUMBER_TOKEN;
sscanf(token->str, "%lf", &token->value);
return;
}
if (isspace(current_char)) {
if (current_char == ‘\n‘) {
token->kind = END_OF_LINE_TOKEN;
return;
}
st_line_pos++;
continue;
}
if (out_pos >= MAX_TOKEN_SIZE-1) {
fprintf(stderr, "token too long.\n");
exit(1);
}
token->str[out_pos] = st_line[st_line_pos];
st_line_pos++;
out_pos++;
token->str[out_pos] = ‘\0‘;
if (current_char == ‘+‘) {
token->kind = ADD_OPERATOR_TOKEN;
return;
} else if (current_char == ‘-‘) {
token->kind = SUB_OPERATOR_TOKEN;
return;
} else if (current_char == ‘*‘) {
token->kind = MUL_OPERATOR_TOKEN;
return;
} else if (current_char == ‘/‘) {
token->kind = DIV_OPERATOR_TOKEN;
return;
} else if (current_char == ‘(‘) {
token->kind = LEFT_PAREN_TOKEN;
return;
} else if (current_char == ‘)‘) {
token->kind = RIGHT_PAREN_TOKEN;
return;
} else if (isdigit(current_char)) {
if (status == INITIAL_STATUS) {
status = IN_INT_PART_STATUS;
} else if (status == DOT_STATUS) {
status = IN_FRAC_PART_STATUS;
}
} else if (current_char == ‘.‘) {
if (status == IN_INT_PART_STATUS) {
status = DOT_STATUS;
} else {
fprintf(stderr, "syntax error.\n");
exit(1);
}
}
}
}
void
set_line(char *line)
{
st_line = line;
st_line_pos = 0;
}
#if 0
void
parse_line(void)
{
Token token;
st_line_pos = 0;
for (;;) {
get_token(&token);
if (token.kind == END_OF_LINE_TOKEN) {
break;
} else {
printf("kind..%d, str..%s\n", token.kind, token.str);
}
}
}
int
main(int argc, char **argv)
{
while (fgets(st_line, LINE_BUF_SIZE, stdin) != NULL) {
parse_line();
}
return 0;
}
#endif
程序清单:parser.c
#include <stdio.h>
#include <stdlib.h>
#include "token.h"
#define LINE_BUF_SIZE (1024)
static Token st_look_ahead_token;
static int st_look_ahead_token_exists;
static void
my_get_token(Token *token)
{
if (st_look_ahead_token_exists) {
*token = st_look_ahead_token;
st_look_ahead_token_exists = 0;
} else {
get_token(token);
}
}
static void
unget_token(Token *token)
{
st_look_ahead_token = *token;
st_look_ahead_token_exists = 1;
}
double parse_expression(void);
static double
parse_primary_expression()
{
Token token;
double value = 0.0;
int minus_flag = 0;
my_get_token(&token);
if (token.kind == SUB_OPERATOR_TOKEN) {
minus_flag = 1;
} else {
unget_token(&token);
}
my_get_token(&token);
if (token.kind == NUMBER_TOKEN) {
value = token.value;
} else if (token.kind == LEFT_PAREN_TOKEN) {
value = parse_expression();
my_get_token(&token);
if (token.kind != RIGHT_PAREN_TOKEN) {
fprintf(stderr, "missing ‘)‘ error.\n");
exit(1);
}
} else {
unget_token(&token);
}
if (minus_flag) {
value = -value;
}
return value;
}
static double
parse_term()
{
double v1;
double v2;
Token token;
v1 = parse_primary_expression();
for (;;) {
my_get_token(&token);
if (token.kind != MUL_OPERATOR_TOKEN
&& token.kind != DIV_OPERATOR_TOKEN) {
unget_token(&token);
break;
}
v2 = parse_primary_expression();
if (token.kind == MUL_OPERATOR_TOKEN) {
v1 *= v2;
} else if (token.kind == DIV_OPERATOR_TOKEN) {
v1 /= v2;
}
}
return v1;
}
double
parse_expression()
{
double v1;
double v2;
Token token;
v1 = parse_term();
for (;;) {
my_get_token(&token);
if (token.kind != ADD_OPERATOR_TOKEN
&& token.kind != SUB_OPERATOR_TOKEN) {
unget_token(&token);
break;
}
v2 = parse_term();
if (token.kind == ADD_OPERATOR_TOKEN) {
v1 += v2;
} else if (token.kind == SUB_OPERATOR_TOKEN) {
v1 -= v2;
} else {
unget_token(&token);
}
}
return v1;
}
double
parse_line(void)
{
double value;
st_look_ahead_token_exists = 0;
value = parse_expression();
return value;
}
int
main(int argc, char **argv)
{
char line[LINE_BUF_SIZE];
double value;
while (fgets(line, LINE_BUF_SIZE, stdin) != NULL) {
set_line(line);
value = parse_line();
printf(">>%f\n", value);
}
return 0;
}
编译指令
cc -o mycalc lexicalanalyzer.c parser.c
至此,一个本来只能处理特定问题的分析器就能够处理通用的高级语言了。
2.2.3编译器中栈的高级应用
2.2.3编译器中树的高级应用
2.2.4编译器与有限状态机
2.3.1操作系统与底层编码
硬件是整个计算机体系的基础,现代计算机系统是一个极其庞大的体系。即使是最基本的便携式电脑也包含极其复杂的I/O处理设备和各种语言处理程序和实用程序。操作系统作为一个中间媒介有很复杂的组织结构,但是不管怎样,操作系统都是要和硬件打交道的。
简单描述一下操作系统与底层硬件的关联:
High-levelLang
Asmberly
OS
InstructionSet
Digital Logic
MircoArchi
当然特殊的微指令是商业机密而且操作系统没必要太过于关心这一层次,但是对于CPU的指令集倒是有好方法来优化。
众所周知,IA-32,IA-64(Intel Architecture 32-bits,64-bits)都是采用的CISC方案。相比ARM还有AVR、MIPS采用的等长指令RISC体系,CISC似乎更显复杂。并不能仅仅只是指责CISC是因为“历史包袱”而存在的而就否定了CISC的价值。采用不等长指令至少有两种好处:一、指令功能多样,IA-32到奔腾为止有数百条指令,这样虽然使得指令体系庞大,但是却能提供丰富的功能;二、采用不等长指令,可以在有限的空间内(如32位)高效的存储指令。现如今Intel的CISC芯片大都也加入了RISC的“DNA”,也可见得CISC并不是完全没有用了。这里CISC的不等长编码就需要设计一种编码方式。
在系统中最短编码的常见方法就是赫夫曼树。以指令被使用的频率作为节点权值来建树,也就是最常用的指令最短。
设计分析如下:
程序清单:ex2_3_1.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef struct
{
int weight; //权值
int parent; //父结点序号
int left; //左子树序号
int right; //右子树序号
}HuffmanTree;
typedef char *HuffmanCode; //Huffman编码
void SelectNode(HuffmanTree *ht,int n,int *bt1,int *bt2)
//从1~x个结点选择parent结点为0,权重最小的两个结点
{
int i;
HuffmanTree *ht1,*ht2,*t;
ht1=ht2=NULL; //初始化两个结点为空
for(i=1;i<=n;++i) //循环处理1~n个结点(包括叶结点和非叶结点)
{
if(!ht[i].parent) //父结点为空(结点的parent=0)
{
if(ht1==NULL) //结点指针1为空
{
ht1=ht+i; //指向第i个结点
continue; //继续循环
}
if(ht2==NULL) //结点指针2为空
{
ht2=ht+i; //指向第i个结点
if(ht1->weight>ht2->weight) //比较两个结点的权重,使ht1指向的结点权重小
{
t=ht2;
ht2=ht1;
ht1=t;
}
continue; //继续循环
}
if(ht1 && ht2) //若ht1、ht2两个指针都有效
{
if(ht[i].weight<=ht1->weight) //第i个结点权重小于ht1指向的结点
{
ht2=ht1; //ht2保存ht1,因为这时ht1指向的结点成为第2小的
ht1=ht+i; //ht1指向第i个结点
}else if(ht[i].weight<ht2->weight){ //若第i个结点权重小于ht2指向的结点
ht2=ht+i; //ht2指向第i个结点
}
}
}
}
if(ht1>ht2){ //增加比较,使二叉树左侧为叶结点
*bt2=ht1-ht;
*bt1=ht2-ht;
}else{
*bt1=ht1-ht;
*bt2=ht2-ht;
}
}
void CreateTree(HuffmanTree *ht,int n,int *w)
{
int i,m=2*n-1;//总的节点数
int bt1,bt2; //二叉树结点序与
if(n<=1) return ; //只有一个结点,无法创建
for(i=1;i<=n;++i) //初始化叶结点
{
ht[i].weight=w[i-1];
ht[i].parent=0;
ht[i].left=0;
ht[i].right=0;
}
for(;i<=m;++i)//初始化后续结点
{
ht[i].weight=0;
ht[i].parent=0;
ht[i].left=0;
ht[i].right=0;
}
for(i=n+1;i<=m;++i) //逐个计算非叶结点,创建Huffman树
{
SelectNode(ht,i-1,&bt1,&bt2); //从1~i-1个结点选择parent结点为0,权重最小的两个结点
ht[bt1].parent=i;
ht[bt2].parent=i;
ht[i].left=bt1;
ht[i].right=bt2;
ht[i].weight=ht[bt1].weight+ht[bt2].weight;
}
}
void HuffmanCoding(HuffmanTree *ht,int n,HuffmanCode *hc)//,char *letters)
{
char *cd;
int start,i;
int current,parent;
cd=(char*)malloc(sizeof(char)*n);//用来临时存放一个字符的编码结果
cd[n-1]=‘\0‘; //设置字符串结束标志
for(i=1;i<=n;i++)
{
start=n-1;
current=i;
parent=ht[current].parent;//获取当前结点的父结点
while(parent) //父结点不为空
{
if(current==ht[parent].left)//若该结点是父结点的左子树
cd[--start]=‘0‘; //编码为0
else //若结点是父结点的右子树
cd[--start]=‘1‘; //编码为1
current=parent; //设置当前结点指向父结点
parent=ht[parent].parent; //获取当前结点的父结点序号
}
hc[i-1]=(char*)malloc(sizeof(char)*(n-start));//分配保存编码的内存
strcpy(hc[i-1],&cd[start]); //复制生成的编码
}
free(cd); //释放编码占用的内存
}
void Encode(HuffmanCode *hc,char *alphabet,char *str,char *code)
//将一个字符串转换为Huffman编码
//hc为Huffman编码表 ,alphabet为对应的字母表,str为需要转换的字符串,code返回转换的结果
{
int len=0,i=0,j;
code[0]=‘\0‘;
while(str[i])
{
j=0;
while(alphabet[j]!=str[i])
j++;
strcpy(code+len,hc[j]); //将对应字母的Huffman编码复制到code指定位置
len=len+strlen(hc[j]); //累加字符串长度
i++;
}
code[len]=‘\0‘;
}
void Decode(HuffmanTree *ht,int m,char *code,char *alphabet,char *decode)
//将一个Huffman编码组成的字符串转换为明文字符串
//ht为Huffman二叉树,m为字符数量,alphabet为对应的字母表,str为需要转换的字符串,decode返回转换的结果
{
int position=0,i,j=0;
m=2*m-1;
while(code[position]) //字符串未结束
{
for(i=m;ht[i].left && ht[i].right; position++) //在Huffman树中查找左右子树为空 ,以构造一个Huffman编码
{
if(code[position]==‘0‘) //编码位为0
i=ht[i].left; //处理左子树
else //编译位为 1
i=ht[i].right; //处理右子树
}
decode[j]=alphabet[i-1]; //得到一个字母
j++;//处理下一字符
}
decode[j]=‘\0‘; //字符串结尾
}
int main()
{
int i,n=4,m;
char test[256];
char code[100],code1[100];
char *alphabet[]={“mov”,”add”,”push”,”pop”}; //假设四个常用的指令mov、add、push、pop
int w[]={10,9,8,4} ;//四个指令的编码
HuffmanTree *ht;
HuffmanCode *hc;
m=2*n-1;
ht=(HuffmanTree *)malloc((m+1)*sizeof(HuffmanTree)); //申请内存,保存赫夫曼树
if(!ht)
{
printf("内存分配失败!\n");
exit(0);
}
hc=(HuffmanCode *)malloc(n*sizeof(char*));
if(!hc)
{
printf("内存分配失败!\n");
exit(0);
}
CreateTree(ht,n,w); //创建赫夫曼树
HuffmanCoding(ht,n,hc); //根据赫夫曼树生成赫夫曼编码
for(i=1;i<=n;i++) //循环输出赫夫曼编码
printf("指令:%c,权重:%d,编码为 %s\n",alphabet[i-1],ht[i].weight,hc[i-1]);
printf(“请输入指令序列\n”);
scanf(“%s”,test);
Encode(hc,alphabet,test,code); //根据赫夫曼编码生成编码字符串
printf("\n指令:\n%s\n转换后为:\n%s\n",test,code);
Decode(ht,n,code,alphabet,code1); //根据编码字符串生成解码后的字符串
printf("\n编码:\n%s\n转换后为:\n%s\n",code,code1);
getchar();
return 0;
}
需要注意的是这里的指令使用只是随便设想的一个权值,实际需要做统计。而且这里的编码也并不是那么完美,这就引出了下一个算法与设计。
2.3.2朴素的自然编码方法
2.3.3操作系统内核的磁盘管理
操作系统需要对磁盘具有很好的管理能力以高效的文件组织手段,比如MS-DOS和WIndows的盘符划分法和Linux的树状倒装法。不管是什么样的方法都需要在磁盘中查找指定的文件。如果采用逐个比较法,那么时间复杂度可达O(n)。这种线性复杂度在文件量足够大时积累起来的时间将会很大。操作系统都会有专门的处理这样的问题方法。方法之一是建立有序数据库,然后按照一定的方法查找。这种情况下使用二分查找法就不合适了,因为二分查找法为了实现随机访问的目的常采用列表来储存,这样就是得删除或添加一个项目变得很慢,这对操作系统来说是不合算的,而二叉搜索树则是一种结合了折半查找优点的搜索策略。
在磁盘中查找已排序的文件(只有文件名与文件类型)可以这样来尝试:
程序清单:tree.c
/* tree.h -- binary search tree */
/* no duplicate items are allowed in this tree */
#ifndef _TREE_H_
#define _TREE_H_
#include <stdbool.h>
/* redefine Item as appropriate */
typedef struct item
{
char filename[20];
char filekind[20];
} Item;
#define MAXITEMS 10
typedef struct node
{
Item item;
struct node * left; /* pointer to right branch */
struct node * right; /* pointer to left branch */
} Node;
typedef struct tree
{
Node * root; /* pointer to root of tree */
int size; /* number of items in tree */
} Tree;
/* function prototypes */
/* operation: initialize a tree to empty */
/* preconditions: ptree points to a tree */
/* postconditions: the tree is initialized to empty */
void InitializeTree(Tree * ptree);
/* operation: determine if tree is empty */
/* preconditions: ptree points to a tree */
/* postconditions: function returns true if tree is */
/* empty and returns false otherwise */
bool TreeIsEmpty(const Tree * ptree);
/* operation: determine if tree is full */
/* preconditions: ptree points to a tree */
/* postconditions: function returns true if tree is */
/* full and returns false otherwise */
bool TreeIsFull(const Tree * ptree);
/* operation: determine number of items in tree */
/* preconditions: ptree points to a tree */
/* postconditions: function returns number of items in */
/* tree */
int TreeItemCount(const Tree * ptree);
/* operation: add an item to a tree */
/* preconditions: pi is address of item to be added */
/* ptree points to an initialized tree */
/* postconditions: if possible, function adds item to */
/* tree and returns true; otherwise, */
/* the function returns false */
bool AddItem(const Item * pi, Tree * ptree);
/* operation: find an item in a tree */
/* preconditions: pi points to an item */
/* ptree points to an initialized tree */
/* postconditions: function returns true if item is in */
/* tree and returns false otherwise */
bool InTree(const Item * pi, const Tree * ptree);
/* operation: delete an item from a tree */
/* preconditions: pi is address of item to be deleted */
/* ptree points to an initialized tree */
/* postconditions: if possible, function deletes item */
/* from tree and returns true; */
/* otherwise, the function returns false*/
bool DeleteItem(const Item * pi, Tree * ptree);
/* operation: apply a function to each item in */
/* the tree */
/* preconditions: ptree points to a tree */
/* pfun points to a function that takes*/
/* an Item argument and has no return */
/* value */
/* postcondition: the function pointed to by pfun is */
/* executed once for each item in tree */
void Traverse (const Tree * ptree, void (* pfun)(Item item));
/* operation: delete everything from a tree */
/* preconditions: ptree points to an initialized tree */
/* postconditions: tree is empty */
void DeleteAll(Tree * ptree);
#endif
程序清单:tree.c
/* tree.c -- tree support functions */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "tree.h"
/* local data type */
typedef struct pair {
Node * parent;
Node * child;
} Pair;
/* protototypes for local functions */
static Node * MakeNode(const Item * pi);
static bool ToLeft(const Item * i1, const Item * i2);
static bool ToRight(const Item * i1, const Item * i2);
static void AddNode (Node * new_node, Node * root);
static void InOrder(const Node * root, void (* pfun)(Item item));
static Pair SeekItem(const Item * pi, const Tree * ptree);
static void DeleteNode(Node **ptr);
static void DeleteAllNodes(Node * ptr);
/* function definitions */
void InitializeTree(Tree * ptree)
{
ptree->root = NULL;
ptree->size = 0;
}
bool TreeIsEmpty(const Tree * ptree)
{
if (ptree->root == NULL)
return true;
else
return false;
}
bool TreeIsFull(const Tree * ptree)
{
if (ptree->size == MAXITEMS)
return true;
else
return false;
}
int TreeItemCount(const Tree * ptree)
{
return ptree->size;
}
bool AddItem(const Item * pi, Tree * ptree)
{
Node * new_node;
if (TreeIsFull(ptree))
{
fprintf(stderr,"Tree is full\n");
return false; /* early return */
}
if (SeekItem(pi, ptree).child != NULL)
{
fprintf(stderr, "Attempted to add duplicate item\n");
return false; /* early return */
}
new_node = MakeNode(pi); /* points to new node */
if (new_node == NULL)
{
fprintf(stderr, "Couldn‘t create node\n");
return false; /* early return */
}
/* succeeded in creating a new node */
ptree->size++;
if (ptree->root == NULL) /* case 1: tree is empty */
ptree->root = new_node; /* new node is tree root */
else /* case 2: not empty */
AddNode(new_node,ptree->root); /* add node to tree */
return true; /* successful return */
}
bool InTree(const Item * pi, const Tree * ptree)
{
return (SeekItem(pi, ptree).child == NULL) ? false : true;
}
bool DeleteItem(const Item * pi, Tree * ptree)
{
Pair look;
look = SeekItem(pi, ptree);
if (look.child == NULL)
return false;
if (look.parent == NULL) /* delete root item */
DeleteNode(&ptree->root);
else if (look.parent->left == look.child)
DeleteNode(&look.parent->left);
else
DeleteNode(&look.parent->right);
ptree->size--;
return true;
}
void Traverse (const Tree * ptree, void (* pfun)(Item item))
{
if (ptree != NULL)
InOrder(ptree->root, pfun);
}
void DeleteAll(Tree * ptree)
{
if (ptree != NULL)
DeleteAllNodes(ptree->root);
ptree->root = NULL;
ptree->size = 0;
}
/* local functions */
static void InOrder(const Node * root, void (* pfun)(Item item))
{
if (root != NULL)
{
InOrder(root->left, pfun);
(*pfun)(root->item);
InOrder(root->right, pfun);
}
}
static void DeleteAllNodes(Node * root)
{
Node * pright;
if (root != NULL)
{
pright = root->right;
DeleteAllNodes(root->left);
free(root);
DeleteAllNodes(pright);
}
}
static void AddNode (Node * new_node, Node * root)
{
if (ToLeft(&new_node->item, &root->item))
{
if (root->left == NULL) /* empty subtree */
root->left = new_node; /* so add node here */
else
AddNode(new_node, root->left);/* else process subtree*/
}
else if (ToRight(&new_node->item, &root->item))
{
if (root->right == NULL)
root->right = new_node;
else
AddNode(new_node, root->right);
}
else /* should be no duplicates */
{
fprintf(stderr,"location error in AddNode()\n");
exit(1);
}
}
static bool ToLeft(const Item * i1, const Item * i2)
{
int comp1;
if ((comp1 = strcmp(i1->petname, i2->petname)) < 0)
return true;
else if (comp1 == 0 &&
strcmp(i1->petkind, i2->petkind) < 0 )
return true;
else
return false;
}
static bool ToRight(const Item * i1, const Item * i2)
{
int comp1;
if ((comp1 = strcmp(i1->petname, i2->petname)) > 0)
return true;
else if (comp1 == 0 &&
strcmp(i1->petkind, i2->petkind) > 0 )
return true;
else
return false;
}
static Node * MakeNode(const Item * pi)
{
Node * new_node;
new_node = (Node *) malloc(sizeof(Node));
if (new_node != NULL)
{
new_node->item = *pi;
new_node->left = NULL;
new_node->right = NULL;
}
return new_node;
}
static Pair SeekItem(const Item * pi, const Tree * ptree)
{
Pair look;
look.parent = NULL;
look.child = ptree->root;
if (look.child == NULL)
return look; /* early return */
while (look.child != NULL)
{
if (ToLeft(pi, &(look.child->item)))
{
look.parent = look.child;
look.child = look.child->left;
}
else if (ToRight(pi, &(look.child->item)))
{
look.parent = look.child;
look.child = look.child->right;
}
else /* must be same if not to left or right */
break; /* look.child is address of node with item */
}
return look; /* successful return */
}
static void DeleteNode(Node **ptr)
/* ptr is address of parent member pointing to target node */
{
Node * temp;
puts((*ptr)->item.petname);
if ( (*ptr)->left == NULL)
{
temp = *ptr;
*ptr = (*ptr)->right;
free(temp);
}
else if ( (*ptr)->right == NULL)
{
temp = *ptr;
*ptr = (*ptr)->left;
free(temp);
}
else /* deleted node has two children */
{
/* find where to reattach right subtree */
for (temp = (*ptr)->left; temp->right != NULL;
temp = temp->right)
continue;
temp->right = (*ptr)->right;
temp = *ptr;
*ptr =(*ptr)->left;
free(temp);
}
}
当然这些都还只是操作系统文件系统的一小部分,而且这种方法还有可能出现搜索不平衡的现象,使得搜索效率大大降低。
2.3.4操作系统对硬件在数据结构上的操作
本来是没有什么“数据结构的”,硬件本身只是一堆离散量,但是有些硬件结构却可以被看做是“数据结构”,这种情况汇编语言程序员十分熟悉,那就是80x86(或其后续产品)芯片中的SP、SS栈寄存器。
比如要使用80286、80386和80486 CPU的扩充功能,软件必须知道它运行在其中一个芯片上,并且要知道在什么芯片上运行。有三种解决确定当前芯片问题的方法。第一种解决方法是基于80386和80486在加电时用DH寄存器的10(标志)字节(3或4)来区分,它是什么芯片。第二种方法是询问用户,他使用的是哪种芯片。第三种方法是从已知的芯片之间的差异来推断出所使用的芯片是哪一种。第一种方法必须包含有已重编程的BIOS芯片,它超出了大多数程序员的能力,对用户而言则更是苛刻。它也不能区分80286到8086之间的芯片。第二种方法假设用户知道其机器是什么CPU;在很多情况下,这种假设是无效的。第三种方法需要做的工作比第二种多,但比第一种少,并且是可靠的。
有一种通过软件测试的方法就可以轻松地解决这个问题,它运用的原理是:在8088中,一个值被推进堆栈后,堆栈指针在写入堆栈之前减少。从80286开始,则是先写值,然后堆栈指针再减少。通过压入堆栈指针,可以检查写入堆栈的值,来确定堆栈指针是在值写入之前还是在之后减少。如果要确定当前使用的是80286、80386或80486,可以尝试设置80286没有使用的标志寄存器位、80286不允许使用这些位,但80386和80486却允许。如果可以改变这些位的值,则是80386或80486。同样的方法也可用来区别80386和80486。注意,使用66h大小的覆盖前缀去强制32位标志寄存器被压入和弹出。
程序清单:ex2_3_4_1.asm
; Determines whether the CPU in use is an 8088/8086, an 80286, an
;80386, or an 80486.Print the CPU and return an errorlevel Of 0,
;2, 3,or4 for 8088/8086, 80286,80386,or 80486, respectively.
.model small
.Stack
. data
say86 db "8088 or 8086$"
say286 db " 80286$"
say386 db "80386$"
say486 db "80486$"
.code
.startup
checkcpu proc
; The first step is to determine whether the chip is an 8088/8086.
;The key difference is based-on what the CPU does when it executes
;the PUSH instruction.The 8088/8086 decrements the stack
; pointer first and then writes the saved value to the stack. The
; 80286, 80386, and 80486 write the value to the stack and then
; decrement the stack pointer. Thus, when SP is pushed and the
; pushed value is popped off, the value popped off equals the
; current stack pointer, unless the chip is an 8088 or 8086.
push sp
pop ax
cmp ax,sp ; if values are not the same,
jne is_86 , it is 8088/8086
; The second step is to detecmine whether the chip is an 80286.
; The key diffecence is the IOPL bits in the flags cegister;
; the 80386 and 80486 have them, and the 80286 does not. The
; 80286 does not let them be Set; the 80386 and 80486 do.
pushf
pop ax ; get flags in ax .
or ax,03000h ; set IOPL bits
push ax ; stuff them back
popf , pop flags--this is where the 80286
; will put them back the way they were
pushf
pop ax ; get flags in ax
test ax,03000h ; if the IOPL bits are reset, the chip
jz iS_286 ; is an 80286
; The third Step is to determine Whether the chip iS an 80386.
; The key difference iS the alignment check bit in the flags
; register; the 80486 has one, and the 80386 does not. The 80386
; does not let you set that bit, but the 80486 does.
db 66h ; (32 bit instruction)
pushf
pop ax ; read low word of flags
and ax,00FFFh ; clear IOPL bits- -level zero
pop dx , read high word
or dx,00004h ; set alignment check bit
push dx ; push flags back
push ax
db 66h ; (32-bit instruction)
popf ; pop flags register- -this is where
; the 80386 undoes your work
db 66h ; (32-bit instruction)
pushf ; push flags back
pop ax ; read what you did
pop dx ; find out if the CPU reset the
test dx,4 ; alignment check bit
jz iS_386 ; if it did, the chip iS an 80386
is_486:
mov dx,offset say486
mov al,4 ; errorlevel 4
jmp sayso
is_386:
mov dx, offset say386
mov al,3 ; errorlevel 3
jmp sayso
iS_286:
mov dx,offset say286
mov al,2 , errorlevel 2
jmp saySo
is_86:
mov dx ,offset say86
mov al,0 ; errorlevel 0
sayso:
push ax ;save errorlevel
mov ah,9 ;call DOS wPite string function
int 21h
pop ax ;retrieve errorlevel
mov ah , 04Ch ; terminate process with return code
int 21h
checkcpu endp
end
同样的方法可以用来检测数学系处理器:
程序清单:ex2_3_4_2.asm
;Determine the type Of math coprocessor(fpu)installed.
.model small
.stack
.data
ScratCh dW (?) ;have the math chip store data here
Saynone db " NO math coprocessor$"
say87 db " 8087$"
say287 db " 80287$"
Say387 db " 80387$"
.Code
.startup
checkfpu proc
fninit ; initialize the fpu
mov scratch , 055AAh;the fpu will not write this
fnstsw Scratch ; have the fpu write its status word
cmp byte ptr scratch,0;check lSb--it shbuld be 0
jne no math ; if not, retyrn
fnstcw scratch ; now have the fpu write its control word
mov ax , Sccatch ; read the value
and ax ,0103Fh ;mask expected bits
cmp ax ,0003Fh ; this is what you should see
jne no_math ; if different , no math chip
;Now that you know that you have an fpu , which one is it?
and Scratch 0FF7Fh ; clear interrupt bit
fldcw scratch ; load the control word
fdisi ; disable interrupts
fstcw Scratch ; write the control word back
test Sccatch , 00080h ; any effect on the word?
jnz found_8087 ; if so , it is an 8087
;The chip is not an 8087 , so it is an 80287 or 80387.
finit ;reinitialize the chip
fld1 ; push +1.0 onto the chip‘s stack
fldz ; push 0.0 onto the chip‘s stack
fdiv ; produce positive infinity
fld st ; produce negative infinity
fcompp ;compare
fstsw scratch ;write the status word
mov ax , scratch
上面的例子都还只是基于芯片级别的操作,实际上,在大多数用户更为熟悉的GUI上,有些算法也是在默默发挥着巨大的作用。比如显卡就常常需要计算点的位置、线与线相交以便完成颜色的填充或者是图形的变换。
比如射线和三角形的相交检测是游戏程序设计中一个常见的问题,最典型的应用就是拾取(Picking),这个方法也是DirectX中采用的方法,该方法速度快,而且存储空间少。
程序清单:ex2_3_4_Demo.cpp
// Determine whether a ray intersect with a triangle
// Parameters
// orig: origin of the ray
// dir: direction of the ray
// v0, v1, v2: vertices of triangle
// t(out): weight of the intersection for the ray
// u(out), v(out): barycentric coordinate of intersection
bool IntersectTriangle(const Vector3& orig, const Vector3& dir,
Vector3& v0, Vector3& v1, Vector3& v2,
float* t, float* u, float* v)
{
// E1
Vector3 E1 = v1 - v0;
// E2
Vector3 E2 = v2 - v0;
// P
Vector3 P = dir.Cross(E2);
// determinant
float det = E1.Dot(P);
// keep det > 0, modify T accordingly
Vector3 T;
if( det >0 )
{
T = orig - v0;
}
else
{
T = v0 - orig;
det = -det;
}
// If determinant is near zero, ray lies in plane of triangle
if( det < 0.0001f )
return false;
// Calculate u and make sure u <= 1
*u = T.Dot(P);
if( *u < 0.0f || *u > det )
return false;
// Q
Vector3 Q = T.Cross(E1);
// Calculate v and make sure u + v <= 1
*v = dir.Dot(Q);
if( *v < 0.0f || *u + *v > det )
return false;
// Calculate t, scale parameters, ray intersects triangle
*t = E2.Dot(Q);
float fInvDet = 1.0f / det;
*t *= fInvDet;
*u *= fInvDet;
*v *= fInvDet;
return true;
}
上面的代码涉及到很多数学的向量知识,不妨从简单的情形开始考虑。判断“二维图形中两个多边形是否相交”,因为多边形都可以分成若干个三角形,所以问题再次归结为如何判断三角形是否相交,考虑到空间中的情形可以进一步将问题简化为判断“多边形A是否存在一个顶点在由多边形B划分成的三角形内部”,因此这里先从判断点是否在三角形内部开始。
下面全部假定△ABC的三个顶点为A(x1,y1),B(x2,y2),C(x3,y3),需要判断的点是P(x0,y0)。
最直接明了的思想是根据海伦公式算出△ABC、△PAB、△PAC、△PBC的面积(由于采用向量法,这里直接计算模是非常方便的),如果S△ABC=S△PAB+S△PAC+S△PBC那么点一定在三角形的内部。
A
P
C
B
代码的实现也很简单:
程序清单:ex2_3_4_5.c
/*通过海伦公式计算三角形面积从而判断点是否在三角形内部*/
#include<stdio.h>
#intlude<math.h>
/*定义结构类型“点”*/
typdef struct{
int x,y;
} Dot;
double helen(Dot , Dot , Dot);/*根据三顶点计算三角形面积的函数原型*/
void getDot(*Dot);/*获得点坐标的函数原型*/
int main(void)
{
Dot A,B,C,P;
printf(“Enter the coordiantes of A,B,C,P”);
getDot(&A);
getDot(&B);
getDot(&C);
getDot(&P);
if (fabs(helen(A,B,C)-(helen(A,B,P)+
helen(A,C,P)+helen(B,C,P)))<1e-16)/*double的精度限制*/
{
printf(“P is inside of triangle ABC\n”);
}
else
{
printf(“P is not inside of triangle ABC\n”);
}
return 0;
}
void getDot(Dot * P)/*获取点的坐标*/
{
scanf(“(%d,%d)”,&P.x,&P.y);
while(getchar()!=’\n’)/*当前行有剩余处理*/
continue;
}
double helen(Dot A,Dot B,Dot C)/*海伦公式*/
{
double a,b,c,p;
a=sqrt(sqr(B.x-C.x)+sqr(B.y-C.y));
b=sqrt(sqr(A.x-C.x)+sqr(A.y-C.y));
c=sqrt(sqr(A.x-B.x)+sqr(A.y-B.y));
p=(a+b+c)/2;
return sqrt(p*(p-a)*(p-b)*(p-c));
}
这样做一方面很麻烦一方面涉及到了浮点数,难免会有误差!
下面用到了向量的方法,准确的说是使用了余弦公式。
A
P
B C
∠APB+∠BPC+∠APC=π
程序清单:ex2_3_4_6.c
/*通过余弦公式计算内角和来判断点是否在三角形内部,若是则和为360度*/
#include<stdio.h>
#include<math.h>
#define PI 3.141492653589793
#define ES 1e-16
/*定义结构类型“点”*/
typdef struct{
int x,y;
} Dot;
/*定义结构类型“向量”*/
typedef struct{
int x,y,z;
} Vector;
void getDot(*Dot);/*获得点坐标的函数原型*/
Vector operate(Dot,Dot);/*生成向量的函数原型*/
double dotMul(Vector a,Vector b);/*向量点乘的函数原型*/
duoble getModle(Vector a,Vector b);/*得到向量的模*/
double calAngle(Dot,Dot,Dot);/*计算夹角的函数原型*/
int main(void)
{
Dot A,B,C,P;
printf(“Enter the coordiantes of A,B,C,P”);
getDot(&A);
getDot(&B);
getDot(&C);
getDot(&P);
if (fabs(calAngla(A,B,P)+calAngle(A,C,P)+calAngle(B,C,P)-2*PI)>=ES)
/*内部的角之和为360*/
{
printf(“P is inside of triangle ABC\n”);
}
else
{
printf(“P is not inside of triangle ABC\n”);
}
return 0;
}
void getDot(Dot * P)/*获取点的坐标*/
{
scanf(“(%d,%d)”,&P.x,&P.y);
while(getchar()!=’\n’)/*当前行有剩余处理*/
continue;
}
Vector operate(Dot A,Dot B)/*计算向量*/
{
Vector AB;
AB.x=B.x-A.x;
AB.y=B.y-A.y;
AB.z=B.z-A.z;
return AB;
}
int dotMul(Vector a,Vector b)/*向量点乘*/
{
int ab;
ab=a.x*b.x+a.y*b.y+a.z*b.z;
return ab;
}
void calAngle(Dot A,Dot B,Dot P)/*计算夹角*/
{
double angle;
double tA1,tA2,tB1,tB2;/*保存距离的临时变量*/
tA1=A.x-P.x;
tA2=A.y-P.y;
tB1=B.x-P.x;
tB2=B.y-P.y;
angle=dotMul(operate(A,P),operate(B,P))/(sqrt(tA1*tA1+tA2*tA2)+
sqrt(tB1*tB1+tB2*tB2));
return angle;
}
下面的思路充分运用了向量的特性和三角形的性质,运算更快。它的原理是:如果一个点P在三角形内部,那么向量PA与向量AB的点乘与向量CB与AB的点乘的叉乘应该大于0且各边如此!
程序清单:ex2_3_4_7.c
/*
This program is designed to determine
whether a dot is inner a triangle using
vector.
*/
#include <stdio.h>
typedef struct{
int x,y,z;
} Dot;
typedef struct{
int x,y,z;
} Vector;
void getDot(Dot * );
Vector operate(Dot,Dot);
Vector crossMul(Vector,Vector);
int pointMul(Vector,Vector);
int sameSide(Dot,Dot,Dot,Dot);
int isInTriangle(Dot,Dot,Dot,Dot);
int main(void)
{
Dot A,B,C,P;
printf("Enter vertex coordinates of A\n");
getDot(&A);
printf("Enter vertex coordinates of B\n");
getDot(&B);
printf("Enter vertex coordinate of C\n");
getDot(&C);
printf("Enter vertex coordinate of P\n");
getDot(&P);
if (isInTriangle(A,B,C,P))
{
printf("P is inner triangle ABC\n");
}
else
{
printf("P isn‘t inner triangle ABC\n");
}
return 0;
}
void getDot(Dot * P)
{
scanf("(%d,%d,%d)",&P->x,&P->y,&P->z);
while(getchar()!=‘\n‘)
continue;
}
Vector operate(Dot A,Dot B)
{
Vector AB;
AB.x=B.x-A.x;
AB.y=B.y-A.y;
AB.z=B.z-A.z;
return AB;
}
Vector crossMul(Vector a,Vector b)
{
Vector c;
c.x=a.y*b.z-a.z*b.y;
c.y=a.z*b.x-a.x*b.z;
c.z=a.x*b.y-a.y*b.x;
return c;
}
int pointMul(Vector a,Vector b)
{
int ab;
ab=a.x*b.x+a.y*b.y+a.z*b.z;
return ab;
}
int sameSide(Dot A,Dot B,Dot C,Dot P)
{
Vector AB,AC,AP;
Vector v1,v2;
int flag;
AB=operate(A,B);
AC=operate(A,C);
AP=operate(A,P);
v1=crossMul(AB,AC);
v2=crossMul(AB,AP);
flag=pointMul(v1,v2);
return flag>=0;
}
int isInTriangle(Dot A,Dot B,Dot C,Dot P)
{
return (sameSide(A,B,C,P)&&sameSide(B,A,C,P)&&sameSide(C,A,B,P));
}
最后一种实际上也是
2.3.5基于51单片机的“实时系统”
51单片机这样的8位单片机本身是没有操作系统的必要的(如果需要更复杂的控制会去选用AVR或者ARM),但是在有些操作中这种系统还是很有用的。下面这个系统是整合了一些实用程序的工具程序,直接用Keil编译即可。
程序清单:ex2_3_5_1.c
#include <regx52.h>
#define MAX_TASKS 5
typedef struct os_task_control_table {
unsigned char os_task_wait_tick;
unsigned char os_task_stack_top;
}TCB;
volatile unsigned char int_count;
volatile unsigned char os_en_cr_count;
#define enter_int() EA=0;int_count++;
#define os_enter_critical() EA=0;os_en_cr_count++;
#define os_exit_critical() if(os_en_cr_count>=1){os_en_cr_count--;if(os_en_cr_count==0)EA=1;}
unsigned char code os_map_tbl[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
volatile unsigned char os_task_int_tbl;
idata volatile TCB os_tcb[MAX_TASKS];
volatile unsigned char os_task_running_id;
volatile unsigned char os_task_rdy_tbl;
unsigned char idata os_task_stack[MAX_TASKS][20];
void os_init(void);
void os_task_create(unsigned char task_id ,unsigned int task_point,unsigned char stack_point);
void os_delay(unsigned char ticks);
void os_start(void);
void os_task_switch(void);
void exit_int(void);
void os_init(void) {
EA = 0;
ET2 = 1;
T2CON = 0X00;
T2MOD = 0X00;
RCAP2H = 0x0D8;
RCAP2L = 0x0F0;
os_task_rdy_tbl = 0;
os_task_int_tbl = 0xff;
int_count = 0;
os_en_cr_count = 0;
}
void os_task_create(unsigned char task_id ,unsigned int task_point,unsigned char stack_point) {
os_enter_critical();
((unsigned char idata *)stack_point)[0] = task_point;
((unsigned char idata *)stack_point)[1] = task_point>>8;
os_tcb[task_id].os_task_stack_top = stack_point+14;
os_task_rdy_tbl |= os_map_tbl[task_id];
os_tcb[task_id].os_task_wait_tick = 0;
os_exit_critical();
}
void os_delay(unsigned char ticks) {
os_enter_critical();
os_tcb[os_task_running_id].os_task_wait_tick = ticks;
os_task_rdy_tbl &= ~os_map_tbl[os_task_running_id];
os_exit_critical();
os_task_switch();
}
void os_start(void) {
os_task_running_id = 0;
os_tcb[os_task_running_id].os_task_stack_top -= 13;
EA = 1;
SP = os_tcb[os_task_running_id].os_task_stack_top;
TR2 = 1;
}
void os_task_switch(void) {
unsigned char i;
EA = 0;
os_tcb[os_task_running_id].os_task_stack_top = SP;
os_task_int_tbl &= ~os_map_tbl[os_task_running_id];
for(i=0; i<MAX_TASKS; i++) {
if(os_task_rdy_tbl&os_map_tbl[i]) {
break;
}
}
os_task_running_id = i;
SP = os_tcb[os_task_running_id].os_task_stack_top;
if(os_task_int_tbl&os_map_tbl[os_task_running_id]) {
__asm POP 7
__asm POP 6 //恢复任务寄存器
__asm POP 5
__asm POP 4
__asm POP 3
__asm POP 2
__asm POP 1
__asm POP 0
__asm POP PSW
__asm POP DPL
__asm POP DPH
__asm POP B
__asm POP ACC
}
EA = 1;
__asm RETI
}
void exit_int(void) {
unsigned char i;
SP -= 2;
if(--int_count == 0) {
os_tcb[os_task_running_id].os_task_stack_top = SP;
os_task_int_tbl |= os_map_tbl[os_task_running_id];
for(i=0; i<MAX_TASKS; i++) {
if(os_task_rdy_tbl&os_map_tbl[i]) {
break;
}
}
os_task_running_id = i;
SP = os_tcb[os_task_running_id].os_task_stack_top;
if(os_task_int_tbl&os_map_tbl[os_task_running_id]) {
__asm POP 7
__asm POP 6 //恢复任务寄存器
__asm POP 5
__asm POP 4
__asm POP 3
__asm POP 2
__asm POP 1
__asm POP 0
__asm POP PSW
__asm POP DPL
__asm POP DPH
__asm POP B
__asm POP ACC
}
EA = 1;
__asm RETI
}
__asm POP 7
__asm POP 6 //恢复任务寄存器
__asm POP 5
__asm POP 4
__asm POP 3
__asm POP 2
__asm POP 1
__asm POP 0
__asm POP PSW
__asm POP DPL
__asm POP DPH
__asm POP B
__asm POP ACC
EA=1;
__asm RETI
}
void timer2_isr(void) interrupt 5 {
unsigned char i;
TF2=0;
enter_int();
for(i=0; i<MAX_TASKS; i++) {
if(os_tcb[i].os_task_wait_tick) {
os_tcb[i].os_task_wait_tick--;
if(os_tcb[i].os_task_wait_tick == 0) {
os_task_rdy_tbl |= os_map_tbl[i];
}
}
}
exit_int();
}
void task_0(void) {
while(1) {
}
}
sbit seg2 = P2^5;
sbit seg3 = P2^6;
sbit seg4 = P2^7;
void delay_ms(unsigned int xms){
unsigned int x,y;
for(x=xms; x>0; x--)
for(y=248; y>0; y--);
}
unsigned char code table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x40,0};
void task_1(void) {
unsigned char gw,sw,bw;
while(1) {
bw = os_tcb[2].os_task_wait_tick/100;
sw = os_tcb[2].os_task_wait_tick%100/10;
gw = os_tcb[2].os_task_wait_tick%10;
P0 = table[bw];
seg2=0;
delay_ms(3);
seg2=1;
P0 = table[sw];
seg3=0;
delay_ms(3);
seg3=1;
P0 = table[gw];
seg4=0;
delay_ms(3);
seg4=1;
}
}
void task_2(void) {
unsigned char i;
while(1) {
i++;
P3 = 0x01<<(i%8);
os_delay(200);
}
}
void task_3(void) {
unsigned char i;
while(1) {
i++;
//P2 = 0x01<<(i%8);
os_delay(7);
}
}
void task_4(void) {
unsigned char i;
while(1) {
i++;
P1 = 0x01<<(i%8);
os_delay(10);
}
}
void main(void) {
os_init();
os_task_create(4,(unsigned int)&task_0,(unsigned char)os_task_stack[4]);
os_task_create(3,(unsigned int)&task_1,(unsigned char)os_task_stack[3]);
os_task_create(2,(unsigned int)&task_2,(unsigned char)os_task_stack[2]);
os_task_create(1,(unsigned int)&task_3,(unsigned char)os_task_stack[1]);
os_task_create(0,(unsigned int)&task_4,(unsigned char)os_task_stack[0]);
os_start();
}
当然这里依然有许多可以改进的地方。STC89C52只有8KM ROM和512B RAM,比起现在上百吉的内存,51单片机就像没有内存一样。要想让程序快速可靠地运行,就得想各种方法,数据结构上的,算法上的,技巧上的。
3.1研究目标完成情况及反思
通过研究与学习,能够完成了基本的词法分析器和操作系统内核的一些高效运用数据结构与算法设计与实现,并由此展开涉及到了一些相关领域的应用。但是仍然有一些论述不彻底的地方,比如关于搜索树和栈都有系统研究下去的必要,况且问题的前提是“数据是有序的”,那么怎样高效的实现有序显然不是简简单单的排序就能实现的,而且研究结束时并没能完整的实现一个编译器或操作系统内核(51单片机的那个严格意义上算不上系统),这也是遗憾。
3.2感想与计划
很多程序员过度的依赖MFC、COM、.NET等等技术,诚然“不重复造轮子”是件明智的事,如果每次写一个程序都要自己从底层来做那就别想让人活了,什么图形库、算法库都得自己写,每开发一个项目就要开发一种新语言……这肯定是不可能的,本研究的主要内容是“基于编译器和操作系统内核的算法设计与实现”,但并不意味着每个程序员都有必要写编译器与操作系统内核,而是意味着程序员都应该具有一种“思想”,也就是高效的、快速的追求最好的方法的意识和能力,并借助编译器与操作系统来了解底层,正如Donald Knuth所说的:“很多人认为推崇机器语言根本就是我所犯的最大错误,不过我坚持认为除非你了解了各种底层细节,否则否则根本不可能为态度认真的程序员写书。”
【1】于渊,《ORANGE’S 一个操作系统的实现》,电子工业出版社,2009
【2】Kip R. Irvine,Assembly Language for Intel-Based Computers,Fifth Edition,电子工业 出版社,2013
【3】余立功,《ACM/ICPC算法训练教程》,清华大学出版社,2013
【4】Linus Torvalds,David Diamond,Just For Fun,2013,人民邮电出版社
【5】Stephen Prata,C Primer Plus,Fifth Editon,2008
【6】Detmann著;熊桂喜等译DOS程序员参考手册清华大学出版社,1995
【7】谢晓啸,《量子计算机算法与理论研究》,2012
【8】前桥和弥,《自制编程语言》,人民邮电出版社,2013
【9】张素琴等,《编译原理(第二版)》,清华大学出版社,2010
标签:
原文地址:http://www.cnblogs.com/Chaobs/p/4870226.html