1. 程式人生 > >xorg-xserver相關完全解析

xorg-xserver相關完全解析

本文主要是從以下幾個方面介紹xorg-xserver 相關的知識
1.linux系統圖形介面框架
2.xserver 和x client啟動過程
3.圖形2d,3d加速原理簡介
4.xserver主分支程式碼解析。
5.xserver,xclient協議簡介
6.一個基於Xlib的簡單例子解析
7.radeon驅動初始化程式碼解析.

1.linux圖形介面框架
參考至:http://dzdl.ipchina.org/site/?uid-9-action-viewspace-itemid-49
linux圖形介面又稱x系統,其主要包含如下幾個部分:
a)xserver
b)顯示管理器 (Display Manager) 例如(gdm kdm xdm等)
c)視窗管理器 (Window Manager) 例如(metacity ,fluxbox等)
d)DM 和 WM之上的一些圖形應用程式
在使用中一般都是b,c,d三者集合起來構成一個完整的整合工作環境,例如KDE ,GNOME等
,這就是我們平時所說的廣義上的xclient

a)xserver 主要提供基本的顯示介面共xclient使用,並將使用者的操作等也反映給xclient,
是xclient與硬體的一箇中間層。xserver相關的兩個主要部分是
(1) xorg.conf
xorg.conf是X Server的主要配置檔案,它包含一個當前系統的硬體資源列表。X Server就是根據這些硬體資源“組織”出基本的圖形能力。xorg.conf檔案在/etc/X11/xorg.conf,主要包含幾個欄位:
Files: X系統使用的字型存放目錄(字型的具體使用由FontConfig工具主持)
InputDevice: 輸入裝置,如鍵盤滑鼠的資訊
Monitor: 顯示器的設定,如解析度,重新整理率等
Device: 顯示卡資訊
Screen: 由Monitor和Device組裝成一個Screen,表示由它們向這個Screen提供輸出能力
ServerLayout: 將一個Screen和InputDevice組裝成一個ServerLayout
在具有多個顯示裝置的系統中,可能有多個Screen和多個ServerLayout,用以實現不同的硬體搭配。
在最近的xorg版本中,X Server已經開始自動偵測硬體,現在的xorg.conf已經都成了預設名稱。具體細節還待查,但基本原理還是不變的。
(2) X session(X會話)
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

b), Display Manager
上面說過,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的管理。

c), Window Manager
X Server提供了基本的圖形顯示能力。然而具體怎麼繪製應用程式的介面,卻是要有應用程式自己解決的。而Window Manager(桌面管理器,後簡稱WM)就是用來提供統一的GUI元件的(視窗、外框、選單、按鈕等)。否則,應用程式們各自為政,既增加了程式開發的負擔,不統一的桌面風格對視覺也是不小的挑戰。
WM的啟動由DM控制,在gdm的登入視窗,我們可以進行選擇。常見的WM有:Metacity(Gnome預設的WM), fluxbox, fvwm, E17等。

d), X Clients
最後,就是X Client了。X客戶端程式,顧名思義,就是使用X服務的程式。firefox,gedit等等都屬於X Client程式。X Client部分值得考慮一下的就是DISPLAY環境變數。它主要用於遠端X Client的使用。該變量表示輸出目的地的位置,由三個要素組成:
[host]:display[.screen]
host指網路上遠端主機的名稱,可以是主機名、IP地址等。預設的host是本地系統,你可以在自己系統上echo $DISPLAY看一下。
display和screen分別代表輸出畫面的編號和螢幕的編號。具體細節由於硬體的缺乏,還有待進一步研究。

2.xserver 和x client啟動過程
參考:http://blog.csdn.net/clozxy/archive/2010/04/15/5488699.aspx

對xserver和x client的啟動過程的探討主要是對startx命令的探討
startx指令碼網上解釋的很多,這裡就不多做介紹,對startx介紹分以下兩個部分
(1)xinit用法
startx其實是個指令碼,最終呼叫的是xinit命令,其用法如下:
xinit 的用法為: xinit [[client] options ] [-- [server] [display] options] 。其中 client 用於指定一個基於 X 的應用程式, client 後面的 options 是傳給這個應用程式的引數, server 是用於指定啟動哪個 X 伺服器,一般為 /usr/bin/X 或 /usr/bin/Xorg , display 用於指定 display number ,一般 為 0 ,表示第一個 display , option 為傳給 server 的引數。

如果不指定 client , xinit 會查詢 HOME ( 環境變數 ) 目錄下的 .xinitrc 檔案,如果存在這個 檔案, xinit 直接呼叫 execvp 函式執行該檔案。如果這個檔案不存在,那麼 client 及其 options 為: xterm -geometry +1+1 -n login -display :0 。

如果不指定 server , xinit 會查詢 HOME( 環境變數 ) 目錄下的 .xserverrc 檔案,如果存在這個檔案, xinit 直接呼叫 execvp 函式執行該檔案。如果這個檔案 不存在,那麼 server 及其 display 為: X :0 。如果系統目錄中不存在 X 命令,那麼我們需要在系統目錄下建立一個名為 X 的連結,使其指向真正的 X server 命令( Ubuntu 下為 Xorg )。

因此startx的用法跟xinit一樣:startx [ [ client ] options … ] [ – [ server ] options … ]

(2)startx的幾種啟動方式
由對 startx 指令碼的分析,我們可以知道 startx 主要有三種啟動方式:
a) 、一種是自己指定要啟動的 client 和 server , 例如: startx /usr/bin/xclock – /usr/bin/X :0 ;
b)、一種是通過在 $HOME 下新建 .xinitrc 檔案來指定要啟動的多個 client 和 .xserverrc 來指定要啟動的 server;
c)、還有一種是直接輸入 startx 而不指定引數,這也就是我們啟動 gnome 桌面的方法。

在 c 這種啟動方法中, startx 指令碼會先去看系統目錄( /etc/X11/xinit/ )下的 rc 檔案是否存在,如果不存在就會用預設的 xterm 和 /usr/bin/X 來啟動 xinit 。顯然, startx 啟動的不是 xterm ,而是 gnome 桌面,因此 gnome 的啟動是通過系統檔案 /etc/X11/xinit/xinitrc 來指定的。

而 /etc/X11/xinit/xinitrc 檔案的內容如下所示:

#!/bin/bash  # 注意 : 該指令碼用的是 bash shell 解析的

# $Xorg: xinitrc.cpp,v 1.3 2000/08/17 19:54:30 cpqbld Exp $

# /etc/X11/xinit/xinitrc
#
# global xinitrc file, used by all X sessions started by xinit (startx)

# invoke global X session script
. /etc/X11/Xsession   # 在當前這個 shell 環境中執行 Xsession 指令碼

因此, gnome 的啟動應該在 Xsession 裡。

而 X Server 的啟動則是通過系統檔案 /etc/X11/xinit/xserverrc 來指定的 , 這個檔案的內容為 :

#!/bin/sh # 注意:該指令碼用的是 Bourne shell 解析的

# $Id: xserverrc 189 2005-06-11 00:04:27Z branden $

exec /usr/bin/X11/X -nolisten tcp

綜上所述, startx 的預設啟動過程為: startx 呼叫並將系統檔案 /etc/X11/xinit/xinitrc 和 /etc/X11/xinit/xserverrc 作為引數傳給 xinit , xinit 就會先執行系統檔案 /etc/X11/xinit/xserverrc 以啟動 X Server ,然後執行 /etc/X11/xinit/xinitrc ,而 xinitrc 則會執行指令碼 /etc/X11/Xsession ,而 Xsession 則會按順序呼叫執行 /etc/X11/Xsession.d 目錄下的檔案,從而最終呼叫了 gnome-session 這個用於 啟動 GNOME 桌面環境的程式

3.圖形2d,3d加速簡介
為了是linux下圖形更加流暢,必須使用加速。常用的加速方法如下
加速常見有三種方式
a)ShadowFB
ShadowFB是xserver自帶的與體系結構無關的2D加速方式,它將系統framebuffer複製一份,並且在拷貝回framebuffer中實現圖形旋轉等操作,這樣可以起到一定加速作用,但是效果不好。
b) XAA
XAA全稱XFree86 Acceleration Architecture,是由 Harm Hanemaayer 在1996年寫的一個顯示卡硬體2D加速的驅動結構,目前大多數的顯示卡去動均支援這種驅動模式
c) EXA
EXA是X.Org發起的用於取代XAA加速的驅動結構,修改的宗旨是是XRender更加好用。
歷史上對2D 和3D加速已經做了區分,2D加速主要使用的是XAA結構,3D加速主要是通過DRM(Direct Rendering Manage) 提供.而EXA提供了比XAA更好整合XRender的結構,同時也提高了XAA的2D加速效果。
EXA採用的方法是通過實現對OpenGL的加速以實現同時對2D,3D影象的加速,這樣2D影象就可以看作是3D影象的一個子集。

4.xserver 主分支程式碼解析
參考網站:http://xwindow.angelfire.com
基於xorg-xserver-1.7.6版本
xserver程式碼是從dix/main.c中的main函式開始執行。
開始的一系列函式執行一些初始化及check的工作
InitRegions();
pixman_disable_out_of_bounds_workaround();
CheckUserParameters(argc, argv, envp);
CheckUserAuthorization();
InitConnectionLimits();
ProcessCommandLine(argc, argv);

隨後main函式進入了一個死迴圈。每次迴圈均包含了
a)xserver初始化
b)xserver迴圈處理client訊息
c)xserver退出
三個階段
這是xserver的main函式最外層的迴圈,一般啟動xserver只會執行一次迴圈:使用者在圖形介面操作時,實際上xserver是處在b)階段。
這個迴圈就保證了xserver出現一般的異常時會自動恢復,比如在執行x時替換了其顯示卡驅動,xserver會觸發異常結束第一次迴圈
並在第二次迴圈中重新載入替換後的顯示卡驅動。

以下分別對這三個階段做解析
a)xserver初始化
xserver初始化函式非常多,以下僅粗略介紹幾個比較熟悉的:

(1)
初始化中有如下程式碼:
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工作。

(2)
InitOutput()是初始化分量較中的一環,處理過程可以分為如下部分:
1)xf86HandleConfigFile 解析xorg.con檔案 ,獲得xserver的配置資訊。
2)xf86BusProbe 獲得video的pci資訊,例如framebuffer地址等。
3)DoConfigure() 根據配置檔案 ,或者傳進來的引數做相應的配置
4)xf86LoadModules load xorg.conf中配置的一系列模組
5)以此遍歷註冊的各個driver,呼叫其identify,probe函式, 這樣就根據顯示卡的型號載入了相應的驅動
6)匹配screen,主要是根據xorg.conf中配置的screen,查詢是否有與其匹配的device
7)遍歷screen,呼叫其匹配device驅動的PreInit函式。這樣就完成了顯示卡驅動的預初始化
8)遍歷screen,呼叫AddScreen函式,分配screenInfo.screen[]的一項,並做初始化ScreenInit.這樣驅動的初始化基本完成。

(3)
InitInput()是初始化輸入裝置,例如鍵盤和滑鼠等。如果xorg.conf中有Section InputDevice配置,會按照
其配置掃描載入裝置

b)xserver迴圈處理client訊息
在初始化結束之後xserver便進入了迴圈處理階段即
Dispatch()函式

該函式的流程主要是一個迴圈結構
while (!dispatchException)
即當不出現異常時迴圈會不斷進行下去
每一次迴圈可以分為如下部分
(1)接受使用者的輸入,併發送給client
if (*icheck[0] != *icheck[1])
{
ProcessInputEvents();
FlushIfCriticalOutputPending();
}

(2)等待clients傳送事件過來
nready = WaitForSomething(clientReady);

(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)若處理函式返回異常則做異常處理
if (result != Success)
{
if (client->noClientException != Success)
CloseDownClient(client);
else
SendErrorToClient(client, MAJOROP,
MinorOpcodeOfRequest(client),
client->errorValue, result);
break;
}
}
5)提交處理結果
FlushAllOutput();

由此Dispatch函式解析結束

c)xserver退出
包含了一系列釋放記憶體,關閉clients等操作,這裡就不多做解析。

5.xserver,xclient協議簡介
由上文對Dispatch函式的分析可以看出,xserver對client的處理主要是三步:
(1)獲得事件資訊
nready = WaitForSomething(clientReady);
(2)獲得操作號
result = ReadRequestFromClient(client);
(3)根據操作號處理
result = (* client->requestVector[MAJOROP])(client);

因此其操作號和操作的對應是xserver與client的協議的一部分,類似操作
系統的系統呼叫號和系統呼叫之間的關係。

在上面介紹InitClients()中有對requestVector初始化
client->requestVector = InitialVector;

InitVector如下:
int (* InitialVector[3]) (
ClientPtr
) =
{
0,
ProcInitialConnection,
ProcEstablishConnection
};

其只有兩個函式,一個是初始化Connection,一個是確立Connection
在ProcEstablishConnection中呼叫SendConnSetup寒酸,
SendConnSetup函式有:
client->requestVector = client->swapped ? SwappedProcVector : ProcVector;
即初始化requestVector為SwappedProcVector或ProcVector
ProcVector如下:
_X_EXPORT int (* ProcVector[256]) (
ClientPtr
) =
{
ProcBadRequest,
ProcCreateWindow,
ProcChangeWindowAttributes,
ProcGetWindowAttributes,
ProcDestroyWindow,
ProcDestroySubwindows,
ProcChangeSaveSet,
ProcReparentWindow,
ProcMapWindow,
ProcMapSubwindows,
ProcUnmapWindow,
。。。。。。。。。。。。。
ProcGetModifierMapping,
0,
0,
0,
0,
0,
0,
0,
ProcNoOperation
};
SwappedProcVector類似。

也就是說Client與server互動時,先按照固定的協議初始化Connector,並且告訴xserver其適合的協議。
然後server按照該協議解析client傳送過來的操作號。

6.一個基於Xlib的簡單例子瞭解Client流程
Xlib是對X協議的的一個簡單的封裝,可以讓程式設計師不用瞭解細節而編寫圖形相關程式。實際上程式設計師直接呼叫Xlib的很少,更多使用的是
GTK+ ,QT等圖形庫。這些又是基於Xlib的圖形庫。
一個簡單的Xlib例子如下

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
Display *d;
Window w;
XEvent e;
char *msg = “Hello, World!”;
int s;

d = XOpenDisplay(NULL);
if (d == NULL) {
fprintf(stderr, “Cannot open display\n”);
exit(1);
}

s = DefaultScreen(d);

w = XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 100, 100, 1,
BlackPixel(d, s), WhitePixel(d, s));

XSelectInput(d, w, ExposureMask | KeyPressMask);

XMapWindow(d, w);

while (1) {

 XNextEvent(d, &e);


 if (e.type == Expose) {
   XFillRectangle(d, w, DefaultGC(d, s), 20, 20, 10, 10);
   XDrawString(d, w, DefaultGC(d, s), 50, 50, msg, strlen(msg));
 }

 if (e.type == KeyPress)
   break;

}

XCloseDisplay(d);

return 0;
}
這個程式就可以看作一個簡單的client,包含client的大體流程。
編譯: gcc input.c -o output -lX11
程式執行方式有兩種:
1.在圖形介面下直接執行程式
2.在使用者目錄下新建一個.xinitrc檔案,寫入
exec input
之後startx,執行的不是預設的圖形介面程式而是input程式

7.radeon驅動初始化程式碼解析.
由上面對xserver初始化的介紹,可以看到,在初始化過程中主要是顯示卡驅動的三個函式的呼叫
Probe , PreInit , ScreenInit
以下以radeon驅動為例(xorg-xserver-video-ati-6.13.1),介紹驅動對顯示卡的初始化過程,以及圖形加速中使用的函式。

(1)Probe函式
在radeon驅動中,probe函式主要是
static Bool
radeon_pci_probe(
DriverPtr pDriver,
int entity_num,
struct pci_device *device,
intptr_t match_data
)
{
return radeon_get_scrninfo(entity_num, (void *)device);
}

在radeon_get_scrninfo函式中有:主要是對pScrn和pENT的初始化。
在pScrn的初始化中給出了將要呼叫的PreInit 和ScreenInit函式
#ifdef XF86DRM_MODE
if (kms == 1) {
pScrn->PreInit = RADEONPreInit_KMS;
pScrn->ScreenInit = RADEONScreenInit_KMS;
pScrn->SwitchMode = RADEONSwitchMode_KMS;
pScrn->AdjustFrame = RADEONAdjustFrame_KMS;
pScrn->EnterVT = RADEONEnterVT_KMS;
pScrn->LeaveVT = RADEONLeaveVT_KMS;
pScrn->FreeScreen = RADEONFreeScreen_KMS;
pScrn->ValidMode = RADEONValidMode;
} else
#endif
{
pScrn->PreInit = RADEONPreInit;
pScrn->ScreenInit = RADEONScreenInit;
pScrn->SwitchMode = RADEONSwitchMode;
pScrn->AdjustFrame = RADEONAdjustFrame;
pScrn->EnterVT = RADEONEnterVT;
pScrn->LeaveVT = RADEONLeaveVT;
pScrn->FreeScreen = RADEONFreeScreen;
pScrn->ValidMode = RADEONValidMode;
}
不妨已RADEONPreInit_KMS , RADEONScreenInit_KMS為例介紹驅動PreInit和ScreenInit過程

(2)PreInit
RADEONPreInit_KMS在結構上大體可以分為三個部分(雖然不嚴格),
a)pScrn->driverPrivate的初始化
例如:
info = RADEONPTR(pScrn);
info->pEnt = xf86GetEntityInfo(pScrn->entityList[pScrn->numEntities - 1]);
f (!radeon_alloc_dri(pScrn))
return FALSE;

其實對pScrn->driverPrivate的初始化貫穿了整個PreInit,但是在前面比較集中。

b)drm的初始化
radeon_open_drm_master(pScrn)
呼叫drmOpen開啟核心drm裝置

drmmode_pre_init(pScrn, &info->drmmode, pScrn->bitsPerPixel / 8)
drmCommandWriteRead(info->dri->drmFD, DRM_RADEON_GEM_INFO, &mminfo, sizeof(mminfo))
等做其他方面的初始化

c)一些相關模組的load
例如:
xf86LoadSubModule(pScrn, “fb”)
load framebuffer相關的so

!xf86LoadSubModule(pScrn, “ramdac”)
load 與游標顯示相關模組

RADEONPreInitAccel_KMS(pScrn)
根據加速方式選擇決定load shadowfb 還是exa模組

細節很多大體上可以分這三個部分理解

(3)ScreenInit
RADEONScreenInit_KMS要比RADEONPreInit_KMS雜亂
但也可以看作如下幾個部分
a)對pScrn->driverPrivate的比較集中的初始化
例如:
info->bufmgr = radeon_bo_manager_gem_ctor(info->dri->drmFD);
info->cs = radeon_cs_create(info->csm, RADEON_BUFFER_SIZE/4);
等比較明顯的
以及
radeon_setup_kernel_mem(pScreen);
初始化地址對映相關的info資訊

b)fbScreenInit
初始化framebuffer資訊

c) 顯示影象畫素相關的初始化及fbPictureInit
例如:
if (pScrn->bitsPerPixel > 8) {
VisualPtr visual;

    visual = pScreen->visuals + pScreen->numVisuals;
    while (--visual >= pScreen->visuals) {
        if ((visual->class | DynamicClass) == DirectColor) {
            visual->offsetRed   = pScrn->offset.red;
            visual->offsetGreen = pScrn->offset.green;
            visual->offsetBlue  = pScrn->offset.blue;
            visual->redMask     = pScrn->mask.red;
            visual->greenMask   = pScrn->mask.green;
            visual->blueMask    = pScrn->mask.blue;
        }
    }
}


fbPictureInit (pScreen, 0, 0);

#ifdef RENDER
if ((s = xf86GetOptValString(info->Options, OPTION_SUBPIXEL_ORDER))) {
if (strcmp(s, “RGB”) == 0) subPixelOrder = SubPixelHorizontalRGB;
else if (strcmp(s, “BGR”) == 0) subPixelOrder = SubPixelHorizontalBGR;
else if (strcmp(s, “NONE”) == 0) subPixelOrder = SubPixelNone;
PictureSetSubpixelOrder (pScreen, subPixelOrder);
}
#endif
這部分是fbPictureInit和對畫素RGB順序的初始化

d)BackStore相關的初始化
例如:
xf86DrvMsgVerb(pScrn->scrnIndex, X_INFO, RADEON_LOGLEVEL_DEBUG,
“Initializing backing store\n”);
miInitializeBackingStore(pScreen);
xf86SetBackingStore(pScreen);

e)加速函式相關的初始化
例如:
if (info->r600_shadow_fb) {
xf86DrvMsg(scrnIndex, X_INFO, “Acceleration disabled\n”);
info->accelOn = FALSE;
} else {
xf86DrvMsgVerb(pScrn->scrnIndex, X_INFO, RADEON_LOGLEVEL_DEBUG,
“Initializing Acceleration\n”);
if (RADEONAccelInit(pScreen)) {
xf86DrvMsg(scrnIndex, X_INFO, “Acceleration enabled\n”);
info->accelOn = TRUE;
} else {
xf86DrvMsg(scrnIndex, X_ERROR,
“Acceleration initialization failed\n”);
xf86DrvMsg(scrnIndex, X_INFO, “Acceleration disabled\n”);
info->accelOn = FALSE;
}
}
中的RADEONAccelInit(pScreen)函式
下面會對RADEONAccelInit(pScreen)函式做仔細的分析

f)游標顯示相關的初始化
例如:

xf86DrvMsgVerb(pScrn->scrnIndex, X_INFO, RADEON_LOGLEVEL_DEBUG,
               "Initializing DPMS\n");
xf86DPMSInit(pScreen, xf86DPMSSet, 0);

xf86DrvMsgVerb(pScrn->scrnIndex, X_INFO, RADEON_LOGLEVEL_DEBUG,
               "Initializing Cursor\n");


xf86SetSilkenMouse(pScreen);


miDCInitialize(pScreen, xf86GetPointerScreenFuncs());

if (!xf86ReturnOptValBool(info->Options, OPTION_SW_CURSOR, FALSE)) {
    if (RADEONCursorInit_KMS(pScreen)) {
    }
}

其中xf86ReturnOptValBool(info->Options, OPTION_SW_CURSOR, FALSE)的判斷決定對游標顯示是否使用硬體加速

g)其他的初始化
例如CloseScreen,BlockHandler 等變數賦值
Crtc初始化xf86CrtcScreenInit (pScreen)
和colormap相關的drmmode_setup_colormap(pScreen, pScrn)。

(4)RADEONAccelInit
需要重點介紹的是RADEONAccelInit函式,因為在這個函式中引入了初始化影象加速相關的函式

以筆者除錯過的RS780為例:
其呼叫的圖形加速相關的初始化是R600DrawInit(pScreen)函式,因為驅動不支援RS780的xaa加速,而軟體加速shodowfb效果不好,必須使用exa加速。
R600DrawInit()函式中包含了眾多加速函式的初始化其中最重要的是如下5系列函式

a)Solid相關的函式
info->accel_state->exa->PrepareSolid = R600PrepareSolid;
info->accel_state->exa->Solid = R600Solid;
info->accel_state->exa->DoneSolid = R600DoneSolid;
Solid即是向某一區域填充色的操作

b)Copy相關的函式
info->accel_state->exa->PrepareCopy = R600PrepareCopy;
info->accel_state->exa->Copy = R600Copy;
info->accel_state->exa->DoneCopy = R600DoneCopy;
Copy是不同區域直接拷貝的函式

c)Composite函式
info->accel_state->exa->CheckComposite = R600CheckComposite;
info->accel_state->exa->PrepareComposite = R600PrepareComposite;
info->accel_state->exa->Composite = R600Composite;
info->accel_state->exa->DoneComposite = R600DoneComposite;
Composite是不同視窗組合在一起的操作

d)UploadToScreen函式
info->accel_state->exa->UploadToScreen = R600UploadToScreenCS;
UploadToScreen是向framebuffer拷貝矩形域資料的函式

e)DownloadFromScreen函式
info->accel_state->exa->DownloadFromScreen = R600DownloadFromScreenCS;
DownloadFromScreen是從framebuffer拷貝出矩形域資料的函式

至此radeon驅動初始化相關的內容做了一次簡單的瀏覽。

X Window其實是一種規範,它有很多不同的實現,在Linux系統下最流行的是實現Xorg和XFree86,微軟Windows系統下也有X Window的實現,蘋果的Mac也是X Window的一種。要了解自己機器上執行的X Window究竟是哪一個,可以使用檢視程序的ps命令
#ps –e |grep tty
  Ubuntu 14.04使用的X Window是Xorg。如果使用ps -ef命令,還可以看到Xorg執行時的命令列引數。
在桌面系統上,X Server和Client程式往往安裝在同一臺機器上,日常使用基本感覺不到它是分層的。但是很顯然,X Server和Client也可以分別執行在不同的機器上,在一臺機器上執行程式,而在另外一臺機器上顯示圖形介面。
在X Window中,Server偏偏是我面前的這臺Ubuntu,X Server執行在Ubuntu上。我可以在CentOS中執行GVim,但是視窗顯示在Ubuntu中,這時,GVim是一個Client程式,它在遠端機器上執行,而它的視窗顯示在本地。
理解display和虛擬控制檯
  很多介紹X Window的文章都是先讓系統進入字元介面,然後手動啟動一個X Server。其實這完全沒有必要,因為在同一臺機器上完全可以執行多個X Server,只需要讓每個X Server的display不同即可。那麼display究竟是什麼?
  在X Window中,可以通過hostname:display_number.screen_number來指定一個螢幕。可以這樣理解:一臺計算機可以有多個display,一個display可以有多個螢幕。所以,display相當於是計算機配備的一套輸入輸出裝置,一般情況下,一臺電腦只配一套鍵盤滑鼠和一個顯示器,特殊情況下,可以配多個顯示器。
  現在問題出來了,我的電腦只有一套鍵盤滑鼠和一個顯示器,也就是隻有一個display,那又怎麼能執行多個X Server呢?那是因為在Linux中,還有虛擬控制檯這樣的高階特性。只需要同時按下Ctrl+Alt+F1、Ctrl+Alt+F2、…、Ctrl+Alt+F7,就可以在不同的虛擬控制檯中進行切換。在Ubuntu 14.04中,虛擬控制檯1到6執行的getty,也就是字元介面,虛擬控制檯7執行的是Xorg。(Fedora/centos中不一樣,虛擬控制檯1執行的是圖形介面,其它的是字元介面。display:1執行在tty7上)
  我們可以直接執行X Server程式來啟動X Server。/usr/bin/X和Xorg都是X Server程式。其實/usr/bin/X是Xorg的符號連結,用哪一個都是一樣的。
啟動X Server的時候可以指定display引數,因為可以省略掉hostname和screen_number,所以可以用:0,:1這樣的格式來指定display。在我的機器上,本來就有一個X Server在執行,display :0已經被佔用了,所以我使用sudo X :1 -retro來在display :1上再執行一個X Server
  其中的-retro引數是為了讓X Server的背景顯示為斜紋,否則背景為純黑色,那就看不出來是否啟動了X Server。
  按Ctrl+Alt+F7回到display :0(ubuntu),再用ps命令看一下,會發現系統中有兩個Xorg在執行,一個執行在虛擬控制檯7,一個執行在虛擬控制檯8。
在新啟動的X Server中執行一個GVim看看效果。執行GVim時,使用-display :1引數指定視窗顯示在新啟動的X Server上,使用-geometry引數指定視窗的大小和位置。然後按Ctrl+Alt+F8切換虛擬控制檯,看效果。
#gvim –display :1 –geometry 700X500+20+20
遠端連線X Server
如果能讓遠端機器上的GVim也把視窗顯示在本地機器的螢幕上,那就比較過癮了。所以,使用ssh連線到CentOS-5.10,然後使用gvim -display Ubuntu-14:1命令,希望將GVim顯示到Ubuntu的display :1上。由於是遠端連線,所以hostname不能省略,需寫成ubuntu:1,也可以使用IP地址,寫成192.168.1.103:1。
#gvim –display ubuntu:1
  很可惜,連線失敗。
  失敗的原因是遠端訪問X Server需要安全認證。這個可以理解,就像登陸郵箱需要輸入使用者名稱和密碼一樣,如果X Server不要認證就可以隨便連線的話,那豈不是桌面上垃圾視窗滿天飛?安全認證的方式有很多種,具體請參考man Xsecurity。安全認證可以使用xhost和xauth這兩個程式來進行,具體使用方法參考它們的文件。(同時注意防火牆#iptables –F清除所有規則)
先用xhost來授權,這個比較簡單。為了執行xhost,需要在X Server上有一個終端,所以執行一個xterm
#xterm –display :1
  在xterm中輸入sodu xhost +192.168.1.109,這樣,CentOS-5.10中執行的GUI程式都可以連線到這個新開啟的X Server了。(注每一個display的安全和環境都不一樣,哪一個display需遠端聯接,哪個就需要授一次權)
在CentOS-5.10中執行GVim
#gvim –display 192.168.1.103:1
  新啟動的X Server介面比較醜陋,我們還是想讓遠端機器上的GUI程式直接顯示在Ubuntu的桌面環境中。所以,指定display為:0
  結果很不幸,無法開啟display。連線不上,為什麼呢?是安全認證的問題嗎?不是,是lightdm的問題,請繼續往下看。
理解lightdm和X Window桌面環境的啟動過程
  X Server的啟動方式有兩種,一種是通過顯示管理器啟動,另一種是手動啟動。在前面的例子中,我通過直接執行/usr/bin/X :1來啟動了一個X Server。直接啟動X Server的方法還有執行startx或者xinit。手動啟動X Server的缺點就是啟動的X Server不好看。而顯示管理器啟動的不僅有X Server,還有一大堆的Client程式,構成了一個完整的桌面環境,介面當然就漂亮多了。
顯示管理器(Display Manager)是什麼呢?前面我講到display就是一個電腦配備的一套鍵盤滑鼠和顯示器,那麼顯示管理器就是這一套裝置的管理器了。顯示管理器可以直接管理這些裝置,所以它可以控制X Server的執行,由它來啟動X Server那是再合適不過了。系統啟動過程是這樣的:核心載入–>init程式執行–>顯示管理器執行–>X Server執行–>顯示管理器連線到X Server,顯示登入介面–>使用者登入後,登入介面關閉,載入桌面環境。從上面的流程可以看出,顯示管理器是X Server的父程序,它負責啟動X Server,當X Server啟動後,它又變成了X Server的一個Client程式,連線到X Server顯示歡迎介面和登入介面,最後,顯示管理器又是所有桌面環境的父程序,它負責啟動桌面環境需要的其它Client程式。

在Ubuntu 14.04中,使用lightdm取代了傳統的xdm、gdm等顯示管理器。簡單來說,就是由lightdm負責啟動X Server和其它的X程式。不知道為什麼,lightdm在啟動X Server的時候,給X Server加上了-nolisten tcp引數,所以遠端計算機就沒有辦法連線到Ubuntu的桌面了。

在debian/kali linux的環境中edit /usr/share/gdm/gdm.schemas, setting the key security/DisallowTCP to false

開啟新的使用X :1 –listen tcp 因為預設是不開啟tcp埠的,只打開6000一個埠。

下一步的目標就是更改lightdm的配置,去掉這個-nolisten tcp引數。
在使用sudo dpkg -L lightdm檢視該軟體包的檔案時,發現它的log檔案放在/var/log/lightdm資料夾下,過去看看:
  終於,從log檔案中看到了lightdm啟動的全過程。首先,看到它從哪幾個目錄載入配置檔案,接著,看到它啟動X Server。從下圖游標所在的行可以看到X Server啟動的所有引數,包括-nolisten tcp選項。
  繼續看log檔案,下面游標所在的行顯示lightdm怎麼啟動gnome-session:
  同時,我發現/etc/lightdm/目錄下沒有lightdm.conf檔案,而/usr/share/doc/lightdm/目錄下有一個lightdm.conf.gz檔案,把該檔案當文件看了一下,發現裡面果然就是lightdm的配置的解釋。趕快將該檔案複製到/etc/lightdm/目錄下並解壓,然後用Vim編輯/etc/lightdm/lightdm.conf檔案,將xserver-allow-tcp=false一行前面的註釋去掉,並且改為xserver-allow-tcp=true。如下圖:
  最後,重啟系統。再用ps檢視程序,發現-nolisten tcp選項已經沒有了。
搞定xauth
  搞定了-nolisten tcp之後,要想從遠端計算接連線到Ubuntu桌面,還是需要安全認證。在前面的例子中,我使用了xhost。xhost是最簡單的認證方式。在這裡我要試一下別的認證方式,比如MIT-MAGIC-COOKIE-1。如上圖,先使用xauth list命令檢視一下當前的授權記錄,發現只有一條,而且display是ubuntu-14/unix:0,很顯然,這是一個本地授權,所以需要使用xauth add命令新增一個使用ip地址的授權,後面的key照抄就行了。最後,使用xauth extract和xauth merge配合管道和ssh將該授權記錄合併到CentOS-5.10中。
  在CentOS-5.10中啟動GVim,指定display為192.168.1.103:0,GVim視窗就出現在了Ubuntu中。
X Server的配置
  可以使用不同的方法對X Server進行配置,前面的例子是直接指定命令列引數。除了指定命令列引數,還可以使用環境變數和配置檔案。X Server的配置檔案為一般是/etc/X11/xorg.conf或/etc/X11/xorg.conf.d/目錄下的.conf檔案,當然,配置檔案也可以放在其它的目錄中,具體資訊,請參看man xorg.conf。
  如果沒有配置檔案,X Server將在啟動的時候自動檢測硬體,然後生成一個內建的配置。Ubuntu系統就沒有配置檔案。不過沒關係,如果需要使用配置檔案的時候,可以通過X Server的-configure引數生成一個配置檔案,裡面包含當前自動檢測出的配置。如果需要任何個性化的配置,對該檔案進行修改即可。
現有的圖形介面中可以執行巢狀的X Server
  我們上面執行的X Server都是直接佔用了計算機的整個顯示器和鍵盤滑鼠,事實上,在現有的圖形介面中,還可以以視窗模式執行另外一個X Server,稱為nested X Server。最常用的nested X Server是Xephyr,在Ubuntu中可以通過如下命令安裝它:
sudo aptitude install xserver-xephyr (yum install Xephyr)
Xephyr的使用非常簡單,可以通過 man Xephyr 命令檢視它的使用手冊。如果輸入 Xephyr :1 (display號不能和已用dsiplay號衝突)命令,就可以在現有圖形介面中開啟一個視窗模式的X Server
  以後再啟動GUI程式,就可以通過程式的-display選項讓程式執行在這個巢狀的X Server中,如:#xterm –display :1 &
  怎麼樣,是不是很好玩呢?除了好玩,還很有用,比如除錯視窗管理器啊、連線遠端桌面啊什麼的都用得著。當然,我這裡只是簡單展示一下原來X Window還可以這麼玩。
總結:
  1.在一個Linux系統中存在多個虛擬控制檯,所以可以啟動多個X Server;
  2.啟動X Server的方式有兩種,一種是使用/usr/bin/X、startx、xinit手動啟動,一種是通過顯示管理器啟動;
  3.Ubuntu使用的顯示管理器是lightdm,這是一個比較新的、輕量級的顯示管理器,但是文件不夠詳細;
  4.遠端計算機連線本地的X Server,需要X Server開放TCP埠,還要搞定安全認證;
  5.X Server的配置,可以通過命令列引數,可以通過環境變數,還可以通過配置檔案;
  6.可以在現有的圖形介面下以視窗模式執行巢狀的X Server,常用的軟體是Xephyr;
  7.我的《Linux入門學習教程:Linux系統折騰筆記》中介紹的方法不夠用,還要加上兩條:①使用ps命令檢視程序;②檢視程式啟動的log檔案。