Sign in Join
Wiki copytemporary

Daemons and Services/zh CN: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

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