深度探索Linux操作系统 —— 构建根文件系统1:https://developer.aliyun.com/article/1598090
6、安装X的然就会明白。
看到输入设备驱动,读者可能会有个疑问:内核中不是包括了各种设备的驱动吗?怎么X中还要安装设备驱动?没错,输入设备的驱动是在内核中,X 中的所谓输入设备的驱动 evdev 谈不上是一个驱动了,只不过大家习惯这么称呼而已。evdev 模块并不面向任何具体输入设备,它只不过是接收和解析内核发送到用户空间的输入事件。仔细观察图 6-12 所示的 Linux 输入子系统的架构,读者自然就会明白。
操作系统将面对各种各样的输入设备,如鼠标、键盘、触摸屏、游戏手柄等。由于这些输入设备大部分不遵循统一的标准,所以导致应用程序,比如 X 将不得不处理来自各种输入设备的五花八门的输入事件。
因此,内核中抽象了一个输入子系统。在输入子系统中,设备驱动面对各种各样具体的硬件设备,而输入事件经过事件处理模块处理后,将以统一的格式发送给用户空间的应用,用户空间的应用无需再为各种各样的输入事件格式疲于奔命。
现在很多输入设备都使用 USB 接口,对于 USB 接口的输入设备,图 6-12 演化为图 6-13 所示。
USB 设备通过主控制器连接到主机,所以内核需要驱动 USB 主控制器。USB 流行的一个主要原因就是具有统一的标准,所以对于 USB 接口的输入设备,它们使用统一的设备驱动,即图 6-13 中的 USB HID 驱动。
通过上面的讨论可见,从操作系统的角度,安装 X 的输入设备驱动事实上有两件事需要做:一是需要配置内核的输入设备相关的驱动和模块;二是安装 X 的 evdev 模块。
7、运行X服务器
X 服务器将建立一个套接字与应用程序进行通信,通常这个套接字被命名为 “/tmp/.X11-unix/X0” ,0 表示是第一个 X 服务器,如果再启动第二个 X 服务器,则为 “/tmp/.X11-unix/X1” 。除了建立套接字外,X 服务器还将在 /tmp 目录下建立一个锁文件,例如对于第一个 X 服务器,这个锁文件为 “/tmp/.X0-lock” 。另外,在前面编译时,我们指定 X 服务器将日志文件存放在 /var/log 目录下,因此,我们需要在根文件系统中建立这两个目录:
为了使书中的截图不至于尺寸过大,笔者将vita系统的 X 服务器的分辨率设置为 “640×480” 。最初,X 服务器完全由用户通过书写配置文件的方式手动配置,在 udev 出现后,X 服务器采用了自动配置技术。但是 X 也给用户留有机会进行手动微调,并且用户手动配置的优先级还要更高。当然读者不必设置分辨率,由 X 服务器自动探测即可。通过 xorg.conf 设定分辨率的方法如下:
最初,X 服务器启动后将创建并显示鼠标指针。后来,X 的开发人员认为只有在应用程序明确表明需要与用户进行交互时,才应该显示鼠标指针。所以,这个默认行为发生了改变,在 X 服务器启动后,不再默认创建并显示鼠标指针,而是在第一个应用明确调用类似 XDefineCursor 这样的函数请求X服务器显示鼠标后,才显示鼠标指针。
但是 X 还是为用户留了余地,增加了一个命令行参数 “-retro” 。如果用户运行 X 服务器启动时即创建和显示鼠标,那么给 X 服务器传递这个参数即可。
在默认情况下,当最后一个 X 应用断开与 X 服务器的连接后,X 服务器默认自动重置。同样,X 也为这个行为提供了修正的机会,用户可以使用命令行参数 “-noreset” 关闭这个特性。vita 系统不需要这个特性,因此我们传递了 “-noreset” 参数给 X 服务器。
最后,使用如下命令运行 X 服务器:
Xorg -retro -noreset &
在 X 服务器启动成功后,将创建一个根窗口,作为未来所有用户窗口的根。默认情况下,这个根窗口只以一个简单的灰色背景显示。并且我们看到,X 也按照我们的要求,创建并显示了鼠标指针。
8、一个简单的X程序
我们使用 Xlib 编写一个简单的 X 程序来确认 X 服务器是否已经正常工作。这个程序非常简单,就是创建一个窗口,并在其上显示字符串 “Hello X Window!”,代码如下:
// /vita/build/hello_x/hello_x.c: #include <X11/Xlib.h> #include <X11/Xatom.h> #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { Display *dpy; int screen_num; Window win; int x, y: unsigned int w, h; Atom atom_win_type, atom_win_type_normal; XEvent e; GC gc; char *s = "Hello x Window !"; if (!(dpy = XOpenDisplay (NULL))) { fprintf(stderr, "Can't connect to X sever!\n"); return -1; } screen_num = DefaultScreen(dpy) ; × = у = 20; w = DisplayWidth(dpy, screen_num) / 2; h = DisplayHeight (dpy, screen_num) / 2; win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), x, y, w, h, 2, BlackPixel(dpy, screen_num), WhitePixel (dpy, screen num)); XStoreName(dpy, win, "Hello X11"); atom_win_type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); atom_win_type_normal = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_NORMAL", False); XChangeProperty(dpy, win, atom_win_type, XA_ATOM, 32, PropModeReplace, (unsigned char *)&atom_win_type_normal, 1); XSelectInput (dpy, win, ExposureMask) gc = XCreateGC(dpy, win, 0, 0); XMapWindow(dpy, win); while (1) { XNextEvent (dpy, &e); switch (e.type) { case Expose: XDrawString(dpy, win, gc, 30, 30, s, strlen(s)); break; } } }
编译这个程序的 Makefile 如下:
# /vita/build/hello_x/Makefile: LDFLAGS=`pkg-config --libs x11` hello_x: hello_x.o clean: rm -rf hello x *.o
在登录到vita的终端中,使用如下命令启动X服务器,并运行应用程序 hello_x:
Xorg -retro & export DISPLAY=:0.0 ./hello_x &
注意环境变量DISPLAY的设置,其格式如下:
hostname: displaynumber.screennumber
如果主机名(hostname)为空,则表示 X 服务器运行在本机。读者可以把 display 理解为一个 X 服务器,screen 这里无须解释。displaynumber 和 screennumber 均从 0 开始计数,如值为 “:0.0” 表示运行在本机的第一个 X 服务器接的第一块屏幕。vita系统只启动了一个 X 服务器,并且只接一块屏。所以自然将环境变量 DISPLAY 设置为 “:0.0” 。
9、配置内核支持 DRM
如果读者是在真实机器上调试的,那么为了使 GPU 的 2D 驱动和 3D 驱动都可以正常工作,内核中还需要进行相关的配置,因为用户空间的 GPU 驱动是通过内核中的 DRM 访问 GPU 的。GPU 用户空间的驱动(2D 和 3D 驱动)和内核空间的驱动(DRM 模块)之间的关系如图 6-22 所示。
九、安装图形库
前面,我们使用 Xlib 编写了一个小程序。但是我们也看到,Xlib 是多么的原始,使用 X 提供的库编写一个如此简单的程序是多么的复杂,更别提具有复杂图形用户界面的程序了。所以先辈开发者们前赴后继,尝试在 Xlib 的基础上为 X 开发更高级的图形库,这些图形库通常被称为 Widget Libraries 或 Toolkits ,其中最著名的就是 GTK 和 QT 。这些图形库引入了控件的概念,极大简化了程序开发,也提高了开发效率。
我们选择 GTK 作为 vita 系统的图形库。这一节,我们就来编译安装 GTK。相比于安装 X ,图形库的安装过程相对要简单,但是我们也提供了一个编译脚本 build-gtk.sh 。必要时,读者可以参考这个脚本。
1、安装 GLib 和 libffi
GLib 是 GTK+ 和 GNOME 工程的基础底层核心程序库,是一个实用的轻量级的库,它提供常用的数据结构、相关的处理函数和一些运行时支承机制,如事件循环、线程、对象系统等。因此安装 GTK+ 前首先需要安装 GLib 。GLib 目前也由开发 GTK+ 的团队维护。
因为 GLib 提供的对象系统(GObject)可以绑定到多种语言,常见的如 C、Python、Ruby 等,因此,GLib 的对象系统借助库 libffi 处理不同语言间的函数调用。libffi 是专门设计的一个库,主要用于不同语言间的相互调用。因此,安装 GLib 前还需要安装 libffi 。
libffi 和 GLib 的编译安装命令如下:
2、安装ATK
ATK(Accessibility ToolKit)是 GTK 中实现辅助功能使用的库,包括辅助视觉、听觉、打字等。这个库也是别无选择,必须要安装的,安装命令如下:
3、安装libpng
图形库当然离不开图片格式处理的库,常用的图片格式有多种,比如 PNG、JPEG 等。但是为了简单起见,vita 系统只支持 PNG 图片格式。处理 PNG 图形格式的库是 libpng ,安装命令如下:
4、安装GdkPixbuf
GTK 使用 GdkPixbuf 进行图片的渲染,是 GTK 图形库的基本依赖之一,是必须安装的,安装命令如下:
5、安装Fontconfig
Linux 最初在我国的程序员中流行时,有很多程序员热衷于 Linux 的美化,其中优化文字的显示是其中主要内容之一,至今在各个 Linux 论坛仍然可见 Linux 美化的身影。文本渲染比较烦琐,除了技术原因外,文本处理机制不断的发展变化,从最初的 X 的核心字体,到 X 的字体服务器,再到现在广泛采用的客户端渲染,也给这个本身就不是特别容易理解的领域增加了很多复杂性。
凡是涉及字体相关的地方,我们经常看到如 Fontconfig、Freetype、Pango, 甚至更多,这些库在文本渲染中都担任什么角色?它们之间的关系又是什么?在我们埋头搭建系统时,还是要不时抬头看看路的。下面,我们就结合图 6-28 来简单地介绍一下文本的渲染。
(1)字符编码(character code)
虽然我们在写程序时,直接使用可读的字符,但事实上,在程序内部,是用字符的编码来代表字符的。字符的编码有多种标准,比如 ISO-8859 系列编码,Unicode 编码以及我国的 GB18030 等。
假设系统使用 UTF8 编码,当程序准备显示字符串 “你好Linux!” 时,程序中将以编码 “4F60 597D 4C 69…” 来记录这个字符串。
(2)字形(glyph)
字形是字的形体的简称,GB/T 16964《信息技术字型信息交换》中关于字形的的定义为:一个可以辨认的抽象的图形符号,它不依赖于任何特定的设计。
这样解释读者可能依然会感到比较生疏,因为平时我们很少使用这个概念,但是提到字体,大家就一定比较熟悉了,因为操作系统中一定要安装字体文件的,否则是不能正确显示字符的。而所谓的这个字体文件,其实就是字形的集合。
以 TrueType 字体文件为例,其中包含两个关键的数据结构:
◆ 一个是字形表,也称为 glyf 。字形表中每一项代表一个字形,使用字形索引访问其中的字形。TrueType 的字形表中,每个字形的描述并非如图 6-28 中的字形表(glyf)中显示的那样直观,字形表中描述的字形信息都是矢量的,字符的每一个笔画都是由多条曲线包围而形成的。一次曲线需要两个点来确定,二次需要三个点,三次就需要四个点。字体内部保存了这些点的坐标。
◆ 一个是字符编码到字形映射表(Character to Glyph Mapping),简称 cmap 。读者可能会有个疑问,cmap 中的第二列为什么不是字形,而是字形索引呢?原因是字体文件可能使用在不同的编码环境中,所以字体文件可能包含多个 cmap 表,比如 UTF8 对应一个 cmap 表,GB18030 对应另外一个 cmap 表。另外,一个字体文件中也可能不只包含一种字体。
(3)排版(layout)
每每谈到文本渲染时,大家更多的关注在字体上,却往往忽略了文本的布局排版。实际上,文字的排版是重要而且复杂的。排版引擎需要将单个字符按照一定的间距美观的排列起来。
除了处理字形信息外,由于世界上有多种文字体系,因此,文本可能是多种语言混合的。而且,还有像阿拉伯文、希伯来文这种文字体系是从右向左书写,更别提布局规则极其复杂的印度系文字。
可见,排版引擎是一位真正的幕后英雄。而且,文本渲染的过程都是由排版引擎牵头开始的,不同的图形库可能使用不同的排版引擎,GTK 使用的排版引擎是 Pango 。
(4)确定字体
在将字符编码转化为字符前,首先需要确定字体文件,否则巧妇也难为无米之炊。一个系统中可能安装了多个字体文件,因此,在众多的字体文件中要选择一个最合适的,这就是 Fontconfig 的主要任务之一。
(5)光栅化
一旦字体确定后,Fontconfig 使用库 Freetype 提供的接口,确定字符编码对应的字形索引,依据的就是如 TrueType 字体文件中的 cmap 表。最后,Freetype 根据字形索引,从字体文件的字形表中获取描述字形的矢量信息,构建具体的字形,这个过程也叫光栅化。经过光栅化的字符编码,就是一普通图形了,接下来无论是显示到具体窗口中,还是进行其他处理,都与处理普通的图形完全相同。
理解了各个库的作用后,下面我们开始安装这些库。
Freetype 在前面安装 X 时已经安装,接下来只需安装 Fontconfig 和 Pango 。由于 Cairo 依赖于 Fontconfig ,而 Pango 又基于 Cairo 进行字体渲染,所以,这里的安装顺序看上去有点奇怪。我们先安装 Fontconfig,中间插播 Cairo,然后才安装 Pango。
安装 Fontconfig 的命令如下:
6、安装Cairo
Cairo 是一个矢量图形库,GTK 使用其作为绘制后端。换句话说,GTK 的绘制动作由 Cairo 完成。看到这里,读者可能会非常困惑:X 上的应用不是由 X 服务器负责绘制吗?没错,暂且不提我们第 8 章讨论的 DRI。事实上,即使普通的 2D 应用也是可以自己绘制的,只不过,应用是将内容绘制在一个离屏的区域,但是最后还是要请求 X 服务器将绘制的内容显示到屏幕上。应用或者将绘制的内容复制到 X 服务器,或者使用 X 提供的 RENDER 扩展。当然,应用也可以将全部绘制请求 X 服务器完成,这就要看具体图形库采用的策略了。
安装 Cairo 的命令如下:
7、安装Pango
安装Pango的命令如下:
8、安装libXi
图形库当然是要接收用户输入的,X 输入扩展协议的实现是库 libXi,安装命令如下:
9、安装GTK
GTK 的基本依赖已经安装完成,只差完成最后一步了,安装GTK的命令如下:
至此,图形库 GTK 的安装过程已经全部完成,读者可以将 /vita/sysroot 目录下的文件系统更新到vita的根文件系统了。
10、安装GTK图形库的善后工作
更新了vita系统的根文件系统后,在运行使用 GTK 编写的程序前,我们还要在vita系统上为图形库做一点收尾工作。注意下面两个操作需要在使用安装了 GTK 图形库的根文件系统重启vita系统后进行。
(1)为 Pango 创建语系和模块对应关系的文件
不同语系,对布局有不同的要求,全世界有各种各样的语系,如汉语、阿拉伯语、印度语等。Pango 采用模块化的方式提供对这些语系的支持。为了提高效率,在运行时,Pango 不会到文件系统中解析具体的模块,查看其支持的语系,而是直接读取/etc/pango目录下的文件pango.modules,其中记录了每个模块及其支持的语系。因此,我们需要为Pango创建文件pango.modules 。
(2)为库 GdkPixbuf 创建模块信息文件
在安装库 GdkPixbuf 时,我们看到,GdkPixbuf 使用模块的形式支持各图形格式。因此,在这个库初始化时,需要加载这些模块。但是这些模块存储在文件系统的什么位置,每个模块又支持什么图形格式等,诸如此类信息从哪里获取呢?为了提高加载速度,GdkPixbuf 没有去再次扫描每个模块,而是直接从系统的一个文件中读取,因此,我们需要为 GdkPixbuf 创建这个文件。
11、一个简单的GTK程序
最后,我们使用一个简单的程序来测试我们的 GTK 是否工作正常,程序代码如下:
编译该程序的Makefile文件如下:
可见,同样是显示一个简单的窗口,使用 GTK 编写就简单多了,那些烦琐的细节已经实现在如 GTK 等这些图形库中。编译这个程序,并将其复制到 vita 系统并运行,步骤与程序 hello_x 完全相同。
十、安装字体
对于基于 Xlib 编写的程序,一般简单的字符使用X中的内置字体就可以应付了。X的内置字体在 libXfont 中:
其中 file_6x13 中记录的就是简单的点阵字体,又称位图字体,显然这个内置的点阵字体是把每一个字符都分成 6×23 个点,然后用每个点的虚实来表示字符的轮廓。
这也是为什么前面在没有安装字体的情况下,使用 Xlib 编写的例子可以显示字符的原因。但是既然有内置的字体,那为什么使用 GTK 的程序不能显示字符呢?原因是 GTK 程序的字体是在客户端绘制的,客户端绘制完成后,将字形位图传给 X 服务器。而 GTK 中并没有像 libXfont 那样内置了字体,所以如果系统中没有安装字体,当然应用就找不到字体了。因此,我们需要安装字体。
字体的安装非常简单,直接把字体文件复制到相关的目录下即可。但是安装在哪个目录下呢?前面我们已经看到,Linux 使用 Fontconfig 寻找字体,因此这个问题要问 Fontconfig 。没错,Fontconfig 在其配置文件中明确指明了其寻找字体文件的目录:
这里,我们使用文泉驿字体,并将其安装到vita系统的 /usr/share/fonts 目录下,命令如下:
安装完字体后,再次执行 gtk_hello,就会发现字符不再是一个一个的 “方框” 了。