From 11a503f1a1383f5a320d664abe72d37aac239159 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 2 Aug 2024 18:22:27 +0200 Subject: [PATCH 1/5] syscall: implement syscall.Kill for libc systems (like darwin) This is needed to be able to test signal support. --- src/syscall/syscall_libc.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 2321292d98..73a1b42e48 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -215,7 +215,11 @@ func Truncate(path string, length int64) (err error) { func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) func Kill(pid int, sig Signal) (err error) { - return ENOSYS // TODO + result := libc_kill(int32(pid), int32(sig)) + if result < 0 { + err = getErrno() + } + return } type SysProcAttr struct{} @@ -465,3 +469,8 @@ func libc_execve(filename *byte, argv **byte, envp **byte) int // //export truncate func libc_truncate(path *byte, length int64) int32 + +// int kill(pid_t pid, int sig); +// +//export kill +func libc_kill(pid, sig int32) int32 From 24ed59547232096ebed6933cb2e87205efc38f2a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 2 Aug 2024 17:10:52 +0200 Subject: [PATCH 2/5] runtime: add support for os/signal This adds support for enabling and listening to signals on Linux and MacOS. TODO: also support disabling signals. --- builder/musl.go | 1 + compileopts/target.go | 14 +++++++---- main_test.go | 9 +++++++ src/os/signal/signal.go | 14 ----------- src/runtime/runtime_unix.go | 38 ++++++++++++++++++++++++++++++ src/runtime/signal.c | 19 +++++++++++++++ src/syscall/syscall_libc_darwin.go | 2 ++ testdata/signal/out.txt | 2 ++ testdata/signal/signal.go | 34 ++++++++++++++++++++++++++ 9 files changed, 115 insertions(+), 18 deletions(-) delete mode 100644 src/os/signal/signal.go create mode 100644 src/runtime/signal.c create mode 100644 testdata/signal/out.txt create mode 100644 testdata/signal/signal.go diff --git a/builder/musl.go b/builder/musl.go index 8130981e6c..ecae118e47 100644 --- a/builder/musl.go +++ b/builder/musl.go @@ -128,6 +128,7 @@ var libMusl = Library{ "mman/*.c", "math/*.c", "multibyte/*.c", + "signal/" + arch + "/*.s", "signal/*.c", "stdio/*.c", "string/*.c", diff --git a/compileopts/target.go b/compileopts/target.go index 26a91086b9..679f4a66f2 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -388,8 +388,11 @@ func defaultTarget(options *Options) (*TargetSpec, error) { "-arch", llvmarch, "-platform_version", "macos", platformVersion, platformVersion, ) - spec.ExtraFiles = append(spec.ExtraFiles, - "src/runtime/runtime_unix.c") + spec.ExtraFiles = append( + spec.ExtraFiles, + "src/runtime/runtime_unix.c", + "src/runtime/signal.c", + ) case "linux": spec.Linker = "ld.lld" spec.RTLib = "compiler-rt" @@ -409,8 +412,11 @@ func defaultTarget(options *Options) (*TargetSpec, error) { // proper threading. spec.CFlags = append(spec.CFlags, "-mno-outline-atomics") } - spec.ExtraFiles = append(spec.ExtraFiles, - "src/runtime/runtime_unix.c") + spec.ExtraFiles = append( + spec.ExtraFiles, + "src/runtime/runtime_unix.c", + "src/runtime/signal.c", + ) case "windows": spec.Linker = "ld.lld" spec.Libc = "mingw-w64" diff --git a/main_test.go b/main_test.go index 40002ee871..3cfb5d06b1 100644 --- a/main_test.go +++ b/main_test.go @@ -75,6 +75,7 @@ func TestBuild(t *testing.T) { "oldgo/", "print.go", "reflect.go", + "signal/", "slice.go", "sort.go", "stdlib.go", @@ -212,6 +213,7 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { // isWebAssembly := strings.HasPrefix(spec.Triple, "wasm") isWASI := strings.HasPrefix(options.Target, "wasi") isWebAssembly := isWASI || strings.HasPrefix(options.Target, "wasm") || (options.Target == "" && strings.HasPrefix(options.GOARCH, "wasm")) + isBaremetal := options.Target == "simavr" || options.Target == "cortex-m-qemu" || options.Target == "riscv-qemu" for _, name := range tests { if options.GOOS == "linux" && (options.GOARCH == "arm" || options.GOARCH == "386") { @@ -269,6 +271,13 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { continue } } + if isWebAssembly || isBaremetal || options.GOOS == "windows" { + switch name { + case "signal/": + // Signals only work on POSIX-like systems. + continue + } + } name := name // redefine to avoid race condition t.Run(name, func(t *testing.T) { diff --git a/src/os/signal/signal.go b/src/os/signal/signal.go deleted file mode 100644 index 41ceaf4853..0000000000 --- a/src/os/signal/signal.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package signal - -import ( - "os" -) - -// Just stubbing the functions for now since signal handling is not yet implemented in tinygo -func Reset(sig ...os.Signal) {} -func Ignore(sig ...os.Signal) {} -func Notify(c chan<- os.Signal, sig ...os.Signal) {} diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index ba5d5a5938..7003e4d4da 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -307,3 +307,41 @@ func growHeap() bool { setHeapEnd(heapStart + heapSize) return true } + +func init() { + // Set up a channel to receive signals into. + // A channel size of 1 should be sufficient in most cases, but using 4 just + // to be sure. + signalChan = make(chan uint32, 4) +} + +var signalChan chan uint32 + +//go:linkname signal_enable os/signal.signal_enable +func signal_enable(s uint32) { + // It's easier to implement this function in C. + tinygo_signal_enable(s) +} + +//export tinygo_signal_enable +func tinygo_signal_enable(s uint32) + +// void tinygo_signal_handler(int sig); +// +//export tinygo_signal_handler +func tinygo_signal_handler(s int32) { + select { + case signalChan <- uint32(s): + default: + // TODO: we should handle this in a better way somehow. + // Maybe just ignore the signal, assuming that nothing is reading from + // the notify channel? + runtimePanic("could not deliver signal: runtime internal channel is full") + } +} + +//go:linkname signal_recv os/signal.signal_recv +func signal_recv() uint32 { + // Function called from os/signal to get the next received signal. + return <-signalChan +} diff --git a/src/runtime/signal.c b/src/runtime/signal.c new file mode 100644 index 0000000000..ffa30543da --- /dev/null +++ b/src/runtime/signal.c @@ -0,0 +1,19 @@ +//go:build none + +// Ignore the //go:build above. This file is manually included on Linux and +// MacOS to provide os/signal support. + +#include +#include +#include + +// Signal handler in the runtime. +void tinygo_signal_handler(int sig); + +// Enable a signal from the runtime. +void tinygo_signal_enable(uint32_t sig) { + struct sigaction act = { 0 }; + act.sa_flags = SA_SIGINFO; + act.sa_handler = &tinygo_signal_handler; + sigaction(sig, &act, NULL); +} diff --git a/src/syscall/syscall_libc_darwin.go b/src/syscall/syscall_libc_darwin.go index 3b0341880a..e02cdd4704 100644 --- a/src/syscall/syscall_libc_darwin.go +++ b/src/syscall/syscall_libc_darwin.go @@ -186,6 +186,8 @@ const ( SIGPIPE Signal = 13 /* write on a pipe with no one to read it */ SIGTERM Signal = 15 /* software termination signal from kill */ SIGCHLD Signal = 20 /* to parent on child stop or exit */ + SIGUSR1 Signal = 30 /* user defined signal 1 */ + SIGUSR2 Signal = 31 /* user defined signal 2 */ ) func (s Signal) Signal() {} diff --git a/testdata/signal/out.txt b/testdata/signal/out.txt new file mode 100644 index 0000000000..c4726d7174 --- /dev/null +++ b/testdata/signal/out.txt @@ -0,0 +1,2 @@ +got expected signal +exiting signal program diff --git a/testdata/signal/signal.go b/testdata/signal/signal.go new file mode 100644 index 0000000000..d465715303 --- /dev/null +++ b/testdata/signal/signal.go @@ -0,0 +1,34 @@ +package main + +// Test POSIX signals. +// TODO: run `tinygo test os/signal` instead, once CGo errno return values are +// supported. + +import ( + "os" + "os/signal" + "syscall" + "time" +) + +func main() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGUSR1) + + // Wait for signals to arrive. + go func() { + for sig := range c { + if sig == syscall.SIGUSR1 { + println("got expected signal") + } else { + println("got signal:", sig.String()) + } + } + }() + + // Send the signal. + syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) + + time.Sleep(time.Millisecond * 100) + println("exiting signal program") +} From 22ec604d3f81965415fa4774de212903620d1bc7 Mon Sep 17 00:00:00 2001 From: leongross Date: Mon, 5 Aug 2024 12:16:20 +0200 Subject: [PATCH 3/5] add stubs for signal_ignore, signal_disable --- src/runtime/runtime_unix.go | 27 +++++++++++++++++++++++++-- src/runtime/signal.c | 20 +++++++++++++++++--- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index 7003e4d4da..4d4e05c572 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -3,6 +3,7 @@ package runtime import ( + "syscall" "unsafe" ) @@ -313,23 +314,45 @@ func init() { // A channel size of 1 should be sufficient in most cases, but using 4 just // to be sure. signalChan = make(chan uint32, 4) + signalIgnored = make([]bool, 65) // 65 is the highest signal number we support } var signalChan chan uint32 +var signalIgnored []bool // TODO: replace with more efficient bitmap? //go:linkname signal_enable os/signal.signal_enable -func signal_enable(s uint32) { +func signal_enable(sig uint32) { // It's easier to implement this function in C. - tinygo_signal_enable(s) + tinygo_signal_enable(sig) } //export tinygo_signal_enable func tinygo_signal_enable(s uint32) +func signal_disable(sig uint32) { + tinygo_signal_disable(sig) +} + +//export tinygo_signal_disable +func tinygo_signal_disable(sig uint32) + +// Ignore the given signal by adding it into the signalIgnored array. +// If the signal is received, it will be ignored in the tinygo_signal_handler. +// The signals SIGKILL and SIGSTOP cannot be caught or ignored. man (2) signal +func tinygo_signal_ignore(sig uint32) { + if syscall.Signal(sig) != syscall.SIGKILL && syscall.Signal(sig) != syscall.SIGSTOP { + signalIgnored[sig] = true + } +} + // void tinygo_signal_handler(int sig); // //export tinygo_signal_handler func tinygo_signal_handler(s int32) { + if signalIgnored[s] { + return + } + select { case signalChan <- uint32(s): default: diff --git a/src/runtime/signal.c b/src/runtime/signal.c index ffa30543da..e5d97fb075 100644 --- a/src/runtime/signal.c +++ b/src/runtime/signal.c @@ -1,8 +1,12 @@ -//go:build none +// go:build none // Ignore the //go:build above. This file is manually included on Linux and // MacOS to provide os/signal support. +// see man (7) feature_test_macros +// _XOPEN_SOURCE is needed to control the definitions that are exposed by +// system header files when a program is compiled +#define _XOPEN_SOURCE 700 #include #include #include @@ -11,9 +15,19 @@ void tinygo_signal_handler(int sig); // Enable a signal from the runtime. -void tinygo_signal_enable(uint32_t sig) { - struct sigaction act = { 0 }; +void tinygo_signal_enable(uint32_t sig) +{ + struct sigaction act = {0}; act.sa_flags = SA_SIGINFO; act.sa_handler = &tinygo_signal_handler; sigaction(sig, &act, NULL); } + +// Disable a signal from the runtime. +// Defer the signal handler to the default handler for now. +void tinygo_signal_disable(uint32_t sig) +{ + struct sigaction act = {0}; + act.sa_handler = SIG_DFL; + sigaction(sig, &act, NULL); +} From f7bd4387d11c9122ead826c5a41302c080987619 Mon Sep 17 00:00:00 2001 From: leongross Date: Mon, 5 Aug 2024 14:52:29 +0200 Subject: [PATCH 4/5] add signal.Ignore, add signal.Ignored, update signal tests, add Exited() Signed-off-by: leongross --- src/os/exec.go | 4 ++++ src/os/signal/signal_linux.go | 39 +++++++++++++++++++++++++++++++++++ src/runtime/runtime_unix.go | 34 ++++++++++++++++++++++++------ src/runtime/signal.c | 10 ++++++++- testdata/signal/signal.go | 17 +++++++++++++-- 5 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 src/os/signal/signal_linux.go diff --git a/src/os/exec.go b/src/os/exec.go index 1ea9dcbd8c..daf22f5da2 100644 --- a/src/os/exec.go +++ b/src/os/exec.go @@ -47,6 +47,10 @@ func (p *ProcessState) Sys() interface{} { return nil // TODO } +func (p *ProcessState) Exited() bool { + return false // TODO +} + // ExitCode returns the exit code of the exited process, or -1 // if the process hasn't exited or was terminated by a signal. func (p *ProcessState) ExitCode() int { diff --git a/src/os/signal/signal_linux.go b/src/os/signal/signal_linux.go new file mode 100644 index 0000000000..0c455894f3 --- /dev/null +++ b/src/os/signal/signal_linux.go @@ -0,0 +1,39 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go: build linux + +package signal + +import ( + "os" + "syscall" +) + +// these functions are linked to the runtime at src/runtime/runtime_unix.go +func signal_enable(sig uint32) +func signal_disable(sig uint32) +func signal_ignore(sig uint32) +func signal_ignored() []bool + +func signum(s os.Signal) int { + return int(s.(syscall.Signal)) +} + +// Ignore causes the provided signals to be ignored. +// If they are received by the program, nothing will happen +func Ignore(sig ...os.Signal) { + for _, s := range sig { + signal_ignore(uint32(signum(s))) + } +} + +// Return true if the provided signal is being ignored +// The runtime keeps track of the ignored signals in the signalIgnored array +func Ignored(sig os.Signal) bool { + return signal_ignored()[signum(sig)] +} + +// TODO: reset all previously set masks +func Reset(sig ...os.Signal) {} diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index 4d4e05c572..bd9505f620 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -3,7 +3,6 @@ package runtime import ( - "syscall" "unsafe" ) @@ -318,7 +317,17 @@ func init() { } var signalChan chan uint32 -var signalIgnored []bool // TODO: replace with more efficient bitmap? +var signalIgnored []bool + +// SignalIgnored returns the signalIgnored array. +// Do not expose signalIgnored directly, as it is mutable. +// +//export SignalIgnored +func SignalIgnored() []bool { + sigs := make([]bool, len(signalIgnored)) + copy(sigs, signalIgnored) + return sigs +} //go:linkname signal_enable os/signal.signal_enable func signal_enable(sig uint32) { @@ -329,6 +338,7 @@ func signal_enable(sig uint32) { //export tinygo_signal_enable func tinygo_signal_enable(s uint32) +// go: link signal_disable os/signal.signal_disable func signal_disable(sig uint32) { tinygo_signal_disable(sig) } @@ -339,10 +349,22 @@ func tinygo_signal_disable(sig uint32) // Ignore the given signal by adding it into the signalIgnored array. // If the signal is received, it will be ignored in the tinygo_signal_handler. // The signals SIGKILL and SIGSTOP cannot be caught or ignored. man (2) signal -func tinygo_signal_ignore(sig uint32) { - if syscall.Signal(sig) != syscall.SIGKILL && syscall.Signal(sig) != syscall.SIGSTOP { - signalIgnored[sig] = true - } +// +// func tinygo_signal_ignore(sig uint32) { +// if syscall.Signal(sig) != syscall.SIGKILL && syscall.Signal(sig) != syscall.SIGSTOP { +// signalIgnored[sig] = true +// } +// } + +//export tinygo_signal_ignore +func tinygo_signal_ignore(sig uint32) + +// go: link signal_ignore os/signal.signal_ignore +func signal_ignore(sig uint32) { + // keep track of ignored signal for Ignore(sig os.Signal) + // the ignore logic itself is tracked by the kernel https://elixir.bootlin.com/linux/v6.10/source/kernel/signal.c#L4142 + signalIgnored[sig] = true + tinygo_signal_ignore(sig) } // void tinygo_signal_handler(int sig); diff --git a/src/runtime/signal.c b/src/runtime/signal.c index e5d97fb075..33a00cf59b 100644 --- a/src/runtime/signal.c +++ b/src/runtime/signal.c @@ -1,4 +1,4 @@ -// go:build none +//go:build none // Ignore the //go:build above. This file is manually included on Linux and // MacOS to provide os/signal support. @@ -31,3 +31,11 @@ void tinygo_signal_disable(uint32_t sig) act.sa_handler = SIG_DFL; sigaction(sig, &act, NULL); } + +// Ignore a signal from the runtime. +void tinygo_signal_ignore(uint32_t sig) +{ + struct sigaction act = {0}; + act.sa_handler = SIG_IGN; + sigaction(sig, &act, NULL); +} \ No newline at end of file diff --git a/testdata/signal/signal.go b/testdata/signal/signal.go index d465715303..59cebc3f04 100644 --- a/testdata/signal/signal.go +++ b/testdata/signal/signal.go @@ -25,10 +25,23 @@ func main() { } } }() + // test signal_enable - // Send the signal. - syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) + // test signal_disable + + // test signal_ignore + signal.Ignore(syscall.SIGUSR1) + if signal.Ignored(syscall.SIGUSR1) { + println("SIGUSR1 is ignored") + } else { + println("SIGUSR1 is not ignored") + } + // test signal_ignore SIGKILL and SIGSTOP that cannot be caught or ignored + signal.Ignore() + + // send the signal. + syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) time.Sleep(time.Millisecond * 100) println("exiting signal program") } From f5607579b6ed044495efca6c5e2d5415d2083dcc Mon Sep 17 00:00:00 2001 From: leongross Date: Tue, 27 Aug 2024 17:08:03 +0200 Subject: [PATCH 5/5] signal: fix go:linkname directives to pick up signal_ignore and signal_recv Signed-off-by: leongross --- src/runtime/runtime_unix.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index bd9505f620..3fd7f0811b 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -338,7 +338,7 @@ func signal_enable(sig uint32) { //export tinygo_signal_enable func tinygo_signal_enable(s uint32) -// go: link signal_disable os/signal.signal_disable +//go:linkname signal_disable os/signal.signal_disable func signal_disable(sig uint32) { tinygo_signal_disable(sig) } @@ -359,7 +359,7 @@ func tinygo_signal_disable(sig uint32) //export tinygo_signal_ignore func tinygo_signal_ignore(sig uint32) -// go: link signal_ignore os/signal.signal_ignore +//go:linkname signal_ignore os/signal.signal_ignore func signal_ignore(sig uint32) { // keep track of ignored signal for Ignore(sig os.Signal) // the ignore logic itself is tracked by the kernel https://elixir.bootlin.com/linux/v6.10/source/kernel/signal.c#L4142