0
点赞
收藏
分享

微信扫一扫

30天OS学习,制作ASCII码字符库

正义的杰克船长 2022-04-22 阅读 25

日本程序员川合秀实先生写了一本书,名叫《30天自制操作系统》,里面在第5章,讲了显示字符串的方法。

我去看川合先生的书的时候,我是想着将其中的代码予以改编,改为可以用流行的编译器来编译的东西。

川合秀实采用的汇编语言编译器,名叫nask。根据作者的叙述,这个编译器,是改编了流行的nasm编译器而形成的。

我在读作者的书的时候,我的一个感觉就是,里面的很多的汇编语法,跟nasm语法挺像的。

在这里,若是改编作者川合秀实先生的代码,最为省事的办法,那就是将其改编为nasm编译器所支持的代码。

可是,我并没有选择将其改编为nasm支持的语法,而是选择了将其改编为GNU AS编译器支持的语法。

之所以选择这个,那是因为,在内核的学习上,我主要地是想要去学习着Linux内核源代码。而Linux内核源代码,所采用的汇编语言,主要地是GNU AS编译器所支持的汇编语言。

为了让自己比较方便地去学习着Linux内核,我是将主要的汇编学习精力,放在了GNU AS汇编方面。

对于nasm,我也不是完全不去学。只是,主要的精力不在这里。对nasm,我只是要求了解。

在代码改编上,我是选择了GNU AS汇编。

然后呢,在这个改编过程中,其实也会涉及其它的一些个问题。

在显示ASCII码字符的工作里,我需要去解决的一个问题,那就是将拥有一个ASCII码字体库文件。

在这个字体库文件方面,作者是提供了一个名为hankaku.txt的文件。这个文件里面,主体是一些个图形,我们来举一个例子。

char 0x41
........
...**...
...**...
...**...
...**...
..*..*..
..*..*..
..*..*..
..*..*..
.******.
.*....*.
.*....*.
.*....*.
***..***
........
........

char 0x42
........
****....
.*..*...
.*...*..
.*...*..
.*...*..
.*..*...
.****...
.*...*..
.*....*.
.*....*.
.*....*.
.*...*..
*****...
........
........

char 0x43
........
..***.*.
.*...**.
.*....*.
*.....*.
*.......
*.......
*.......
*.......
*.......
*.....*.
.*....*.
.*...*..
..***...
........
........

如上面的图所示,我是给出了三个字符的图画。这些内容,都是hankaku.txt里面的内容。大写字母A的ASCII码为0x41,上图的char 0x41,下面的点阵图画,就是A的图画。char 0x42下面的点阵图画,是大写字母B的图画,0x43是C的图画。
  想要显示英文字符,就需要将这样的一些个字体数据给编译出来。

如何来编译呢?作者本人是提供了相关的软件的。可是,那个软件似乎不是开源的,而且,还只能是在Windows里面用。我想要用Linux来改编代码,作者提供的Windows软件,我就用不了的。

好在,我观察了一下文件,发现,在作者的代码里面,点阵图画与其它的内容分的还是比较清楚的。点阵图画,要么是点号,要么是星号。其它的内容,要么是作者用日文写的注释,要么是英文字符与数字,这些个东西,与点号和星号都是区分得很清楚的,并没有混淆在一起。

这样一来呢,我就只需要通过编程,来处理着点号与星号,将其转换成为其它的东西就可以了。

起初,我是想要用C++来编译的。因为,我手边没有C语言的书,而只有C++的书。我去查了查资料,查完了以后,我去试着编程,结果呢,书上说的一些个知识点还是错的。

我对C++的语法啥的,还是不熟悉啊。相比之下,我还是对C语言的语法更熟悉一些。虽说忘记了其中的一些个东西,稍微地查一查资料,应该还是能够编写出程序来的。

经过一番编程与改错,最终呢,这个制作字符库的工作,我算是完成了。

代码如下。

#include <stdio.h>
#include <stdlib.h>

int a[8] = {128, 64, 32, 16, 8, 4, 2, 1};
int main(void)
{
  FILE * fp_in, *fp_out;
  char ch;
  int i, j;
  int n;
  char infile_name[60] = "hankaku.txt";
  char outfile_name[60] = "hankaku.h";
  
  i = 0;
  j = 0;
  n = 0;
  
  fp_in = fopen(infile_name, "r");
  fp_out = fopen(outfile_name, "w+");
  if (!fp_in || !fp_out)
  {
    printf("open file fail.\n\n");
    exit(0);
  }
  else
  {
    printf("open file successfully.\n\n");
  }
  
  fprintf(fp_out, "#ifndef _HANKAKU_H_\n");
  fprintf(fp_out, "#define _HANKAKU_H_\n\n");
  fprintf(fp_out, "unsigned char hankaku[4096] = {\n");
  while ((ch = fgetc(fp_in)) != EOF)
  {
    if (ch == '.')
    {
      n += a[i] * 0;
      i++;
    }
    else if (ch == '*')
    {
      n += a[i] * 1;
      i++;
    }
    else
    {
      continue;
    }
    
    if (i >= 8)
    {
      fprintf(fp_out, "0x%X", n);
      n = 0;
      i = 0;
      j++;
    }
    else
    {
      continue;
    }
    
    if (j >= 4096)
    {
      printf("this task is done.\n");
      printf("happy every day.\n\n");
      break;
    }
    else if (j % 16 == 0)
    {
      fprintf(fp_out, ",\n\n");
    }
    else if (j % 4 == 0)
    {
      fprintf(fp_out, ",\n");
    }
    else
    {
      fprintf(fp_out, ",\t");
    }    
  }

  fprintf(fp_out, "\n};\n\n");
  fprintf(fp_out, "#endif\n\n\n");  
  
  fclose(fp_in);
  fclose(fp_out);

  printf("task is ok.\n\n");  
  if (j < 4096)
  {
    printf("this is a test file, not a real task.\n\n");
    printf("You can pratice it next time.");
  }
  else
  {  
    printf("this is a real task.\n");
    printf("good job.\n");
    printf("Hope you success.\n\n\n");
  }
  
  return 0;
}

在这个代码里面,我所做的事情是,建立一个大的数组,将所有的ASCII码字符点阵图画,转化为一个字节一个字节的十六进制数字.

在处理着点阵图画的时候,我将点号转换为一个2进制位的0,将星号转换为一个2进制的1。8个01序列,组合为一个字节的十六进制数。组合的时候,依据不同的数位的2进制的位权,进行了数学计算。

关于位权,我们自己去计算二进制数的时候,我们用的是乘方的运算。在编程的时候,当然可以根据不同2进制位,用循环来体现着位权的概念。比如说,如果是位0,那就是乘以1之后,再乘以0个2。如果是位1,在乘以1之后,再乘以1个2.如果是位6,在乘以1之后,再乘以6个2,用循环来处理这个事情,应该是能够做到的。

我在程序里面,没有采用循环,而是声明了一个整型数组,不同的2进制位,直接让其乘以位权值。比如说,位5,就要乘以32。位4,就要乘以16.8个2进制位,申请一个含有8个元素的整型数组也就够用了。

每8个01序列,可以通过计算,转换为一个字节的十六进制数。在转换的时候,我是在每一个数的前面,加上了0x前缀。

其实不必非得用16进制来显示的,也可以用十进制数来做这个工作。不过呢,我在处理的时候,我还是选择了将每一个字节的数,以十六进制的形式来显示了。这个,其实是因为,学习汇编语言时间久了,就对十六进制比较感兴趣吧。

在编程里面,其实2进制,十六进制,比十进制数要好用很多。比如,你去进行着逻辑与,逻辑或的操作,你直接用十进制来算,那就很不直观。若是采用2进制与十六进制,那就很方便了。

在这里,在转换数据的时候,我是将最终的格式转换为了十六进制,且每一个数的前面,都给加上了0x的前缀。

在排版上,我是让每一行显示着4个数,每当满4个数,就要换行了。每当满16个数的时候,要换两行。使得16个数,成为一堆。

这个是因为,一个ASCII码字符,它采用的是8*16的点阵图画。乘数中的8,刚好可以对应着8个二进制位,也就是一个字节。后面的16,它是说,用十六个字节,可以表示一个ASCII码字符。这样一来呢,我是让转换好的16个数,成为一堆。就是让每一个字符的数据放在一堆。

ASCII码字符,一共是256个,每一个字符占用着16个字节,16乘以256,乘积为4096,所以,我是让程序转换输出为一个头文件,头文件申请了一个unisgned char类型的数组,数组的元素个数为4096。

由于涉及文件的读写操作,我在C语言代码里面,采用了文件操作的函数。主要地,是采用了fgetc()与fprintf()函数。

在输出文件的开头,是关于头文件的套话。

#ifndef    xyz

#define     xyz

然后呢,在文件的最后,还得是写上    #endif

在将开头结尾的固定格式与中间的声明与转换数组的操作都完成了以后,这个程序,其主体工作也就算是完成了。

程序还有一些个辅助性的提示语。

程序应该是不难读懂,大家稍微看一看,应该是能够理解。

里面也没有涉及windows平台或者是Linux平台专有的链接库,都是C语言函数库里面的东西,你在Windows与Linux里面,都可以编译的。

最终呢,程序执行以后,会生成一个hankaku.h文件。你可以将这个头文件包含在你自己的程序里面,在程序里面,引用其中的数据。

以上是我对C语言代码的大体的解读。

改编这些个代码的时候,算是很兴奋,觉得很有意思。同时呢,我也觉得,这个代码改编的工作,实在是不太容易。

显示ASCII码字符的工作,其实还算是好的。相比之下,还会有着其它的一些个问题。比如说啊,作者在切换保护模式的时候,采用的是多段的模型。而我自己在采用着作者的那种GDT表的设置的时候,根本就不能够通过编译。为了能够通过编译,为了能够成功地运行程序,最终呢,我是将作者的那种GDT表的设置,全给改了,改成了平坦模型。

然后呢,作者在引导启动阶段里,所设置的程序的加载地址,也叫我给改了。

比如说啊,作者在ipl10.asm里面,要从磁盘加载10个柱面的数据。加载完了以后,要跳转到0xC200。我自己在改编代码的时候,我是将其设置为跳转到0x8200。

之所以会有这种不同,那是因为,作者所加载的,是一个带有格式的文件。而我自己在编译的时候,将所有的程序文件,都编译成了纯二进制文件。

目前,我所掌握的,其实还是纯二进制文件的编译。这个是因为,目前,我主要学习的,主要所掌握的,还是Linux 0.12内核的编译。0.12内核的组织方式,编译的方式,我比较熟悉一些。其它的,各种的带格式的文件的编译,我并不熟悉。

这样一来,我自己在改编《操作系统真象还原》的代码的时候,我也是用0.12内核的编译方式,来进行着转换。

在内核代码上,其实我还是比较喜欢0.12内核。

0.12内核,它的有些个机制,是比较简单一些的。

流行的内核,在很多的地方,采用了新的机制。而0.12内核的好多地方,它的处理,其实还是比较简化的。比如说,内存管理上,《操作系统真象还原》,采用的是内存池,用一个2进制位,来表示一个4k内存页面的分配情况。而0.12内核则是采用了1个字节来表示着4k页面的分配情形。

其它的还有一些个东西,那是0.12内核与流行的内核不一样的地方的。

相比之下,我还是喜欢0.12内核一些。0.12内核,它有它的朴素的一面。

不知道各位学习内核的同学的学习体验如何。

在这里,我是觉得,想要真正地学好内核,那么,Linux内核源代码的阅读与学习,那是必不可少的一个工作的。

GCC文档,英特尔开发手册,这都是你绕不开的东西的。

像是反汇编,调试技巧,都是需要我们慢慢地来学习和掌握的。

祝各位学习愉快。

举报

相关推荐

0 条评论