在介绍完MaQueOS的显示器驱动程序的实现后,为了提供在显示器上显示字符串的接口,MaQueOS实现了一个接口函数printk,它的作用是将传递给它的字符串参数显示在显示器上。本章实验code1的main函数如代码清单1.3所示。
代码清单1.3 main函数(第1版)
下面分析代码清单1.3。
·第3行: MaQueOS实现了一个由显示器和键盘组成的控制台。调用con_init函数对控制台进行初始化。con_init函数的实现详见代码清单1.4。
·第4行: 调用printk函数,在显示器上显示字符串“hello,world.”。显示过程详见1.2.1节。
·第5~6行: 进入死循环。
代码清单1.4 con_init函数(第1版)
在 第5~6行 中,MaQueOS定义了2个全局变量:x和y(具体定义见第1行),用于指示下一个显示字符在显示器上的坐标。在con_init函数中,将x和y变量初始化为0,即控制台初始化完成后,第1个显示字符在显示器上的坐标为(0,0)。
在显示器驱动程序中,printk函数用于将指定的字符串显示到显示器上,显示位置由(x,y)坐标指定。printk函数的参数为需显示字符串的起始地址。printk函数的实现详见代码清单1.5。本节以在main函数中调用printk函数显示字符串“hello,world.”为例,分析printk函数的处理过程。
代码清单1.5 printk函数
下面分析代码清单1.5。
·第14~15行: 通过while循环计算字符串的长度,计算得到字符串“hello,world.\n”的长度为14,其中字符“\n”为换行符(ASCII值为10)。
·第16行: 调用erase_char函数擦除光标,具体擦除过程详见1.2.2节。光标由字符“_”表示,用于指示下一个显示字符在显示器上的位置。因此,在显示字符前,需要先将光标清除。
·第17~34行: 循环遍历字符串中的所有字符,根据字符的ASCII值进行相应的处理。其中,第19行表示从字符串中获取待处理字符的ASCII值。
·第20~27行: 若该字符为可显示字符(ASCII值为32~126),则调用write_char函数(第22行)将字符直接显示在显示器上,显示过程详见1.1.2节。 第23行 表示将显示器上当前行已显示字符的个数保存到sum_char_x数组中,每行对应数组中的1项,总共50项,sum_char_x数组的定义详见第7行。在 第24行 中,将列数加一。在 第25~26行 中,若该字符为当前行的最后一个字符,则调用cr_lf函数进行回车换行处理,处理过程详见1.2.3节。
·第28~29行: 若该字符为换行符(ASCII值为10)或回车符(ASCII值为13),则调用cr_lf函数进行处理。当字符串“hello,world.”被处理到最后一个字符“\n”时,调用cr_lf函数进行回车换行处理。
·第30~31行: 若该字符为删除符(ASCII值为127),则调用del函数进行处理,处理过程详见1.2.5节。
·第32~33行: 若该字符不属于以上情况,则视为不可识别字符,调用panic函数显示错误信息,处理过程详见1.2.6节。
·第35行: 当字符串处理结束后,调用write_char函数在字符串末尾显示光标,并指示下一个显示字符在显示器上的位置。
字符擦除函数erase_char用于擦除显示器上给定坐标(xx,yy)处的字符。erase_char函数的实现详见代码清单1.6。所谓擦除,就是将像素绘制成背景色(黑色)。
代码清单1.6 erase_char函数
下面分析代码清单1.6。
·第12行: 获取待擦除字符的起始像素在显存中的起始地址,计算过程详见代码清单1.2的第14行。
·第13~23行: 按行循环遍历待擦除字符的像素,共遍历16行。在 第15~21行 中,每行按列循环遍历待擦除字符的像素,共遍历8个像素。将每个像素在显存中的(B,G,R)对应的字节设置为(0,0,0),表示将该像素绘制为黑色。在 第22行 中,每行遍历结束后,重新获取字符下一行像素的起始像素在显存中的起始地址。
对回车换行的处理由函数cr_lf实现,cr_lf函数的实现详见代码清单1.7。
代码清单1.7 cr_lf函数
下面分析代码清单1.7。
·第7行: 将列坐标x清0,实现回车处理。
·第8~11行: 进行换行处理。在 第8~9行 中,若当前行不是显示器的最后一行,则通过将行坐标y加1,实现换行处理。在 第10~11行 中,若当前行是显示器的最后一行,则需要调用scrup函数进行卷屏处理,处理过程详见1.2.4节。
当显示内容超出显示器最后一行时,需要调用scrup函数进行卷屏处理。卷屏的主要处理过程是将显示器第1~49行的内容分别复制到第0~48行中,并将第49行的内容擦除。scrup函数的实现详见代码清单1.8。
代码清单1.8 scrup函数
下面分析代码清单1.8。
·第15行: 获取显示器第0行第0列字符的起始像素在显存中的地址0x40000000。
·第16行: 获取显示器第1行第0列字符的起始像素在显存中的地址0x40000000+(16×1280×4)=0x40014000,计算过程详见代码清单1.2的第14行。
·第17~18行: 将显示器第1~49行中所有像素在显存中的内容,以字节大小为单位(每个像素对应4字节)复制到第0~48行对应的显存中,需要复制的字节总数为(800-16)×1280×4=4014080。
·第19~20行: 循环调用erase_char函数,擦除第49行中的160个字符。
·第21~22行: 将显示器第1~49行在数组sum_char_x中对应项的值分别复制到第0~48行对应的项中。
·第23行: 将显示器最后一行在数组sum_char_x中的对应项清0。
对字符的删除处理由函数del实现,del函数的实现详见代码清单1.9。
代码清单1.9 del函数
下面分析代码清单1.9。
·第3~7行: 此时坐标(x,y)指向光标所在的位置,若光标不位于行首,则在 第5行 中,对列坐标x减1后,坐标(x,y)指向待删除的字符。在 第6行 中,更新sum_char_x数组。
·第8~13行: 若光标位于行首,并且不在首行,表示待删除字符是上一行的最后1个字符(由sum_char_x数组指定),则在 第10行 中将当前行的字符数清0,在 第11行 中将行坐标减1,在 第12行 中从sum_char_x数组获取待删除字符的行坐标。此时,坐标(x,y)指向待删除的字符。
·第14行: 调用erase_char函数,删除待删除字符。删除过程详见1.2.2节。此时,坐标(x,y)指向下一个显示字符在显示器上的位置。
当系统发生错误需要终止运行时,调用panic函数显示出错信息,并进入死循环。panic函数的实现详见代码清单1.10。
代码清单1.10 panic函数
下面分析代码清单1.10。
·第3行: 调用printk函数显示错误信息,显示过程详见1.2.1节。
·第4~5行: 进入死循环。