/* Copyright (C) 2014 Stony Brook University
This file is part of Graphene Library OS.
Graphene Library OS is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Graphene Library OS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see . */
/*
* shim_ipc_child.c
*
* This file contains functions and callbacks to handle IPC between parent
* processes and their children.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct thread_info {
IDTYPE vmid;
unsigned int exitcode;
unsigned int term_signal;
};
/* walk_simple_thread_list callback; exit each simple thread of child process vmid. */
static int child_sthread_exit(struct shim_simple_thread* thread, void* arg, bool* unlocked) {
__UNUSED(unlocked); /* FYI: notifies about unlocked thread_list_lock */
struct thread_info* info = (struct thread_info*)arg;
int found_exiting_thread = 0;
lock(&thread->lock);
if (thread->vmid == info->vmid) {
found_exiting_thread = 1;
if (thread->is_alive) {
thread->exit_code = -info->exitcode;
thread->term_signal = info->term_signal;
thread->is_alive = false;
/* arrange exit event for subsequent wait4(thread->tid) */
DkEventSet(thread->exit_event);
}
}
unlock(&thread->lock);
return found_exiting_thread;
}
/* walk_thread_list callback; exit each thread of child process vmid. */
static int child_thread_exit(struct shim_thread* thread, void* arg, bool* unlocked) {
__UNUSED(unlocked); /* FYI: notifies about unlocked thread_list_lock */
struct thread_info* info = (struct thread_info*)arg;
int found_exiting_thread = 0;
lock(&thread->lock);
if (thread->vmid == info->vmid) {
found_exiting_thread = 1;
if (thread->is_alive) {
thread->exit_code = -info->exitcode;
thread->term_signal = info->term_signal;
unlock(&thread->lock);
/* remote thread is "virtually" exited: SIGCHLD is generated for
* the parent thread and exit events are arranged for subsequent
* wait4(). */
thread_exit(thread, false);
goto out;
}
}
unlock(&thread->lock);
out:
return found_exiting_thread;
}
/* IPC helper thread invokes this fini function when main IPC port for
* communication with child process is disconnected/removed by host OS.
*
* Similarly to benign case of receiving an explicit IPC_CLD_EXIT message
* from exiting remote thread (see ipc_cld_exit_callback()), we want to
* delete all remote threads associated with disconnected child process.
*/
void ipc_port_with_child_fini(struct shim_ipc_port* port, IDTYPE vmid, unsigned int exitcode) {
__UNUSED(port);
/* NOTE: IPC port may be closed by host OS because the child process
* exited on host OS (and so host OS closed all its sockets).
* This may happen before arrival of the "expected" IPC_CLD_EXIT
* message from child process. Ideally, we would inspect whether
* we previously sent SIGINT/SIGTERM/SIGKILL to this child and
* use the corresponding termination signal. For now, we simply
* report that child process was killed by SIGKILL. */
struct thread_info info = {.vmid = vmid, .exitcode = exitcode, .term_signal = SIGKILL};
/* message cannot come from our own threads (from ourselves as process) */
assert(vmid != cur_process.vmid);
int ret;
int exited_threads_cnt = 0;
if ((ret = walk_thread_list(&child_thread_exit, &info)) > 0)
exited_threads_cnt += ret;
if ((ret = walk_simple_thread_list(&child_sthread_exit, &info)) > 0)
exited_threads_cnt += ret;
debug(
"Child process %u got disconnected: assuming that child exited and "
"forcing %d of its threads to exit\n",
vmid & 0xFFFF, exited_threads_cnt);
}
DEFINE_PROFILE_INTERVAL(ipc_cld_exit_turnaround, ipc);
DEFINE_PROFILE_INTERVAL(ipc_cld_exit_send, ipc);
DEFINE_PROFILE_INTERVAL(ipc_cld_exit_callback, ipc);
/* The exiting thread of this process calls this function to broadcast
* IPC_CLD_EXIT notification to its parent process (technically, to all
* processes of type DIRPRT or DIRCLD but the only interesting case is
* the notification of parent). */
int ipc_cld_exit_send(IDTYPE ppid, IDTYPE tid, unsigned int exitcode, unsigned int term_signal) {
__attribute__((unused)) unsigned long send_time = GET_PROFILE_INTERVAL();
BEGIN_PROFILE_INTERVAL_SET(send_time);
size_t total_msg_size = get_ipc_msg_size(sizeof(struct shim_ipc_cld_exit));
struct shim_ipc_msg* msg = __alloca(total_msg_size);
init_ipc_msg(msg, IPC_CLD_EXIT, total_msg_size, 0);
struct shim_ipc_cld_exit* msgin = (struct shim_ipc_cld_exit*)&msg->msg;
msgin->ppid = ppid;
msgin->tid = tid;
msgin->exitcode = exitcode;
msgin->term_signal = term_signal;
#ifdef PROFILE
msgin->time = send_time;
#endif
debug("IPC broadcast: IPC_CLD_EXIT(%u, %u, %d, %u)\n", ppid, tid, exitcode, term_signal);
int ret = broadcast_ipc(msg, IPC_PORT_DIRPRT | IPC_PORT_DIRCLD,
/*exclude_port=*/NULL);
SAVE_PROFILE_INTERVAL(ipc_cld_exit_send);
return ret;
}
/* IPC helper thread invokes this callback on an IPC_CLD_EXIT message received
* from a specific thread msgin->tid of the exiting child process with vmid
* msg->src. The thread of the exiting child process informs about its exit
* code in msgin->exit_code and its terminating signal in msgin->term_signal.
*
* The callback finds this remote thread of the child process among our
* process's threads/simple threads (recall that parent process maintains
* remote child threads in its thread list, marking them as in_vm == false).
* The remote thread is "virtually" exited: SIGCHLD is generated for the
* parent thread and exit events are arranged for subsequent wait4().
*/
int ipc_cld_exit_callback(struct shim_ipc_msg* msg, struct shim_ipc_port* port) {
__UNUSED(port);
int ret = 0;
struct shim_ipc_cld_exit* msgin = (struct shim_ipc_cld_exit*)&msg->msg;
#ifdef PROFILE
unsigned long time = msgin->time;
if (!time)
time = GET_PROFILE_INTERVAL();
#endif
BEGIN_PROFILE_INTERVAL_SET(time);
SAVE_PROFILE_INTERVAL(ipc_cld_exit_turnaround);
debug("IPC callback from %u: IPC_CLD_EXIT(%u, %u, %d, %u)\n", msg->src & 0xFFFF, msgin->ppid,
msgin->tid, msgin->exitcode, msgin->term_signal);
/* message cannot come from our own threads (from ourselves as process) */
assert(msg->src != cur_process.vmid);
/* First try to find remote thread which sent this message among normal
* threads. In the common case, we (as parent process) keep remote child
* threads in the thread list. But sometimes the message can arrive twice
* or very late, such that the corresponding remote thread was already
* exited and deleted; in such cases, we fall back to simple threads. */
struct shim_thread* thread = lookup_thread(msgin->tid);
if (thread) {
lock(&thread->lock);
thread->exit_code = -msgin->exitcode;
thread->term_signal = msgin->term_signal;
#ifdef PROFILE
thread->exit_time = time;
#endif
unlock(&thread->lock);
/* Remote thread is "virtually" exited: SIGCHLD is generated for the
* parent thread and exit events are arranged for subsequent wait4(). */
ret = thread_exit(thread, /*send_ipc=*/false);
put_thread(thread);
} else {
/* Uncommon case: remote child thread was already exited and deleted
* (probably because the same message was already received earlier).
* Find or create a simple thread for a sole purpose of arranging
* exit events for subsequent wait4(). */
struct shim_simple_thread* sthread = lookup_simple_thread(msgin->tid);
if (!sthread) {
sthread = get_new_simple_thread();
sthread->vmid = msg->src;
sthread->tid = msgin->tid;
add_simple_thread(sthread);
}
lock(&sthread->lock);
sthread->is_alive = false;
sthread->exit_code = -msgin->exitcode;
sthread->term_signal = msgin->term_signal;
#ifdef PROFILE
sthread->exit_time = time;
#endif
unlock(&sthread->lock);
DkEventSet(sthread->exit_event); /* for wait4(msgin->tid) */
put_simple_thread(sthread);
}
SAVE_PROFILE_INTERVAL(ipc_cld_exit_callback);
return ret;
}
DEFINE_PROFILE_INTERVAL(ipc_send_profile, ipc);
#ifdef PROFILE
int ipc_cld_profile_send(void) {
struct shim_ipc_port* port = NULL;
IDTYPE dest = (IDTYPE)-1;
/* port and dest are initialized to parent process */
lock(&cur_process.lock);
if (cur_process.parent && (port = cur_process.parent->port)) {
get_ipc_port(port);
dest = cur_process.parent->vmid;
}
unlock(&cur_process.lock);
if (!port || (dest == (IDTYPE)-1))
return -ESRCH;
unsigned long time = GET_PROFILE_INTERVAL();
size_t nsending = 0;
for (size_t i = 0; i < N_PROFILE; i++) {
switch (PROFILES[i].type) {
case OCCURENCE:
if (atomic_read(&PROFILES[i].val.occurence.count))
nsending++;
break;
case INTERVAL:
if (atomic_read(&PROFILES[i].val.interval.count))
nsending++;
break;
case CATEGORY:
break;
}
}
size_t total_msg_size = get_ipc_msg_size(sizeof(struct shim_ipc_cld_profile) +
sizeof(struct profile_val) * nsending);
struct shim_ipc_msg* msg = __alloca(total_msg_size);
init_ipc_msg(msg, IPC_CLD_PROFILE, total_msg_size, dest);
struct shim_ipc_cld_profile* msgin = (struct shim_ipc_cld_profile*)&msg->msg;
size_t nsent = 0;
for (size_t i = 0; i < N_PROFILE && nsent < nsending; i++) {
switch (PROFILES[i].type) {
case OCCURENCE: {
unsigned long count = atomic_read(&PROFILES[i].val.occurence.count);
if (count) {
msgin->profile[nsent].idx = i + 1;
msgin->profile[nsent].val.occurence.count = count;
debug("Send %s: %lu times\n", PROFILES[i].name, count);
nsent++;
}
break;
}
case INTERVAL: {
unsigned long count = atomic_read(&PROFILES[i].val.interval.count);
if (count) {
msgin->profile[nsent].idx = i + 1;
msgin->profile[nsent].val.interval.count = count;
msgin->profile[nsent].val.interval.time =
atomic_read(&PROFILES[i].val.interval.time);
debug("Send %s: %lu times, %lu msec\n", PROFILES[i].name, count,
msgin->profile[nsent].val.interval.time);
nsent++;
}
break;
}
case CATEGORY:
break;
}
}
msgin->time = time;
msgin->nprofile = nsent;
debug("IPC send to %u: IPC_CLD_PROFILE\n", dest & 0xFFFF);
int ret = send_ipc_message(msg, port);
put_ipc_port(port);
return ret;
}
int ipc_cld_profile_callback(struct shim_ipc_msg* msg, struct shim_ipc_port* port) {
debug("IPC callback from %u: IPC_CLD_PROFILE\n", msg->src & 0xFFFF);
struct shim_ipc_cld_profile* msgin = (struct shim_ipc_cld_profile*)&msg->msg;
for (int i = 0; i < msgin->nprofile; i++) {
int idx = msgin->profile[i].idx;
if (idx == 0)
break;
idx--;
switch (PROFILES[idx].type) {
case OCCURENCE:
debug("Receive %s: %u times\n", PROFILES[idx].name,
msgin->profile[i].val.occurence.count);
atomic_add(msgin->profile[i].val.occurence.count,
&PROFILES[idx].val.occurence.count);
break;
case INTERVAL:
debug("Receive %s: %u times, %lu msec\n", PROFILES[idx].name,
msgin->profile[i].val.interval.count, msgin->profile[i].val.interval.time);
atomic_add(msgin->profile[i].val.interval.count, &PROFILES[idx].val.interval.count);
atomic_add(msgin->profile[i].val.interval.time, &PROFILES[idx].val.interval.time);
break;
case CATEGORY:
break;
}
}
SAVE_PROFILE_INTERVAL_SINCE(ipc_send_profile, msgin->time);
return 0;
}
#endif