Common Lisp 中的 Unix 信号处理

13

我已经对这个主题进行了一些研究,但并没有找到什么有价值的信息。在Common Lisp中,实现依赖方式似乎有多种处理Unix信号的方法,但是否有一种跨平台的包可以处理信号呢?

我主要想监听SIGINT信号,并在我的应用程序中实现优雅的关机。我正在Linux上使用Clozure CL 1.7……如上所述,如果有一个能够做到这点的包,那将是很好的选择,但如果必须采用实现特定的代码,那也没问题。

我也不完全局限于使用SIGINT(尽管这是理想的)。如果需要,我可以使用其他信号。

如果这会变得很麻烦,是否有其他建议可以从应用程序外部优雅地关闭Lisp应用程序?我想到的一个想法是创建一个文件,应用程序检测到该文件后就会关闭……但这种方法有点hacky。

谢谢!


1
你尝试过CFFI吗? - Daimrod
不,我没有尝试过CFFI,我更希望能够通过ASDF获得一个可以抽象化这个过程的库。我认为为了在C中创建一个跨实现的Unix信号处理程序并编写一个CFFI包装器可能会比它值得的麻烦(对我来说)...特别是因为我从未编写过C API。我并不反对这个想法,但现在真的没有时间去做它。 - andrew
@Daimrod,您的建议让我思考更多了。请查看下面我的回答。当我看到您的评论时,我不知道是否可能在不制作整个包装库的情况下处理Unix信号。您可以直接从CFFI调用“signal” C函数以替换信号处理程序。 - andrew
5个回答

9
虽然一开始我对Daimrod在问题下面使用CFFI的评论持怀疑态度,但是出于无知,我查找了更多资料,并在http://clozure.com/pipermail/openmcl-devel/2010-July/011675.html 找到了相关信息。我已经改编它来使用CFFI,并已确认它可以在Linux上非常良好地运行于SBCL/CCL/clisp(可能还有其他):
(defmacro set-signal-handler (signo &body body)
  (let ((handler (gensym "HANDLER")))
    `(progn
       (cffi:defcallback ,handler :void ((signo :int))
         (declare (ignore signo))
         ,@body)
       (cffi:foreign-funcall "signal" :int ,signo :pointer (cffi:callback ,handler)))))

(set-signal-handler 2
  (format t "Quitting lol!!!11~%")
  ;; fictional function that lets the app know to quit cleanly (don't quit from callback)
  (signal-app-to-quit))

注意,据我所知,回调函数中的任何内容都必须简短明了!不要进行冗长的处理。在链接的文章中,该宏实际上创建了一个单独的线程来处理信号,这对于我的目的来说有些过度,因为我只是将全局变量从nil设置为t并返回。
希望这对其他人有所帮助!

8
这是一个晚回答,但如果还有其他人在寻找这方面的内容,可以看看Quicklisp上提供的trivial-signal,它基于CFFI。

示例

(signal-handler-bind ((:int  (lambda (signo)
                               (declare (ignorable signo))
                               ...handler...)))
  ...body...)

1
谢谢,这看起来很有用。cl-async 中也有一些信号处理的东西,但那只在事件循环中工作。对于一般的信号处理,这似乎是最好的选择。 - andrew

7
我无法找到一个通用的信号处理库。然而,Slime 为大多数Lisp实现实现了“创建自定义SIGINT处理程序”。通过查看该代码中的CCL情况,我找到了 ccl:*break-hook*。虽然ccl:*break-hook*没有在文档中记录,但介绍它的提交在这里:here
以下是可以在我的系统(CCL 1.8,linux x86)上工作的简单示例代码:
(setf ccl:*break-hook* 
  (lambda (cond hook)                              
    (declare (ignore cond hook))
    (format t "Cleaning up ...")
    (ccl:quit)))

在非Slime REPL中输入此代码后,发送SIGINT将导致程序打印“清理中…”并退出。

Slime 中“创建自定义 SIGINT 处理程序”的功能在哪里?我试图在其中的各个子字符串上使用 apropos,但没有成功找到它。哦,我在 Slime 专用的 Apropos 中找到了 SWANK-BACKEND:INSTALL-SIGINT-HANDLER,但我不确定如何运行它。无论如何,如果找不到现有库的正确答案,也许我们可以收集这些信息,并通过这个问题找出编写一个所需的东西。 :) - lindes
1
“install-sigint-handler” 似乎只在一些实现中实现了(请查看“swank-{prog-name}.lisp”)。我通过查看ccl的“call-with-debugger-hook”代码找到了“break-hook”。我认为有些Lisp可能没有像ccl的“break-hook”这样的机制,因此使用名称为“#+”的条件的“handler-case”或“handler-bind”将提供最大的可移植性。(是的,“trivial-sigint”库听起来像一个好主意。) - krzysz00
当我不包含ccl :: quit时,它仍然在此函数运行后触发中断条件。有没有办法取消这个?基本上,我想向一些线程发出信号“嘿,是时候清理并优雅地退出了。”那么有没有办法绑定* break-hook *,然后在它自动运行后继续执行?无论如何,感谢您找到这个问题,这非常有帮助! - andrew
1
如果你想这样做,你需要从钩子中“跳”到程序的另一个部分。throw / catch、条件、重启或其他控制转移机制可能有效。但是,*break-hook*可能已经在系统调用的中间被调用,因此在退出之前不要尝试做太多事情。 - krzysz00

1
如果您使用SBCL,更改信号掩码会导致SBCL崩溃。请向nyef询问如何修复SBCL的技巧...

0

所以就像提到的那样有个trivial-signal。这里是我在代码中捕获C-c的方法:

(handler-case
        (my-app-main-function)
      ;; AFAIK trivial-signal is supposed to handle the implementation differences.
      (#+sbcl sb-sys:interactive-interrupt
        #+ccl  ccl:interrupt-signal-condition
        #+clisp system::simple-interrupt-condition
        #+ecl ext:interactive-interrupt
        #+allegro excl:interrupt-signal
        () (progn
             (format *error-output* "Aborting.~&")
             (exit)))
      (error (c) (format t "Woops, an unknown error occured:~&~a~&" c)))

这只处理SIGINT。我如何在SBCL中处理SIGTERM? - Flux
使用 trivial-signal。示例 - Ehvince

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接