Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions builder/musl.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ var libMusl = Library{
"mman/*.c",
"math/*.c",
"multibyte/*.c",
"signal/" + arch + "/*.s",
"signal/*.c",
"stdio/*.c",
"string/*.c",
Expand Down
14 changes: 10 additions & 4 deletions compileopts/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
9 changes: 9 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func TestBuild(t *testing.T) {
"oldgo/",
"print.go",
"reflect.go",
"signal/",
"slice.go",
"sort.go",
"stdlib.go",
Expand Down Expand Up @@ -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") {
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/os/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 0 additions & 14 deletions src/os/signal/signal.go

This file was deleted.

39 changes: 39 additions & 0 deletions src/os/signal/signal_linux.go
Original file line number Diff line number Diff line change
@@ -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) {}
83 changes: 83 additions & 0 deletions src/runtime/runtime_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,86 @@ 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)
signalIgnored = make([]bool, 65) // 65 is the highest signal number we support
}

var signalChan chan uint32
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) {
// It's easier to implement this function in C.
tinygo_signal_enable(sig)
}

//export tinygo_signal_enable
func tinygo_signal_enable(s uint32)

//go:linkname signal_disable os/signal.signal_disable
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
// }
// }

//export tinygo_signal_ignore
func tinygo_signal_ignore(sig uint32)

//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
signalIgnored[sig] = true
tinygo_signal_ignore(sig)
}

// 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:
// 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
}
41 changes: 41 additions & 0 deletions src/runtime/signal.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//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 <stdint.h>
#include <signal.h>
#include <unistd.h>

// 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);
}

// 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);
}

// 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);
}
11 changes: 10 additions & 1 deletion src/syscall/syscall_libc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down Expand Up @@ -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
2 changes: 2 additions & 0 deletions src/syscall/syscall_libc_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
Expand Down
2 changes: 2 additions & 0 deletions testdata/signal/out.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
got expected signal
exiting signal program
47 changes: 47 additions & 0 deletions testdata/signal/signal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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())
}
}
}()
// test signal_enable

// 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")
}