Skip to content

Commit

Permalink
Add kill() support - sort of
Browse files Browse the repository at this point in the history
This patch adds support for the Linux kill(2) function.
The next patch will add alarm(2) support, which uses kill(2).

To be honest, we sort-of implement kill(). This implementation is
compatible with the API, but the semantics are somewhat different:
While in Linux kill() causes the signal handler to run on one of the
existing threads, in this implementation, the signal handler is run in a
*new* thread.

Implementing the exact Linux semantics in OSv would require tracking when
OSv runs kernel code (i.e., code in the main executable, not a shared
object) so we can delay running the signal handler until returning to the
user code. Moreover, we'll need to be able to interrupt sleeping kernel
code. This is complicated and adds overhead even if signals aren't used
(and they aren't used in most modern code).

I expect that this code will be "good enough" in many use cases.
This code will *not* be good in enough in programs that expect one of the
following:

1. A program that by using Posix Thread's "signal masks" tried to ensure
   that the signal is delivered to one specific thread, and not to an
   arbitrary thread.

2. A program that used kill() or alarm() not intending to run a signal
   handler, but rather intending to interrupt a sleeping system call
   like sleep() or read(). Our kill() does not interrupt sleeping OSv
   function calls, which will continue to sleep on the thread they run
   on.

The support in this patch (and see next patch, for alarm()) is good
enough for netperf's use of alarm().

P.S. kill() can be used only to send a signal to the current process, the
only process we have in OSv (you can also use pid=0 and pid=-1 to achieve
the same results).

This patch also adds a test for kill() and alarm(). The alarm() test
will fail until the next patch :-)

Signed-off-by: Nadav Har'El <nyh@cloudius-systems.com>
  • Loading branch information
nyh committed Sep 29, 2013
1 parent 580c1f9 commit b9ed15c
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 0 deletions.
1 change: 1 addition & 0 deletions bootfs.manifest
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions build.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
70 changes: 70 additions & 0 deletions libc/signal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@

#include "signal.hh"
#include <string.h>
#include <unistd.h>
#include <string.h>
#include <debug.hh>
#include <osv/printf.hh>
#include <sched.hh>

namespace osv {

Expand Down Expand Up @@ -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;
}
81 changes: 81 additions & 0 deletions tests/tst-kill.cc
Original file line number Diff line number Diff line change
@@ -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 <sys/types.h>
#include <signal.h>

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


0 comments on commit b9ed15c

Please sign in to comment.