linux图形界面又称x系统,其主要包含如下几个部分:
1) XServer
2) 显示管理器 (Display Manager) 例如(gdm kdm xdm等)
3) 窗口管理器 (Window Manager) 例如(metacity ,fluxbox等)
4) DM 和 WM之上的一些图形应用程序
在使用中一般都是2,3,4三者集合起来构成一个完整的集成工作环境,例如KDE ,GNOME等,这就是我们平时所说的广义上的xclient。
主要提供基本的显示接口共xclient使用,并将用户的操作等也反映给xclient,是xclient与硬件的一个中间层。xserver相关的两个主要部分是
xorg.conf是XServer的主要配置文件,它包含一个当前系统的硬件资源列表。X Server就是根据这些硬件资源“组织”出基本的图形能力。xorg.conf文件在/etc/X11/xorg.conf,主要包含几个字段:
在具有多个显示设备的系统中,可能有多个Screen和多个ServerLayout,用以实现不同的硬件搭配。在最近的xorg版本中,X Server已经开始自动侦测硬件,现在的xorg.conf已经都成了默认名称。具体细节还待查,但基本原理还是不变的。
X session是指X server启动后直到X server关闭之间的这段时间。这期间一切跟X相关的动作都属于X session的内容。管理X session的程序称为Display Manager,常听说的gdm或kdm就是gnome/kde所分别对应的Display Manager。
开启一个X session,也就是开始了图形界面的使用。在开启的过程中,Display Manager会对用户进行认证(也就是用户名密码的输入),运行事先设置好的程序(比如scim输入法就是这个时候启动的)等等。
这个开启过程要执行的一系列操作都可以在/etc/X11/Xseesion以及/etc/X11/Xsession.d/目录下看到,其他还有一些配置文件如Xsession.options, Xresource等,都是执行的X session的初始化过程。仔细阅读这些脚本或配置文件,可以帮助你更好地理解X
上面说过,Display Manager(后简称DM)是管理X session的程序,常见的有gdm, kdm, xdm等。对于默认进入X界面的Linux系统,必须将DM程序在开机时执行,即:/etc/rc2.d/S13gdm。下面我们从手工启动X的过程,看一下DM为我们做了哪些工作。
如果没有设置DM在开机时运行的话,手动启动X使用startx命令。
man startx 命令可以知道,startx的作用可以看作是Display Manager的一种隐性实现。它使用xinit命令,分别根据/etc/X11/xinit/xinitrc和/etc/X11/xinit/xserverrc中所指定的设置唤起X。其中,xserverrc执行X server的运行任务;xinitrc则运行Xsession命令。从/etc/X11/Xsession脚本的内容可以看出,它也就是进入/etc /X11/Xsession.d/目录轮询地执行所有脚本。很明显,这些也就是前面所说的Xsession初始化工作。
综合起来说,Display Manager完成三个任务:1, X Server的启动; 2, X session的初始化; 3, X session的管理。
X Server提供了基本的图形显示能力。然而具体怎么绘制应用程序的界面,却是要有应用程序自己解决的。而Window Manager(桌面管理器,后简称WM)就是用来提供统一的GUI组件的(窗口、外框、菜单、按钮等)。否则,应用程序们各自为政,既增加了程序开发的负担,不统一的桌面风格对视觉也是不小的挑战。WM的启动由DM控制,在gdm的登录窗口,我们可以进行选择。常见的WM有:Metacity(Gnome默认的WM), fluxbox, fvwm, E17等。
最后,就是X Client了。X客户端程序,顾名思义,就是使用X服务的程序。firefox,gedit等等都属于X Client程序。X Client部分值得考虑一下的就是DISPLAY环境变量。它主要用于远程X Client的使用。该变量表示输出目的地的位置,由三个要素组成:
[host]:display[.screen]
host指网络上远程主机的名称,可以是主机名、IP地址等。默认的host是本地系统,你可以在自己系统上echo $DISPLAY看一下。
display和screen分别代表输出画面的编号和屏幕的编号。具体细节由于硬件的缺乏,还有待进一步研究。
开始的一系列函数执行一些初始化及check的工作
InitRegions();
pixman_disable_out_of_bounds_workaround();
CheckUserParameters(argc, argv, envp);
CheckUserAuthorization();
InitConnectionLimits();
ProcessCommandLine(argc, argv);
随后main函数进入了一个死循环。每次循环均包含了一下三个阶段:
2.1 xserver初始化
2.2 xserver循环处理client消息
2.3 xserver退出
这是xserver的main函数最外层的循环,一般启动xserver只会执行一次循环:用户在图形界面操作时,实际上xserver是处在2.2阶段。这个循环就保证了xserver出现一般的异常时会自动恢复,比如在运行x时替换了其显卡驱动,xserver会触发异常结束第一次循环并在第二次循环中重新加载替换后的显卡驱动。以下分别对这三个阶段做解析:
xserver初始化函数非常多,以下仅粗略介绍几个比较熟悉的,初始化中有如下代码:
if(serverGeneration == 1) { CreateWellKnownSockets(); InitProcVectors(); for (i=1; i<MAXCLIENTS; i++) clients[i] = NullClient; serverClient = xalloc(sizeof(ClientRec)); if (!serverClient) FatalError("couldn't create server client"); InitClient(serverClient, 0, (pointer)NULL); } else ResetWellKnownSockets ();
当第一次循环时serverGeneration=1,执行的是第一个分支代码。CreateWellKnownSockets() 初始化一系列sockets监听是否有clients申请连接。InitProcVectors() 初始化ProcVector,SwappedProcVector结构。for循环是生成并初始化clients数组。之后便是serverClient变量的生成即初始化,serverClient是clients数组中索引为0的项,因为他是拥有root window的client。当之后的循环时serverGeneration = 0,执行的是ResetWellKnownSockets即重置sockets工作。
InitOutput()是初始化分量较中的一环,处理过程可以分为如下部分:
2.1.1) xf86HandleConfigFile 解析xorg.con文件 ,获得xserver的配置信息。
2.1.2)xf86BusProbe 获得video的pci信息,例如framebuffer地址等。
2.1.3)DoConfigure() 根据配置文件 ,或者传进来的参数做相应的配置
2.1.4)xf86LoadModules load xorg.conf中配置的一系列模块
2.1.5)以此遍历注册的各个driver,调用其identify,probe函数, 这样就根据显卡的型号加载了相应的驱动
2.1.6)匹配screen,主要是根据xorg.conf中配置的screen,查询是否有与其匹配的device
2.1.7)遍历screen,调用其匹配device驱动的PreInit函数。这样就完成了显卡驱动的预初始化
2.1.8)遍历screen,调用AddScreen函数,分配screenInfo.screen[]的一项,并做初始化ScreenInit.这样驱动的初始化基本完成。
InitInput()是初始化输入设备,例如键盘和鼠标等。如果xorg.conf中有Section InputDevice配置,会按照其配置扫描加载设备
在初始化结束之后xserver便进入了循环处理阶段即Dispatch()函数。该函数的流程主要是一个循环结构while (!dispatchException),即当不出现异常时循环会不断进行下去,每一次循环可以分为如下部分:
2.2.1 接受用户的输入,并发送给client
if (*icheck[0] != *icheck[1])
{
ProcessInputEvents();
FlushIfCriticalOutputPending();
}
2.2.2 等待clients发送事件过来
nready = WaitForSomething(clientReady);
2.2.3 遍历每个发送信息的client,做如下处理:
1)接受用户输入并发送 if (*icheck[0] != *icheck[1]) ProcessInputEvents(); FlushIfCriticalOutputPending(); 2)获得client的请求号 result = ReadRequestFromClient(client); 3) 根据请求号调用队列中相应的处理函数 if (result > (maxBigRequestSize << 2)) result = BadLength; else { result = XaceHookDispatch(client, MAJOROP); if (result == Success) result = (* client->requestVector[MAJOROP])(client); XaceHookAuditEnd(client, result); } 4) 提交处理结果 FlushAllOutput();
由此Dispatch函数解析结束