rt-thread-official/components/lwp/lwp_signal.c

607 lines
13 KiB
C

/*
* Copyright (c) 2006-2020, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2019-11-12 Jesven first version
*/
#include <rthw.h>
#include <rtthread.h>
#include "lwp.h"
#include "lwp_arch.h"
#include "sys/signal.h"
rt_inline void lwp_sigaddset(lwp_sigset_t *set, int _sig)
{
unsigned long sig = _sig - 1;
if (_LWP_NSIG_WORDS == 1)
{
set->sig[0] |= 1UL << sig;
}
else
{
set->sig[sig / _LWP_NSIG_BPW] |= 1UL << (sig % _LWP_NSIG_BPW);
}
}
rt_inline void lwp_sigdelset(lwp_sigset_t *set, int _sig)
{
unsigned long sig = _sig - 1;
if (_LWP_NSIG_WORDS == 1)
{
set->sig[0] &= ~(1UL << sig);
}
else
{
set->sig[sig / _LWP_NSIG_BPW] &= ~(1UL << (sig % _LWP_NSIG_BPW));
}
}
rt_inline int lwp_sigisemptyset(lwp_sigset_t *set)
{
switch (_LWP_NSIG_WORDS)
{
case 4:
return (set->sig[3] | set->sig[2] |
set->sig[1] | set->sig[0]) == 0;
case 2:
return (set->sig[1] | set->sig[0]) == 0;
case 1:
return set->sig[0] == 0;
default:
return 1;
}
}
rt_inline int lwp_sigismember(lwp_sigset_t *set, int _sig)
{
unsigned long sig = _sig - 1;
if (_LWP_NSIG_WORDS == 1)
{
return 1 & (set->sig[0] >> sig);
}
else
{
return 1 & (set->sig[sig / _LWP_NSIG_BPW] >> (sig % _LWP_NSIG_BPW));
}
}
rt_inline int next_signal(lwp_sigset_t *pending, lwp_sigset_t *mask)
{
unsigned long i, *s, *m, x;
int sig = 0;
s = pending->sig;
m = mask->sig;
x = *s & ~*m;
if (x)
{
sig = rt_hw_ffz(~x) + 1;
return sig;
}
switch (_LWP_NSIG_WORDS)
{
default:
for (i = 1; i < _LWP_NSIG_WORDS; ++i)
{
x = *++s &~ *++m;
if (!x)
continue;
sig = rt_hw_ffz(~x) + i*_LWP_NSIG_BPW + 1;
break;
}
break;
case 2:
x = s[1] &~ m[1];
if (!x)
break;
sig = rt_hw_ffz(~x) + _LWP_NSIG_BPW + 1;
break;
case 1:
/* Nothing to do */
break;
}
return sig;
}
int lwp_suspend_sigcheck(rt_thread_t thread, int suspend_flag)
{
struct rt_lwp *lwp = (struct rt_lwp*)thread->lwp;
int ret = 0;
switch (suspend_flag)
{
case RT_INTERRUPTIBLE:
if (!lwp_sigisemptyset(&thread->signal))
{
break;
}
if (thread->lwp && !lwp_sigisemptyset(&lwp->signal))
{
break;
}
ret = 1;
break;
case RT_KILLABLE:
if (lwp_sigismember(&thread->signal, SIGKILL))
{
break;
}
if (thread->lwp && lwp_sigismember(&lwp->signal, SIGKILL))
{
break;
}
ret = 1;
break;
case RT_UNINTERRUPTIBLE:
ret = 1;
break;
default:
RT_ASSERT(0);
break;
}
return ret;
}
int lwp_signal_check(void)
{
rt_base_t level;
struct rt_thread *thread;
struct rt_lwp *lwp;
uint32_t have_signal = 0;
level = rt_hw_interrupt_disable();
thread = rt_thread_self();
if (thread->signal_in_process)
{
goto out;
}
lwp = (struct rt_lwp*)thread->lwp;
if (lwp->signal_in_process)
{
goto out;
}
have_signal = !lwp_sigisemptyset(&thread->signal);
if (have_signal)
{
thread->signal_in_process = 1;
goto out;
}
have_signal = !lwp_sigisemptyset(&lwp->signal);
if (have_signal)
{
lwp->signal_in_process = 1;
}
out:
rt_hw_interrupt_enable(level);
return have_signal;
}
int lwp_signal_backup(void *user_sp, void *user_pc, void* user_flag)
{
rt_base_t level;
struct rt_thread *thread;
struct rt_lwp *lwp;
int signal;
level = rt_hw_interrupt_disable();
thread = rt_thread_self();
if (thread->signal_in_process)
{
thread->user_ctx.sp = user_sp;
thread->user_ctx.pc = user_pc;
thread->user_ctx.flag = user_flag;
signal = next_signal(&thread->signal, &thread->signal_mask);
RT_ASSERT(signal != 0);
lwp_sigaddset(&thread->signal_mask, signal);
thread->signal_mask_bak = signal;
lwp_sigdelset(&thread->signal, signal);
}
else
{
lwp = (struct rt_lwp*)thread->lwp;
lwp->user_ctx.sp = user_sp;
lwp->user_ctx.pc = user_pc;
lwp->user_ctx.flag = user_flag;
signal = next_signal(&lwp->signal, &lwp->signal_mask);
RT_ASSERT(signal != 0);
lwp_sigaddset(&lwp->signal_mask, signal);
lwp->signal_mask_bak = signal;
lwp_sigdelset(&lwp->signal, signal);
}
rt_hw_interrupt_enable(level);
return signal;
}
struct rt_user_context *lwp_signal_restore(void)
{
rt_base_t level;
struct rt_thread *thread;
struct rt_lwp *lwp;
struct rt_user_context *ctx;
level = rt_hw_interrupt_disable();
thread = rt_thread_self();
if (thread->signal_in_process)
{
ctx = &thread->user_ctx;
thread->signal_in_process = 0;
lwp_sigdelset(&thread->signal_mask, thread->signal_mask_bak);
thread->signal_mask_bak = 0;
}
else
{
lwp = (struct rt_lwp*)thread->lwp;
ctx = &lwp->user_ctx;
RT_ASSERT(lwp->signal_in_process != 0);
lwp->signal_in_process = 0;
lwp_sigdelset(&lwp->signal_mask, lwp->signal_mask_bak);
lwp->signal_mask_bak = 0;
}
rt_hw_interrupt_enable(level);
return ctx;
}
rt_inline int _lwp_check_ignore(int sig)
{
if (sig == SIGCHLD || sig == SIGCONT)
{
return 1;
}
return 0;
}
void sys_exit(int value);
lwp_sighandler_t lwp_sighandler_get(int sig)
{
lwp_sighandler_t func = RT_NULL;
struct rt_lwp *lwp;
rt_thread_t thread;
rt_base_t level;
if (sig == 0 || sig > _LWP_NSIG)
{
return func;
}
level = rt_hw_interrupt_disable();
thread = rt_thread_self();
#ifndef ARCH_MM_MMU
if (thread->signal_in_process)
{
func = thread->signal_handler[sig - 1];
goto out;
}
#endif
lwp = (struct rt_lwp*)thread->lwp;
func = lwp->signal_handler[sig - 1];
if (!func)
{
if (_lwp_check_ignore(sig))
{
goto out;
}
if (lwp->signal_in_process)
{
lwp_terminate(lwp);
}
sys_exit(0);
}
out:
rt_hw_interrupt_enable(level);
if (func == (lwp_sighandler_t)SIG_IGN)
{
func = RT_NULL;
}
return func;
}
void lwp_sighandler_set(int sig, lwp_sighandler_t func)
{
rt_base_t level;
if (sig == 0 || sig > _LWP_NSIG)
return;
if (sig == SIGKILL || sig == SIGSTOP)
return;
level = rt_hw_interrupt_disable();
((struct rt_lwp*)rt_thread_self()->lwp)->signal_handler[sig - 1] = func;
rt_hw_interrupt_enable(level);
}
#ifndef ARCH_MM_MMU
void lwp_thread_sighandler_set(int sig, lwp_sighandler_t func)
{
rt_base_t level;
if (sig == 0 || sig > _LWP_NSIG)
return;
level = rt_hw_interrupt_disable();
rt_thread_self()->signal_handler[sig - 1] = func;
rt_hw_interrupt_enable(level);
}
#endif
int lwp_sigaction(int sig, const struct lwp_sigaction *act,
struct lwp_sigaction *oact, size_t sigsetsize)
{
rt_base_t level;
struct rt_lwp *lwp;
int ret = -RT_EINVAL;
lwp_sigset_t newset;
level = rt_hw_interrupt_disable();
lwp = (struct rt_lwp*)rt_thread_self()->lwp;
if (!lwp)
{
goto out;
}
if (sigsetsize != sizeof(lwp_sigset_t))
{
goto out;
}
if (!act && !oact)
{
goto out;
}
if (oact)
{
oact->sa_flags = lwp->sa_flags;
oact->sa_mask = lwp->signal_mask;
oact->sa_restorer = RT_NULL;
oact->__sa_handler._sa_handler = lwp->signal_handler[sig - 1];
}
if (act)
{
lwp->sa_flags = act->sa_flags;
newset = act->sa_mask;
lwp_sigdelset(&newset, SIGKILL);
lwp_sigdelset(&newset, SIGSTOP);
lwp->signal_mask = newset;
lwp_sighandler_set(sig, act->__sa_handler._sa_handler);
}
ret = 0;
out:
rt_hw_interrupt_enable(level);
return ret;
}
rt_inline void sigorsets(lwp_sigset_t *dset, const lwp_sigset_t *set0, const lwp_sigset_t *set1)
{
switch (_LWP_NSIG_WORDS)
{
case 4:
dset->sig[3] = set0->sig[3] | set1->sig[3];
dset->sig[2] = set0->sig[2] | set1->sig[2];
case 2:
dset->sig[1] = set0->sig[1] | set1->sig[1];
case 1:
dset->sig[0] = set0->sig[0] | set1->sig[0];
default:
return;
}
}
rt_inline void sigandsets(lwp_sigset_t *dset, const lwp_sigset_t *set0, const lwp_sigset_t *set1)
{
switch (_LWP_NSIG_WORDS)
{
case 4:
dset->sig[3] = set0->sig[3] & set1->sig[3];
dset->sig[2] = set0->sig[2] & set1->sig[2];
case 2:
dset->sig[1] = set0->sig[1] & set1->sig[1];
case 1:
dset->sig[0] = set0->sig[0] & set1->sig[0];
default:
return;
}
}
int lwp_sigprocmask(int how, const lwp_sigset_t *sigset, lwp_sigset_t *oset)
{
int ret = -1;
rt_base_t level;
struct rt_lwp *lwp;
struct rt_thread *thread;
lwp_sigset_t newset;
level = rt_hw_interrupt_disable();
thread = rt_thread_self();
lwp = (struct rt_lwp*)thread->lwp;
if (!lwp)
{
goto out;
}
if (oset)
{
rt_memcpy(oset, &lwp->signal_mask, sizeof(lwp_sigset_t));
}
if (sigset)
{
switch (how)
{
case SIG_BLOCK:
sigorsets(&newset, &lwp->signal_mask, sigset);
break;
case SIG_UNBLOCK:
sigandsets(&newset, &lwp->signal_mask, sigset);
break;
case SIG_SETMASK:
newset = *sigset;
break;
default:
ret = RT_EINVAL;
goto out;
}
lwp_sigdelset(&newset, SIGKILL);
lwp_sigdelset(&newset, SIGSTOP);
lwp->signal_mask = newset;
}
ret = 0;
out:
rt_hw_interrupt_enable(level);
return ret;
}
int lwp_thread_sigprocmask(int how, const lwp_sigset_t *sigset, lwp_sigset_t *oset)
{
rt_base_t level;
struct rt_thread *thread;
lwp_sigset_t newset;
level = rt_hw_interrupt_disable();
thread = rt_thread_self();
if (oset)
{
rt_memcpy(oset, &thread->signal_mask, sizeof(lwp_sigset_t));
}
if (sigset)
{
switch (how)
{
case SIG_BLOCK:
sigorsets(&newset, &thread->signal_mask, sigset);
break;
case SIG_UNBLOCK:
sigandsets(&newset, &thread->signal_mask, sigset);
break;
case SIG_SETMASK:
newset = *sigset;
break;
default:
goto out;
}
lwp_sigdelset(&newset, SIGKILL);
lwp_sigdelset(&newset, SIGSTOP);
thread->signal_mask = newset;
}
out:
rt_hw_interrupt_enable(level);
return 0;
}
static void _do_signal_wakeup(rt_thread_t thread, int sig)
{
if ((thread->stat & RT_THREAD_SUSPEND_MASK) == RT_THREAD_SUSPEND_MASK)
{
int need_schedule = 1;
if ((thread->stat & RT_SIGNAL_COMMON_WAKEUP_MASK) != RT_SIGNAL_COMMON_WAKEUP_MASK)
{
rt_thread_wakeup(thread);
}
else if ((sig == SIGKILL) && ((thread->stat & RT_SIGNAL_KILL_WAKEUP_MASK) != RT_SIGNAL_KILL_WAKEUP_MASK))
{
rt_thread_wakeup(thread);
}
else
{
need_schedule = 0;
}
/* do schedule */
if (need_schedule)
{
rt_schedule();
}
}
}
int lwp_kill(pid_t pid, int sig)
{
rt_base_t level;
struct rt_lwp *lwp;
int ret = -1;
rt_thread_t thread;
if (sig < 0 || sig >= _LWP_NSIG)
{
rt_set_errno(EINVAL);
return ret;
}
level = rt_hw_interrupt_disable();
lwp = lwp_from_pid(pid);
if (!lwp || lwp->finish)
{
rt_set_errno(ESRCH);
goto out;
}
if (sig)
{
/* check main thread */
thread = rt_list_entry(lwp->t_grp.prev, struct rt_thread, sibling);
if (!lwp_sigismember(&lwp->signal_mask, sig)) /* if signal masked */
{
lwp_sigaddset(&lwp->signal, sig);
_do_signal_wakeup(thread, sig);
}
}
ret = 0;
out:
rt_hw_interrupt_enable(level);
return ret;
}
int lwp_thread_kill(rt_thread_t thread, int sig)
{
rt_base_t level;
int ret = -RT_EINVAL;
if (!thread)
{
rt_set_errno(ESRCH);
return ret;
}
if (sig < 0 || sig >= _LWP_NSIG)
{
rt_set_errno(EINVAL);
return ret;
}
level = rt_hw_interrupt_disable();
if (!thread->lwp)
{
rt_set_errno(EPERM);
goto out;
}
if (!lwp_sigismember(&thread->signal_mask, sig)) /* if signal masked */
{
lwp_sigaddset(&thread->signal, sig);
_do_signal_wakeup(thread, sig);
}
ret = 0;
out:
rt_hw_interrupt_enable(level);
return ret;
}