go & 信号

信号

操作系统信号是IPC中一种异步的通信方法,本质是用软件来模拟硬件的中断,它用来通知某个进程有事件发生了。每一个操作系统信号都以SIG作为前缀,如SIGINT,信号都是用正整数表示,称为信号的编号,在linux下可通过kill -l 来查看。

kill -l
 1) SIGHUP   2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP
 6) SIGABRT  7) SIGBUS   8) SIGFPE   9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG  24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF 28) SIGWINCH    29) SIGIO   30) SIGPWR
31) SIGSYS  34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX    

Linux支持的信号有62种,1-31属于标准信号,34-64属于实时信号

  • 标准信号:同种类的标准信号只会被记录和处理一次,如果有多种,处理顺序也是不确定的
  • 实时信号:同种的都会记录在案,处理顺序也是发送的顺序

进程响应信号的方式 包括:

  • 忽略
  • 捕捉
  • 执行默认操作
    Linux针对每一个标准信号都有默认的操作方式。针对不同种类的标准信号,其默认的操作方式一定会是下列操作之一:终止进程,忽略该信号,终止进程并保存内存信息,停止进程,恢复进程(如果进程已停止)

默认下godoc命令展示的是当前设备的架构和操作系统下的文档,如果想查看其他的操作系统和架构下的文档,如darwin,amd64下的Go文档,需设置GOARCH和GOOS为对应的架构和操作系统:

GOOS=darwin GOARCH=amd64 godoc -http=localhost:6060 -analysis=type

以下内容均在linux,amd64下测试

go对信号的处理

os包中定义了Signal接口类型,os.Signal类型定义如下。只有os.Interrupt(给进程发送中断)和os.Kill(强制进程退出) 这两个值在所有系统平台都确保存在

// A Signal represents an operating system signal.
// The usual underlying implementation is operating system-dependent:
// on Unix it is syscall.Signal.
type Signal interface {
    String() string
    Signal() // to distinguish from other Stringers
}
// The only signal values guaranteed to be present in the os package on all
// systems are os.Interrupt (send the process an interrupt) and os.Kill (force
// the process to exit). On Windows, sending os.Interrupt to a process with
// os.Process.Signal is not implemented; it will return an error instead of
// sending a signal.
var (
    Interrupt Signal = syscall.SIGINT
    Kill      Signal = syscall.SIGKILL
)

os/signal包定义对信号进行处理的函数:

  • Notify:当操作系统向当前进程发送指定信号时发出通知,参数sig表示我们想自己处理的信号
  • Stop:取消掉在之前调用Notify函数时告知signal处理程序需要自行处理若干信号的行为。signal接收通道将不会再被发送任何信号。如果存在阻塞代码,可手动关闭signal通道。如果只是想取消部分信号的监听,可以再一次调用Notify函数。
  • Ignore:忽略指定的信号

下面是一个例子说明Notify的使用

func main()  {
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, syscall.SIGINT, syscall.SIGQUIT)
    for mm := range sig {
        fmt.Printf("receive (%v)\n", mm)
        os.Exit(0)
    }
}

syscall.Signal实现了os.Signal接口,并且Signal方法实现为空。syscall.Signal是int的别名类型,与它所表示的信号在操作系统中的编号一致。

// A Signal is a number describing a process signal.
// It implements the os.Signal interface.
type Signal int

func (s Signal) Signal() {}

func (s Signal) String() string {
    if 0 <= s && int(s) < len(signals) {
        str := signals[s]
        if str != "" {
            return str
        }
    }
    return "signal " + itoa(int(s))
}

在syscall.Signal的String()方法里,有一个变量signals,定义了信号的描述,signals的下标和信号的编号对应

// Signal table
var signals = [...]string{
    1:  "hangup",
    2:  "interrupt",
    3:  "quit",
    4:  "illegal instruction",
    5:  "trace/breakpoint trap",
    6:  "aborted",
    7:  "bus error",
    8:  "floating point exception",
    9:  "killed",
    10: "user defined signal 1",
    11: "segmentation fault",
    12: "user defined signal 2",
    13: "broken pipe",
    14: "alarm clock",
    15: "terminated",
    16: "stack fault",
    17: "child exited",
    18: "continued",
    19: "stopped (signal)",
    20: "stopped",
    21: "stopped (tty input)",
    22: "stopped (tty output)",
    23: "urgent I/O condition",
    24: "CPU time limit exceeded",
    25: "file size limit exceeded",
    26: "virtual timer expired",
    27: "profiling timer expired",
    28: "window changed",
    29: "I/O possible",
    30: "power failure",
    31: "bad system call",
}
// Signals
const (
    SIGABRT   = Signal(0x6)
    SIGALRM   = Signal(0xe)
    SIGBUS    = Signal(0x7)
    SIGCHLD   = Signal(0x11)
    SIGCLD    = Signal(0x11)
    SIGCONT   = Signal(0x12)
    SIGFPE    = Signal(0x8)
    SIGHUP    = Signal(0x1)
    SIGILL    = Signal(0x4)
    SIGINT    = Signal(0x2)
    SIGIO     = Signal(0x1d)
    SIGIOT    = Signal(0x6)
    SIGKILL   = Signal(0x9)
    SIGPIPE   = Signal(0xd)
    SIGPOLL   = Signal(0x1d)
    SIGPROF   = Signal(0x1b)
    SIGPWR    = Signal(0x1e)
    SIGQUIT   = Signal(0x3)
    SIGSEGV   = Signal(0xb)
    SIGSTKFLT = Signal(0x10)
    SIGSTOP   = Signal(0x13)
    SIGSYS    = Signal(0x1f)
    SIGTERM   = Signal(0xf)
    SIGTRAP   = Signal(0x5)
    SIGTSTP   = Signal(0x14)
    SIGTTIN   = Signal(0x15)
    SIGTTOU   = Signal(0x16)
    SIGUNUSED = Signal(0x1f)
    SIGURG    = Signal(0x17)
    SIGUSR1   = Signal(0xa)
    SIGUSR2   = Signal(0xc)
    SIGVTALRM = Signal(0x1a)
    SIGWINCH  = Signal(0x1c)
    SIGXCPU   = Signal(0x18)
    SIGXFSZ   = Signal(0x19)
)

The signals SIGKILL and SIGSTOP may not be caught by a program, and therefore cannot be affected by this package.

在类Unix系统中,有两种信号既不能自行处理,也不会被忽略,他们是SIGKILL和SIGSTOP,对他们的响应只能是执行系统的默认操作。原因是他们向系统的超级用户提供了使进程终止或停止的可靠方法。

运行程序并向该进程发送信号

这里使用os/exec包来获取通过go run命令运行的go程序的进程pid,然后给该进程发送信号。
用go run命令运行go程序,如

go run path/signal.go
func main() {
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, syscall.SIGINT)

    go sendSignal()

    select {
    case temp := <-sig:
        fmt.Printf("receive sig: %v", temp)
    case <-time.After(time.Second * 10):    // 超时控制
        fmt.Println("time out")
    }
}

func sendSignal() {
    time.Sleep(3 * time.Second)
    defer func() {
        if err := recover(); err != nil {
            fmt.Printf("Fatal Error: %s\n", err)
            debug.PrintStack()
        }
    }()
    // 执行命令 ps aux | grep "signal" | grep -v "grep" | grep -v "go run" | awk '{print $2}'
    cmds := [...]*exec.Cmd{
        exec.Command("ps", "aux"),
        exec.Command("grep", "signal"),
        exec.Command("grep", "-v", "grep"),
        exec.Command("grep", "-v", "go run"),
        exec.Command("awk", "{print $2}"),
    }
    isFirst := true
    var lastOutput bytes.Buffer
    // 遍历每一个命令
    for _, cmdTemp := range cmds {
        out := bytes.Buffer{}
        cmdTemp.Stdout = &out
        if !isFirst {
            cmdTemp.Stdin = &lastOutput
        }
        if err := cmdTemp.Start(); err != nil {
            fmt.Printf("start failed%v", err)
        }
        if err := cmdTemp.Wait(); err != nil {
            fmt.Printf("wait failed%v", err)
        }
        lastOutput = out
        isFirst = false
    }
    pids := []int{}
    for {
        str, err := lastOutput.ReadString(byte('\n'))
        if err != nil {
            if err != io.EOF {
                fmt.Printf("ReadString failed%v\n", err)
            }
            break
        }
        pidTemp, err := strconv.Atoi(strings.TrimSpace(str))
        fmt.Println("get pid: ", pidTemp)
        if err != nil {
            fmt.Printf("convert failed %v", err)
        }
        pids = append(pids, pidTemp)
    }
    // 通过pid获取进程,并发送信号
    for _, pidT := range pids {
        process, err := os.FindProcess(pidT)
        if err != nil {
            fmt.Printf("find porcess failed %v", err)
        }
        err = process.Signal(syscall.SIGINT)
        if err != nil {
            fmt.Printf("send sig failed %v", err)
        }
    }
}

参考:《Go并发编程实战》

你可能感兴趣的:(go & 信号)