概要
在上一篇文章了解了X Window System的基本架构,现在来讨论如何使用。
本文关注的是如何在本机启动基于X的桌面环境,以及如何配置使其自动启动,最后讨论了执行 X
命令所需的权限。
本文主要涉及这几个概念或程序。
- xinit - X Window System initializer
- startx - initialize an X session
- Xsession - initialize X session
在本地启动X Server和X Client并连接
在上一篇文章讲了手动启动X Server,然后再启动X Client,使其连接到所创建X Server的方法。
但是使用那种操作方式虽然直观,但是一旦X Server启动成功,虚拟机显示器和其它外设便会由X Server接管,
也就无法在虚拟机本机同时完成X Server和X Client的启动。那么如何只使用一台机器完成操作呢?
也许使用对X使用vt参数可以借助虚拟终端完成本地启动?
这个想法在我电脑的Ubuntu系统上是可行的,可以在启动
X
时通过vtN
参数指定其所占用的虚拟终端,这样一来执行X
之后还能通过Ctrl+Alt+FN
切回原来的console/tty,在其中启动X Client,并连接至刚才启动的X Server。
但是在虚拟机中的Arch里面似乎在试图切换到其它tty的时候会死机,暂不清楚原因,也许跟视频驱动有关系。
可以使用shell脚本完成。
使用SHELL脚本完成GUI启动
可以创建如下脚本,使用bash执行。
1 | # !/usr/bin/sh |
使用root权限执行脚本就可以看到i3桌面环境了,如果想要回到刚才的命令行界面,可以在i3桌面下调出xterm,执行pkill Xorg
。
之前由于未加sleep语句而导致的愚蠢错误
破案了!!!在X Client前面加上一句
sleep 3
就能正常启动了,推测是因为X Server还没完全启动,xterm就尝试连接,发现失败,然后报错了,,,
之前犯的愚蠢错误,可以忽略
虽然说X Client应该没有什么限制,但至少可以排除一个xterm,该程序似乎无法用超级用户权限执行(这么说应该不合适,因为随后进入i3桌面之后可以调出xterm,但是把脚本中i3换成xterm确实看不到它正常显示),也就是说如果把脚本中的i3换成xterm,看起来和只执行X是一样的效果,这一现象一度使我误以为X Server启动之后,后面的命令便无法执行了(显然这种猜测不合常理)。 报错信息如下:
模拟非root用户登录
上面的脚本执行完毕会以root用户的身份启动X Client应用。这和平时所用的桌面环境行为并不一致,那么如何修改上述脚本从而以普通用户的身份启动i3桌面呢?可以像下面这样:
1 | # !/usr/bin/sh |
该脚本与上面的不同之处仅在X Client上,借助 su $USER -l -c ...
来以普通用户的身份启动X Client即可。
在这里使用 -l
参数是为了模拟login
,避免当前root用户身份下的一些配置被传递给 i3 进程。
当时我遇到的情况是,由于环境变量PWD=/root
被传递给了i3,我在i3中启动的xterm初始路径会是/root
,尽管当时是以 lpy
的身份启动i3的。
1 | -, -l, --login |
至此我们了解了利用脚本启动X Window System的方法。
这种方式还有个致命的缺陷,一旦一不小心退出了X Client,由于X Server还控制着显示器和其它外设,我们将无法进行其它操作。
新的疑问:能否在脚本里面指定vt_n
这样的参数将其运行在某个虚拟终端上,而不是像现在这样无法切换虚拟终端?
xinit 和 startx
自己完全控制X Server 和 X Client的启动及连接并不方便,现在介绍另外两个用于启动X Window System的工具。
xinit
xinit 是X Window System的初始化程序。
该程序用于在不使用Diaplay Manager(例如xdm)的系统中启动X Server和第一个X Client程序,当其启动的第一个X Client程序退出之后,xinit将会关闭X Server,然后退出。
从console执行下面的命令可以启动i3,并且一旦i3退出,相应X Server也会退出,可以回到console。
1 | sudo xinit i3 |
官方文档似乎提到了对出现在xinit参数中的程序名格式的限制,但这里并没有遵守,也可以正常执行
An alternate client and/or server may be specified on the command line. The de‐
sired client program and its arguments should be given as the first command line
arguments to xinit. To specify a particular server command line, append a double
dash (—) to the xinit command line (after any client and arguments) followed by
the desired server command.Both the client program name and the server program name must begin with a slash
(/) or a period (.). Otherwise, they are treated as an arguments to be appended
to their respective startup lines. This makes it possible to add arguments (for
example, foreground and background colors) without having to retype the whole
command line.
具体用法参考官方文档。
关于xinit,很重要的一点是要理解它的行为,它会启动两个程序,一个叫做作 A
(称为Client),另一个叫作B
(称为Server),一旦它检测到 A
进程结束,它就会终止 B
进程。
也许可以这么说(我瞎猜的),xinit程序本身并不知道它所启动的到底是两个什么程序,它所做的事情仅仅是等待 A
完成,然后终止 B
。只要按照约定将 X Client
程序传递给它作为 A
,将 X Server
传递给它作为 B
,就能实现 X Client
进程结束时自动终止 X Server
的效果。也许可以尝试使用SHELL脚本模拟这个过程。
理解了这个过程,就可以讨论 xinit
所接受的程序参数
类型了。既然只要可以运行为一个进程就可以,也就不是必须传递给它二进制程序了,SHELL脚本也是可以的,因为可以由xinit创建一个sh进程执行脚本,然后将此sh进程作为所谓的的 Client
进程,等待它结束然后去终止 Server
进程,同样的,所谓的 Server
进程也可以是shell脚本。
在实际使用中,几乎总是使用脚本作为程序参数
传递给xinit,并在 Server
脚本的最后使用 exec
命令启动X Server
,这样一来就能将此sh进程转变成 X Server
进程,在 Client
脚本的最后使用 exec
命令启动 X Client
,然后xinit按照其运行逻辑等待 Client
进程结束并终止 Server
即可。
而且,如何没有给xinit提供client和server参数,xinit会使用缺省参数,它会将 /etc/X11/xinit/xserverrc
作为 Server
,将 /etx/X11/xinit/xinitrc
作为 Client
。
下面的就是 /etc/X11/xinit/xserverrc
的内容,其实它就做了一件事,就是使用 exec
在sh进程空间运行 X
。注意命令最后的 $@
,意思是接收所有传递给该脚本的的命令行参数并传递给 /usr/bin/X
。
1 |
|
startx
简介
startx
是 xinit
的一个前端,是一个shell脚本,位于 /usr/bin/startx
。
既然已经有了xinit
,为什么还需要 startx
呢?
因为 xinit
的缺省配置并不友好,它的缺省 DISPLAY
总是 :0
,默认情况不会使用 vt_N
参数使用虚拟终端,它仅仅提供了最基本的功能,几乎需要指明各种参数。
而 startx
所做的事情就是在 xinit
的基础上按照其定义的策略确定一组恰当的默认配置参数,然后用这组参数调用 xinit
从而完成启动,从而减轻用户使用负担。它负责确定的配置,包括 DISPLAY
、 vt_N
、client
、client_options
、server
、server_options
等。除了负责确定一组合适的xinit调用参数,它还会进行一些 X Window System
鉴权相关操作,比如设置 xhost
,xauth
等。
startx
的参数格式也与 xinit
完全相同,可以像调用 xinit
那样直接调用 startx
。下面的命令就可以在第8个虚拟终端上启动X Server和i3wm。
1 | startx i3 -- :0 vt8 |
配置文件
配置文件其实就是要传递给 xinit 的 Client 和 Server 脚本。在 commandline options 中提供的 Server 和 Client 优先级最高,如果用户没有提供 Client 和 Server 参数,startx 就会按照其约定的规则搜索确定要使用的 Client 和 Server 脚本。
startx 相比 xinit 增加了用户配置文件的概念,会优先搜索用户配置 ~/.xinitrc
作为 xinit 的 client,搜索 ~/.xserverrc
作为 xinit 的 server,如果用户配置不存在,才会继续搜索系统配置文件。
具体配置文件路径及匹配规则如下:
1 | To determine the client to run, startx looks for the following files, in order: |
基于Debian的系统-XSession文件
1 | Note that in the Debian system, what many people traditionally put in the .xini‐ |
使用 man Xsession
命令进一步了解,这是Debian对Xsession的介绍。
在我的Ubuntu系统上,/etc/X11/xinit/xinitrc
只是简单地执行了 /etc/X11/Xsession
1 | #!/bin/sh |
startx脚本分析
如果需要在console启动X Windows System,一般会使用 startx
, 因此理解 startx
的执行过程有助于更好地理解其配置方法。下面是我对startx脚本的简单注释,主要是指出了一些我之前未曾接触过的shell语法。
1 |
|
Ubuntu默认配置下,Gnome是如何被启动的
经过查看,我的Ubuntu系统上并不存在 ~/.xinitrc
,~/.xsession
等文件,因此启动Gnone这样的设定应该是某个系统配置文件所为,根据前面所说,在Debian系列系统上 /etc/X11/xinit/xinitrc
本身并无作用,只是在其中执行了 . /etc/X11/Xsession
,因此推测应该是 Xsession
部分的某个系统配置文件所为。
下面这段文档讲了Xsession的一个执行环节,其中涉及多个配置文件:
1 | /etc/X11/Xsession.d/50x11-common_determine-startup |
由于我的用户目录没有Xsession相关配置,所以重点关注系统配置文件。
上面文档提到的第一个系统配置文件 /usr/bin/x-session-manager
是个多级符号链接
内容如下,可以看到其中有对 gnome-session-binary
的调用,Gnome的启动应该与此脚本有关。
1 |
|
具体启动路径推测
只考虑在 multi-user 模式下,在 console 执行 startx 手动启动 GUI 的过程。
我的 Ubuntu 系统的情况是,/home/lpy 里面没有任何 .xinitrc/.xsession/.xserverrc 等X Window System 的用户配置文件。
具体启动步骤可能是这样的:
- 用户执行 startx
- startx 脚本根据文档所述规则,寻找用户配置失败
- startx 确定使用
/etc/X11/xinit/xinitrc
作为client
,使用/etc/X11/xinit/xserverrc
作为Server
- startx 调用
xinit /etc/X11/xinit/xinitrc [options...] -- /etc/X11/xinit/xserverrc [options...]
启动 xinit - xinit 启动一个shell进程执行
/etc/X11/xinit/xserverrc
,该shell进程作为 Server,脚本执行过程中使用exec X ...
在shell进程空间内执行X Server
,此后原shell进程本质上已经变成X Server
进程了 - xinit 启动另一个shell进程执行
/etc/X11/xinit/xinitrc
,该shell进程作为 Client,脚本执行过程如下 /etc/X11/xinit/xinitrc
执行. /etc/X11/Xsession
/etc/X11/Xsession
执行. /etc/X11/Xsession.d/50x11-common_determine-startup
/etc/X11/Xsession.d/50x11-common_determine-startup
选定STARTUP
=/usr/bin/x-session-manager
/etc/X11/Xsession
执行/etc/X11/Xsession.d/99x11-common_start
/etc/X11/Xsession.d/99x11-common_start
执行exec $STARTUP
,也就是exec /usr/bin/x-session-manager
,在此shell进程空间中执行/usr/bin/x-session-manager
程序,经过查看,/usr/bin/x-session-manager
实际上是/usr/bin/gnome-session
的符号链接,也就是此shell进程最后实际上成为了/usr/bin/gnome-session
进程- 至此gnome相关程序已经成功启动了
startx-gnome-pstree
使用 startx 启动 gnome-session,进程关系如下:
通过Xsession配置文件默认启动 i3wm
这种方式理论上来说,在使用startx启动GUI时应该是会生效的。
如果使用Display Manger登录,我还不太确定是否应该生效,但是似乎在我的Ubuntu系统上测试并没有生效。
添加~/.xsession
文件
内容如下,最关键的是最后的 exec /usr/bin/i3
1 | # Disable DPMS turning off the screen |
运行逻辑
与gnome启动过程类似,区别主要发生在 /etc/X11/Xsession.d/50x11-common_determine-startup
的执行过程中:
- 用户执行 startx
- startx 脚本根据文档所述规则,寻找用户配置失败
- startx 确定使用
/etc/X11/xinit/xinitrc
作为client
,使用/etc/X11/xinit/xserverrc
作为Server
- startx 调用
xinit /etc/X11/xinit/xinitrc [options...] -- /etc/X11/xinit/xserverrc [options...]
启动 xinit - xinit 启动一个shell进程执行
/etc/X11/xinit/xserverrc
,该shell进程作为 Server,脚本执行过程中使用exec X ...
在shell进程空间内执行X Server
,此后原shell进程本质上已经变成X Server
进程了 - xinit 启动另一个shell进程执行
/etc/X11/xinit/xinitrc
,该shell进程作为 Client,脚本执行过程如下 /etc/X11/xinit/xinitrc
执行. /etc/X11/Xsession
/etc/X11/Xsession
执行. /etc/X11/Xsession.d/50x11-common_determine-startup
/etc/X11/Xsession.d/50x11-common_determine-startup
选定STARTUP
=$HOME/.xsession
/etc/X11/Xsession
执行/etc/X11/Xsession.d/99x11-common_start
/etc/X11/Xsession.d/99x11-common_start
执行exec $STARTUP
,也就是exec $HOME/.xsession
- 具体如何exec一个脚本我还不是很确定,但是肯定是需要shell的,推测
exec $HOME/.xsession
的实际执行结果是在原先的shell进程空间运行了shell,然后在shell环境下执行$HOME/.xsession
这个脚本的内容 - 执行
$HOME/.xsession
这个脚本时遇到命令exec /usr/bin/i3
,然后在其shell进程空间中执行i3
程序,也就是此shell进程最后实际上成为了i3
进程 - 至此
i3
已经成功启动了
startx-i3-pstree
使用 startx 启动 i3,进程关系如下:
执行 X
程序所需权限
默认情况下,执行 X
命令是需要 root 权限的。但是在我的Ubuntu系统上不经配置就可以以普通用户的身份执行 X
。难道是在Ubuntu上原本就允许以普通用户的的身份直接执行 X
吗?
通过执行 startx
,然后使用 pstree -u
就可以看到带有用户信息的进程树了,在图中可以看到,尽管startx
和xinit
都是以用户身份执行的,但是 Xorg
进程所属用户却是 root
。
从这个信息可以知道,Xorg
并不是以用户的身份执行的,而是以 root
用户的身份执行的。可以推测,用户启动 startx
时,借助某个 setuid
程序完成了 Xorg
程序的启动。
那么是哪个setuid程序呢?我们可以沿着startx的执行路径寻找。在默认情况下,startx 会选定 /etc/X11/xinit/xserverrc
作为传递给 xinit
的Server参数,然后 xinit
启动它作为 Server
。可以看到/etc/X11/xinit/xserverrc
所做的事情是执行了X
命令,继续追踪下去会发现 X
也是一个shell脚本,它的内容如下:
1 |
|
它的运行逻辑是,如果存在 /usr/lib/xorg/Xorg.wrap
便启动它,否则启动 /usr/lib/xorg/Xorg
,下面我们来看一些这两个文件的信息:
从图中可以看到,这两个都是可执行二进制文件,并且 Xorg.wrap
是 setuid
和 setgid
的,简单地说就是任何对 Xorg.wrap
的执行都将以 Xorg.wrap
这个程序所属用户和用户组的身份进行,进一步查看就会发现它所属用户和用户组都是 root
,也就是不管谁执行 Xorg.wrap
这个程序,都会以root身份进行,这样一来,普通用户能执行 X
与 X
要求root权限两件事情就不矛盾了。
根据上面的讨论,可以确定正是借助 Xorg.wrap
,我才能在Ubuntu系统上以普通用户的身份执行 X
,也就可以执行基于X的 xinit
和 startx
了。
但是我的虚拟机中的 Arch 没有 Xorg.wrap
, 因此以普通用户身份执行 X
会出错也就不奇怪了,经过搜索,我得知在Arch中,Xorg.wrap
是由 xorg-server
这个程序包提供的,因此应该可以安装这个包并经过合适的配置,使得普通用户也可以启动桌面环境。(我没配好)