马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
在 Ubuntu 桌面环境下,想让某个程序跟着系统一起启动,最常见的两条路是:交给桌面会话自己管(「启动应用程序」),或者写成 systemd 服务由系统统一拉起。前一种改起来直观,但能力有限;后一种能定义依赖顺序、崩溃重启策略,还能把日志 收进 journald,代价是要手写一个 .service 文件。本文以 Ubuntu 22.04 带图形界面的环境为例,把两种方式的实现过程和差异讲清楚,并重点说明带界面的程序做成服务时最容易失败的 DISPLAY 问题。
两种实现思路的差别
「启动应用程序」本质上是桌面会话在用户登录时扫描用户目录下的 autostart 条目,逐个执行命令。它绑定的是图形会话:系统刚开机、还没进到桌面时这些程序不会启动,注销再登录反而会再跑一遍。而 systemd 服务跟图形会话是解耦的,开机进入多用户目标就执行,适合后台进程;带界面的程序同样能跑,只是需要额外告诉它「连到哪块显示屏幕」。
- 启动应用程序:随桌面登录触发,配置落在 ~/.config/autostart,改起来最简单。
- systemd 服务:随开机触发,支持依赖、重启策略、日志
归集,需要写标准单元文件。
- 选型的粗暴判断:只跟桌面会话共存亡、不需要保活的小工具用第一种;要保活、要等网络就绪、要看日志
的服务用第二种。
方式一:桌面环境的「启动应用程序」
优点很明显:图形化、点几下就能用。缺点是灵活性差,一旦启动有先后顺序的依赖、或者需要守护一个常驻进程,就只能另写 shell 脚本绕。
操作路径是在系统设置里找到「启动应用程序」,点击「添加」,在「命令」一栏填入需要自启动程序的可执行文件绝对路径,名称可以随便填一个标识,保存之后这个条目就写进了 ~/.config/autostart 目录。之后每次登录桌面,这个程序都会自动起来。
注意这里填的路径必须是绝对路径,写相对路径或者只给个文件名,桌面会话找不到执行体,条目会静默失效——桌面环境下这类失败没有任何报错,只能去看日志。
ls ~/.config/autostart
cat ~/.config/autostart/*.desktop
想临时验证某条自启动规则是否生效,最直接的办法是注销再登录一次,而不是重启机器。
方式二:写成 systemd 服务
服务的做法是进 /etc/systemd/system 目录,新建一个以 .service 结尾的文件,名字自定,然后按下面结构填入启动配置。
[Unit]
Description=WebServer
After=network.target
[Service]
User=mhf
WorkingDirectory=/home/mhf/Code/bin64
ExecStart=/home/mhf/Code/bin64/ChatServer
Restart=always
RestartSec=5
Environment=DISPLAY=:0
[Install]
WantedBy=multi-user.target
几个关键字段解释一下:User 指定以哪个用户身份跑,带图形界面的程序务必用普通用户而不是 root,用 root 起 GUI 程序大概率启动失败;WorkingDirectory 是进程的工作目录,程序相对路径的资源会以此为基准;ExecStart 是真正启动的可执行文件路径;Restart=always 表示进程退出后总是重启;RestartSec=5 是两次重启之间等待的秒数;Environment=DISPLAY=:0 这一行是带界面程序的命门,下面单独讲。
服务文件字段逐项说明
[Unit] 段管启动顺序与依赖关系:Description 是当前服务的简单描述;After 指定本服务在哪些服务之后启动,Before 则相反;还有两个容易混的依赖字段,Requires 是强依赖,被依赖的服务异常时当前服务一并停止,Wants 是弱依赖,被依赖方出问题不影响自己继续跑。日常写服务,用 After 排序、用 Wants 表达软依赖是最稳妥的组合。
[Service] 段管启动行为,Type 定义启动类型,可取 simple、exec、forking、oneshot、dbus、notify、idle 等。simple 表示由 ExecStart 启动的进程就是主进程,这是绝大多数场景的默认选择;forking 表示 ExecStart 用 fork 方式拉起、父进程退出、子进程成为主进程,传统守护进程写法常用它。这一段的其他常用字段还有:环境配置文件 EnvironmentFile、重载命令 ExecReload、启动前后追加命令 ExecStartPre 与 ExecStartPost、停止后追加命令 ExecStopPost。
还有两个字段值得单独记住:RemainAfterExit 设为 yes 表示进程退出以后服务仍保持「已启动」状态,这对 oneshot 类型的任务很有用;KillMode 定义 systemd 如何结束服务,取值包括默认的控制组控制群(杀掉组内所有子进程)、process(只杀主进程)、mixed(主进程收 SIGTERM、子进程收 SIGKILL)、none(都不杀)。
[Install] 段一般就写一行 WantedBy=multi-user.target,表示在多用户环境下启用该服务,系统启动时链接到这个目标。
DISPLAY 变量:带界面程序做服务的最大坑
Unix 系统可以跑多个 X 服务器,也就是多个显示,物理显示器是一个显示,SSH 转发过来的是一个显示,虚拟显示同样是显示。指定显示的基本语法是 HOST:NUMBER,省略 HOST 就代表本地。显示从 0 开始编号,所以 :0 是这台机器上启动的第一个本地显示,典型桌面会话下就是插在显示器上的那块屏。带图形界面的程序通过 DISPLAY 才能跟 X Server 通信,服务进程不继承桌面会话的环境变量,所以必须在服务文件里显式声明。
echo $DISPLAY
ps -eo pid,comm,args | grep Xorg
如果本机实际显示编号不是 0(例如起过多个会话),把服务文件里的 DISPLAY 改成对上号的值即可。
加载配置、启动、停止与开机启用
服务文件写完之后不会自动生效,必须先重载 systemd 配置让新单元被读到,这一步在改过任何服务文件后都要做。
sudo systemctl daemon-reload
sudo systemctl start ChatServer.service
sudo systemctl status ChatServer
sudo systemctl stop ChatServer.service
sudo systemctl enable ChatServer.service
另外别忘了启用开机自启:enable 是建立开机链接,不写这一行,daemon-reload 之后服务能手动拉起来但重启机器不会自动起。想看程序输出,用 journalctl -u ChatServer -f 实时跟,比把日志写文件省事。
图形程序服务的典型报错
如果 GUI 程序既没用普通用户、也没设 Environment=DISPLAY=:0,journal 里会打出非常典型的一串信息:先是 Authorization required, but no authorization protocol specified,接着是 qt.qpa.xcb: could not connect to display,再往后是 qt.qpa.plugin: Could not load the Qt platform plugin "xcb",最后是 This application failed to start because no Qt platform plugin could be initialized。系统还会记录主进程异常退出,状态显示为 core-dump。看到这条链就别再折腾重装应用了,十有八九就是缺 DISPLAY 或用了 root。
另一种常见情况是程序明明起来了但界面空白、过几秒自己退出,这通常伴随 xcb 插件路径不对,可以用 QT_DEBUG_PLUGINS=1 跑一次手动启动看插件加载细节。
选型建议
如果只是给自己用的小工具、开机登录后手动点一下也不嫌烦,图形界面的「启动应用程序」完全够用,改一条配置只要几秒。如果要的是服务级能力——等网络就绪、崩溃自动拉起、日志统一收、开机不依赖桌面会话——那就老老实实写 systemd 单元文件。二者并不互斥:一个后台服务走系统服务,一个托盘小工具走桌面自启动,是桌面运维里最常见的组合。
qidao123.com企服评测-应用市场 编辑提示:本文为 Linux 桌面运维实操笔记整理,所有命令与字段均可在 Ubuntu 22.04 环境复现,涉及权限与环境的调整请先在小范围验证。 |