如何在 Go 中处理操作系统信号

Go 编程语言不是为像 C 那样的系统级编程而构建的,但是有一些功能可以帮助开发人员在低级别与底层操作系统进行交互,例如使用信号os/signals包可帮助开发人员使用此功能。事实上,有时需要在类 UNIX 环境中处理信号。这个 Goland 编程教程大致了解了信号的概念,并展示了编码人员如何在 Go 程序中实现它们。

Golang 中的信号是什么?

信号是由基于 UNIX 或 Linux 的操作系统生成的事件。系统根据生成的信号采取一些行动。例如,如果我们想取消正在运行的程序,我们按下CTRL+C。这会向程序发送一个称为SIGINT的信号,并且底层系统会根据生成的信号采取适当的行动。了解SIGINT是与指定特定信号的数字相关联的名称。

信号实际上是可以通过编程方式生成的软件中断。它们提供了一种处理异步事件的方法。通常,为了安全起见,信号是由它们的名称而不是它们的编号生成的。了解大多数信号及其特定操作是由操作系统定义的。它们还在操作系统的范围内执行它们的功能。因此,能够处理某些事件并不能让有限的用户自由地对系统做任何事情;有些信号即使生成,也会被操作系统忽略。由操作系统决定允许程序处理哪些信号。

例如,不能捕获、阻止或忽略SIGKILLSIGSTOP信号,因为这些信号对于维护操作系统功能的“健全性”至关重要。它们为内核和 root 用户提供了一种在极端条件下停止进程的方法。与SIGKILL关联的数字是9

在 Linux 中有一个名为kill的实用程序,它发送一个SIGTERM信号来终止正在运行的程序。查找所有支持信号列表的常用方法是在终端中使用以下命令:

OS Signals in Go

在 Go 中使用 os/signal 包

Go 中的os包提供了一个独立于平台的接口来使用操作系统功能,例如使用文件系统、创建文件和目录等。子包os/signal帮助开发人员专门处理信号。有许多信号可用,但并非所有信号都可以由程序处理。像SIGKILLSIGSTOP这样的信号既不能被程序调用,也不能被程序忽略。原因很简单:它们太挑剔了,不允许被恶作剧的程序滥用。

有两种类型的信号:同步异步。同步信号是与另一个信号(通常是时钟)同步的信号。时钟产生的脉冲用于向接收器指示数据已到达。因此,在同步信令中,事件和事件的时序都很重要。同步信号通常由程序执行中的错误触发。信号,例如SIGBUSSIGFPESIGSEGV,除非由os.Process.Kill触发,否则被认为是同步信号。Go 程序将同步信号转换为运行时恐慌。这是 Go 中的默认行为。

另一方面,异步信号不需要它与时钟脉冲同步。例如,SIGHUP向进程发出信号,表明它控制的终端已关闭。也许我们通过按下中断字符CTRL+C生成的最常见的异步信号是SIGINT。当我们在程序执行期间生成中断(通过按CTRL+C)时,发送SIGINT信号并且程序立即终止。

了解这里的问题:信号SIGINT是按下CTRL+C时键盘中断的信号;它实际上并没有终止程序。它只是意味着已经生成了一个特定的键盘中断,并且该中断的处理程序负责以它喜欢的任何方式处理它。其中,默认行为是终止进程。但是,我们可以覆盖默认行为并编写我们自己的处理程序。事实上,许多信号都可以被覆盖,Go 开发人员可以编写自己的处理程序来定义信号的自定义行为。但是,除非有很好的理由,否则更改信号的默认行为并不是一个明智的主意。

Golang 中使用信号的示例

下面是一个非常简单的程序来说明如何在 Go 中处理信号:

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func handler(signal os.Signal) {
    if signal == syscall.SIGTERM {
        fmt.Println("Got kill signal. ")
        fmt.Println("Program will terminate now.")
        os.Exit(0)
    } else if signal == syscall.SIGINT {
        fmt.Println("Got CTRL+C signal")
        fmt.Println("Closing.")
        os.Exit(0)
    } else {
        fmt.Println("Ignoring signal: ", signal)
    }
}

func main() {
    sigchnl := make(chan os.Signal, 1)
    signal.Notify(sigchnl)
    exitchnl := make(chan int)

    go func() {
        for {
            s := <-sigchnl
            handler(s)
        }
    }()

    exitcode := <-exitchnl
    os.Exit(exitcode)
}

我们的示例 Go 代码中的程序包含以下步骤:

  • 由于 Go 信号通知通过通道发送os.Signal值,因此我们创建了名为sigchnl的通道来帮助接收通知。
  • 然后我们需要使用signal.Notify()注册给定的频道以接收给定频道的通知。然后它中继传入的信号。程序员可以指定他们想要处理的传入信号,例如signal.Notify(sigchnl, syscall.SIGTERM, syscall.SIGINT)。如果没有给出信号,则所有传入的信号都被中继到指定的通道。
  • 接下来,我们实现一个作为 goroutine 运行的匿名函数。在这个函数内部,我们运行一个无限循环来调用用户定义的处理函数。处理函数的参数是os.Signal接口指定的值。
  • 请注意,还有另一个名为exitchnl的通道被创建,以使程序等待直到生成退出代码,否则程序将简单地执行并退出。但是,我们也可以使用以下示例代码让程序等待,而不是编写exitchnl代码:
for {
            time.Sleep(30 * time.Second)
        }

系统信号量列表

以下是前 15 个围棋信号的备忘单以及它们的含义,以供快速参考。更完整的参考可以在 signal(7)-Linux 手册页中找到。

  • SIGHUP : ‘HUP’ = ‘挂断’。当在控制终端上检测到控制进程死亡或挂起时,会生成此信号。
  • SIGINT : 当一个进程被键盘中断时,按下 CTRL+C
  • SIGQUIT : 从键盘退出
  • SIGILL:非法指令。SIGPWR()的同义词——电源故障
  • SIGABRT:程序调用abort()函数——紧急停止。
  • SIGBUS:错误的内存访问。试图不恰当地访问内存
  • SIGFPE : FPE = 浮点异常
  • SIGKILL : 终止信号。该进程被明确杀死。
  • SIGUSR1:此信号对程序员开放以编写自定义行为。
  • SIGSEGV : 无效的内存引用。在 C 语言中,当我们尝试访问超出数组限制的内存时,会生成此信号。
  • SIGUSR2:此信号对程序员开放以编写自定义行为。
  • SIGPIPE:这表明我们可以让程序员编写自定义行为。
  • SIGALRM:进程请求操作系统唤醒呼叫,例如通过调用alarm()函数。
  • SIGTERM : 进程被杀死

Go 中处理多个信号

我们可以稍微修改上面的代码来说明如何在 Go 中处理多个但具体的信号:

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func multiSignalHandler(signal os.Signal) {

    switch signal {
    case syscall.SIGHUP:
        fmt.Println("Signal:", signal.String())
        os.Exit(0)
    case syscall.SIGINT:
        fmt.Println("Signal:", signal.String())
        os.Exit(0)
    case syscall.SIGTERM:
        fmt.Println("Signal:", signal.String())
        os.Exit(0)
    case syscall.SIGQUIT:
        fmt.Println("Signal:", signal.String())
        os.Exit(0)
    default:
        fmt.Println("Unhandled/unknown signal")
    }
}

func main() {
    sigchnl := make(chan os.Signal, 1)
    signal.Notify(sigchnl, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) //we can add more sycalls.SIGQUIT etc.
    exitchnl := make(chan int)

    go func() {
        for {
            s := <-sigchnl
            multiSignalHandler(s)
        }
    }()

    exitcode := <-exitchnl
    os.Exit(exitcode)
}

现在,要测试代码,请打开两个终端(在 Linux 中)。使用以下命令从终端 1运行此程序:

终端 1:运行 main.go

转到终端2,使用命令找到正在运行的程序main的PID(进程ID)*:

$ ps -e

假设进程 ID是 13014。然后我们希望使用以下kill命令向正在运行的主程序发出特定信号:

kill -SIGHUP 13014

这将挂起在终端 1中运行的进程。

接下来,再次运行main.go并测试另一个信号,例如kill -SIGINT 13014。重复这个过程几次,直到你感到舒服为止。请注意我们如何在不实际按下CTRL+C的情况下以编程方式生成软件(键盘 – CTRL+C)中断。

最后

在 C 中,信号处理实际上很常见。有趣的是,Go 还提供了处理一些低级代码的功能。这可能非常有用,因为当开发人员处理信号或编写他们自己的自定义处理函数时,我们可以编写一些清理代码来优雅地终止程序,比如服务器,或者即使按下CTRL+C也能优雅地终止任何进程。否则这些低水平的好处是不可能的。但是,要明白 Go 不是为了取代 C 而构建的;在系统级编程方面,C 语言一直是并且将继续是至高无上的。

BOM 头是什么,怎么除去
Golang 字符串
标签:

发表我的评论

电子邮件地址不会被公开。 必填项已用*标注

80 + 22 =

ajax-loader