=Start=
缘由:
之前在第一次学习Go 语言的时候其实整理了一篇文章「Linux下守护进程的Golang语言实现」,但因为后续有其他事情,所以就没有继续下去了。最近刚好在复习Go 语言,所以想着再整理实践一下用Go 语言实现守护进程的功能,就有了这篇文章。
正文:
参考解答:
首先,如果你只是想实现类似于守护进程在后台默默运行的功能的话,可以考虑先用 go build
命令构建出可执行程序,然后(二者选其一即可):
- 借助 daemonize 或 upstart 将进程以daemon形式运行;
- 使用 nohup 结合 & 的方式,再加上 supervisor 管理。
如果你还是希望能用Go 语言实现一个daemon进程,可以参考一下这段代码(在Linux环境下测试验证OK,macOS未通过):
package main import ( "fmt" "log" "os" "runtime" "syscall" "time" ) func daemon(nochdir, noclose int) int { var ret, ret2 uintptr var err syscall.Errno darwin := runtime.GOOS == "darwin" // 如果当前进程的ppid等于1,则表明已经是一个守护进程了,直接返回即可(already a daemon) if syscall.Getppid() == 1 { return 0 } // 通过 syscall 包调用 fork 派生子进程 ret, ret2, err = syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0) if err != 0 { log.Println("syscall.RawSyscall") return -1 } // ret2 表示子进程收到的 fork 返回(暂不完全确定?),正常派生情况下 ret2 应该等于0 if ret2 < 0 { os.Exit(-1) } // 针对 darwin 系统的错误处理(没啥用,在macOS上还是跑不了) if darwin && ret2 == 1 { log.Printf("OS: %v, ret: %v, ret2: %v\n", darwin, ret, ret2) // ret = 0 } // ret 表示fork成功执行后产生的子进程的pid(由父进程接收) if ret > 0 { os.Exit(0) // 退出当前的父进程 } // create a new SID for the child process s_ret, s_errno := syscall.Setsid() if s_errno != nil { log.Printf("Error: syscall.Setsid errno: %d", s_errno) } if s_ret < 0 { log.Println("syscall.Setsid") return -1 } // 调用 fork 派生子进程 ret, ret2, err = syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0) if err != 0 { log.Println("syscall.RawSyscall") return -1 } // ret2 表示子进程收到的 fork 返回,正常派生情况下 ret2 应该等于0 if ret2 < 0 { os.Exit(-1) } // ret 表示fork成功执行后产生的子进程的pid(由父进程接收) if ret > 0 { os.Exit(0) // 退出当前的父进程 } if nochdir == 0 { os.Chdir("/") } /* Change the file mode mask */ _ = syscall.Umask(0) if noclose == 0 { f, e := os.OpenFile("/dev/null", os.O_RDWR, 0) if e == nil { fd := f.Fd() syscall.Dup2(int(fd), int(os.Stdin.Fd())) syscall.Dup2(int(fd), int(os.Stdout.Fd())) syscall.Dup2(int(fd), int(os.Stderr.Fd())) } } return 0 } func main() { daemon(0, 1) for { fmt.Println("hello") time.Sleep(2 * time.Second) } }
如果你用C或是其它编程语言开发过daemon进程的话你就会发现,实现daemon的主要逻辑步骤在各个语言中都是一样的:
1、创建子进程,父进程退出 fork()
2、子进程创建新会话 setsid()
3、再次创建子进程结束当前进程 fork() #通过使进程不再是会话首进程来禁止进程重新打开控制终端
4、改变当前工作目录 chdir()
5、重设文件权限掩码 umask()
6、关闭无关文件描述符 close()
只不过在Go 语言中,对我来说,现在实现起来最大的问题在于——不论是Syscall.RawSyscall()还是Syscall.Syscall()它们的资料都太少了,参数、返回值的功能和作用都只能靠猜……
一个进程是否是守护进程的验证方法(判断是否符合如下要求):
- 守护进程不应该有控制终端,所以(TTY = ?)
- 守护进程的父进程ID为1,即init进程
- 其中 PID != SID 表明该进程不是会话的leader进程,因为第二个fork()的作用
- 因为 PID != SID 所以该守护进程无法重新打开/控制一个TTY
参考链接:
- syscall 包
https://golang.org/src/syscall/syscall_unix.go?s=751:820#L21
https://godoc.org/golang.org/x/sys/unix#RawSyscall - Details of Syscall.RawSyscall() & Syscall.Syscall() in Go?
- GO 语言系统调用简析
- Golang 系统调用 syscall
- Creating a daemon in Linux
- ==
- How to create a daemon process in Golang?
- A library for writing system daemons in golang.
- How to start a Go program as a daemon in Ubuntu?
- https://www.reddit.com/r/golang/comments/35v5bm/best_way_to_run_go_server_as_a_daemon/
=END=
《 “Go语言编程练习#2-守护进程” 》 有 7 条评论
如何在Go的函数中得到调用者函数名?
https://colobu.com/2018/11/03/get-function-name-in-go/
`
有时候在Go的函数调用的过程中,我们需要知道函数被谁调用,比如打印日志信息等。例如下面的函数,我们希望在日志中打印出调用者的名字。
func printMyName() string {
pc, _, _, _ := runtime.Caller(1)
return runtime.FuncForPC(pc).Name()
}
func printCallerName() string {
pc, _, _, _ := runtime.Caller(2)
return runtime.FuncForPC(pc).Name()
}
// func Caller(skip int) (pc uintptr, file string, line int, ok bool)
Caller可以返回函数调用栈的某一层的程序计数器、文件信息、行号。
0 代表当前函数,也是调用runtime.Caller的函数。1 代表上一层调用者,以此类推。
`
golang 后台服务设计精要
http://litang.me/post/golang-server-design/
`
守护进程
优雅的结束进程
响应信号
等待所有协程退出
goroutine 生命期管理
数据库操作与 ORM
标准库中的数据库操作接口
ORM
HTTP 服务
标准库 net/http 包
httprouter
middleware
gin
总结
参考资料
`
Gotorch – 多机定时任务管理系统
http://www.cnblogs.com/zhenbianshu/p/7905678.html
https://github.com/zhenbianshu/gotorch
`
“学一门语言最好的方式是使用它”
`
曹春晖:谈一谈 Go 和 Syscall
https://mp.weixin.qq.com/s/9_dkjpLHy9TCBcLO8DoqEg
https://page.xiaojukeji.com/market/ddPage_0CjObmii.html
Go ARM64 vDSO优化之路
https://mzh.io/golang-arm64-vdso
GO 语言系统调用简析
http://blog.studygolang.com/2016/06/go-syscall-intro/
VDSO与vsyscall
https://blog.csdn.net/luozhaotian/article/details/79609077
Go配置文件热加载 – 发送系统信号
https://mp.weixin.qq.com/s/fJy5iQqPJvyt2PSsdjYr0w
https://github.com/apptut/go-labs/blob/master/hotload/signal/main/signal.go
`
在日常项目的开发中,我们经常会使用配置文件来保存项目的基本元数据,配置文件的类型有很多,如:JSON、xml、yaml、甚至可能是个纯文本格式的文件。不管是什么类型的配置数据,在某些场景下,我们可会有热更新当前配置文件内容的需求,比如:使用Go运行的一个常驻进程,运行了一个 Web Server 服务进程。
此时,如果配置文件发生变化,我们如何让当前程序重新读取新的配置文件内容呢?接下来,我们将使用如下两种方式实现配置文件的更新:
1、使用系统信号(手动式)。
2、使用inotify, 监听文件修改事件。
不管是哪一种方式,都会用到Go语言中 goroutine 的概念,我打算使用 goroutine 新起一个协程,新协程目的是用来接收系统信号(signal)或者监听文件被修改的事件,如果你对 goroutine 的概念不是很了解,那么建议你先查阅相关资料。
鉴于文章篇幅考虑,本文中我们只实现了第一种文件更新方式。下一篇文章中,我们将使用第二种方式:使用inotify监听配置文件的变化,以实现配置文件的自动更新,期待你的关注~
`
Golang生态:使用viper管理配置
https://mp.weixin.qq.com/s/p-eFNb6VDSbnNXJxT8LgGA
https://github.com/spf13/viper
Wikipedia: System call
https://en.wikipedia.org/wiki/System_call
The GNU C Library (glibc)
https://www.gnu.org/software/libc/
4.1 函数调用 · Go 语言设计与实现
https://draven.co/golang/docs/part2-foundation/ch04-basic/golang-function-call/
Measurements of system call performance and overhead
http://arkanis.de/weblog/2017-01-05-measurements-of-system-call-performance-and-overhead
s1bench – syscall benchmark 1. Tests a syscall & think loop.
https://github.com/brendangregg/Misc/blob/master/s1bench/s1bench.c
Fastest Linux system call
https://stackoverflow.com/a/48913894
Wikipedia: Interrupt
https://en.wikipedia.org/wiki/Interrupt
Hardware & Software interrupts
https://en.wikipedia.org/wiki/Interrupt#Hardware_interrupts
Michael Kerrisk. 2010. The Linux Programming Interface: A Linux and UNIX System Programming Handbook (1st. ed.). Chapter 3. P44. No Starch Press, USA.
“int 0x80” system call path performance implications. P82.
https://francescoquaglia.github.io/TEACHING/PMMC/SLIDES/kernel-programming-basics.pdf
runtime, syscall: use int $0x80 to invoke syscalls on android/386
https://go-review.googlesource.com/c/go/+/16996/
runtime, syscall: switch linux/386 to use int 0x80
https://go-review.googlesource.com/c/go/+/19833/
Intel P6 vs P7 system call performance
https://lkml.org/lkml/2002/12/9/13
Wikipedia: Model-specific register
https://en.wikipedia.org/wiki/Model-specific_register
Wikipedia: vDSO
https://en.wikipedia.org/wiki/VDSO
Kernel and userspace setup
https://vvl.me/pdf/LPC_vDSO.pdf