Skip to content

Commit d49e3db

Browse files
committed
runtime: add support for os/signal
This adds support for enabling and listening to signals on Linux and MacOS. TODO: also support disabling signals.
1 parent 4884d68 commit d49e3db

File tree

10 files changed

+108
-15
lines changed

10 files changed

+108
-15
lines changed

builder/musl.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ var Musl = Library{
124124
"malloc/mallocng/*.c",
125125
"mman/*.c",
126126
"math/*.c",
127+
"signal/" + arch + "/*.s",
127128
"signal/*.c",
128129
"stdio/*.c",
129130
"string/*.c",

compileopts/target.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
356356
"-arch", arch,
357357
"-platform_version", "macos", platformVersion, platformVersion,
358358
)
359+
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/signal.c")
359360
} else if goos == "linux" {
360361
spec.Linker = "ld.lld"
361362
spec.RTLib = "compiler-rt"
@@ -375,6 +376,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
375376
// proper threading.
376377
spec.CFlags = append(spec.CFlags, "-mno-outline-atomics")
377378
}
379+
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/signal.c")
378380
} else if goos == "windows" {
379381
spec.Linker = "ld.lld"
380382
spec.Libc = "mingw-w64"

lib/macos-minimal-sdk

main_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ func TestBuild(t *testing.T) {
7676
"oldgo/",
7777
"print.go",
7878
"reflect.go",
79+
"signal/",
7980
"slice.go",
8081
"sort.go",
8182
"stdlib.go",
@@ -202,6 +203,7 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
202203
// isWebAssembly := strings.HasPrefix(spec.Triple, "wasm")
203204
isWASI := strings.HasPrefix(options.Target, "wasi")
204205
isWebAssembly := isWASI || strings.HasPrefix(options.Target, "wasm") || (options.Target == "" && strings.HasPrefix(options.GOARCH, "wasm"))
206+
isBaremetal := options.Target == "simavr" || options.Target == "cortex-m-qemu" || options.Target == "riscv-qemu"
205207

206208
for _, name := range tests {
207209
if options.GOOS == "linux" && (options.GOARCH == "arm" || options.GOARCH == "386") {
@@ -254,6 +256,13 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
254256
continue
255257
}
256258
}
259+
if isWebAssembly || isBaremetal || options.GOOS == "windows" {
260+
switch name {
261+
case "signal/":
262+
// Signals only work on POSIX-like systems.
263+
continue
264+
}
265+
}
257266

258267
name := name // redefine to avoid race condition
259268
t.Run(name, func(t *testing.T) {

src/os/signal/signal.go

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/runtime/runtime_unix.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,3 +256,41 @@ func growHeap() bool {
256256
setHeapEnd(heapStart + heapSize)
257257
return true
258258
}
259+
260+
func init() {
261+
// Set up a channel to receive signals into.
262+
// A channel size of 1 should be sufficient in most cases, but using 4 just
263+
// to be sure.
264+
signalChan = make(chan uint32, 4)
265+
}
266+
267+
var signalChan chan uint32
268+
269+
//go:linkname signal_enable os/signal.signal_enable
270+
func signal_enable(s uint32) {
271+
// It's easier to implement this function in C.
272+
tinygo_signal_enable(s)
273+
}
274+
275+
//export tinygo_signal_enable
276+
func tinygo_signal_enable(s uint32)
277+
278+
// void tinygo_signal_handler(int sig);
279+
//
280+
//export tinygo_signal_handler
281+
func tinygo_signal_handler(s int32) {
282+
select {
283+
case signalChan <- uint32(s):
284+
default:
285+
// TODO: we should handle this in a better way somehow.
286+
// Maybe just ignore the signal, assuming that nothing is reading from
287+
// the notify channel?
288+
runtimePanic("could not deliver signal: runtime internal channel is full")
289+
}
290+
}
291+
292+
//go:linkname signal_recv os/signal.signal_recv
293+
func signal_recv() uint32 {
294+
// Function called from os/signal to get the next received signal.
295+
return <-signalChan
296+
}

src/runtime/signal.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//go:build none
2+
3+
// Ignore the //go:build above. This file is manually included on Linux and
4+
// MacOS to provide os/signal support.
5+
6+
#include <stdint.h>
7+
#include <signal.h>
8+
#include <unistd.h>
9+
10+
// Signal handler in the runtime.
11+
void tinygo_signal_handler(int sig);
12+
13+
// Enable a signal from the runtime.
14+
void tinygo_signal_enable(uint32_t sig) {
15+
struct sigaction act = { 0 };
16+
act.sa_flags = SA_SIGINFO;
17+
act.sa_handler = &tinygo_signal_handler;
18+
sigaction(sig, &act, NULL);
19+
}

src/syscall/syscall_libc_darwin.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ const (
9393
SIGPIPE Signal = 13 /* write on a pipe with no one to read it */
9494
SIGTERM Signal = 15 /* software termination signal from kill */
9595
SIGCHLD Signal = 20 /* to parent on child stop or exit */
96+
SIGUSR1 Signal = 30 /* user defined signal 1 */
97+
SIGUSR2 Signal = 31 /* user defined signal 2 */
9698
)
9799

98100
func (s Signal) Signal() {}

testdata/signal/out.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
got expected signal
2+
exiting signal program

testdata/signal/signal.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package main
2+
3+
// Test POSIX signals.
4+
// TODO: run `tinygo test os/signal` instead, once CGo errno return values are
5+
// supported.
6+
7+
import (
8+
"os"
9+
"os/signal"
10+
"syscall"
11+
"time"
12+
)
13+
14+
func main() {
15+
c := make(chan os.Signal, 1)
16+
signal.Notify(c, syscall.SIGUSR1)
17+
18+
// Wait for signals to arrive.
19+
go func() {
20+
for sig := range c {
21+
if sig == syscall.SIGUSR1 {
22+
println("got expected signal")
23+
} else {
24+
println("got signal:", sig.String())
25+
}
26+
}
27+
}()
28+
29+
// Send the signal.
30+
syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
31+
32+
time.Sleep(time.Millisecond * 100)
33+
println("exiting signal program")
34+
}

0 commit comments

Comments
 (0)