summaryrefslogtreecommitdiff
path: root/include/linux
diff options
context:
space:
mode:
authorAndrew Morton <akpm@osdl.org>2004-04-11 23:33:21 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2004-04-11 23:33:21 -0700
commit0e568881178ff0e0aceeafdb51f9fecab39e1923 (patch)
treef1ca13b88af17f86e57e973bc95df8e0e048277f /include/linux
parent2aa53d1884db24b1e7364487a23e5ed2b49b9b14 (diff)
[PATCH] fix posix-timers to have proper per-process scope
From: Roland McGrath <roland@redhat.com> The posix-timers implementation associates timers with the creating thread and destroys timers when their creator thread dies. POSIX clearly specifies that these timers are per-process, and a timer should not be torn down when the thread that created it exits. I hope there won't be any controversy on what the correct semantics are here, since POSIX is clear and the Linux feature is called "posix-timers". The attached program built with NPTL -lrt -lpthread demonstrates the bug. The program is correct by POSIX, but fails on Linux. Note that a until just the other day, NPTL had a trivial bug that always disabled its use of kernel timer syscalls (check strace for lack of timer_create/SYS_259). So unless you have built your own NPTL libs very recently, you probably won't see the kernel calls actually used by this program. Also attached is my patch to fix this. It (you guessed it) moves the posix_timers field from task_struct to signal_struct. Access is now governed by the siglock instead of the task lock. exit_itimers is called from __exit_signal, i.e. only on the death of the last thread in the group, rather than from do_exit for every thread. Timers' it_process fields store the group leader's pointer, which won't die. For the case of SIGEV_THREAD_ID, I hold a ref on the task_struct for it_process to stay robust in case the target thread dies; the ref is released and the dangling pointer cleared when the timer fires and the target thread is dead. (This should only come up in a buggy user program, so noone cares exactly how the kernel handles that case. But I think what I did is robust and sensical.) /* Test for bogus per-thread deletion of timers. */ #include <stdio.h> #include <error.h> #include <time.h> #include <signal.h> #include <stdint.h> #include <sys/time.h> #include <sys/resource.h> #include <unistd.h> #include <pthread.h> /* Creating timers in another thread should work too. */ static void *do_timer_create(void *arg) { struct sigevent *const sigev = arg; timer_t *const timerId = sigev->sigev_value.sival_ptr; if (timer_create(CLOCK_REALTIME, sigev, timerId) < 0) { perror("timer_create"); return NULL; } return timerId; } int main(void) { int i, res; timer_t timerId; struct itimerspec itval; struct sigevent sigev; itval.it_interval.tv_sec = 2; itval.it_interval.tv_nsec = 0; itval.it_value.tv_sec = 2; itval.it_value.tv_nsec = 0; sigev.sigev_notify = SIGEV_SIGNAL; sigev.sigev_signo = SIGALRM; sigev.sigev_value.sival_ptr = (void *)&timerId; for (i = 0; i < 100; i++) { printf("cnt = %d\n", i); pthread_t thr; res = pthread_create(&thr, NULL, &do_timer_create, &sigev); if (res) { error(0, res, "pthread_create"); continue; } void *val; res = pthread_join(thr, &val); if (res) { error(0, res, "pthread_join"); continue; } if (val == NULL) continue; res = timer_settime(timerId, 0, &itval, NULL); if (res < 0) perror("timer_settime"); res = timer_delete(timerId); if (res < 0) perror("timer_delete"); } return 0; }
Diffstat (limited to 'include/linux')
-rw-r--r--include/linux/init_task.h2
-rw-r--r--include/linux/sched.h6
2 files changed, 5 insertions, 3 deletions
diff --git a/include/linux/init_task.h b/include/linux/init_task.h
index 5c4843a08917..29189706ea57 100644
--- a/include/linux/init_task.h
+++ b/include/linux/init_task.h
@@ -49,6 +49,7 @@
.shared_pending = { \
.list = LIST_HEAD_INIT(sig.shared_pending.list), \
.signal = {{0}}}, \
+ .posix_timers = LIST_HEAD_INIT(sig.posix_timers), \
}
#define INIT_SIGHAND(sighand) { \
@@ -107,7 +108,6 @@ extern struct group_info init_groups;
.list = LIST_HEAD_INIT(tsk.pending.list), \
.signal = {{0}}}, \
.blocked = {{0}}, \
- .posix_timers = LIST_HEAD_INIT(tsk.posix_timers), \
.alloc_lock = SPIN_LOCK_UNLOCKED, \
.proc_lock = SPIN_LOCK_UNLOCKED, \
.switch_lock = SPIN_LOCK_UNLOCKED, \
diff --git a/include/linux/sched.h b/include/linux/sched.h
index b72c38420d71..17bbedd6bb3d 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -270,6 +270,9 @@ struct signal_struct {
/* thread group stop support, overloads group_exit_code too */
int group_stop_count;
+ /* POSIX.1b Interval Timers */
+ struct list_head posix_timers;
+
/* job control IDs */
pid_t pgrp;
pid_t tty_old_pgrp;
@@ -433,7 +436,6 @@ struct task_struct {
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_incr;
struct timer_list real_timer;
- struct list_head posix_timers; /* POSIX.1b Interval Timers */
unsigned long utime, stime, cutime, cstime;
unsigned long nvcsw, nivcsw, cnvcsw, cnivcsw; /* context switch counts */
u64 start_time;
@@ -728,7 +730,7 @@ extern void exit_signal(struct task_struct *);
extern void __exit_signal(struct task_struct *);
extern void exit_sighand(struct task_struct *);
extern void __exit_sighand(struct task_struct *);
-extern void exit_itimers(struct task_struct *);
+extern void exit_itimers(struct signal_struct *);
extern NORET_TYPE void do_group_exit(int);