Sign in Join
Wiki copytemporary

Daemons and Services/zh CN: what changed

Revision 3 to 4. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## 守护进程、服务和代理的概念
22
33Unix 中的“守护进程”和 Windows 中的“服务”是系统级程序,运行时无需用户交互;macOS 中的“代理”是用户级程序(相对系统级的守护进程而言),运行时有无用户交互均有可能。尽管叫法不同,但他们的功能类似:比如 **www** 或 **ftp** 服务在 Linux 下称作守护进程,而在 Windows 下则叫服务。由于不直接与用户交互,因此在启动时会关闭stdin、stdout、stderr 描述符。
44
55在 Free Pascal 和 Lazarus 中,可以利用 Lazarus 的 lazdaemon 包以平台无关的方式编写守护进程/服务。为避免与 Delphi 组件命名冲突,这些类命名为“daemon”。
66
77
88
99![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** 本文重点介绍 Windows 和 Linux。如需了解 Lazarus 和 macOS 的“代理”,请参阅[[macOS daemons and agents|macOS 守护进程和代理]]。
1010
1111## 用 Lazarus 和可视化组件创建服务/守护进程
1212
1313![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** 完整源代码在文末提供。
1414
1515### 前提:安装 LazDaemon 包
1616
1717在开始使用 Lazarus IDE 创建守护进程之前,必须通过“软件包”-“安装/卸载软件包”安装 LazDaemon 包,或者直接从.../lazarus/components/daemon/lazdaemon.lpk 安装 lpk 文件。
1818
1919LazDaemon 包会在 IDE 中安装一些新的组件和菜单。
2020
2121[![2022-02-21 14 34 28-LINDER-LAZW7 - VMware Workstation.png](/wiki-images/3/31/2022-02-21_14_34_28-LINDER-LAZW7_-_VMware_Workstation.png)](</File:2022-02-21_14_34_28-LINDER-LAZW7_-_VMware_Workstation.png>)
2222
2323鉴于 Lazarus 软件包的工作方式,安装 LazDaemon 包需要“保存并重新构建IDE”。
2424
2525### 创建并使用 Daemon 项目框架
2626
2727LazDaemon 包安装完成后,从“项目-新建项目”菜单选择“守护进程(服务)应用”。
2828
2929[![2022-02-17 16 31 12-Create a new project.png](/wiki-images/e/ec/2022-02-17_16_31_12-Create_a_new_project.png)](</File:2022-02-17_16_31_12-Create_a_new_project.png>)
3030
3131这会自动创建两个单元,一个派生自 TDaemon(DaemonUnit),另一个派生自 TDaemonMapper(DaemonMapperUnit)。以及一个主项目文件(TestDaemon.lpr),需要稍作修改才能使用:
3232```
3333Program TestDaemon;
3434
3535 Uses
3636
3737 // This is the scaffolded code, needs modification like shown below,
3838 // since UseCThreads is usually not definied, and is not needed here
3939 // {$IFDEF UNIX}{$IFDEF UseCThreads}
4040 // CThreads,
4141 // {$ENDIF}{$ENDIF}
4242
4343 {$IFDEF UNIX}
4444 CThreads,
4545 {$ENDIF}
4646 DaemonApp, lazdaemonapp, daemonmapperunit, DaemonUnit
4747 { add your units here };
4848
4949 {$R *.res}
5050
5151 begin
5252 Application.Initialize;
5353 Application.Run;
5454 end.
5555```
5656
5757[![Warning-icon.png](/wiki-images/b/b2/Warning-icon.png)](</File:Warning-icon.png>)
5858
5959**Warning:** 自动生成的 .lpr 文件中多了一条 {$IFDEF UseCThreads} ... {$ENDIF} 子句(上述示例中已被注释掉了),据观察,除非另作定义 UseCThreads,否则这个子句无法工作。可整句删除也可如上所示注释掉,或者在 Linux 下编译守护进程时添加 -dUseCThreads 选项。编写 Windows 服务不受影响。
6060
6161守护进程虽然没有 GUI,但 DaemonApp 和 DaemonMapper 单元都支持 Lazarus 窗体编辑器。这样就可以用熟悉的图形界面来设置守护进程和映射类的各种属性。当然,也可以完全用代码初始化所有属性,如下所示。
6262
6363自动生成的 DaemonApp 和 DaemonMapper 单元中都有多余的“var”定义(var DaemonMapper1: TDaemonMapper1; var Daemon1: TDaemon1;)。这两个变量在守护进程运行时并未实际用到,依然保持未初始化状态,对他们进行访问会导致致命错误 access violation。只要进一步了解了守护进程应用的内部工作原理,就会发现代码只与类相关(TDaemonMapper1、TDaemon),这两个变量完全可忽略或删除。
6464
6565TDaemon 类的代码主要用于响应操作系统发送的各种控制消息。TDaemonMapper 包含了描述服务的一些数据结构。这两个类都需要在守护进程应用框架中注册,将在守护进程启动时供内部使用,完成对守护进程的配置。在以下示例中,注册是在单元的“Initialization”部分完成的。
6666
6767只要在单元的 uses 部分加入 DaemonApp,即可引入守护进程类应用的主“Application”对象。守护进程应用的“Application”提供了完整的服务框架,包括安装和卸载、记录日志之类的高级功能。守护进程的“工作线程”需要自行创建,可参阅后续的示例代码,请勿使用 TDaemon 类的“Execute”方法。
6868
6969### 发布 DaemonMapper 类
7070
7171下面先填充一些基本属性,让程序跑起来。请注意,DaemonClassName 必须与 DaemonClass 单元中定义的类名完全一致。
7272
7373[![2022-01-31 23 32 52-Settings.png](/wiki-images/1/19/2022-01-31_23_32_52-Settings.png)](</File:2022-01-31_23_32_52-Settings.png>)
7474
7575![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** 跨平台程序员请注意:Unix 系统区分大小写,通常使用小写字母,因此如果如上定义了大小写混合的服务名称,可能会引发一些混淆。
7676
7777不妨简单介绍一下 WinBindings 属性,即可通过 Windows 服务管理器配置各种服务属性,例如启动类型和用户帐户。虽然这个简单的守护应用中并未用到,但对于真正的守护进程应用程序而言,合理设置这些选项非常重要。WinBindings 在 Linux 中无效,请参阅关于 Linux 安装和卸载功能的章节,了解如何在 systemd “单元”(.service)文件中设置选项,以便为 Linux 实现相同的功能。
7878
7979### 编写守护进程的方法
8080
8181TDaemons 支持以下方法:
8282
83-方法/事件 | 说明
84----|---
85-`OnStart` | 守护进程将启动时调用。本方法必须立即返回并置 _OK:=True_ 。
86-`OnStop` | 守护进程将停止时调用。本方法必须立即返回并置 _OK:=True_ 。
87-`OnShutDown` | 守护进程将被杀掉时调用,因为系统将要关闭。本方法必须立即停止守护进程,并置 _OK:=True_ 返回。本方法在 Linux 系统中不会触发。Linux 会直接杀掉守护进程。
88-`OnPause` | 守护进程将暂停时调用。本方法必须立即返回并置 _OK:=True_ 。本方法在 Linux 系统中不会触发,系统内核会停止整个守护进程并触发 OnStop 事件。
89-`OnContinue` | \- | `OnExecute` | 不要用本方法实现工作线程,请参考示例代码实现工作线程的任务。
90-`BeforeInstall` | 服务即将安装时调用。
91-`AfterInstall` | 服务安装成功后调用。
92-`BeforeUninstall` | 服务卸载前调用。
93-`AfterUninstall` | 服务卸载完成后调用。
94-
83+| 方法/事件 | 说明 | | |
84+|---|---|---|---|
85+| `OnStart` | 守护进程将启动时调用。本方法必须立即返回并置 _OK:=True_ 。 | | |
86+| `OnStop` | 守护进程将停止时调用。本方法必须立即返回并置 _OK:=True_ 。 | | |
87+| `OnShutDown` | 守护进程将被杀掉时调用,因为系统将要关闭。本方法必须立即停止守护进程,并置 _OK:=True_ 返回。本方法在 Linux 系统中不会触发。Linux 会直接杀掉守护进程。 | | |
88+| `OnPause` | 守护进程将暂停时调用。本方法必须立即返回并置 _OK:=True_ 。本方法在 Linux 系统中不会触发,系统内核会停止整个守护进程并触发 OnStop 事件。 | | |
89+| `OnContinue` | \- | `OnExecute` | 不要用本方法实现工作线程,请参考示例代码实现工作线程的任务。 |
90+| `BeforeInstall` | 服务即将安装时调用。 | | |
91+| `AfterInstall` | 服务安装成功后调用。 | | |
92+| `BeforeUninstall` | 服务卸载前调用。 | | |
93+| `AfterUninstall` | 服务卸载完成后调用。 | | |
94+
9595以下代码片段展示了成功实现简单守护进程所需的主要事件处理程序。已在 Windows 7/10/11 和几个 Linux 版本(Debian 10.8 和 Ubuntu 20.04.3)中测试通过。
9696
9797有些便利性辅助函数已剥离至单独的单元中,以防扰乱守护进程的核心代码。
9898
9999 * FileLoggerUnit:线程安全的记录日志文件的辅助工具。日志是必需记录的,因为 TDaemon 应用的控制信号接收器和服务代码需运行于单独的线程,否则服务代码运行时守护进程将无法响应控制信号。因此,在守护进程中至少涉及两个任务,这两个任务同时访问日志文件时可能会发生冲突。关于如何运用 TRTLCriticalSection 保证代码线程安全,实现对单一资源的串行访问,详情请参阅 Lazarus 维基的[[Multithreaded Application Tutorial|多线程应用开发教程]]。FileLoggerUnit 会将日志文件写入程序所在目录,请确保程序拥有写入权限。这里的代码会每天写一个文件,文件名包含创建日期。如果需要更高级的日志记录工具,可以考虑使用 [[LazLogger]] 单元。
100100 * [DaemonWorkerThread](<https://gitlab.com/freepascal.org/lazarus/lazarus/-/tree/main/examples/TDaemon>):TThread 的派生类,可容纳守护进程的“工作”代码。关于 TThread.Execute 方法,其中不可避免包含了“无限”循环,详情请参阅 Lazarus 维基的[[Multithreaded Application Tutorial|多线程应用开发教程]]。由 TDaemon 启动的工作线程并没有什么特别的,就和其他线程一样。
101101 * [<https://gitlab.com/freepascal.org/lazarus/lazarus/-/tree/main/examples/TDaemon> DaemonSystemdInstallerUnit:该单元为 Linux 系统提供 -install 和 -uninstall 命令行参数支持。通过对 /lib/systemd/system 写入相应的控制文件,加入对 systemd/systemctl 的支持。
102102
103103### 启动/停止信号的处理
104104```
105105// ---------------------
106106 // 启动和停止信号
107107 // ---------------------
108108
109109 procedure TDaemon1.DataModuleStart(Sender: TCustomDaemon; var OK: Boolean);
110110 begin
111111 LogToFile(Format('Daemon received start signal, PID:%d', [GetProcessID]));
112112 // 创建挂起的工作线程 - 参见 DaemonWorkerThread 单元
113113 FDaemonWorkerThread := TDaemonWorkerThread.Create;
114114 // 设置参数
115115 FDaemonWorkerThread.FreeOnTerminate := False;
116116 // 启动工作线程
117117 FDaemonWorkerThread.Start;
118118 OK := True;
119119 end;
120120
121121 procedure TDaemon1.DataModuleStop(Sender: TCustomDaemon; var OK: Boolean);
122122 begin
123123 LogToFile('Daemon received stop signal');
124124 // 停止并销毁工作线程
125125 if assigned(FDaemonWorkerThread) then
126126 begin
127127 FDaemonWorkerThread.Terminate;
128128 // 等待线程停止
129129 FDaemonWorkerThread.WaitFor;
130130 FreeAndNil(FDaemonWorkerThread);
131131 end;
132132 LogToFile('Daemon stopped');
133133 OK := True;
134134 end;
135135```
136136
137137上述代码负责处理操作系统发出的启动和停止信号。守护进程启动时,将会调用 DataModuleStart,生成 DaemonWorkerThread 单元定义的“工作”线程,至于守护进程的实际工作代码请参阅 TDeamonWorkerThread.Execute 方法。对线程代码的理解,可参考[[Multithreaded Application Tutorial|多线程应用开发教程]]和 TThread 的文档。
138138
139139[![Warning-icon.png](/wiki-images/b/b2/Warning-icon.png)](</File:Warning-icon.png>)
140140
141141**Warning:** 编写守护进程的代码时,或许有人想在 TDaemon 的“execute”方法中实现处理过程。但这样的运行效果并不理想,特别是在 Windows 系统中。因为 Execute 方法并不会作为单独的线程运行,在其运行期间守护进程会停止处理控制消息。于是,守护进程可以启动并正常运行,但要用 Windows 服务管理器或 sc 命令来停止/暂停/恢复时,守护进程将不会响应,就像被挂起了一样。
142142
143143### 关机消息的处理
144144
145145此处的处理代码仅适用于 Windows 系统,当操作系统关闭时用于停止守护进程。如果不做处理,Windows 会直接强行杀死守护进程,已打开的文件可能会遭到损坏。下面演示的服务较为简单,可直接调用 DataModuleStop 事件处理程序,执行正常停止服务的代码。Linux 系统不支持关机消息的处理,而会直接调用停止服务的处理代码。
146146```
147147procedure TDaemon1.DataModuleShutDown(Sender: TCustomDaemon);
148148 // 仅 Windows 系统支持
149149 begin
150150 self.Stop; // 系统关机时调用停止服务的处理代码。对于本演示程序而言完全够用了。
151151 LogToFile('Daemon received shutdown signal');
152152 end;
153153```
154154
155155### Linux 下的安装和卸载(systemd)
156156
157157LazDaemon 中内置的安装和卸载功能(通过 -install 和 -uninstall 命令行参数)仅适用于 Windows。以下给出了简单的示例代码,展示了如何为安装和卸载事件创建自定义处理过程,以便添加 Linux 下的类似功能。
158158```
159159// --------------------------------
160160 // Installation and De-Installation
161161 // --------------------------------
162162
163163 procedure TDaemon1.DataModuleAfterInstall(Sender: TCustomDaemon);
164164
165165 var
166166 isInstalled: boolean = True;
167167 FilePath: string;
168168
169169 begin
170170 LogToFile('Daemon installing');
171171 {$IFDEF UNIX}
172172 FilePath := GetSystemdControlFilePath(Self.Definition.Name);
173173 isInstalled := CreateSystemdControlFile(self, FilePath);
174174 if not isInstalled then
175175 LogToFile('Error creating systemd control file: ' + FilePath);
176176 {$ENDIF}
177177 if isInstalled then
178178 LogToFile('Daemon installed');
179179 end;
180180
181181 procedure TDaemon1.DataModuleBeforeUnInstall(Sender: TCustomDaemon);
182182 var
183183 isUnInstalled: boolean = True;
184184 FilePath: string;
185185
186186 begin
187187 LogToFile('Daemon uninstalling');
188188 {$IFDEF UNIX}
189189 FilePath := GetSystemdControlFilePath(Self.Definition.Name);
190190 isUnInstalled := RemoveSystemdControlFile(FilePath);
191191 if not isUninstalled then
192192 LogToFile('Error removing systemd control file: ' + FilePath);
193193 {$ENDIF}
194194 if isUninstalled then
195195 LogToFile('Daemon uninstalled');
196196 end;
197197```
198198
199199上述处理代码仅适用于 Unix/Linux 系统,利用 DaemonSystemdInstallerUnit 中的函数建立一个 systemd 控制文件(.service 文件),并放入 /lib/systemd/system 目录,以便用 systemctl 命令来控制守护进程。在卸载守护进程时,将会删除此 .service 文件。
200200
201201如果 .service 文件中未指定用户账户,systemd 将会以 root 身份运行此守护进程。出于安全考虑,以“root”(Linux)或“LocalSystem”(Windows)身份运行守护进程不是个好习惯,可能会带来很多其他障碍,其中之一就是难以调试正在运行的守护进程。可以考虑在 DaemonSystemdInstallerUnit.CreateSystemdControlFile 中加入指定用户的代码:
202202```
203203[...]
204204 f.WriteString('Service', 'Type', 'simple');
205205 f.WriteString('Service', 'User', '...'); // insert a service account here, otherwise the daemon will run as root
206206 f.WriteString('Service', 'ExecStart', Application.ExeName + ' -r');
207207 [...]
208208```
209209
210210![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** 程序运行时,TDaemonMapper 的全部参数,特别是TDaemonMapper.WinBindings 的设置,都可从“self.Definition”和“self.Definition.WinBindings”结构读取。尽可以用其中的一些参数来填充 .service 文件中的设置。
211211
212212若要全面了解 systemd .service 文件(有时也称为 systemd“单元文件”)中所有可用设置,请参阅 Linux 的发行文档。
213213
214214### 工作线程
215215
216216守护进程的主要内容都有一段执行实际工作的代码,这里称作“工作”线程。因为守护进程要继续在后台监听控制消息并作出处理,所以工作线程需为单独的线程。上述 TDaemon.DataModuleStart 和 TDaemon.DataModuleStop 例程已给出了工作线程的创建和销毁代码。
217217```
218218procedure TDaemonWorkerThread.Execute;
219219
220220 var
221221 i: integer;
222222
223223 begin
224224 LogToFile('Daemon worker thread executing');
225225 while not Terminated do
226226 begin
227227 // 以下仅为占位代码,请在此插入实际的服务代码
228228 // ...
229229 LogToFile('Daemon worker thread running');
230230 // 线程和 CPU 负载都不高的延时5秒循环
231231 for i := 1 to 50 do
232232 begin
233233 if Terminated then break;
234234 Sleep(100);
235235 end;
236236 // ...
237237 // ----------------------------------------
238238 end;
239239 LogToFile('Daemon worker thread terminated');
240240 end;
241241```
242242
243243上述工作线程没有太多任务,只是循环至线程终止,每5秒在日志中写入消息。
244244
245245将两个“...”之间的代码替换掉,即可实现自己的守护进程功能。
246246
247247TThread.Execute 方法的内部循环必须以某种方式频繁检查“Terminated”标志。Terminated 标志由 TThread.Terminate 方法设置,正如前文 TDaemon.DataModuleStop 所示,TThread.Terminate 方法用于优雅地结束线程的执行。关于 TThread 的更多信息,请参阅[[Multithreaded Application Tutorial|多线程应用开发教程]]。
248248
249249为了让示例代码尽量简单,这里没有以任何方式处理运行时错误。记录日志单元的错误将静默忽略,守护进程的其他运行时错误将导致进程崩溃。请参阅[[Exceptions|异常]]以总体了解异常的捕获和处理方式,查看 TThread.HandleException 文档以了解在工作线程的 execute 方法中正确捕获和转发异常的方式。
250250
251251### 用代码初始化 TDaemon 属性
252252
253253如果不想用 Lazarus IDE 及其窗体编辑器设置 TDaemon 和 TDaemonMapper 的属性,完全用代码完成也是没问题的。请注意,这时项目不能像其他应用那样基于 LCL/Form 创建,而应该基于 DaemonApp。否则会报大量的流加载错误,代码将无法运行。
254254
255255前文已介绍了 TDaemon 的用法(事件驱动的守护进程编程),现在的区别只是在代码中要将事件处理过程赋值如下:
256256```
257257TDaemon1 = class(TDaemon)
258258 {...}
259259 procedure DataModuleStart(Sender: TCustomDaemon; var OK: Boolean); // 事件处理过程的示例
260260 {...}
261261 public
262262 constructor Create(AOwner: TComponent); override; // 覆盖构造函数,初始化事件处理过程
263263 end;
264264
265265 {...}
266266
267267 constructor TDaemon1.Create(AOwner: TComponent);
268268 begin
269269 inherited Create(AOwner);
270270 {...}
271271 onStart := @DataModuleStart; // 赋值示例的事件处理过程
272272 {...}
273273 end;
274274```
275275
276276TDaemonMapper 的编写方式类似。
277277
278278![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** 如果更愿意用OOP继承的方式定制 TDaemon 和 TDaemonMapper 类,也可以如下操作:从 TCustomDaemon 继承自己的守护进程,从 TCustomDaemonMapper 继承自己的映射器,并根据需要重写“Protected”部分的各个虚函数。
279279
280280以下是 DaemonApp 单元中显示可用属性的部分代码:
281281```
282282TCustomDaemon = Class(TDataModule)
283283 private
284284
285285 [...]
286286
287287 Protected
288288 Function Start : Boolean; virtual;
289289 Function Stop : Boolean; virtual;
290290 Function Pause : Boolean; virtual;
291291 Function Continue : Boolean; virtual;
292292 Function Execute : Boolean; virtual;
293293 Function ShutDown : Boolean; virtual;
294294 Function Install : Boolean; virtual;
295295 Function UnInstall: boolean; virtual;
296296
297297 [...]
298298```
299299
300300## 纯代码创建服务/守护进程
301301
302302![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** 文末提供了完整的示例代码链接。
303303
304304TestDaemonCodeOnly 作为演示项目,用到了 TCustomDaemon 和 TCustomDaemonMapper类,并且不依赖 Lazarus IDE。因此,该项目无需 .lpr 或 .lfm 之类的 Lazarus 辅助文件,并且可用任何纯文本编辑器进行维护。该项目并不使用 GUI 核心代码那种事件驱动模式,而是基于 OOP 和继承。至于其他的那些辅助单元则与以上 GUI 示例的相同,包括记录文件日志、守护进程工作线程、基于 Linux systemd 的 -install 和 -uninstall 支持等功能。
305305
306306Linux 的 -install 和 -uninstall 支持功能,同样由以上示例用到的 DaemonSystemdInstallerUnit 提供。Windows 服务的安装和卸载功能已内置于 DaemonApp 单元中了。TDaemonMapper 类的 WinBindings 属性可用于配置 Windows 服务,Linux 系统中不用此属性也没有效果。DeamonSystemdInstallerUnit 中的代码为 Linux/systemd/systemctl 提供了类似的功能。
307307
308308Windows 不支持 -run 参数,而是提供了自己的服务控制工具(Windows 服务控制管理器或“sc”命令行工具)。
309309
310310守护进程的代码需要用多个线程实现,一个用于处理守护进程控制消息,另一个独立线程用于守护进程的“工作”代码。仅仅重写 TCustomDaemon.Execute 方法是不够的,因为守护进程在 TDaemon.Execute 方法中循环时,无法处理控制消息。因此,在 Windows 服务控制管理器中会显示为挂起状态,并且不会响应 sc 命令。“工作”线程的代码与以上 GUI 示例中的相同,并且已在 DaemonWorkerThread 单元中实现。
311311
312312因为没有了 GUI 和控制台,所以需要用到记录日志功能。TCustomDaemon 对象提供了日志记录功能(通过 EventLog 属性),但不是线程安全的。FileLoggerUnit 提供了简单的线程安全的文件日志记录功能。如果需要更复杂的日志记录功能,可以考虑使用[[LazLogger]]单元。
313313
314314[![Warning-icon.png](/wiki-images/b/b2/Warning-icon.png)](</File:Warning-icon.png>)
315315
316316**Warning:** FileLoggerUnit 生成的日志文件将写入程序所在目录,请确保程序拥有写入权限。
317317
318318关于多线程的详细信息,以及通过 CriticalSection 保障代码的线程安全,请参阅[[Multithreaded Application Tutorial|多线程应用开发教程]]。
319319```
320320program TestDaemonCodeOnly;
321321
322322 {$mode objfpc}{$H+}
323323
324324 uses
325325 {$IFDEF UNIX}
326326 cthreads,
327327 {$ENDIF}
328328 Classes,
329329 SysUtils,
330330 { you can add units after this }
331331 DaemonApp,
332332 FileLoggerUnit, // 线程安全的文件日志记录工具
333333 DaemonWorkerThread, // TThread 的派生类,用于执行守护工作
334334 DaemonSystemdInstallerUnit; // Linux/systemd 的 -install 和 -uninstall 功能
335335
336336
337337 // ------------------------------------------------------------------
338338 // TDaemonMapper: 定义守护进程的配置
339339 // ------------------------------------------------------------------
340340
341341 type
342342 TDaemonMapper1 = class(TCustomDaemonMapper)
343343 public
344344 constructor Create(AOwner: TComponent); override;
345345 end;
346346
347347 constructor TDaemonMapper1.Create(AOwner: TComponent);
348348
349349 begin
350350 inherited Create(AOwner);
351351 with TDaemonDef(self.DaemonDefs.Add) do
352352 begin
353353 DaemonClassName := 'TDaemon1'; // 必须与守护进程类名完全一样
354354 Name := 'TestDaemonCodeOnly'; // 服务名
355355 DisplayName := 'Test Daemon (CodeOnly)'; // 显示的服务名
356356 Description := 'Lazarus Daemons and Services Wiki Demo Service (Created in Code only)';
357357 Options := [doAllowStop, doAllowPause];
358358 WinBindings.StartType := stManual; // stBoot, stSystem, stAuto, stManual, stDisabled
359359 WinBindings.ServiceType := stWin32;
360360 end;
361361 end;
362362
363363 // -------------------------------------------------------------------
364364 // TDaemon: 定义守护进程任务,并处理 Windows/Linux 服务管理器的事件
365365 // -------------------------------------------------------------------
366366
367367 type
368368
369369 { TDaemon1 }
370370
371371 TDaemon1 = class(TCustomDaemon)
372372 private
373373 FDaemonWorkerThread: TDaemonWorkerThread;
374374 public
375375 function Start: boolean; override; // 启动工作线程
376376 function Stop: boolean; override; // 停止工作线程
377377 function Pause: boolean; override; // 暂停工作线程(仅限 Windows)
378378 function Continue: boolean; override; // 回复工作线程(仅限 Windows)
379379 function ShutDown: boolean; override; // 由于操作系统关机而停止工作线程
380380 function Install: boolean; override; // 在 Linux 下添加 -install 功能
381381 function UnInstall: boolean; override; // 在 Linux 下添加 -uninstall 功能
382382 end;
383383
384384 { TDaemon1 }
385385
386386 // ------------------------------------------------
387387 // 启动和停止信号的处理
388388 // ------------------------------------------------
389389
390390 function TDaemon1.Start: boolean;
391391 begin
392392 // 创建挂起的工作线程 - 参阅 DaemonWorkerThread 单元
393393 FDaemonWorkerThread := TDaemonWorkerThread.Create;
394394 // 设置参数
395395 FDaemonWorkerThread.FreeOnTerminate := False;
396396 // 启动工作线程
397397 FDaemonWorkerThread.Start;
398398 LogToFile(Format('TDaemon1: service %s started, PID=%d', [self.Definition.Name, GetProcessID]));
399399 Result := True;
400400 end;
401401
402402 function TDaemon1.Stop: boolean;
403403 begin
404404 // 停止并销毁工作线程
405405 if assigned(FDaemonWorkerThread) then
406406 begin
407407 FDaemonWorkerThread.Terminate;
408408 // 等待线程终止
409409 FDaemonWorkerThread.WaitFor;
410410 FreeAndNil(FDaemonWorkerThread);
411411 end;
412412 Result := True;
413413 LogToFile(Format('TDaemon1: service %s stopped', [self.Definition.Name]));
414414 end;
415415
416416 // ------------------------------------------------
417417 // 暂停和恢复运行信号的处理(仅限 Windows)
418418 // ------------------------------------------------
419419
420420 function TDaemon1.Pause: boolean;
421421 begin
422422 FDaemonWorkerThread.Suspend; // 已过时,不过还能用
423423 LogToFile(Format('TDaemon1: service %s paused', [self.Definition.Name]));
424424 Result := True;
425425 end;
426426
427427 function TDaemon1.Continue: boolean;
428428
429429 begin
430430 LogToFile(Format('TDaemon1: service %s continuing', [self.Definition.Name]));
431431 FDaemonWorkerThread.Resume; // 已过时,不过还能用
432432 Result := True;
433433 end;
434434
435435 // --------------------------------------------------------------
436436 // 操作系统关机时停止信号的处理(仅限 Windows)
437437 // --------------------------------------------------------------
438438
439439 function TDaemon1.ShutDown: boolean;
440440 begin
441441 Result := self.Stop; // 关机时,触发停止服务的事件处理。 对示例而言足够了。
442442 LogToFile(Format('TDaemon1: service %s shutdown', [self.Definition.Name]));
443443 end;
444444
445445 // -----------------------------------------------------------------------------------------
446446 // Linux 下的安装和卸载功能, Windows 下已内置于 TCustomDaemon
447447 // -----------------------------------------------------------------------------------------
448448
449449 function TDaemon1.Install: boolean;
450450
451451 var
452452 FilePath: string;
453453
454454 begin
455455 Result := False;
456456 {$IFDEF WINDOWS}
457457 Result := inherited Install;
458458 {$ELSE}
459459 {$IFDEF UNIX}
460460 FilePath := GetSystemdControlFilePath(Self.Definition.Name);
461461 LogToFile(Format('TDaemon1: installing control file: %s',[FilePath]));
462462 Result := CreateSystemdControlFile(self, FilePath);
463463 if not Result then
464464 LogToFile('TDaemon1: Error creating systemd control file: ' + FilePath);
465465 {$ENDIF}
466466 {$ENDIF}
467467 LogToFile(Format('TDaemon1: service %s installed: %s', [self.Definition.Name, BoolToStr(Result, 'ok', 'failure')]));
468468 end;
469469
470470 function TDaemon1.UnInstall: boolean;
471471
472472 var
473473 FilePath: string;
474474
475475 begin
476476 Result := False;
477477 {$IFDEF WINDOWS}
478478 Result := inherited UnInstall;
479479 {$ELSE}
480480 {$IFDEF UNIX}
481481 FilePath := GetSystemdControlFilePath(Self.Definition.Name);
482482 Result := RemoveSystemdControlFile(FilePath);
483483 if not Result then
484484 LogToFile('TDaemon1: Error removing systemd control file: ' + FilePath);
485485 {$ENDIF}
486486 {$ENDIF}
487487 LogToFile(Format('TDaemon1: service %s uninstalled: %s', [self.Definition.Name, BoolToStr(Result, 'ok', 'failure')]));
488488 end;
489489
490490 // ---------------------
491491 // 主程序,包含初始化代码
492492 // ---------------------
493493
494494 begin
495495 RegisterDaemonClass(TDaemon1);
496496 RegisterDaemonMapper(TDaemonMapper1);
497497 Application.Run;
498498 end.
499499```
500500
501501## 守护进程/服务的安装
502502
503503### Windows
504504
505505在任一提权后的命令提示符下,只要带 -install 参数启动可执行文件,即可安装服务。
506506
507507打开一个提权(以管理员身份运行)的终端窗口,进入编译完成的 TestDaemon 所在目录,即可执行以下命令:
508508
509-命令 | 说明
510----|---
511-`TestDaemon -install` | 安装服务
512-`sc query TestDaemon` | 查看服务状态
513-`sc config TestDaemon start=auto` | 将服务配置为随机器启动而自动运行
514-`sc config TestDaemon start=manual` | 将服务配置为手动启动
515-`sc config TestDaemon start=disabled` | 禁用服务(无法手动启动)
516-`TestDaemon -uninstall` | 删除服务
517-
509+| 命令 | 说明 |
510+|---|---|
511+| `TestDaemon -install` | 安装服务 |
512+| `sc query TestDaemon` | 查看服务状态 |
513+| `sc config TestDaemon start=auto` | 将服务配置为随机器启动而自动运行 |
514+| `sc config TestDaemon start=manual` | 将服务配置为手动启动 |
515+| `sc config TestDaemon start=disabled` | 禁用服务(无法手动启动) |
516+| `TestDaemon -uninstall` | 删除服务 |
517+
518518[![Screenshot taken 2/2022 on a Windows 11 machine](/wiki-images/a/a4/2022-01-31_23_50_28-Lazarus_IDE_v2.2.0RC2_-_Daemon_application.png)](</File:2022-01-31_23_50_28-Lazarus_IDE_v2.2.0RC2_-_Daemon_application.png> "Screenshot taken 2/2022 on a Windows 11 machine")
519519
520520服务安装成功后,可通过 sc 命令行或“服务”管理控制台中的 GUI 对服务进行控制。在较新的 Windows 版本中,任务管理器的“服务”页也提供了基础的服务控制功能。
521521
522522服务安装后,最重要的 sc 控制命令包括:
523523
524-命令 | 说明
525----|---
526-`sc start TestDaemon` | 启动服务
527-`sc query TestDaemon` | 查询服务状态
528-`sc stop TestDaemon` | 停止服务
529-
524+| 命令 | 说明 |
525+|---|---|
526+| `sc start TestDaemon` | 启动服务 |
527+| `sc query TestDaemon` | 查询服务状态 |
528+| `sc stop TestDaemon` | 停止服务 |
529+
530530也可以使用 Windows 的服务控制管理器。
531531
532532[![ServiceControlManager.png](/wiki-images/0/07/ServiceControlManager.png)](</File:ServiceControlManager.png>)
533533
534534![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** Windows 服务的大多数属性,都可以通过 DaemonMapperUnit1 中的 TDaemonMapper1.WinBindings 属性进行设置,比如随系统自动启动或使用指定帐户等。
535535
536536### Linux
537537
538538与 Windows 不同,TDaemonApp 应用不提供自动安装功能,因为在 Linux/Unix 系统下存在各种各样的服务控制子系统。可在 TCustomDaemon 对象的安装和卸载事件处理过程中实现自己的安装脚本,类似上述代码示例中展示的对 systemd 的支持。如果采用这种方法,那么安装和卸载的过程基本与 Windows 相同。
539539
540540可以在终端中用 sudo 再加 -install 或 -uninstall 参数来启动可执行文件,以便安装或卸载服务:
541541
542-命令 | 说明
543----|---
544-`sudo ./TestDaemon -install` | 安装服务
545-`sudo ./TestDaemon -uninstall` | 卸载服务
546-`systemctl enable TestDaemon` | 将服务配置为随系统自动启动
547-`systemctl disable TestDaemon` | 将服务配置为不随系统自动启动
548-
542+| 命令 | 说明 |
543+|---|---|
544+| `sudo ./TestDaemon -install` | 安装服务 |
545+| `sudo ./TestDaemon -uninstall` | 卸载服务 |
546+| `systemctl enable TestDaemon` | 将服务配置为随系统自动启动 |
547+| `systemctl disable TestDaemon` | 将服务配置为不随系统自动启动 |
548+
549549服务安装完成后,可以用 systemctl 命令进行控制:
550550
551-命令 | 说明
552----|---
553-`systemctl start TestDaemon` | 启动服务
554-`systemctl status TestDaemon` | 查询服务状态
555-`systemctl stop TestDaemon` | 停止服务
556-
551+| 命令 | 说明 |
552+|---|---|
553+| `systemctl start TestDaemon` | 启动服务 |
554+| `systemctl status TestDaemon` | 查询服务状态 |
555+| `systemctl stop TestDaemon` | 停止服务 |
556+
557557[![OnLinuxWithSystemctl.png](/wiki-images/4/4f/OnLinuxWithSystemctl.png)](</File:OnLinuxWithSystemctl.png>)
558558
559559由命令行参数 -install 触发的 DaemonSystemdInstallerUnit.CreateSystemdControlFile 代码,将会读取 TDaemonMapper 类最为基本的属性,并在 /lib/systemd/system 目录下写入一个简单的 systemd .service 文件,使得守护进程接受 systemd 的控制。若要进行手动操作,可以用任何文本编辑器按照以下模板创建自己的 .service 文件:
560560```
561561[Unit]
562562 Description=Long description of your application
563563 After=network.target
564564
565565 [Service]
566566 Type=simple
567567 User=name_of_a_service_user_account
568568 ExecStart=complete_path_and_file_name -r
569569 RemainAfterExit=yes
570570 TimeoutSec=25
571571
572572 [Install]
573573 WantedBy=multi-user.target
574574```
575575
576576 * 请编辑以下内容
577577 * Description - 服务的详细说明
578578 * ExecStart - 编译好的全路径可执行文件名
579579 * User - 指定用户,省略则以 root 身份运行,但强烈不建议以 root 身份运行守护进程
580580
581581 * 保存文件
582582 * 进入 /lib/systemd/system/
583583 * 将文件命名为 name_of_your_service.service
584584
585585在 DaemonSystemdInstallerUnit.CreateSystemdControlFile 创建的 .service 文件中,还有很多条目可用于自定义 systemd 的控制方式,请参阅 Linux systemd “unit”文件参考手册。
586586
587587## 日志文件的读取
588588
589589以下是个在 Windows 下创建的日志文件示例,显示守护进程运行时的内部状态,在 Linux 下的显示将完全相同。
590590
591591[![2022-02-01 12 17 27-TestDaemon.png](/wiki-images/d/d1/2022-02-01_12_17_27-TestDaemon.png)](</File:2022-02-01_12_17_27-TestDaemon.png>)
592592
593593[![Warning-icon.png](/wiki-images/b/b2/Warning-icon.png)](</File:Warning-icon.png>)
594594
595595**Warning:** FileLoggerUnit 将把日志写入守护程序所在目录,请确保程序拥有写入权限。
596596
597597## 守护进程/服务的调试
598598
599599守护进程的调试并不像普通应用程序那么直接,因为运行中的守护进程通常既没有 GUI 也没有控制台,并且其运行/停止状态通常由操作系统管理。Windows 和 Linux 的调试方式也不一样,特别是调试会话的启动方式。一旦将调试进程附加到守护进程并到达第一个断点,后续的操作就与调试其他任何应用一样了,可以直接从 Lazarus GUI 控制调试器和守护进程的源代码。
600600
601601在开始调试之前,需用“调试”模式编译服务/守护进程的代码,以便插入调试代码并生成相应的调试符号文件(.dbg 文件)。
602602
603603### Windows
604604
605605在 Windows 中的调试策略取决于要查看哪部分代码:
606606
607607\- 若要调试 -install 或 -uninstall 部分,请在 Lazarus GUI 中将“运行-运行参数”设为 -install 或 -uninstall,设置好断点,然后运行代码[F9]。
608608
609609\- 若需调试服务的工作线程代码,请通过操作系统(使用 sc 命令或 Windows 服务管理器)启动服务,确定进程ID(PID),然后选择“运行-附加到程序”。一旦附加成功,调试器将自动停止于 ntdll.dll!DbgUserBreakPoint 处预设的临时断点,此时服务将运行于工作线程代码的循环内。在工作线程的代码中设好断点和监视后,用[F9]运行代码继续调试。
610610
611611[![2022-02-21 21 25 22-Lazarus IDE v2.2.0 - Daemon application \(debugging ...\).png](/wiki-images/3/38/2022-02-21_21_25_22-Lazarus_IDE_v2.2.0_-_Daemon_application_%28debugging_...%29.png)](</File:2022-02-21_21_25_22-Lazarus_IDE_v2.2.0_-_Daemon_application_\(debugging_...\).png>)
612612
613613[![Warning-icon.png](/wiki-images/b/b2/Warning-icon.png)](</File:Warning-icon.png>)
614614
615615**Warning:** 如果是远程调试正在运行的进程,Lazarus 快捷菜单中的暂停按钮似乎不起作用,如果按下暂停则会挂起调试器。请在添加断点后用“运行”[F9]继续调试。
616616
617617\- “若需调试创建或启动服务的处理代码,或是在“附加到程序”之前就可能要执行的代码,可插入以下代码让代码等待,直至调试进程附加完成:
618618```
619619// 以下代码将维持循环至调试进程附件完成
620620 While not IsDebuggerPresent do sleep(100); // 循环至调试进程附件完成
621621 // ... 在循环后可加入断点,以便在循环结束后立即捕获异常
622622```
623623
624624[![2022-02-21 21 36 38-Lazarus IDE v2.2.0 - Daemon application \(debugging ...\).png](/wiki-images/c/c7/2022-02-21_21_36_38-Lazarus_IDE_v2.2.0_-_Daemon_application_%28debugging_...%29.png)](</File:2022-02-21_21_36_38-Lazarus_IDE_v2.2.0_-_Daemon_application_\(debugging_...\).png>)
625625
626626调试进程完成并按下[F9]后,守护进程将停止于断点位置。
627627
628628![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** Linux 支持的 -run 参数在 Windows 上不可用。Windows 必须用服务控制管理器启动和停止服务。若要把调试进程附加到正在运行的服务进程上,需以管理员权限(提权)运行 Lazarus IDE,否则在尝试附加到服务进程时会收到错误5(拒绝访问)。如果 Lazarus 已提权运行了,但仍难以附加到服务进程上(“拒绝访问”或“运行-附加到程序”呈灰色状态),请确保在“项目-项目设置-调试器”中正确设置了调试器,很可能需选择“Gdb(Gnu 调试器)”。
629629
630630### Linux
631631
632632从某种程度上说,在 Linux 中守护进程可能更容易调试,因为可在 Lazarus IDE 中像其他程序一样运行,只需在“运行-运行参数”菜单的“命令行参数”中输入 -run 即可。不过请记得,很多问题都源自操作系统的安全限制。当守护进程在用户模式下运行时,这些限制很多都不适用。
633633
634634[![2022-02-22 12 18 20-Debian 10.8 Buster - VMware Workstation.png](/wiki-images/f/fd/2022-02-22_12_18_20-Debian_10.8_Buster_-_VMware_Workstation.png)](</File:2022-02-22_12_18_20-Debian_10.8_Buster_-_VMware_Workstation.png>)
635635
636636现在可以像普通应用一样,用[F9]启动守护进程并进行调试了。
637637
638638[![2022-02-22 12 19 41-Debian 10.8 Buster - VMware Workstation.png](/wiki-images/e/e9/2022-02-22_12_19_41-Debian_10.8_Buster_-_VMware_Workstation.png)](</File:2022-02-22_12_19_41-Debian_10.8_Buster_-_VMware_Workstation.png>)
639639
640640如果确实需要将调试器附加到正在运行的守护进程上,比如怀疑问题与某项安全限制相关,那么可通过“运行-附加到程序”来实现,前提是已将守护进程配置为指定用户运行,而不是 root 用户。请参阅上述关于 Linux 安装和卸载功能的内容,了解如何向 .service 文件添加适当的参数。
641641
642642很遗憾,Linux 不支持像 Windows 那么方便的“IsDebuggerPresent”,但以下技巧的效果也不错。
643643```
644644procedure WaitForDebugger;
645645
646646 var
647647 SetMeValue:boolean=false;
648648
649649 begin
650650 // Linux 技巧:附加调试进程,SetMeValue 改为 true。
651651 while not SetMeValue do
652652 sleep(100); // 在此设置断点
653653 end;
654654```
655655
656656请在“sleep”命令的那行设置一个断点。一旦守护进程由 systemctl 启动后,将在 while 循环中的断点处停止运行。此时可将调试进程附加于从 systemctl 状态信息获取的 PID,对“SetMeValue”变量设置监视([Ctrl-F5]),然后用“评估/修改”上下文菜单将“SetMeValue”变量值由“false”改为“true”,并继续运行([F9])。于是程序会立即退出 while 循环,并运行至下一个断点。
657657
658658## 已知问题
659659
660660### 与权限相关的问题
661661
662662虽然大部分核心守护进程代码可在某个用户帐户下和 IDE(Lazarus)中完成开发,但实时守护进程很可能要在不同的上下文中“后台”运行。出于安全考虑,现代操作系统运行守护进程的环境或多或少都存在一些限制。守护进程没有终端和 GUI 来显示消息,因此此类问题可能会很难调试。遗憾的是,在 FPC/Lazarus 支持的所有操作系统中,没有通用的方法可以解决全部与服务相关的问题,因为 Windows、Linux 和 macOS 对服务的限制策略差别很大,甚至相同操作系统的不同版本也在不断变化,新版本的限制往往更多。
663663
664664当从开发者帐户迁至“上线”状态,并测试服务用户帐户或“Localsystem”、“root”账户下运行的守护进程时(顺便提一下,用超级帐户运行历来视为不安全做法),请花时间研究一下操作系统的安全文档,了解代码在后台运行时所受的安全限制和其他特性。
665665
666666![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** 请参阅上文,了解将调试器附加到正在运行的代码以调试后台运行的守护进程。守护进程由操作系统启动后,应等待调试器附加完成。然后即可观察执行过程,检测与安全限制相关的异常,以及其他与安全/后台操作相关的棘手问题。
667667
668668### EStreamError 异常:"no streaming method available"
669669```
670670Exception at 0041A30E: EStreamError:
671671 Failed to initialize component class "TDaemon1": No streaming method available.
672672```
673673
674674说明在纯用代码创建的守护进程项目中,包含了可视化组件(这些组件需由 GUI 应用的 .lfm 文件创建的内部资源流进行初始化)。如果决定用纯代码创建守护进程,那么可能既不应包含 LazDaemon 包,也不应在 uses 部分加入“LazDaemonApp”。否则,项目虽然可以通过编译,但在启动 exe 文件时,会遇到上述错误。
675675
676676### “Stream Read”和“Unkown Properties”错误 ...
677677
678678用 Lazarus IDE 加载守护进程项目时……。
679679
680680要打开的很可能是一个基于 TDaemon(GUI 组件)的项目,但 LazDaemon 包还未安装。
681681
682682请中止所有加载错误,并按照前文的说明用 Lazarus 软件包管理器安装 LazDaemon 包。重新编译 Lazarus 后,此项目应能加载并顺利编译了。
683683
684684[![Image 9.png](/wiki-images/f/fb/Image_9.png)](</File:Image_9.png>)
685685
686686[![Image 10.png](/wiki-images/b/b3/Image_10.png)](</File:Image_10.png>)
687687
688688### Lazarus IDE 建议清理 .lpm 中的非法属性
689689
690690要打开基于 TDaemon(GUI 组件)的项目时,会出现的另一个问题,LazDaemon 包尚未安装。
691691
692692[![Image 11.png](/wiki-images/1/13/Image_11.png)](</File:Image_11.png>)
693693
694694请中止所有加载错误,并按照前文说明用 Lazarus 软件包管理器安装 LazDaemon 包。重新编译 Lazarus 后,该项目应能加载并顺利编译。
695695
696696### 因不支持线程而在 Unix 中无法启动守护进程
697697
698698代码编译没有问题,但用控制台和 -run 参数运行守护进程时,会显示类似“no thread support compiled in”的信息。如果用 systemctl 启动守护进程则不显示任何信息,貌似根本就没有运行。可用“systemctl status”查看守护进程的最后一条报错信息,会与用 -run 参数启动时的崩溃信息相同。
699699
700700` This binary has no thread support compiled in. Recompile the application with a thread-driver in the program uses clause before other units using thread. Runtime error 232 at ...
701701...
702702No heap dump by heaptrc unit Exitcode = 232 `
703703
704704很有可能在编译项目时没有启用对 CThreads 库的支持(在 Windows 中不需要此库)。请参阅前文的项目 lpr 文件示例代码和“UseCThreads”的说明文档。简而言之,需打开 .lpr 文件,并移除包裹“uses CThreads”语句的 {$IFDEF UseCThreads} ... {$ENDIF} 条件编译指令,这样在 Linux 中就会将 CThreads 库链接到代码中。在 Windows 中用不到 CThreads 库。
705705
706706### Windows 系统中出现的调试问题
707707
708708在 Lazarus 的快捷菜单栏中,断点命中后会激活“暂停”按钮。但远程调试正在运行的进程时,按下按钮似乎并不起作用,反而会挂起调试器。因此断点命中后,请换用“运行”[F9]键继续调试。
709709
710710若要将调试器附加到正在运行的守护进程上,需以管理员权限(提权)运行 Lazarus IDE,否则会收到“Error 5 (access denied)”的提示。
711711
712712如果“运行”菜单中的所有调试项均不可用(灰色),请进入“项目-项目选项-调试器”,将“调试器后端”设为“Gdb [GNU调试器(gdb)]”。据观察,如果将项目源码文件从一台计算机/Lazarus 环境复制到另一台时,可能会出现此类问题。
713713
714714### Linux 系统中出现的调试问题
715715
716716用“附加到程序”功能通过进程ID(PID)调试正在运行的守护进程,似乎并不起作用,报错“Debugger Error”……“Attach failed”。其中一个原因可能是未在服务控制文件中指定用户账号,因此服务以“root”身份运行,使得调试无法进行。除了使用专用于服务的用户运行服务之外,还有一种解决办法是在 Lazarus 中加载守护进程,即在“运行-运行参数”输入 -run 命令行参数。这样确实可以进行调试,但请注意代码这时运行于不同的上下文中(是当前用户而不是系统或服务专用用户),运行效果可能因权限问题而发生变化。
717717
718718### 守护进程无权限访问网络/云存储(Onedrive 和 Dropbox)
719719
720720This may be confusing, since -install and -uninstall will likely work and log fine, because they are usually executed by hand and thus run in your user's context. 若要选择守护进程可执行文件存放数据的目录(默认也用于记录日志),请记住后台运行的守护进程通常会运行于系统或服务账户的上下文中。如果虚拟驱动器是由某个普通用户创建的,则通常其他用户和系统用户均无法使用。从网络/云驱动器很可能无法启动守护进程,因为操作系统找不到可执行文件,并且日志也会写入失败,因为日志文件默认将写入可执行文件所在位置。而 -install 和 -uninstall 命令通常是手动执行的,即运行于当前用户的上下文中,因此或许可以正常执行并记入日志,于是或许会很令人困惑。
721721
722722### Linux 中指定运行服务的用户无法生效
723723
724724守护进程启动后,如果查看其运行于哪个用户下,虽然在 systemd 的 .service 文件中指定了用户账号,但似乎仍以 root 身份在运行。
725725
726726有(未经证实的)报告称,(在某些版本的 Linux 中?)控制 systemd 的 .service 文件中,“User”条目必须位于“ExecStart”条目之前才能生效,Linux 安装和卸载功能部分的例程已有展示。
727727
728728### 系统代码页 / UTF-8
729729
730730[陈旧内容,需要验证 3/2022, 或许已废弃]
731731
732732LazDeamon 项目正在使用默认的、非 UTF-8 的代码页。 _项目选项 ..._ -> _编译器选项_ -> _附加和覆盖_ -> _使用系统编码_ 中的 _-dDisableUTF8RTL_ 选项必须启用。
733733
734734## [一些旧的内容:有待验证、合并或删除]
735735
736736[Old content, needs verfification 3/2022, may be obsolete]
737737
738738### Linux(仅限旧版 Debian)
739739
740740下载、配置、另存为 - 网上的归档文件 [[1]](<https://web.archive.org/web/20130209102726/http://aurawin.com/lazarus/debian-service.sh>) (原始链接已失效久矣)。
741741
742742 * SVC_ALIAS 是应用的详细说明。
743743 * SVC_FILENAME 是编译好的服务应用的实际文件名。
744744 * SVC_DIR 是服务应用的存放位置。
745745 * SVC_SERVICE_SCRIPT 是将自定义 debian-service.sh 另存后的最终名称。
746746
747747将自定义脚本放入 /etc/init.d/ 目录。
748748
749749用“sudo service Name_Of_Your_Script start”命令启动服务。
750750
751751![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** sudo 有很多版本的用法,比如:
752752```
753753sudo -s #
754754
755755 sudo -H #
756756
757757 sudo -i #
758758
759759 sudo su #
760760```
761761
762762
763763sudo sh #
764764
765765服务若需在系统启动时能自动运行,可用 update-rc.d 命令或第三方工具来实现。
766766
767767第一种方式:
768768```
769769sudo update-rc.d Name_Of_Your_Script defaults
770770```
771771
772772第二种方式:
773773```
774774sudo apt-get install chkconfig
775775 sudo chkconfig --add Name_Of_Your_Script
776776 sudo chkconfig --level 2345 Name_Of_Your_Script on
777777```
778778
779779## 参阅
780780
781781 * [GitHub Repository](<https://github.com/arminlinder/Lazarus-FPC-Wiki-Code-Samples/zipball/master/>) 示例项目完整源码(.zip)
782782 * [[macOS daemons and agents]] \- macOS 原生代理,用到了 launchd
783783 * [[ServiceManager]] Windows 服务管理的 Free Pascal 示例单元
784784 * [Michaël Van Canneyt 撰写的文章:Taming the daemon](<https://www.freepascal.org/~michael/articles/daemons/daemons.pdf>)
785785 * [[Docker Containerization]] \- 容器化,一种创建守护进程/服务的手段
786786 * [[UniqueInstance]]
787-
788787---
789788*From the Free Pascal wiki, page "Daemons and Services/zh CN", as the Internet Archive kept it on 2025-03-27, last changed on 2024-12-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history