[PATCH] task delay accounting fixes

Cleanup allocation and freeing of tsk->delays used by delay accounting.
This solves two problems reported for delay accounting:

1. oops in __delayacct_blkio_ticks
http://www.uwsg.indiana.edu/hypermail/linux/kernel/0608.2/1844.html

Currently tsk->delays is getting freed too early in task exit which can
cause a NULL tsk->delays to get accessed via reading of /proc/<tgid>/stats.
The patch fixes this problem by freeing tsk->delays closer to when
task_struct itself is freed up. As a result, it also eliminates the use of
tsk->delays_lock which was only being used (inadequately) to safeguard
access to tsk->delays while a task was exiting.

2. Possible memory leak in kernel/delayacct.c
http://www.uwsg.indiana.edu/hypermail/linux/kernel/0608.2/1389.html

The patch cleans up tsk->delays allocations after a bad fork which was
missing earlier.

The patch has been tested to fix the problems listed above and stress
tested with rapid calls to delay accounting's taskstats command interface
(which is the other path that can access the same data, besides the /proc
interface causing the oops above).

Signed-off-by: Shailabh Nagar <nagar@watson.ibm.com>
Cc: Balbir Singh <balbir@in.ibm.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>

authored by Shailabh Nagar and committed by Linus Torvalds 35df17c5 30f3174d

+11 -23
+7 -3
include/linux/delayacct.h
··· 59 59 __delayacct_tsk_init(tsk); 60 60 } 61 61 62 - static inline void delayacct_tsk_exit(struct task_struct *tsk) 62 + /* Free tsk->delays. Called from bad fork and __put_task_struct 63 + * where there's no risk of tsk->delays being accessed elsewhere 64 + */ 65 + static inline void delayacct_tsk_free(struct task_struct *tsk) 63 66 { 64 67 if (tsk->delays) 65 - __delayacct_tsk_exit(tsk); 68 + kmem_cache_free(delayacct_cache, tsk->delays); 69 + tsk->delays = NULL; 66 70 } 67 71 68 72 static inline void delayacct_blkio_start(void) ··· 105 101 {} 106 102 static inline void delayacct_tsk_init(struct task_struct *tsk) 107 103 {} 108 - static inline void delayacct_tsk_exit(struct task_struct *tsk) 104 + static inline void delayacct_tsk_free(struct task_struct *tsk) 109 105 {} 110 106 static inline void delayacct_blkio_start(void) 111 107 {}
-1
include/linux/sched.h
··· 994 994 */ 995 995 struct pipe_inode_info *splice_pipe; 996 996 #ifdef CONFIG_TASK_DELAY_ACCT 997 - spinlock_t delays_lock; 998 997 struct task_delay_info *delays; 999 998 #endif 1000 999 };
-16
kernel/delayacct.c
··· 41 41 42 42 void __delayacct_tsk_init(struct task_struct *tsk) 43 43 { 44 - spin_lock_init(&tsk->delays_lock); 45 - /* No need to acquire tsk->delays_lock for allocation here unless 46 - __delayacct_tsk_init called after tsk is attached to tasklist 47 - */ 48 44 tsk->delays = kmem_cache_zalloc(delayacct_cache, SLAB_KERNEL); 49 45 if (tsk->delays) 50 46 spin_lock_init(&tsk->delays->lock); 51 - } 52 - 53 - void __delayacct_tsk_exit(struct task_struct *tsk) 54 - { 55 - struct task_delay_info *delays = tsk->delays; 56 - spin_lock(&tsk->delays_lock); 57 - tsk->delays = NULL; 58 - spin_unlock(&tsk->delays_lock); 59 - kmem_cache_free(delayacct_cache, delays); 60 47 } 61 48 62 49 /* ··· 105 118 struct timespec ts; 106 119 unsigned long t1,t2,t3; 107 120 108 - spin_lock(&tsk->delays_lock); 109 - 110 121 /* Though tsk->delays accessed later, early exit avoids 111 122 * unnecessary returning of other data 112 123 */ ··· 146 161 spin_unlock(&tsk->delays->lock); 147 162 148 163 done: 149 - spin_unlock(&tsk->delays_lock); 150 164 return 0; 151 165 } 152 166
-1
kernel/exit.c
··· 908 908 audit_free(tsk); 909 909 taskstats_exit_send(tsk, tidstats, group_dead, mycpu); 910 910 taskstats_exit_free(tidstats); 911 - delayacct_tsk_exit(tsk); 912 911 913 912 exit_mm(tsk); 914 913
+4 -2
kernel/fork.c
··· 117 117 security_task_free(tsk); 118 118 free_uid(tsk->user); 119 119 put_group_info(tsk->group_info); 120 + delayacct_tsk_free(tsk); 120 121 121 122 if (!profile_handoff_task(tsk)) 122 123 free_task(tsk); ··· 1012 1011 retval = -EFAULT; 1013 1012 if (clone_flags & CLONE_PARENT_SETTID) 1014 1013 if (put_user(p->pid, parent_tidptr)) 1015 - goto bad_fork_cleanup; 1014 + goto bad_fork_cleanup_delays_binfmt; 1016 1015 1017 1016 INIT_LIST_HEAD(&p->children); 1018 1017 INIT_LIST_HEAD(&p->sibling); ··· 1278 1277 bad_fork_cleanup_cpuset: 1279 1278 #endif 1280 1279 cpuset_exit(p); 1281 - bad_fork_cleanup: 1280 + bad_fork_cleanup_delays_binfmt: 1281 + delayacct_tsk_free(p); 1282 1282 if (p->binfmt) 1283 1283 module_put(p->binfmt->module); 1284 1284 bad_fork_cleanup_put_domain: