下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922

计算机系统是怎样显示字符的一系列的概念

作者:课课家教育     来源: http://www.kokojia.com点击数:1338发布时间: 2017-04-09 10:00:41

标签: 计算机大数据系统

     正巧,今天我遇到了难缠的有关于计算机系统字符问题,因此今天写了这样一段文章,就拿出来和大家分享。希望大家能够从其中学到知识~

      我来就 Linux + Xorg 系统回答一下吧。我不知道,也没办法知道 Windows 上的情况是怎么样的。我的回答在细节方面可能不会太详细,一方面是因为细节并不影响理解,另一方面也是因为我对一些细节的了解也不是那么深。

  从按键到字符在屏幕上显示,其实是一个很复杂的过程……我粗略估计了一下,这个过程中执行的指令,对应到源代码,可能要超过10万行。

  那,就从按下键开始讲起吧。当你按下键后,一个电信号从键盘通过 USB 接口或者 PS/2 接口送到了总线,然后引起了一个中断。如果是在老机子上,管理中断的可能是8259A 芯片,新的机子上则可能是 APIC 。CPU 收到中断之后,会暂停当前的程序的执行,调用事先设定好的中断处理过程。Linux系统的中断处理一般分为两个部分,Top half 和 bottom half。其中直接响应中断的是 top half,因为系统不能在中断处理过程中停太久,所以 top half 只是记录中断的发生,真正中断处理过程是在 bottom half 中完成的。

  bottom half 在 top half 之后执行,具体执行取决于进程调度。在 bottom half 中内核会找来负责的驱动,比如说 USB HID 驱动,处理键盘输入。这个键盘输入会反映在 /dev 下面某一个字符设备中,比如说 /dev/input/event0 。Xorg 为了获取键盘输入,会对这个设备进行 select() 。select 这个系统调用会将进程 block ,直到有可用输入。在内核中的反应就是,内核会将进程的状态设为 TASK_INTERRUPTIBLE ,然后调用 schedule() 将运行权让给别的进程。当键盘输入到来的时候,内核会查看是否有进程正在 select /dev/input/event0 这个设备,如果有,内核会将对应的进程从select 中唤醒,以便处理这个输入。

  于是Xorg就被唤醒了,我们也终于从 kernel space 里逃了出来,来到了 user space 。Xorg 会从 /dev/input/event0 读出一组数据。这组数据的定义如下:

  struct input_event {

  struct timeval time;

  unsigned short type;

  unsigned short code;

  unsigned int value;

  };

  time 是事件发生的时间,type 是事件类型,对应键盘就是 EV_KEY ,code 在这种情况下是一个 key code,value 则代表是按下键还是放开键。

  Xorg 在读到事件之后,还需要将来自 kernel 的 key code 翻译成 Xorg 自己的 key code 。完成这个的是 libxkb ,键盘布局也是在这个阶段起作用的。然后,Xorg 会找出当前焦点所在的窗口,然后将事件发给它——这是简单的情况。如果你运行着一个窗口管理器的话,这个事件很可能还需要在窗口管理器中过一遍,并且由窗口管理器决定是否要发给窗口。如果接受按键的程序支持输入法,这个按键可能还要在输入法里走一圈。这个过程中的事件发送/接受都是通过 socket ,使用的协议是 Xorg 的协议,使用的库(归根结底)一般是 xlib 或者 libxcb 。

  (补充一段关于编码的)

  应用程序在接到 Xorg 发来的事件之后,就会对事件作出反应。比如说一个文本编辑器,在接到按下‘X’的事件之后,需要在文件中插入一个‘X’。那么这个‘X’是怎样在文件中表示的呢?你应该知道,内存也好硬盘也好,是用二进制来保存数据的。要表示一个‘X’,我们必须先给它分配一个对应的数字。幸好,早在近60年前,就有人帮我们把这件事办好了。这就是所谓的 ASCII 码。‘X’在其中对应的数字是120。

  但是,如果我通过我的输入法,输入了一个中文字的话怎么办?当然,我们还是用一样的办法,给每个中文字一个编号。这个编号,在中国的国标码中被称作区位码,在 unicode 中被称作一个code point。比如说,国标码给“啊”字编的号就是1601。

  虽然现在我们给中文字也编上了号,我们还不知道应该怎么把它存到文件中去。你可能会觉得这很简单,直接把编号存进去不就是了?比如1601就存两个字节0x06和0x41。但是这会造成一个问题。假如你的文本编辑器同时支持ASCII码和我们设计的这个中文编码,它要怎样才能知道某个文件是按什么编码的?0x16和0x41在 ASCII 中都是合法编码,这样就会造成一个文件存在歧义。不过幸好,ASCII 只使用了0~127来编码字符,所以只要我们只用128~255,就可以和ASCII区分开来。比如说,我们可以将编号按7位二进制分段,然后在第一位添上一个1:

  1601 -> 11001000001 -> 0001100/1000001 -> 10001100 11000001 -> 0x8c 0xc1

  在国标码里,这个编码后的结果就是这个字符的内码,注意内码和区位码的区别。而 unicode 和 UTF8,UTF16 也是类似。unicode 是一种给字符编号的方法,UTF8、UTF16则是把这个编号记录到文件里的方法。

  到这里,我们的旅程才完成了一半。而且是比较简单的一半。

  现在你的输入已经跑完前半程,终于到达应用程序了,不过等等——这个程序,是什么程序?是一个终端虚拟器,文本编辑器,还是chrome里头的某个文本框?取决于对这个问题的回答,后半程的路线会非常不一样。

  假如说这个程序是 xterm ,它会通过 X core font API ,将这个字符直接送给 Xorg ,由 Xorg 来完成字体渲染(当然现在 xterm 也支持 libXft 了,不过先不管这个)。Xorg 拿到应用程序送来的请求之后,会在字体中检索每个字对应的字形,然后渲染出来。这个检索过程中用到的索引,就是我们之前给字符编上的号。如果要指定字体,需要向Xorg提供一个长得像这样:“-adobe-times-medium-r-normal--12-120-75-75-p-64-iso8859-1” 的字符串来指定字体。当然,这样渲染出来的字体不会很好看,像这样:

计算机系统是怎样显示字符的一系列的概念_计算机_大数据_课课家教育

  (我不是很清楚 X core font 是否有反锯齿一类的能力,应该是没有,欢迎纠正。)

  如果这个应用程序是 gedit,那么它就会选择自己渲染字体,然后将渲染结果直接发给 Xorg,由 Xorg 呈现。在 Linux 下,你想用这种方式渲染字体,有两个库你是逃不过的:fontconfig 和 FreeType2 。大致上来说,fontconfig 让你能通过一个字体名找到对应的字体,FreeType2 则进行具体的渲染。

  那么字体是怎么被渲染的呢?不管你用的字体是TrueType还是OpenType还是什么格式,从根本上来说,(除掉点阵字体)都是一些矢量图的集合。渲染字体,只是把矢量图画出来而已——说的轻松!

  渲染文字是个复杂的工作,我也不能说我对此了解很深,只能大致讲讲。

  字体如果是显示在屏幕上,因为屏幕的dpi和纸张没法比,所以必须反锯齿,否则就会这样:

字体如果是显示在屏幕上,因为屏幕的dpi和纸张没法比,所以必须反锯齿,否则就会这样:

  这只是通过灰度进行反锯齿,我们还可以做的更好。如果你是在使用LCD显示器的话,你屏幕上的像素,长的可能是这个样子的(图片来源,wikipedia):

这只是通过灰度进行反锯齿,我们还可以做的更好。如果你是在使用LCD显示器的话,你屏幕上的像素,长的可能是这个样子的

  灰度反锯齿只是在整个像素尺度上进行的,但是既然像素中还可以分为更小的成分,那么我们为什么不通过这更小的单位进行反锯齿?这种方法就叫做 Subpixel Rendering,或者一个更被人熟知的叫法:ClearType。下面是两种反锯齿的比较,图还是来自 Wiki :

这种方法就叫做 Subpixel Rendering,或者一个更被人熟知的叫法:ClearType。下面是两种反锯齿的比较,

  可以看到 subpixel 渲染的结果会让边缘带一点色彩。一般来说 subpixel 渲染可以字体看起来更清晰一点,你可在自己的系统上开启/关闭 subpixel redenering 比较一下。

  人们为了让字体在屏幕上看起来能清晰一点,真是用尽了心思。因为反锯齿会让字体的边缘变得模糊,让人觉得字体看起来不是那么“锐利”,人们又想出来要给字体做Hinting(不知道如何翻译)。Hinting将字体微调,让横和竖都对齐像素,来尽量减少反锯齿带来的模糊。下面是个(应该一下就能看明白的)例子(图还是来自 Wiki):

人们为了让字体在屏幕上看起来能清晰一点,真是用尽了心思。因为反锯齿会让字体的边缘变得模糊,让人觉得字体看起来不是那么“锐利”,人们又想出来要给字体做Hinting(不知道如何翻译)。Hinting将字体微调,让横和竖都对齐像素,来尽量减少反锯齿带来的模糊。

  Hinting 的具体实现是很复杂的。因为不同字体需要不同的 Hinting 对策,所以字体中会存一些 Hinting 数据来指导 Hinting。而这些数据,每一个都是一个用特定汇编语言写成的小程序!FreeType2中则包含了一个虚拟机,来运行这些程序,来将字体对齐到像素。

  当然也有些字体会不带 Hinting 数据,但是 FreeType2 还是可以进行 Hinting ,也就是 Auto-hinting。具体的算法我就不清楚了。

  值得一提的是,因为 Apple 拿着很多有关 Hinting 的专利,FreeType2 默认是关闭 Hinting 的,而只使用 Auto-hinting 。直到2011年这些专利全都过期之后,FreeType2才默认支持 Hinting 。

  好了,不管你是让Xorg进行字体渲染,还是让应用程序自己进行渲染,最后的结果都是一小块比特图。我们要怎么把这块图显示在屏幕上呢?

  Xorg 之中,有一个部件叫做 EXA,是用来提供硬件加速的2D渲染的。EXA的历史很长,最早可以追溯到1996年 Harm Hanemaayer 给 Xorg 实现的 XAA。XAA 后来被 EXA 所接替。现在 EXA 又开始要被 UXA 取代。这些部件虽然都有同样的目的,但是实现都有所不同。具体的区别在这里就不展开了。总的来说,他们的功能是,在显卡驱动的帮助下,将二维图形显示在屏幕上。

  到这里,好像就结束了。但其实还没有。2011年的光棍节,Glamor诞生了。Glamor试图将2D的渲染也用 OpenGL 来完成。一张二维图,大概会被转变成 OpenGL 的贴图,然后渲染在屏幕上。目前这个项目还在开发中,性能(在大部分情况下)还不如EXA。

  如果所有的渲染都由 OpenGL 也就罢了。如果你还在用 EXA,但是你的某个程序(比如说Chrome)非想用 OpenGL 来显示文字怎么办?显然的办法可以是让程序把所有的 OpenGL 请求都发给 Xorg,让 Xorg 代理。这种方法就叫做间接渲染(Indirect rendering),Xorg 的一个扩展:AIGLX,就是用来完成这个工作的。

  有一个额外的中介大概是会影响性能。虽然我没有具体的数据来证明这一点,不过既然后来 DRI 的出现大概是一个佐证。DRI 是 Direct Rendering Infrastructure 的缩写,分为两个部分,分别存在于内核与 Xorg 中。内核的部分叫做 DRM,用来协调多个程序对 GPU 的访问;Xorg 的部分则是一个驱动,也叫 DRI 。程序想要使用 OpenGL ,先要通过 DRI 向 Xorg 申请一块缓冲区,然后通过 DRM 访问 GPU 向这个缓冲区进行渲染。当然这些事不会直接让程序去做,都是通过 Mesa 这个 GL 库完成的。

  到这里,还是没有结束。

  万一你不幸有个带 NVIDIA® Optimus™ 的本子怎么办?使用 Optimus 技术的独显不会直接和显示器连接,所以想要直接让独显渲染是不行的。现有的解决方案是 BumbleBee,就是之前那个删了用户 /usr 目录,出了点小名的那个。BumbleBee 的做法是再启动一个独立的、跑在独显上的 Xorg,然后用它进行渲染。但是独显没法输出到屏幕,怎么办呢——只好把渲染结果在两个 Xorg 之间传来传去了。BumbleBee 用了 VirtualGL 来完成这个任务。

  另外一个还在开发中的解决方案叫做 Prime(开发者就是喜欢和变形金刚过不去)。Prime 利用的是一个还在开发中的内核功能:DMA-BUF,让两个硬件共享一块内存区域。我不清楚具体的原理,不过大概是让一块显卡渲染完之后,DMA 写到内存里,再让连接显示器的显卡 DMA 读出,输出到屏幕吧。

  用来 Optimus 以后,感觉本来的长跑,直接就变成了马拉松……

  以上差不多就是全部了。不知道你们是不是还有兴趣,要是把场景换成 Wayland ,还有很多可以讲。

  小分享;编码问题的产生:

  我们知道,计算机是美国人发明的,人家的英语体系总从来就只有26个英文字母和一些数字、特殊字符等,为了储存文字信息,于是使用了最早的ascii码进行字符编码。而后来由于计算机的普及,多国语言文字变得重要起来,于是多语言的特性成为了计算机的必备,各国进行各国的国家标准编码,中国的便是GB2312(1980年),而后1995年又颁布了《汉字编码扩展规范》(GBK),GBK与GB2312相兼容,但又增加了一些兼容汉字,方便了和Big5码等进行转换。这套GBK编码,逐渐成为了中国计算机的主流编码。

      小知识:

  1.要想深刻的理解计算机如何显示字符,我推荐的步骤如下:

  2.学习使用最简单的51单片机配合一块最简单的12864液晶屏显示一个字符。

  3.如果嫌麻烦,端详12864液晶屏幕,仔细观察上边的小方块,心中想一个字符,脑补小方块被点亮的情景,就理解了字符是如何显示出来的。

     本文好多内容是基于我自己的理解写成,如有疏漏错误,还请指正。如果大家还有什么关于计算机知识方面的问题的话,请咨询课课家教育网站~

赞(0)
踩(0)
分享到:
华为认证网络工程师 HCIE直播课视频教程