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并发编程实战》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容