X Window System是1984年由麻省理工学院(MIT)和DEC公司共同开发研究的,是运行在UNIX系统上的视窗系统。严格地说,X Window System并不是一个软件,而是一个协议,这个协议定义一个系统成品所必需具备的功能(就如同TCP/IP、DECnet或IBM的SNA,这些也都是协议,定义软件所应具备的功能)。能满足此协议及符合X协会其他规范的系统便可称为X。X Window System独有的网络通透性(Network Transparency),使其成为UNIX平台上的工业标准,现在UNIX的工作站或大型主机几乎都执行着X Window。X Window是非常巧妙的设计,很多时候它在概念上比其它窗口系统先进,以至于经过很多年它仍然是工作站上的工业标准。许多其它窗口系统的概念都是从X Window学来的。
X Window System本身是一个非常复杂的图形化作业环境,我们可以将它分成3个部分,分别是X Server、X Client和X Protocol。X Server主要是处理输入输出的信息,X Client执行大部分应用程序的运算功能,X Protocol则是建立X Server和X Client的沟通管道。
(1)X Server
X Server主要负责处理输入输出的信息,并且维护字体、颜色等相关资源。它接收输入设备(如键盘、鼠标)的信息,将这些信息交给X Client处理,而X Client所传来的信息就由X Server负责输出到输出设备(如显示卡、荧幕)上。X Server传给X Client的信息称作Events(事件)。X Client传给X Server的信息称作Request(要求)。Events(事件)主要包括键盘的输入和鼠标的移动、按下等动作,而Request(要求)主要是X Client要求对显示卡及屏幕的输出作调整。
(2)X Client
X Client主要负责应用程序的运算处理部分,它将X Server所传来的Events作运算处理后,再将结果以Request的方式去要求X Server显示在屏幕上的图形视窗。在X Window System的结构中,X Server和X Client所负责的部分是分开的,所以X Client和硬件无关,只和程序运算有关。这样有一个好处,例如更换显示卡时,X Client部分不需要重新编写;因为X Server和X Client是分开的,所以可以将两者分别安装在不同电脑上,这样就可以利用本地端的屏幕、键盘和鼠标来操作远端的X Client程序。常见的X Client有大家熟悉的gdm, xterm, xeyes等。
(3)X Protocol
X Protocol就是X server与X client之间通信的协议。X protocol支持现在常用的网络通信协议。例如测试TCP/IP,可以看到X server侦听在tcp 6000端口上。那X protocol就是位于传输层以上了,应该属于应用层。通常,X server和X client在两台机器上时,则之间一般使用TCP/IP协议通信,若在同一台机器,则使用高效的操作系统内部通信协议。
(4)X Library、X Toolkit和Widget
X Client主要就应用程序,而开发程序大多会提供函数库,以方便开发人员开发,在X则提供有X Library(X Lib),X Library主要提供X Protocol的存取能力,由于X Server只是根据X Client所给的Request(要求)去显示画面,因此所有的图形使用界面都交由X Client负责。我们没有必要每写一个应用程序都得从头再开发一个界面,所以有了图形界面库X Toolkit和Widget的产生,开发者就可以使用Toolkit和Widget来创建按钮、对话框、轴、窗口等视窗结构,这样开发者可以容易地开发各种程序。
总结下运行过程:
(1)用户通过鼠标键盘对X server下达操作命令
(2)X server利用Event传递用户操作信息给X client
(3)X client进行程序运算
(4)X client利用Request传回所要显示的结果
(5)X server将结果显示在屏幕上
可以看出,X Window的工作方式跟Microsoft Windows有着本质的不同。Microsoft Windows的图形用户界面(GUI)是跟系统紧密相联的。而X Window则不是,它实际上是在系统核心(kernel)的上面运行的一个应用程序。
X Window的运行分为4层。最底层的是X Server(服务器),提供图形界面的驱动,为X Window提供服务。上面的一层是用于网络通信的网络协议,即X网络协议,这部分使远程运行X Window成为可能。只需要在服务器上运行一个X Server,而客户机(Client)上运行更上一层的程序,则可以实现X Window的远程运行。再往上的一层是称作Xlib的函数接口,介于基础系统和较高层应用程序之间。应用程序的实现是通过调用这一层的函数实现的。最顶层就是窗口管理器了,也就是一般所说的WM(Window Manager),这一层的软件是用户经常接触的,比如fvwm、AfterStep、Enlightment以及WindowMaker等。
从上面的介绍来看,X Window的运行是一种客户机/服务器(Client/Server)的模式,服务器用于显示客户机运行的应用程序,又被称为显示服务器 (Display Server)。显示服务器位于硬件和客户机之间,它跟踪所有来自输入设备(比如键盘、鼠标)的输入动作,经过处理后将其送回客户机。这样,用户可以在 Microsoft Windows的机器上运行X Client,截取并传送用户的输入,只是将X Window的屏幕输出显示在用户的屏幕上。客户机的输入和输出系统跟X服务器之间的通信都是遵守X协议的。
搞清楚X server与X client关系很重要。一般X server很简单,就是/usr/bin/X11/X程序或Xorg程序,在Microsoft Windows上常用的X server有Xming,Xmanager,Exceed和X-win32等。而X client则花样繁多,从高级的CDE,GNOME,KDE,到低级一点的只有twm,Window Maker,blackbox等窗口管理器,再到最简陋的只有xterm,rxvt,xeyes等单个x程序。正是由于X client的各种搭配,使得我们的X Window System看起来多样化。
注意,X中所提及的“客户端”和“服务器”等字眼用词与人们一般想定的相反,“服务器”反而是在用户本地端的自有机器上运行,而非是在远程的另一部机器上运行。很多熟悉Internet原理的人首次遇到X Window的这两个概念都会搞错。如果他从一台Windows机器上使用Exceed通过XDMCP登录到一台Sun服务器,他就说Exceed是客户端(client),而Sun机器是服务器(server)。这就完全搞错了。X server不是指你登录的那台机器,而是指一个程序,它负责在某台机器上接受客户的要求,在屏幕上显示客户请求的图形,并且把消息(键盘,鼠标,窗口消息)通知客户程序。这个例子里本地的Exceed就是一个X server,它负责控制这台Windows机器上的显示(display),远程Sun机器上的程序xterm, xxgdb, dtwm(CDE的窗口管理器)等,是客户程序。它们通常会使用TCP 6000号端口连接Windows机器,而Windows机器的6000号端口是由Exceed来bind和listen的。比如,当你通过telnet启动Sun机器上的xterm,就会 Exceed的屏幕上显示一个窗口。实际发生的事情是: xterm请求连接Windows机器的6000号端口,跟Exceed连接,然后xterm请求得到资源,然后xterm请求在屏幕上显示一个窗口。你在xterm的窗口里按下"A"键时,Exceed会把这个事件通知xterm进程,然后xterm会发送数据报,请求Exceed, “请在坐标(100,30)处显示一个字母A,然后在后面显示一个矩形作为光标。”这样你的xterm窗口里就会多显示一个字母。
实际的远端客户端的例子有:图形化管理远程计算机;在远端UNIX计算机上运行计算密集的仿真程序并把结果显示到本地的Windows桌面计算机;用一套显示器、键盘和鼠标控制同时运行在多台计算机上的图形化软件。
2、X Window System的启动过程
从控制台(即字符界面)进入X一般是用startx命令。在前面“Linux init程序分析“中介绍过startx(以Fedora为例),这里以Ubuntu为例。startx是用xinit程序来启动X的。首先man startx和man xinit可以看到staratx和xinit的使用方法:
startx [ [client] options .....] [-- [server] [display] options ....]
xinit [ [client] options ] [-- [server] [display] options ...]
把上面[client]和[server]分别称为X client程序和X server程序。man手册里写明其必须以/或者./开头,即必须是绝对路径或从当前路径开始。
下面看看/usr/bin/startx这个脚本。
- #!/bin/bash
- #
- # This is just a sample implementation of a slightly less primitive
- # interface than xinit. It looks for user .xinitrc and .xserverrc
- # files, then system xinitrc and xserverrc files, else lets xinit choose
- # its default. The system xinitrc should probably do things like check
- # for .Xresources files and merge them in, startup up a window manager,
- # and pop a clock and serveral xterms.
- #
- # Site administrators are STRONGLY urged to write nicer versions.
- #
- unset DBUS_SESSION_BUS_ADDRESS
- unset SESSION_MANAGER
- userclientrc=$HOME/.xinitrc # 用户的client定义文件
- sysclientrc=/etc/X11/xinit/xinitrc # 系统的client定义文件
- userserverrc=$HOME/.xserverrc # 用户的server定义文件
- sysserverrc=/etc/X11/xinit/xserverrc # 系统的server定义文件
- defaultclient=xterm # 默认的client程序
- defaultserver=/usr/bin/X # 默认的server程序
- defaultclientargs="" # 下面定义了client和server的参数变量
- defaultserverargs=""
- defaultdisplay=":0"
- clientargs=""
- serverargs=""
- enable_xauth=1
- # 参数解析:将whoseargs变量赋值为字符串“client”,表示当前解析的指定client的参数
- whoseargs="client"
- while [ x"$1" != x ]; do # 若第一个参数为空,则直接退出循环
- case "$1" in
- # 需要单引号''以避免cpp把"/*"当作注释
- /''*|\./''*) # 如果$1是/*或者./*形式 (xinit程序要求其指定的client程序和server程序必须以/或./开头,
- # 否则会被视为client程序和server程序本身的options参数,见man xinit)
- if [ "$whoseargs" = "client" ]; then # 解析client程序的参数
- if [ x"$client" = x ] && [ x"$clientargs" = x ]; then
- client="$1" # 解析出用户指定的client程序
- else
- clientargs="$clientargs $1" #解析出client程序的options参数
- fi
- else
- if [ x"$server" = x ] && [ x"$serverargs" = x ]; then
- server="$1" # 解析出用户指定的X server程序
- else
- serverargs="$serverargs $1" # 解析出X server的options参数
- fi
- fi
- ;;
- --) # 遇到“- -”就解析server
- whoseargs="server"
- ;;
- *)
- if [ "$whoseargs" = "client" ]; then
- clientargs="$clientargs $1"
- else
- # 解析[display]参数,屏幕编号[display]必须为第一个给server程序的参数,以:x的形式(x为数字)
- if [ x"$serverargs" = x ] && \
- expr "$1" : ':[0-9][0-9]*> /dev/null 2>&1; then
- display="$1"
- else
- serverargs="$serverargs $1" # 解析[display]以外的参数
- fi
- fi
- ;;
- esac
- shift # 所有参数左移一次
- done
- # 处理client参数
- if [ x"$client" = x ]; then # 如果用户没有指定X client,则使用默认的X client即xterm
- client=$defaultclient
- # 如果没有client的选项参数,则用rc文件代替
- if [ x"$clientargs" = x ]; then
- if [ -f "$userclientrc" ]; then
- client=$userclientrc
- elif [ -f "$sysclientrc" ]; then
- client=$sysclientrc
- fi
- clientargs=$defaultclientargs
- fi
- fi
- # 处理server参数
- if [ x"$server" = x ]; then # 如果用户没有指定X server,则使用默认的X server即X
- server=$defaultserver
- # 如果没有server的选项参数或display参数,使用默认值
- if [ x"$serverargs" = x -a x"$display" = x ]; then
- # 为了兼容性的考虑,如果没有server的命令行参数,只使用xserverrc文件
- if [ -f "$userserverrc" ]; then
- server=$userserverrc
- elif [ -f "$sysserverrc" ]; then
- server=$sysserverrc
- fi
- serverargs=$defaultserverargs
- display=$defaultdisplay
- fi
- fi
- if [ x"$enable_xauth" = x1 ] ; then
- if [ x"$XAUTHORITY" = x ]; then # 如果环境变量XAUTHORITY为空,就设定为$HOME/.Xauthority
- XAUTHORITY=$HOME/.Xauthority
- export XAUTHORITY
- fi
- removelist=
- # 设置本机的默认Xauth信息
- # 检查GNU主机名,如果hostname –version中不包含GNU 就将hostname变量设定为命令hostname –f返回的字符串
- if hostname --version > /dev/null 2>&1; then
- if [ -z "`hostname --version 2>&1 | grep GNU`" ]; then
- hostname=`hostname -f`
- fi
- fi
- if [ -z "$hostname" ]; then
- hostname=`hostname`
- fi
- authdisplay=${display:-:0}
- mcookie=`/usr/bin/mcookie`
- if test x"$mcookie" = x; then
- echo "Couldn't create cookie"
- exit 1
- fi
- dummy=0
- # 为server创建带有auth信息的文件,假设为屏幕编号为':0'
- xserverauthfile=`mktemp --tmpdir serverauth.XXXXXXXXXX`
- trap "rm -f '$xserverauthfile'" HUP INT QUIT ILL TRAP KILL BUS TERM
- xauth -q -f "$xserverauthfile" << EOF
- add :$dummy . $mcookie
- EOF
- serverargs=${serverargs}" -auth "${xserverauthfile}
- # now add the same credentials to the client authority file
- # if '$displayname' already exists do not overwrite it as another
- # server man need it. Add them to the '$xserverauthfile' instead.
- for displayname in $authdisplay $hostname$authdisplay; do
- authcookie=`xauth list "$displayname" \
- | sed -n "s/.*$displayname[[:space:]*].*[[:space:]*]//p"` 2>/dev/null;
- if [ "z${authcookie}" = "z" ] ; then
- xauth -q << EOF
- add $displayname . $mcookie
- EOF
- removelist="$displayname $removelist"
- else
- dummy=$(($dummy+1));
- xauth -q -f "$xserverauthfile" << EOF
- add :$dummy . $authcookie
- EOF
- fi
- done
- fi
- # #下面的语句通过xinit启动X server和Clients,把处理的参数传给它
- xinit "$client" $clientargs -- "$server" $display $serverargs
- retval=$?
- if [ x"$enable_xauth" = x1 ] ; then
- if [ x"$removelist" != x ]; then
- xauth remove $removelist
- fi
- if [ x"$xserverauthfile" != x ]; then
- rm -f "$xserverauthfile"
- fi
- fi
- exit $retval
以上即为X Window System的启动过程,startx只是负责一些参数传递,真正的X启动由xinit实现。我们可以知道,startx将会先解析用户的参数,如果该用户指定了该参数(即解析结果不为空),那么startx就会以该参数来启动xinit,否则就会解析(与其说是解析,还不如说是执行)$HOME目录下的rc文件,如果该文件不存在,就会解析系统目录下(/etc/X11/xinit/)的rc文件,如果这个文件也不存在,那startx就将以默认的client(xterm)和server(/usr/bin/X)为参数来启动xinit。例如,可以在用户目录下构造.xinitrc(即X client)和.xserverrc(即X server)文件。在.xserverrc里写入/usr/bin/X11/X :1。.xinitrc里写入/usr/bin/X11/xeyes -display localhost:1。这就是最简单的X server+ X client了,只不过把屏幕编号从默认的0改为了1。
到目前为止,我们还不知道仅仅在终端输入startx是怎么样启动gnome桌面的,gnome当然属于X client了。通过对startx的分析可知,startx主要有三种启动方式:
(1)一种是自己指定要启动的client和server, 例如:startx /usr/bin/xclock -- /usr/bin/X :0;
(2)一种是通过在$HOME下新建.xinitrc文件来指定要启动的多个client和.xserverrc来指定要启动的server(注意:这两个文件本来是不存在的);
(3)还有一种是直接输入startx而不指定参数,这也就是我们启动gnome桌面的方法。
在第(3)中启动方法中,我们可以知道,startx脚本会先去看系统目录(/etc/X11/xinit/)下的rc文件是否存在,如果不存在就会用默认的xterm和/usr/bin/X来启动xinit。显然,startx启动的不是xterm,而是gnome桌面,因此gnome的启动是通过系统文件/etc/X11/xinit/xinitrc来指定的。在“Linux init程序分析”中详细介绍过的gnome的启动。这里以Ubuntu为例,/etc/X11/xinit/xinitrc文件中只包含了. /etc/X11/Xsession一句话,因此gnome是通过Xsession脚本启动的(在Fedora中则是直接用xinitrc来启动gnome)。下面是Xsession文件:
- #!/bin/sh # 注意该脚本用的是Bourne shell解析的
- #
- # /etc/X11/Xsession
- #
- # global Xsession file -- used by display managers and xinit (startx)
- # $Id: Xsession 967 2005-12-27 07:20:55Z dnusinow $
- set -e # 打开errexit选项,该选项表示如果下面有命令返回的状态非0,则退出程序
- PROGNAME=Xsession
- # 下面4个是信息输出函数,可以不管
- message () {
- # pretty-print messages of arbitrary length; use xmessage if it
- # is available and $DISPLAY is set
- MESSAGE="$PROGNAME: $*"
- echo "$MESSAGE" | fold -s -w ${COLUMNS:-80} >&2
- if [ -n "$DISPLAY" ] && which xmessage > /dev/null 2>&1; then
- echo "$MESSAGE" | fold -s -w ${COLUMNS:-80} | xmessage -center -file -
- fi
- }
- message_nonl () {
- # pretty-print messages of arbitrary length (no trailing newline); use
- # xmessage if it is available and $DISPLAY is set
- MESSAGE="$PROGNAME: $*"
- echo -n "$MESSAGE" | fold -s -w ${COLUMNS:-80} >&2;
- if [ -n "$DISPLAY" ] && which xmessage > /dev/null 2>&1; then
- echo -n "$MESSAGE" | fold -s -w ${COLUMNS:-80} | xmessage -center -file -
- fi
- }
- errormsg () {
- # exit script with error
- message "$*"
- exit 1
- }
- internal_errormsg () {
- # exit script with error; essentially a "THIS SHOULD NEVER HAPPEN" message
- # One big call to message() for the sake of xmessage; if we had two then
- # the user would have dismissed the error we want reported before seeing the
- # request to report it.
- errormsg "$*" \
- "Please report the installed version of the \"x11-common\"" \
- "package and the complete text of this error message to" \
- "<debian-x@lists.debian.org>."
- }
- # 初始化被所有session脚本使用的变量
- OPTIONFILE=/etc/X11/Xsession.options
- SYSRESOURCES=/etc/X11/Xresources
- USRRESOURCES=$HOME/.Xresources
- SYSSESSIONDIR=/etc/X11/Xsession.d
- USERXSESSION=$HOME/.xsession
- USERXSESSIONRC=$HOME/.xsessionrc
- ALTUSERXSESSION=$HOME/.Xsession
- ERRFILE=$HOME/.xsession-errors
- # 尝试创建一个error文件,如果不能创建则退出
- if (umask 077 && touch "$ERRFILE") 2> /dev/null && [ -w "$ERRFILE" ] &&
- [ ! -L "$ERRFILE" ]; then
- chmod 600 "$ERRFILE"
- elif ERRFILE=$(tempfile 2> /dev/null); then
- if ! ln -sf "$ERRFILE" "${TMPDIR:=/tmp}/xsession-$USER"; then
- message "warning: unable to symlink \"$TMPDIR/xsession-$USER\" to" \
- "\"$ERRFILE\"; look for session log/errors in" \
- "\"$TMPDIR/xsession-$USER\"."
- fi
- else
- errormsg "unable to create X session log/error file; aborting."
- fi
- # 截短ERRFILE,如果它太大了,以避免硬盘空间的DoS攻击
- if [ "`stat -c%s \"$ERRFILE\"`" -gt 500000 ]; then
- T=`mktemp -p "$HOME"`
- tail -c 500000 "$ERRFILE" > "$T" && mv -f "$T" "$ERRFILE" || rm -f "$T"
- fi
- exec >>"$ERRFILE" 2>&1
- echo "$PROGNAME: X session started for $LOGNAME at $(date)"
- # 理智的检查:#如果/etc/X11/Xsession.d不存在或不是一个目录则打印错误信息并退出
- if [ ! -d "$SYSSESSIONDIR" ]; then
- errormsg "no \"$SYSSESSIONDIR\" directory found; aborting."
- fi
- # Attempt to create a file of non-zero length in /tmp; a full filesystem can
- # cause mysterious X session failures. We do not use touch, :, or test -w
- # because they won't actually create a file with contents. We also let standard
- # error from tempfile and echo go to the error file to aid the user in
- # determining what went wrong.
- WRITE_TEST=$(tempfile)
- if ! echo "*" >>"$WRITE_TEST"; then
- message "warning: unable to write to ${WRITE_TEST%/*}; X session may exit" \
- "with an error"
- fi
- rm -f "$WRITE_TEST"
- # use run-parts to source every file in the session directory; we source
- # instead of executing so that the variables and functions defined above
- # are available to the scripts, and so that they can pass variables to each
- # other
- SESSIONFILES=$(run-parts --list $SYSSESSIONDIR) # 将/etc/X11/Xsession.d目录中的所有文件都读出,并存入SESSIONFILES变量中
- if [ -n "$SESSIONFILES" ]; then
- set +e # 关闭errexit选项
- for SESSIONFILE in $SESSIONFILES; do # 执行Xsession.d下的每一个脚本
- . $SESSIONFILE
- done
- set -e
- fi
- exit 0
- # vim:set ai et sts=2 sw=2 tw=80:
(1)20x11-common_process-args:这个文件主要是处理传给/etc/X11/xinit/ xinitrc脚本文件的参数的。该参数个数只能为0或一个,否则将不进行任何处理。如果该参数是failsafe,则该脚本将执行x-terminal-emulator,否则就执行该参数。需要说明的是,x-terminal-emulator是一个符号链接,指向/etc/alternatives/x-terminal-emulator,同时,/etc/alternatives/x-terminal-emulator也是一个符号链接,它指向/usr/bin/gnome-terminal.wrapper,而gnome-terminal.wrapper则是一个perl脚本,它最终是调用了gnome-terminal。
(2)30x11-common_xresources:该文件主要是调用xrdb,根据/etc/X11/Xresources目录下及$HOME/.Xresources目录下的文件的内容来设置根窗口的屏幕0上的RESOURCE_MANAGER属性的内容。
(3)40x11-common_xsessionrc:该文件主要是判断$HOME/.xsessionrc文件是否存在,如果存在则执行该脚本文件。
(4)50x11-common_determine-startup:该文件主要先查看配置文件/etc/X11/Xsession.options中是否允许使用用户的xsession,如果/etc/X11/Xsession.options中存在allow-user-xsession字段,则查看用户指定的$HOME/.xsession是否存在并有执行权限,如果是,则将STARTUP变量设置为该文件,如果没有执行权限就将STARTUP变量设置为“sh 该xsession文件”。如果此时STARTUP变量仍然为空(即没有使用用户指定的.xsession脚本),则将其设置为/usr/bin下的x-session-manager,x-window-manager或x-terminal-emulator,它们都是指向/etc/alternatives/下相应程序的符号链接。注意这个STARTUP将会在后面的脚本中被启动。
(5)55gnome-session_gnomerc:该文件会先得到STARTUP的basename,如STARTUP=/usr/bin/x-session-manager,则其basename为x-session-manager。再判断该basename是否为gnome-session,或者为x-session-manager并且x-session-manager是个符号链接,它指向/usr/bin/gnome-session,如果选择的是gnome-session,则执行$HOME/.gnomerc(如果该文件存在并且可读)。
(6)60x11-common_localhost:使用xhost程序,把本机的用户名添加到允许连接X server的用户名列表中。用户名由`id -un`命令给出。
(7)60xdg_path-on-session:根据选择的窗口会话,通过设置XDG_CONFIG_DIRS变量来添加额外的xdg路径。
(8)60xdg-user-dirs-update:用xdg-user-dirs-update自动生成$HOME下的文件夹,该命令主要是根据/etc/xdg/user-dirs.defaults文件的内容来为用户创建文件夹的。
(9)70gconfd_path-on-session:根据选择窗口会话,添加额外的gconf路径。
(10)75dbus_dbus-launch:把use-session-dbus选择放入Xsession.options文件中,表示使用在窗口会话中使用dbus。把启动/usr/bin/dbus-launch程序的选择添加到STARTUP变量中。
(11)80im-switch:该文件主要用于设置输入法。具体的请自己参考文件内容。
(12)90consolekit:如果环境变量$XDG_SESSION_COOKIE为空,并且/usr/bin/ck-launch-session可执行,则将STARTUP重新赋值为” /usr/bin/ck-launch-session $STARTUP”。至于ck-launch-session的功能,我也不是很清楚,估计是和session有关,对窗口会话进行一些检查。
(13)90x11-common_ssh-agent:该文件主要先查看配置文件/etc/X11/Xsession.options中是否使用ssh agent,如果/etc/X11/Xsession.options中存在use-ssh-agent字段,则判断/usr/bin/ssh-agent是否可执行,并且环境变量$SSH_AUTH_SOCK和$SSH2_AUTH_SOCK是否都为空,如果是,这将STARTUP重新赋值为” /usr/bin/ssh-agent $STARTUP”。
(14)99x11-common_start:它仅仅是用exec启动$STARTUP。关于exec,在Bourne shell中,它与fork的区别就在于它执行一个新的脚本不需创建sub-shell,而它与Source和Dot的区别就在与在这条语句后面的语句将不会再被执行。此时,我们可以发现变量$STARTUP的值为:“startup=/usr/bin/ssh-agent /usr/bin/ck-launch-session /usr/bin/seahorse-agent --execute x-session-manager”, 因此,最终将会被执行的就是这么一条语句。而x-session-manager最终指向的是gnome-session。
gnome-session就是最终启动GNOME桌面环境的,这个程序一般被登入管理器gdm、xdm和脚本startx调用。总结一下Ubuntu中Gnome的启动过程,核心的流程总结如下:
- /usr/bin/startx
- --->xinit /etc/X11/xinit/xinitrc -- /etc/X11/xinit/xinitrc
- --->./etc/X11/Xsession
- --->/etc/X11/Xsession.d/
- --->50x11-common_determine-startup
- --->/etc/X11/Xsession.options # 寻找相关配置选项
- --->STARTUP=$HOME/.xsession # 如果有.xsession脚本,则设置成运行它
- --->STARTUP=/usr/bin/x-session-manager # 如果没有.xsession脚本,则设置成运行x-session-manager
- --->/usr/bin/gnome-session # x-session-manager指向gnome-session程序
- --->55gnome-session_gnomerc
- --->$HOME/.gnomerc # 选择了gnome-session,则运行$HOME/.gnomerc
- --->75dbus_dbus-launch # 启动dbus
- --->80im-switch # 设置输入法
- --->99x11-common_start
- --->exec $STARTUP # 启动gnome会话,进入gnome桌面
通常用来做服务器的系统(Linux,FreeBSD,Solaris等等)都用字符界面,不会装X server,甚至很多都没有显示器。这样可以在这些系统里安装简单的X client,以GUI的方式远程显示在管理员们所坐的X server里。例如可以用FreeBSD做网关,提供WWW,FTP服务,一般在管理员的本地机器起个X server,然后通过ssh或telnet登录远程到FreeBSD系统,运行X client程序显示在本地显示器上,当然,也可用XDMCP(X Display Manager Control Protocol)的方式来登录运行。man xsession手册里提到/etc/X11/Xsession一般被startx(Ubuntu中在/etc/X11/xinit/xinitrc里调用 Xsession脚本)或display manager调用,但有的display manager只调用Xsession而不是xinitrc,故为了startx和display manager两种方式下都可正常启动GUI,最好把X client启动的程序放在Xsession文件里。远程运行X client程序需要设置DISPLAY环境变量,设置为 Xserver主机名称:屏幕编号(如192.168.1.115:0,则表示X server是192.168.1.115这台机器上的0号屏幕);或是给X client程序加个—display参数。这里我们在TCP/IP网络环境中测试X Window System。
在VMWare虚拟机中安装Fedora 14,地址为192.168.1.115,使用/usr/bin/X作为X server。安装Ubuntu 10.04,地址为192.168.1.116。现在我们要使它们默认启动到字符界面。Fedora中比较简单,修改/etc/inittab文件,把其中的默认运行由5改成3,重启即可。Ubuntu中使用grub v2,需要把/etc/default/grub文件中的GRUB_CMDLINE_LINUX_DEFAULT变量修改成"quiet splash text",即加上一个text参数,然后运行sudo update-grub,重新生成grub启动菜单的配置文件/boot/grub/grub.cfg。重启即可。
(1)配置X Server
第一步:在$HOME下生成.Xauthority认证文件中
[jackzhou@localhost ~]$ xauth add 192.168.1.115:0 . `mcookie`
xauth: creating new authority file /root/.Xauthority
第二步:查看认证文件
[jackzhou@localhost ~]$ xauth list
192.168.1.115:0 MIT-MAGIC-COOKIE-1 728ef8138827dcc82b7aa562d946796a
第三步:通过上面的认证文件生成一个密钥文件jackcookie
[jackzhou@localhost ~]$ xauth extract jackcookie 192.168.1.115:0
[jackzhou@localhost ~]$ ls -l jackcookie
-rw-------. 1 jackzhou jackzhou 49 Jul 17 13:20 jackcookie
第四步:将生成的密钥文件jackcookie,拷贝到X Client端。注意这需要Ubuntu上安装有open ssh server,可以用sudo apt-get install openssh-server安装。
[jackzhou@localhost ~]$ scp jackcookie 192.168.1.116:$HOME
The authenticity of host '192.168.1.116 (192.168.1.116)' can't be established.
RSA key fingerprint is fe:a2:ef:ab:75:37:04:47:c6:4d:02:9d:58:1c:f7:35.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.1.116' (RSA) to the list of known hosts.
jackzhou@192.168.1.116's password:
jackcookie 100% 49 0.1KB/s 00:00
第五步:在后台启动X Server
[jackzhou@localhost ~]$ X -auth $HOME/.Xauthority &
这样X server会在tty7上运行(tty7上是运行图形界面的),通过Ctrl+Alt+F7可以切换到X界面,上面一片黑暗,只显示一个光标。可以通过Ctrl+Alt+F1切换回原来的字符界面,由于在不同的tty上运行,它们互不影响。
(2)配置X Client
第一步:将X Server传送过来的密码文件jackcookie,进行导入操作
jackzhou@jackzhou-desktop:~$ ls -l jackcookie
-rw------- 1 jackzhou jackzhou 2011-07-17 13:17 jackcookie
jackzhou@jackzhou-desktop:~$ xauth merge jackcookie
jackzhou@jackzhou-desktop:~$ ls -l jackcookie
-rw------- 1 jackzhou jackzhou 49 2011-07-17 13:21 jackcookie
第二步:配置DISPLAY环境变量
jackzhou@jackzhou-desktop:~$ export DISPLAY=192.168.1.115:0
第三步:启动各种X Client,如xterm和xeye、、xclock、twm、gedit、gnome-terminal,甚至gnome-session程序,都行。注意twm在Ubuntu中默认没有安装,可以用apt-get安装。
jackzhou@jackzhou-desktop:~$ xterm &
jackzhou@jackzhou-desktop:~$ xeyes &
jackzhou@jackzhou-desktop:~$ twm &
这样在Fedora的X Server上就会显示Ubuntu中相应的X client界面了。通常的应用场景是X client机器在远程且没有显示器,X Server机器在本地,你可以通过在Fedora中使用ssh 192.168.1.116登录到远程的X client机器,来配置X Client,而不用直接跑到远程的机器上去配。
要更加深入地研究X Window System,可参考man xserver和man x的手册页,Wikipedia的介绍http://zh.wikipedia.org/zh-cn/X_Window,以及Xorg Foundation的官方站点http://www.x.org/wiki/。下载X11R7.6的源代码,里面有各模块设计的详细文档说明。