diff options
Diffstat (limited to 'kernel/cgroup_freezer.c')
| -rw-r--r-- | kernel/cgroup_freezer.c | 138 |
1 files changed, 59 insertions, 79 deletions
diff --git a/kernel/cgroup_freezer.c b/kernel/cgroup_freezer.c index 2bc4a2256444..a79e40f9d700 100644 --- a/kernel/cgroup_freezer.c +++ b/kernel/cgroup_freezer.c | |||
| @@ -21,6 +21,7 @@ | |||
| 21 | #include <linux/uaccess.h> | 21 | #include <linux/uaccess.h> |
| 22 | #include <linux/freezer.h> | 22 | #include <linux/freezer.h> |
| 23 | #include <linux/seq_file.h> | 23 | #include <linux/seq_file.h> |
| 24 | #include <linux/mutex.h> | ||
| 24 | 25 | ||
| 25 | /* | 26 | /* |
| 26 | * A cgroup is freezing if any FREEZING flags are set. FREEZING_SELF is | 27 | * A cgroup is freezing if any FREEZING flags are set. FREEZING_SELF is |
| @@ -42,9 +43,10 @@ enum freezer_state_flags { | |||
| 42 | struct freezer { | 43 | struct freezer { |
| 43 | struct cgroup_subsys_state css; | 44 | struct cgroup_subsys_state css; |
| 44 | unsigned int state; | 45 | unsigned int state; |
| 45 | spinlock_t lock; | ||
| 46 | }; | 46 | }; |
| 47 | 47 | ||
| 48 | static DEFINE_MUTEX(freezer_mutex); | ||
| 49 | |||
| 48 | static inline struct freezer *css_freezer(struct cgroup_subsys_state *css) | 50 | static inline struct freezer *css_freezer(struct cgroup_subsys_state *css) |
| 49 | { | 51 | { |
| 50 | return css ? container_of(css, struct freezer, css) : NULL; | 52 | return css ? container_of(css, struct freezer, css) : NULL; |
| @@ -57,7 +59,7 @@ static inline struct freezer *task_freezer(struct task_struct *task) | |||
| 57 | 59 | ||
| 58 | static struct freezer *parent_freezer(struct freezer *freezer) | 60 | static struct freezer *parent_freezer(struct freezer *freezer) |
| 59 | { | 61 | { |
| 60 | return css_freezer(css_parent(&freezer->css)); | 62 | return css_freezer(freezer->css.parent); |
| 61 | } | 63 | } |
| 62 | 64 | ||
| 63 | bool cgroup_freezing(struct task_struct *task) | 65 | bool cgroup_freezing(struct task_struct *task) |
| @@ -71,10 +73,6 @@ bool cgroup_freezing(struct task_struct *task) | |||
| 71 | return ret; | 73 | return ret; |
| 72 | } | 74 | } |
| 73 | 75 | ||
| 74 | /* | ||
| 75 | * cgroups_write_string() limits the size of freezer state strings to | ||
| 76 | * CGROUP_LOCAL_BUFFER_SIZE | ||
| 77 | */ | ||
| 78 | static const char *freezer_state_strs(unsigned int state) | 76 | static const char *freezer_state_strs(unsigned int state) |
| 79 | { | 77 | { |
| 80 | if (state & CGROUP_FROZEN) | 78 | if (state & CGROUP_FROZEN) |
| @@ -93,7 +91,6 @@ freezer_css_alloc(struct cgroup_subsys_state *parent_css) | |||
| 93 | if (!freezer) | 91 | if (!freezer) |
| 94 | return ERR_PTR(-ENOMEM); | 92 | return ERR_PTR(-ENOMEM); |
| 95 | 93 | ||
| 96 | spin_lock_init(&freezer->lock); | ||
| 97 | return &freezer->css; | 94 | return &freezer->css; |
| 98 | } | 95 | } |
| 99 | 96 | ||
| @@ -110,14 +107,7 @@ static int freezer_css_online(struct cgroup_subsys_state *css) | |||
| 110 | struct freezer *freezer = css_freezer(css); | 107 | struct freezer *freezer = css_freezer(css); |
| 111 | struct freezer *parent = parent_freezer(freezer); | 108 | struct freezer *parent = parent_freezer(freezer); |
| 112 | 109 | ||
| 113 | /* | 110 | mutex_lock(&freezer_mutex); |
| 114 | * The following double locking and freezing state inheritance | ||
| 115 | * guarantee that @cgroup can never escape ancestors' freezing | ||
| 116 | * states. See css_for_each_descendant_pre() for details. | ||
| 117 | */ | ||
| 118 | if (parent) | ||
| 119 | spin_lock_irq(&parent->lock); | ||
| 120 | spin_lock_nested(&freezer->lock, SINGLE_DEPTH_NESTING); | ||
| 121 | 111 | ||
| 122 | freezer->state |= CGROUP_FREEZER_ONLINE; | 112 | freezer->state |= CGROUP_FREEZER_ONLINE; |
| 123 | 113 | ||
| @@ -126,10 +116,7 @@ static int freezer_css_online(struct cgroup_subsys_state *css) | |||
| 126 | atomic_inc(&system_freezing_cnt); | 116 | atomic_inc(&system_freezing_cnt); |
| 127 | } | 117 | } |
| 128 | 118 | ||
| 129 | spin_unlock(&freezer->lock); | 119 | mutex_unlock(&freezer_mutex); |
| 130 | if (parent) | ||
| 131 | spin_unlock_irq(&parent->lock); | ||
| 132 | |||
| 133 | return 0; | 120 | return 0; |
| 134 | } | 121 | } |
| 135 | 122 | ||
| @@ -144,14 +131,14 @@ static void freezer_css_offline(struct cgroup_subsys_state *css) | |||
| 144 | { | 131 | { |
| 145 | struct freezer *freezer = css_freezer(css); | 132 | struct freezer *freezer = css_freezer(css); |
| 146 | 133 | ||
| 147 | spin_lock_irq(&freezer->lock); | 134 | mutex_lock(&freezer_mutex); |
| 148 | 135 | ||
| 149 | if (freezer->state & CGROUP_FREEZING) | 136 | if (freezer->state & CGROUP_FREEZING) |
| 150 | atomic_dec(&system_freezing_cnt); | 137 | atomic_dec(&system_freezing_cnt); |
| 151 | 138 | ||
| 152 | freezer->state = 0; | 139 | freezer->state = 0; |
| 153 | 140 | ||
| 154 | spin_unlock_irq(&freezer->lock); | 141 | mutex_unlock(&freezer_mutex); |
| 155 | } | 142 | } |
| 156 | 143 | ||
| 157 | static void freezer_css_free(struct cgroup_subsys_state *css) | 144 | static void freezer_css_free(struct cgroup_subsys_state *css) |
| @@ -175,7 +162,7 @@ static void freezer_attach(struct cgroup_subsys_state *new_css, | |||
| 175 | struct task_struct *task; | 162 | struct task_struct *task; |
| 176 | bool clear_frozen = false; | 163 | bool clear_frozen = false; |
| 177 | 164 | ||
| 178 | spin_lock_irq(&freezer->lock); | 165 | mutex_lock(&freezer_mutex); |
| 179 | 166 | ||
| 180 | /* | 167 | /* |
| 181 | * Make the new tasks conform to the current state of @new_css. | 168 | * Make the new tasks conform to the current state of @new_css. |
| @@ -197,21 +184,13 @@ static void freezer_attach(struct cgroup_subsys_state *new_css, | |||
| 197 | } | 184 | } |
| 198 | } | 185 | } |
| 199 | 186 | ||
| 200 | spin_unlock_irq(&freezer->lock); | 187 | /* propagate FROZEN clearing upwards */ |
| 201 | |||
| 202 | /* | ||
| 203 | * Propagate FROZEN clearing upwards. We may race with | ||
| 204 | * update_if_frozen(), but as long as both work bottom-up, either | ||
| 205 | * update_if_frozen() sees child's FROZEN cleared or we clear the | ||
| 206 | * parent's FROZEN later. No parent w/ !FROZEN children can be | ||
| 207 | * left FROZEN. | ||
| 208 | */ | ||
| 209 | while (clear_frozen && (freezer = parent_freezer(freezer))) { | 188 | while (clear_frozen && (freezer = parent_freezer(freezer))) { |
| 210 | spin_lock_irq(&freezer->lock); | ||
| 211 | freezer->state &= ~CGROUP_FROZEN; | 189 | freezer->state &= ~CGROUP_FROZEN; |
| 212 | clear_frozen = freezer->state & CGROUP_FREEZING; | 190 | clear_frozen = freezer->state & CGROUP_FREEZING; |
| 213 | spin_unlock_irq(&freezer->lock); | ||
| 214 | } | 191 | } |
| 192 | |||
| 193 | mutex_unlock(&freezer_mutex); | ||
| 215 | } | 194 | } |
| 216 | 195 | ||
| 217 | /** | 196 | /** |
| @@ -228,9 +207,6 @@ static void freezer_fork(struct task_struct *task) | |||
| 228 | { | 207 | { |
| 229 | struct freezer *freezer; | 208 | struct freezer *freezer; |
| 230 | 209 | ||
| 231 | rcu_read_lock(); | ||
| 232 | freezer = task_freezer(task); | ||
| 233 | |||
| 234 | /* | 210 | /* |
| 235 | * The root cgroup is non-freezable, so we can skip locking the | 211 | * The root cgroup is non-freezable, so we can skip locking the |
| 236 | * freezer. This is safe regardless of race with task migration. | 212 | * freezer. This is safe regardless of race with task migration. |
| @@ -238,24 +214,18 @@ static void freezer_fork(struct task_struct *task) | |||
| 238 | * to do. If we lost and root is the new cgroup, noop is still the | 214 | * to do. If we lost and root is the new cgroup, noop is still the |
| 239 | * right thing to do. | 215 | * right thing to do. |
| 240 | */ | 216 | */ |
| 241 | if (!parent_freezer(freezer)) | 217 | if (task_css_is_root(task, freezer_cgrp_id)) |
| 242 | goto out; | 218 | return; |
| 243 | 219 | ||
| 244 | /* | 220 | mutex_lock(&freezer_mutex); |
| 245 | * Grab @freezer->lock and freeze @task after verifying @task still | 221 | rcu_read_lock(); |
| 246 | * belongs to @freezer and it's freezing. The former is for the | 222 | |
| 247 | * case where we have raced against task migration and lost and | 223 | freezer = task_freezer(task); |
| 248 | * @task is already in a different cgroup which may not be frozen. | 224 | if (freezer->state & CGROUP_FREEZING) |
| 249 | * This isn't strictly necessary as freeze_task() is allowed to be | ||
| 250 | * called spuriously but let's do it anyway for, if nothing else, | ||
| 251 | * documentation. | ||
| 252 | */ | ||
| 253 | spin_lock_irq(&freezer->lock); | ||
| 254 | if (freezer == task_freezer(task) && (freezer->state & CGROUP_FREEZING)) | ||
| 255 | freeze_task(task); | 225 | freeze_task(task); |
| 256 | spin_unlock_irq(&freezer->lock); | 226 | |
| 257 | out: | ||
| 258 | rcu_read_unlock(); | 227 | rcu_read_unlock(); |
| 228 | mutex_unlock(&freezer_mutex); | ||
| 259 | } | 229 | } |
| 260 | 230 | ||
| 261 | /** | 231 | /** |
| @@ -281,22 +251,24 @@ static void update_if_frozen(struct cgroup_subsys_state *css) | |||
| 281 | struct css_task_iter it; | 251 | struct css_task_iter it; |
| 282 | struct task_struct *task; | 252 | struct task_struct *task; |
| 283 | 253 | ||
| 284 | WARN_ON_ONCE(!rcu_read_lock_held()); | 254 | lockdep_assert_held(&freezer_mutex); |
| 285 | |||
| 286 | spin_lock_irq(&freezer->lock); | ||
| 287 | 255 | ||
| 288 | if (!(freezer->state & CGROUP_FREEZING) || | 256 | if (!(freezer->state & CGROUP_FREEZING) || |
| 289 | (freezer->state & CGROUP_FROZEN)) | 257 | (freezer->state & CGROUP_FROZEN)) |
| 290 | goto out_unlock; | 258 | return; |
| 291 | 259 | ||
| 292 | /* are all (live) children frozen? */ | 260 | /* are all (live) children frozen? */ |
| 261 | rcu_read_lock(); | ||
| 293 | css_for_each_child(pos, css) { | 262 | css_for_each_child(pos, css) { |
| 294 | struct freezer *child = css_freezer(pos); | 263 | struct freezer *child = css_freezer(pos); |
| 295 | 264 | ||
| 296 | if ((child->state & CGROUP_FREEZER_ONLINE) && | 265 | if ((child->state & CGROUP_FREEZER_ONLINE) && |
| 297 | !(child->state & CGROUP_FROZEN)) | 266 | !(child->state & CGROUP_FROZEN)) { |
| 298 | goto out_unlock; | 267 | rcu_read_unlock(); |
| 268 | return; | ||
| 269 | } | ||
| 299 | } | 270 | } |
| 271 | rcu_read_unlock(); | ||
| 300 | 272 | ||
| 301 | /* are all tasks frozen? */ | 273 | /* are all tasks frozen? */ |
| 302 | css_task_iter_start(css, &it); | 274 | css_task_iter_start(css, &it); |
| @@ -317,21 +289,29 @@ static void update_if_frozen(struct cgroup_subsys_state *css) | |||
| 317 | freezer->state |= CGROUP_FROZEN; | 289 | freezer->state |= CGROUP_FROZEN; |
| 318 | out_iter_end: | 290 | out_iter_end: |
| 319 | css_task_iter_end(&it); | 291 | css_task_iter_end(&it); |
| 320 | out_unlock: | ||
| 321 | spin_unlock_irq(&freezer->lock); | ||
| 322 | } | 292 | } |
| 323 | 293 | ||
| 324 | static int freezer_read(struct seq_file *m, void *v) | 294 | static int freezer_read(struct seq_file *m, void *v) |
| 325 | { | 295 | { |
| 326 | struct cgroup_subsys_state *css = seq_css(m), *pos; | 296 | struct cgroup_subsys_state *css = seq_css(m), *pos; |
| 327 | 297 | ||
| 298 | mutex_lock(&freezer_mutex); | ||
| 328 | rcu_read_lock(); | 299 | rcu_read_lock(); |
| 329 | 300 | ||
| 330 | /* update states bottom-up */ | 301 | /* update states bottom-up */ |
| 331 | css_for_each_descendant_post(pos, css) | 302 | css_for_each_descendant_post(pos, css) { |
| 303 | if (!css_tryget_online(pos)) | ||
| 304 | continue; | ||
| 305 | rcu_read_unlock(); | ||
| 306 | |||
| 332 | update_if_frozen(pos); | 307 | update_if_frozen(pos); |
| 333 | 308 | ||
| 309 | rcu_read_lock(); | ||
| 310 | css_put(pos); | ||
| 311 | } | ||
| 312 | |||
| 334 | rcu_read_unlock(); | 313 | rcu_read_unlock(); |
| 314 | mutex_unlock(&freezer_mutex); | ||
| 335 | 315 | ||
| 336 | seq_puts(m, freezer_state_strs(css_freezer(css)->state)); | 316 | seq_puts(m, freezer_state_strs(css_freezer(css)->state)); |
| 337 | seq_putc(m, '\n'); | 317 | seq_putc(m, '\n'); |
| @@ -373,7 +353,7 @@ static void freezer_apply_state(struct freezer *freezer, bool freeze, | |||
| 373 | unsigned int state) | 353 | unsigned int state) |
| 374 | { | 354 | { |
| 375 | /* also synchronizes against task migration, see freezer_attach() */ | 355 | /* also synchronizes against task migration, see freezer_attach() */ |
| 376 | lockdep_assert_held(&freezer->lock); | 356 | lockdep_assert_held(&freezer_mutex); |
| 377 | 357 | ||
| 378 | if (!(freezer->state & CGROUP_FREEZER_ONLINE)) | 358 | if (!(freezer->state & CGROUP_FREEZER_ONLINE)) |
| 379 | return; | 359 | return; |
| @@ -414,47 +394,47 @@ static void freezer_change_state(struct freezer *freezer, bool freeze) | |||
| 414 | * descendant will try to inherit its parent's FREEZING state as | 394 | * descendant will try to inherit its parent's FREEZING state as |
| 415 | * CGROUP_FREEZING_PARENT. | 395 | * CGROUP_FREEZING_PARENT. |
| 416 | */ | 396 | */ |
| 397 | mutex_lock(&freezer_mutex); | ||
| 417 | rcu_read_lock(); | 398 | rcu_read_lock(); |
| 418 | css_for_each_descendant_pre(pos, &freezer->css) { | 399 | css_for_each_descendant_pre(pos, &freezer->css) { |
| 419 | struct freezer *pos_f = css_freezer(pos); | 400 | struct freezer *pos_f = css_freezer(pos); |
| 420 | struct freezer *parent = parent_freezer(pos_f); | 401 | struct freezer *parent = parent_freezer(pos_f); |
| 421 | 402 | ||
| 422 | spin_lock_irq(&pos_f->lock); | 403 | if (!css_tryget_online(pos)) |
| 404 | continue; | ||
| 405 | rcu_read_unlock(); | ||
| 423 | 406 | ||
| 424 | if (pos_f == freezer) { | 407 | if (pos_f == freezer) |
| 425 | freezer_apply_state(pos_f, freeze, | 408 | freezer_apply_state(pos_f, freeze, |
| 426 | CGROUP_FREEZING_SELF); | 409 | CGROUP_FREEZING_SELF); |
| 427 | } else { | 410 | else |
| 428 | /* | ||
| 429 | * Our update to @parent->state is already visible | ||
| 430 | * which is all we need. No need to lock @parent. | ||
| 431 | * For more info on synchronization, see | ||
| 432 | * freezer_post_create(). | ||
| 433 | */ | ||
| 434 | freezer_apply_state(pos_f, | 411 | freezer_apply_state(pos_f, |
| 435 | parent->state & CGROUP_FREEZING, | 412 | parent->state & CGROUP_FREEZING, |
| 436 | CGROUP_FREEZING_PARENT); | 413 | CGROUP_FREEZING_PARENT); |
| 437 | } | ||
| 438 | 414 | ||
| 439 | spin_unlock_irq(&pos_f->lock); | 415 | rcu_read_lock(); |
| 416 | css_put(pos); | ||
| 440 | } | 417 | } |
| 441 | rcu_read_unlock(); | 418 | rcu_read_unlock(); |
| 419 | mutex_unlock(&freezer_mutex); | ||
| 442 | } | 420 | } |
| 443 | 421 | ||
| 444 | static int freezer_write(struct cgroup_subsys_state *css, struct cftype *cft, | 422 | static ssize_t freezer_write(struct kernfs_open_file *of, |
| 445 | char *buffer) | 423 | char *buf, size_t nbytes, loff_t off) |
| 446 | { | 424 | { |
| 447 | bool freeze; | 425 | bool freeze; |
| 448 | 426 | ||
| 449 | if (strcmp(buffer, freezer_state_strs(0)) == 0) | 427 | buf = strstrip(buf); |
| 428 | |||
| 429 | if (strcmp(buf, freezer_state_strs(0)) == 0) | ||
| 450 | freeze = false; | 430 | freeze = false; |
| 451 | else if (strcmp(buffer, freezer_state_strs(CGROUP_FROZEN)) == 0) | 431 | else if (strcmp(buf, freezer_state_strs(CGROUP_FROZEN)) == 0) |
| 452 | freeze = true; | 432 | freeze = true; |
| 453 | else | 433 | else |
| 454 | return -EINVAL; | 434 | return -EINVAL; |
| 455 | 435 | ||
| 456 | freezer_change_state(css_freezer(css), freeze); | 436 | freezer_change_state(css_freezer(of_css(of)), freeze); |
| 457 | return 0; | 437 | return nbytes; |
| 458 | } | 438 | } |
| 459 | 439 | ||
| 460 | static u64 freezer_self_freezing_read(struct cgroup_subsys_state *css, | 440 | static u64 freezer_self_freezing_read(struct cgroup_subsys_state *css, |
| @@ -478,7 +458,7 @@ static struct cftype files[] = { | |||
| 478 | .name = "state", | 458 | .name = "state", |
| 479 | .flags = CFTYPE_NOT_ON_ROOT, | 459 | .flags = CFTYPE_NOT_ON_ROOT, |
| 480 | .seq_show = freezer_read, | 460 | .seq_show = freezer_read, |
| 481 | .write_string = freezer_write, | 461 | .write = freezer_write, |
| 482 | }, | 462 | }, |
| 483 | { | 463 | { |
| 484 | .name = "self_freezing", | 464 | .name = "self_freezing", |
