607 lines
13 KiB
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;
|
|
}
|