mirror of
https://git.proxmox.com/git/mirror_ubuntu-kernels.git
synced 2025-11-07 09:54:19 +00:00
Queue posixtimers which have their signal ignored on the ignored list:
1) When the timer fires and the signal has SIG_IGN set
2) When SIG_IGN is installed via sigaction() and a timer signal
is already queued
This only happens when the signal is for a valid timer, which delivered the
signal in periodic mode. One-shot timer signals are correctly dropped.
Due to the lock order constraints (sighand::siglock nests inside
timer::lock) the signal code cannot access any of the timer fields which
are relevant to make this decision, e.g. timer::it_status.
This is addressed by establishing a protection scheme which requires to
lock both locks on the timer side for modifying decision fields in the
timer struct and therefore makes it possible for the signal delivery to
evaluate with only sighand:siglock being held:
1) Move the NULLification of timer->it_signal into the sighand::siglock
protected section of timer_delete() and check timer::it_signal in the
code path which determines whether the signal is dropped or queued on
the ignore list.
This ensures that a deleted timer cannot be moved onto the ignore
list, which would prevent it from being freed on exit() as it is not
longer in the process' posix timer list.
If the timer got moved to the ignored list before deletion then it is
removed from the ignored list under sighand lock in timer_delete().
2) Provide a new timer::it_sig_periodic flag, which gets set in the
signal queue path with both timer and sighand locks held if the timer
is actually in periodic mode at expiry time.
The ignore list code checks this flag under sighand::siglock and drops
the signal when it is not set.
If it is set, then the signal is moved to the ignored list independent
of the actual state of the timer.
When the signal is un-ignored later then the signal is moved back to
the signal queue. On signal delivery the posix timer side decides
about dropping the signal if the timer was re-armed, dis-armed or
deleted based on the signal sequence counter check.
If the thread/process exits then not yet delivered signals are
discarded which means the reference of the timer containing the
sigqueue is dropped and frees the timer.
This is way cheaper than requiring all code paths to lock
sighand::siglock of the target thread/process on any modification of
timer::it_status or going all the way and removing pending signals
from the signal queues on every rearm, disarm or delete operation.
So the protection scheme here is that on the timer side both timer::lock
and sighand::siglock have to be held for modifying
timer::it_signal
timer::it_sig_periodic
which means that on the signal side holding sighand::siglock is enough to
evaluate these fields.
In posixtimer_deliver_signal() holding timer::lock is sufficient to do the
sequence validation against timer::it_signal_seq because a concurrent
expiry is waiting on timer::lock to be released.
This completes the SIG_IGN handling and such timers are not longer self
rearmed which avoids pointless wakeups.
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Frederic Weisbecker <frederic@kernel.org>
Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://lore.kernel.org/all/20241105064214.120756416@linutronix.de
249 lines
7.1 KiB
C
249 lines
7.1 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
#ifndef _linux_POSIX_TIMERS_H
|
|
#define _linux_POSIX_TIMERS_H
|
|
|
|
#include <linux/alarmtimer.h>
|
|
#include <linux/list.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/pid.h>
|
|
#include <linux/posix-timers_types.h>
|
|
#include <linux/rcuref.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/timerqueue.h>
|
|
|
|
struct kernel_siginfo;
|
|
struct task_struct;
|
|
struct sigqueue;
|
|
struct k_itimer;
|
|
|
|
static inline clockid_t make_process_cpuclock(const unsigned int pid,
|
|
const clockid_t clock)
|
|
{
|
|
return ((~pid) << 3) | clock;
|
|
}
|
|
static inline clockid_t make_thread_cpuclock(const unsigned int tid,
|
|
const clockid_t clock)
|
|
{
|
|
return make_process_cpuclock(tid, clock | CPUCLOCK_PERTHREAD_MASK);
|
|
}
|
|
|
|
static inline clockid_t fd_to_clockid(const int fd)
|
|
{
|
|
return make_process_cpuclock((unsigned int) fd, CLOCKFD);
|
|
}
|
|
|
|
static inline int clockid_to_fd(const clockid_t clk)
|
|
{
|
|
return ~(clk >> 3);
|
|
}
|
|
|
|
#ifdef CONFIG_POSIX_TIMERS
|
|
|
|
#include <linux/signal_types.h>
|
|
|
|
/**
|
|
* cpu_timer - Posix CPU timer representation for k_itimer
|
|
* @node: timerqueue node to queue in the task/sig
|
|
* @head: timerqueue head on which this timer is queued
|
|
* @pid: Pointer to target task PID
|
|
* @elist: List head for the expiry list
|
|
* @firing: Timer is currently firing
|
|
* @nanosleep: Timer is used for nanosleep and is not a regular posix-timer
|
|
* @handling: Pointer to the task which handles expiry
|
|
*/
|
|
struct cpu_timer {
|
|
struct timerqueue_node node;
|
|
struct timerqueue_head *head;
|
|
struct pid *pid;
|
|
struct list_head elist;
|
|
bool firing;
|
|
bool nanosleep;
|
|
struct task_struct __rcu *handling;
|
|
};
|
|
|
|
static inline bool cpu_timer_enqueue(struct timerqueue_head *head,
|
|
struct cpu_timer *ctmr)
|
|
{
|
|
ctmr->head = head;
|
|
return timerqueue_add(head, &ctmr->node);
|
|
}
|
|
|
|
static inline bool cpu_timer_queued(struct cpu_timer *ctmr)
|
|
{
|
|
return !!ctmr->head;
|
|
}
|
|
|
|
static inline bool cpu_timer_dequeue(struct cpu_timer *ctmr)
|
|
{
|
|
if (cpu_timer_queued(ctmr)) {
|
|
timerqueue_del(ctmr->head, &ctmr->node);
|
|
ctmr->head = NULL;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static inline u64 cpu_timer_getexpires(struct cpu_timer *ctmr)
|
|
{
|
|
return ctmr->node.expires;
|
|
}
|
|
|
|
static inline void cpu_timer_setexpires(struct cpu_timer *ctmr, u64 exp)
|
|
{
|
|
ctmr->node.expires = exp;
|
|
}
|
|
|
|
static inline void posix_cputimers_init(struct posix_cputimers *pct)
|
|
{
|
|
memset(pct, 0, sizeof(*pct));
|
|
pct->bases[0].nextevt = U64_MAX;
|
|
pct->bases[1].nextevt = U64_MAX;
|
|
pct->bases[2].nextevt = U64_MAX;
|
|
}
|
|
|
|
void posix_cputimers_group_init(struct posix_cputimers *pct, u64 cpu_limit);
|
|
|
|
static inline void posix_cputimers_rt_watchdog(struct posix_cputimers *pct,
|
|
u64 runtime)
|
|
{
|
|
pct->bases[CPUCLOCK_SCHED].nextevt = runtime;
|
|
}
|
|
|
|
void posixtimer_rearm_itimer(struct task_struct *p);
|
|
bool posixtimer_init_sigqueue(struct sigqueue *q);
|
|
int posixtimer_send_sigqueue(struct k_itimer *tmr);
|
|
bool posixtimer_deliver_signal(struct kernel_siginfo *info, struct sigqueue *timer_sigq);
|
|
void posixtimer_free_timer(struct k_itimer *timer);
|
|
|
|
/* Init task static initializer */
|
|
#define INIT_CPU_TIMERBASE(b) { \
|
|
.nextevt = U64_MAX, \
|
|
}
|
|
|
|
#define INIT_CPU_TIMERBASES(b) { \
|
|
INIT_CPU_TIMERBASE(b[0]), \
|
|
INIT_CPU_TIMERBASE(b[1]), \
|
|
INIT_CPU_TIMERBASE(b[2]), \
|
|
}
|
|
|
|
#define INIT_CPU_TIMERS(s) \
|
|
.posix_cputimers = { \
|
|
.bases = INIT_CPU_TIMERBASES(s.posix_cputimers.bases), \
|
|
},
|
|
#else
|
|
struct cpu_timer { };
|
|
#define INIT_CPU_TIMERS(s)
|
|
static inline void posix_cputimers_init(struct posix_cputimers *pct) { }
|
|
static inline void posix_cputimers_group_init(struct posix_cputimers *pct,
|
|
u64 cpu_limit) { }
|
|
static inline void posixtimer_rearm_itimer(struct task_struct *p) { }
|
|
static inline bool posixtimer_deliver_signal(struct kernel_siginfo *info,
|
|
struct sigqueue *timer_sigq) { return false; }
|
|
static inline void posixtimer_free_timer(struct k_itimer *timer) { }
|
|
#endif
|
|
|
|
#ifdef CONFIG_POSIX_CPU_TIMERS_TASK_WORK
|
|
void clear_posix_cputimers_work(struct task_struct *p);
|
|
void posix_cputimers_init_work(void);
|
|
#else
|
|
static inline void clear_posix_cputimers_work(struct task_struct *p) { }
|
|
static inline void posix_cputimers_init_work(void) { }
|
|
#endif
|
|
|
|
/**
|
|
* struct k_itimer - POSIX.1b interval timer structure.
|
|
* @list: List node for binding the timer to tsk::signal::posix_timers
|
|
* @ignored_list: List node for tracking ignored timers in tsk::signal::ignored_posix_timers
|
|
* @t_hash: Entry in the posix timer hash table
|
|
* @it_lock: Lock protecting the timer
|
|
* @kclock: Pointer to the k_clock struct handling this timer
|
|
* @it_clock: The posix timer clock id
|
|
* @it_id: The posix timer id for identifying the timer
|
|
* @it_status: The status of the timer
|
|
* @it_sig_periodic: The periodic status at signal delivery
|
|
* @it_overrun: The overrun counter for pending signals
|
|
* @it_overrun_last: The overrun at the time of the last delivered signal
|
|
* @it_signal_seq: Sequence count to control signal delivery
|
|
* @it_sigqueue_seq: The sequence count at the point where the signal was queued
|
|
* @it_sigev_notify: The notify word of sigevent struct for signal delivery
|
|
* @it_interval: The interval for periodic timers
|
|
* @it_signal: Pointer to the creators signal struct
|
|
* @it_pid: The pid of the process/task targeted by the signal
|
|
* @it_process: The task to wakeup on clock_nanosleep (CPU timers)
|
|
* @rcuref: Reference count for life time management
|
|
* @sigq: Embedded sigqueue
|
|
* @it: Union representing the various posix timer type
|
|
* internals.
|
|
* @rcu: RCU head for freeing the timer.
|
|
*/
|
|
struct k_itimer {
|
|
struct hlist_node list;
|
|
struct hlist_node ignored_list;
|
|
struct hlist_node t_hash;
|
|
spinlock_t it_lock;
|
|
const struct k_clock *kclock;
|
|
clockid_t it_clock;
|
|
timer_t it_id;
|
|
int it_status;
|
|
bool it_sig_periodic;
|
|
s64 it_overrun;
|
|
s64 it_overrun_last;
|
|
unsigned int it_signal_seq;
|
|
unsigned int it_sigqueue_seq;
|
|
int it_sigev_notify;
|
|
enum pid_type it_pid_type;
|
|
ktime_t it_interval;
|
|
struct signal_struct *it_signal;
|
|
union {
|
|
struct pid *it_pid;
|
|
struct task_struct *it_process;
|
|
};
|
|
struct sigqueue sigq;
|
|
rcuref_t rcuref;
|
|
union {
|
|
struct {
|
|
struct hrtimer timer;
|
|
} real;
|
|
struct cpu_timer cpu;
|
|
struct {
|
|
struct alarm alarmtimer;
|
|
} alarm;
|
|
} it;
|
|
struct rcu_head rcu;
|
|
};
|
|
|
|
void run_posix_cpu_timers(void);
|
|
void posix_cpu_timers_exit(struct task_struct *task);
|
|
void posix_cpu_timers_exit_group(struct task_struct *task);
|
|
void set_process_cpu_timer(struct task_struct *task, unsigned int clock_idx,
|
|
u64 *newval, u64 *oldval);
|
|
|
|
int update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new);
|
|
|
|
#ifdef CONFIG_POSIX_TIMERS
|
|
static inline void posixtimer_putref(struct k_itimer *tmr)
|
|
{
|
|
if (rcuref_put(&tmr->rcuref))
|
|
posixtimer_free_timer(tmr);
|
|
}
|
|
|
|
static inline void posixtimer_sigqueue_getref(struct sigqueue *q)
|
|
{
|
|
struct k_itimer *tmr = container_of(q, struct k_itimer, sigq);
|
|
|
|
WARN_ON_ONCE(!rcuref_get(&tmr->rcuref));
|
|
}
|
|
|
|
static inline void posixtimer_sigqueue_putref(struct sigqueue *q)
|
|
{
|
|
struct k_itimer *tmr = container_of(q, struct k_itimer, sigq);
|
|
|
|
posixtimer_putref(tmr);
|
|
}
|
|
#else /* CONFIG_POSIX_TIMERS */
|
|
static inline void posixtimer_sigqueue_getref(struct sigqueue *q) { }
|
|
static inline void posixtimer_sigqueue_putref(struct sigqueue *q) { }
|
|
#endif /* !CONFIG_POSIX_TIMERS */
|
|
|
|
#endif
|