diff --git a/bootfs.manifest b/bootfs.manifest index 8071f16fc4..5e920d5caa 100644 --- a/bootfs.manifest +++ b/bootfs.manifest @@ -58,6 +58,7 @@ /&/tests/tst-loadbalance.so: ./& /&/tests/tst-dns-resolver.so: ./& /&/tests/tst-fs-link.so: ./& +/&/tests/tst-kill.so: ./& /testrunner.so: ./tests/testrunner.so /java/Hello.class: ./tests/hello/Hello.class /java.so: java/java.so diff --git a/build.mk b/build.mk index c7fecf88b4..4e59ba1209 100644 --- a/build.mk +++ b/build.mk @@ -150,6 +150,7 @@ tests += tests/tst-tcp-hash-srv.so tests += tests/tst-loadbalance.so tests += tests/tst-dns-resolver.so tests += tests/tst-fs-link.so +tests += tests/tst-kill.so tests/hello/Hello.class: javabase=tests/hello diff --git a/libc/signal.cc b/libc/signal.cc index 01328e5155..0539d309d8 100644 --- a/libc/signal.cc +++ b/libc/signal.cc @@ -7,6 +7,11 @@ #include "signal.hh" #include +#include +#include +#include +#include +#include namespace osv { @@ -160,3 +165,68 @@ int sigignore(int signum) act.sa_handler = SIG_IGN; return sigaction(signum, &act, nullptr); } + +// Partially-Linux-compatible support for kill(2). +// Note that this is different from our generate_signal() - the latter is only +// suitable for delivering SIGFPE and SIGSEGV to the same thread that called +// this function. +// +// Handling kill(2)/signal(2) exactly like Linux, where one of the existing +// threads runs the signal handler, is difficult in OSv because it requires +// tracking of when we're in kernel code (to delay the signal handling until +// it returns to "user" code), and also to interrupt sleeping kernel code and +// have it return sooner. +// Instead, we provide a simple "approximation" of the signal handling - +// on each kill(), a *new* thread is created to run the signal handler code. +// +// This approximation will work in programs that do not care about the signal +// being delivered to a specific thread, and that do not intend that the +// signal should interrupt a system call (e.g., sleep() or hung read()). +// FIXME: think if our handling of nested signals is ok (right now while +// handling a signal, we can get another one of the same signal and start +// another handler thread. We should probably block this signal while +// handling it. + +int kill(pid_t pid, int sig) +{ + // OSv only implements one process, whose pid is getpid(). + // Sending a signal to pid 0 or -1 is also fine, as it will also send a + // signal to the same single process. + if (pid != getpid() && pid != 0 && pid != -1) { + errno = ESRCH; + return -1; + } + if (sig == 0) { + // kill() with signal 0 doesn't cause an actual signal 0, just + // testing the pid. + return 0; + } + if (sig < 0 || sig >= (int)nsignals) { + errno = EINVAL; + return -1; + } + if (is_sig_dfl(signal_actions[sig])) { + // Our default is to abort the process + abort(osv::sprintf("Uncaught signal %d (\"%s\"). Aborting.\n", + sig, strsignal(sig)).c_str()); + } else if(!is_sig_ign(signal_actions[sig])) { + // User-defined signal handler. Run it in a new thread. This isn't + // very Unix-like behavior, but if we assume that the program doesn't + // care which of its threads handle the signal - why not just create + // a completely new thread and run it there... + const auto& sa = signal_actions[sig]; + sched::thread::attr a; + a.detached = true; + a.stack.size = 65536; // TODO: what is a good size? + auto t = new sched::thread([=] { + if (sa.sa_flags & SA_SIGINFO) { + // FIXME: proper second (siginfo) and third (context) arguments (See example in call_signal_handler) + sa.sa_sigaction(sig, nullptr, nullptr); + } else { + sa.sa_handler(sig); + } + }, a); + t->start(); + } + return 0; +} diff --git a/tests/tst-kill.cc b/tests/tst-kill.cc new file mode 100644 index 0000000000..fad9976bfa --- /dev/null +++ b/tests/tst-kill.cc @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2013 Cloudius Systems, Ltd. + * + * This work is open source software, licensed under the terms of the + * BSD license as described in the LICENSE file in the top-level directory. + */ + +// Test for kill() and alarm() approximations in libc/signal.cc + +#include +#include + +#include "debug.hh" + +int tests = 0, fails = 0; + +static void report(bool ok, const char* msg) +{ + ++tests; + fails += !ok; + debug("%s: %s\n", (ok ? "PASS" : "FAIL"), msg); +} + +int global = 0; + +void handler1(int sig) { + debug("handler1 called, sig=%d\n", sig); + global = 1; +} + +int main(int ac, char** av) +{ + // Test kill() of current process: + report (global == 0, "'global' initially 0"); + auto sr = signal(SIGUSR1, handler1); + report(sr != SIG_ERR, "set SIGUSR1 handler"); + int r; + r = kill(0, SIGUSR1); + report(r == 0, "kill SIGUSR1 succeeds"); + for (int i=0; i<100; i++) { + if (global == 1) break; + usleep(10000); + } + report(global == 1, "'global' is now 1"); + // Test various edge cases for kill(): + r = kill(0, 0); + report(r == 0, "kill with signal 0 succeeds (and does nothing)"); + r = kill(-1, 0); + report(r == 0, "kill of pid -1 is also fine"); + r = kill(17, 0); + report(r == -1 && errno == ESRCH, "kill of non-existant process"); + r = kill(0, -2); + report(r == -1 && errno == EINVAL, "kill with invalid signal number"); + r = kill(0, 12345); + report(r == -1 && errno == EINVAL, "kill with invalid signal number"); + + // Test alarm(); + global = 0; + sr = signal(SIGALRM, handler1); + report(sr != SIG_ERR, "set SIGALRM handler"); + auto ar = alarm(1); + report(ar == 0, "set alarm for 1 second - no previous alarm"); + usleep(500000); + report(global == 0, "after 0.5 seconds - still global==0"); + sleep(1); + report(global == 1, "after 1 more second - now global==1"); + + // Test cancel of alarm(); + global = 0; + ar = alarm(1); + report(ar == 0, "set alarm for 1 second - no previous alarm"); + usleep(500000); + report(global == 0, "after 0.5 seconds - still global==0"); + ar = alarm(0); + sleep(1); + report(global == 0, "1 more second after cancelling alarm - still global==0"); + + debug("SUMMARY: %d tests, %d failures\n", tests, fails); +} + +