diff options
| author | Jianpeng Ma <majianpeng@gmail.com> | 2013-07-03 07:25:24 -0400 |
|---|---|---|
| committer | Jens Axboe <axboe@kernel.dk> | 2013-07-03 07:25:24 -0400 |
| commit | d50235b7bc3ee0a0427984d763ea7534149531b4 (patch) | |
| tree | acf1916e7926c1a0dddbe08db11ca2426a3816cc | |
| parent | a6b3f7614ca690e49e934c291f707b0c19312194 (diff) | |
elevator: Fix a race in elevator switching
There's a race between elevator switching and normal io operation.
Because the allocation of struct elevator_queue and struct elevator_data
don't in a atomic operation.So there are have chance to use NULL
->elevator_data.
For example:
Thread A: Thread B
blk_queu_bio elevator_switch
spin_lock_irq(q->queue_block) elevator_alloc
elv_merge elevator_init_fn
Because call elevator_alloc, it can't hold queue_lock and the
->elevator_data is NULL.So at the same time, threadA call elv_merge and
nedd some info of elevator_data.So the crash happened.
Move the elevator_alloc into func elevator_init_fn, it make the
operations in a atomic operation.
Using the follow method can easy reproduce this bug
1:dd if=/dev/sdb of=/dev/null
2:while true;do echo noop > scheduler;echo deadline > scheduler;done
The test method also use this method.
Signed-off-by: Jianpeng Ma <majianpeng@gmail.com>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
| -rw-r--r-- | block/cfq-iosched.c | 17 | ||||
| -rw-r--r-- | block/deadline-iosched.c | 16 | ||||
| -rw-r--r-- | block/elevator.c | 25 | ||||
| -rw-r--r-- | block/noop-iosched.c | 17 | ||||
| -rw-r--r-- | include/linux/elevator.h | 6 |
5 files changed, 51 insertions, 30 deletions
diff --git a/block/cfq-iosched.c b/block/cfq-iosched.c index d5cd3131c57a..d5bbdcfd0dab 100644 --- a/block/cfq-iosched.c +++ b/block/cfq-iosched.c | |||
| @@ -4347,18 +4347,28 @@ static void cfq_exit_queue(struct elevator_queue *e) | |||
| 4347 | kfree(cfqd); | 4347 | kfree(cfqd); |
| 4348 | } | 4348 | } |
| 4349 | 4349 | ||
| 4350 | static int cfq_init_queue(struct request_queue *q) | 4350 | static int cfq_init_queue(struct request_queue *q, struct elevator_type *e) |
| 4351 | { | 4351 | { |
| 4352 | struct cfq_data *cfqd; | 4352 | struct cfq_data *cfqd; |
| 4353 | struct blkcg_gq *blkg __maybe_unused; | 4353 | struct blkcg_gq *blkg __maybe_unused; |
| 4354 | int i, ret; | 4354 | int i, ret; |
| 4355 | struct elevator_queue *eq; | ||
| 4356 | |||
| 4357 | eq = elevator_alloc(q, e); | ||
| 4358 | if (!eq) | ||
| 4359 | return -ENOMEM; | ||
| 4355 | 4360 | ||
| 4356 | cfqd = kmalloc_node(sizeof(*cfqd), GFP_KERNEL | __GFP_ZERO, q->node); | 4361 | cfqd = kmalloc_node(sizeof(*cfqd), GFP_KERNEL | __GFP_ZERO, q->node); |
| 4357 | if (!cfqd) | 4362 | if (!cfqd) { |
| 4363 | kobject_put(&eq->kobj); | ||
| 4358 | return -ENOMEM; | 4364 | return -ENOMEM; |
| 4365 | } | ||
| 4366 | eq->elevator_data = cfqd; | ||
| 4359 | 4367 | ||
| 4360 | cfqd->queue = q; | 4368 | cfqd->queue = q; |
| 4361 | q->elevator->elevator_data = cfqd; | 4369 | spin_lock_irq(q->queue_lock); |
| 4370 | q->elevator = eq; | ||
| 4371 | spin_unlock_irq(q->queue_lock); | ||
| 4362 | 4372 | ||
| 4363 | /* Init root service tree */ | 4373 | /* Init root service tree */ |
| 4364 | cfqd->grp_service_tree = CFQ_RB_ROOT; | 4374 | cfqd->grp_service_tree = CFQ_RB_ROOT; |
| @@ -4433,6 +4443,7 @@ static int cfq_init_queue(struct request_queue *q) | |||
| 4433 | 4443 | ||
| 4434 | out_free: | 4444 | out_free: |
| 4435 | kfree(cfqd); | 4445 | kfree(cfqd); |
| 4446 | kobject_put(&eq->kobj); | ||
| 4436 | return ret; | 4447 | return ret; |
| 4437 | } | 4448 | } |
| 4438 | 4449 | ||
diff --git a/block/deadline-iosched.c b/block/deadline-iosched.c index ba19a3afab79..20614a332362 100644 --- a/block/deadline-iosched.c +++ b/block/deadline-iosched.c | |||
| @@ -337,13 +337,21 @@ static void deadline_exit_queue(struct elevator_queue *e) | |||
| 337 | /* | 337 | /* |
| 338 | * initialize elevator private data (deadline_data). | 338 | * initialize elevator private data (deadline_data). |
| 339 | */ | 339 | */ |
| 340 | static int deadline_init_queue(struct request_queue *q) | 340 | static int deadline_init_queue(struct request_queue *q, struct elevator_type *e) |
| 341 | { | 341 | { |
| 342 | struct deadline_data *dd; | 342 | struct deadline_data *dd; |
| 343 | struct elevator_queue *eq; | ||
| 344 | |||
| 345 | eq = elevator_alloc(q, e); | ||
| 346 | if (!eq) | ||
| 347 | return -ENOMEM; | ||
| 343 | 348 | ||
| 344 | dd = kmalloc_node(sizeof(*dd), GFP_KERNEL | __GFP_ZERO, q->node); | 349 | dd = kmalloc_node(sizeof(*dd), GFP_KERNEL | __GFP_ZERO, q->node); |
| 345 | if (!dd) | 350 | if (!dd) { |
| 351 | kobject_put(&eq->kobj); | ||
| 346 | return -ENOMEM; | 352 | return -ENOMEM; |
| 353 | } | ||
| 354 | eq->elevator_data = dd; | ||
| 347 | 355 | ||
| 348 | INIT_LIST_HEAD(&dd->fifo_list[READ]); | 356 | INIT_LIST_HEAD(&dd->fifo_list[READ]); |
| 349 | INIT_LIST_HEAD(&dd->fifo_list[WRITE]); | 357 | INIT_LIST_HEAD(&dd->fifo_list[WRITE]); |
| @@ -355,7 +363,9 @@ static int deadline_init_queue(struct request_queue *q) | |||
| 355 | dd->front_merges = 1; | 363 | dd->front_merges = 1; |
| 356 | dd->fifo_batch = fifo_batch; | 364 | dd->fifo_batch = fifo_batch; |
| 357 | 365 | ||
| 358 | q->elevator->elevator_data = dd; | 366 | spin_lock_irq(q->queue_lock); |
| 367 | q->elevator = eq; | ||
| 368 | spin_unlock_irq(q->queue_lock); | ||
| 359 | return 0; | 369 | return 0; |
| 360 | } | 370 | } |
| 361 | 371 | ||
diff --git a/block/elevator.c b/block/elevator.c index eba5b04c29b1..668394d18588 100644 --- a/block/elevator.c +++ b/block/elevator.c | |||
| @@ -150,7 +150,7 @@ void __init load_default_elevator_module(void) | |||
| 150 | 150 | ||
| 151 | static struct kobj_type elv_ktype; | 151 | static struct kobj_type elv_ktype; |
| 152 | 152 | ||
| 153 | static struct elevator_queue *elevator_alloc(struct request_queue *q, | 153 | struct elevator_queue *elevator_alloc(struct request_queue *q, |
| 154 | struct elevator_type *e) | 154 | struct elevator_type *e) |
| 155 | { | 155 | { |
| 156 | struct elevator_queue *eq; | 156 | struct elevator_queue *eq; |
| @@ -170,6 +170,7 @@ err: | |||
| 170 | elevator_put(e); | 170 | elevator_put(e); |
| 171 | return NULL; | 171 | return NULL; |
| 172 | } | 172 | } |
| 173 | EXPORT_SYMBOL(elevator_alloc); | ||
| 173 | 174 | ||
| 174 | static void elevator_release(struct kobject *kobj) | 175 | static void elevator_release(struct kobject *kobj) |
| 175 | { | 176 | { |
| @@ -221,16 +222,7 @@ int elevator_init(struct request_queue *q, char *name) | |||
| 221 | } | 222 | } |
| 222 | } | 223 | } |
| 223 | 224 | ||
| 224 | q->elevator = elevator_alloc(q, e); | 225 | err = e->ops.elevator_init_fn(q, e); |
| 225 | if (!q->elevator) | ||
| 226 | return -ENOMEM; | ||
| 227 | |||
| 228 | err = e->ops.elevator_init_fn(q); | ||
| 229 | if (err) { | ||
| 230 | kobject_put(&q->elevator->kobj); | ||
| 231 | return err; | ||
| 232 | } | ||
| 233 | |||
| 234 | return 0; | 226 | return 0; |
| 235 | } | 227 | } |
| 236 | EXPORT_SYMBOL(elevator_init); | 228 | EXPORT_SYMBOL(elevator_init); |
| @@ -935,16 +927,9 @@ static int elevator_switch(struct request_queue *q, struct elevator_type *new_e) | |||
| 935 | spin_unlock_irq(q->queue_lock); | 927 | spin_unlock_irq(q->queue_lock); |
| 936 | 928 | ||
| 937 | /* allocate, init and register new elevator */ | 929 | /* allocate, init and register new elevator */ |
| 938 | err = -ENOMEM; | 930 | err = new_e->ops.elevator_init_fn(q, new_e); |
| 939 | q->elevator = elevator_alloc(q, new_e); | 931 | if (err) |
| 940 | if (!q->elevator) | ||
| 941 | goto fail_init; | ||
| 942 | |||
| 943 | err = new_e->ops.elevator_init_fn(q); | ||
| 944 | if (err) { | ||
| 945 | kobject_put(&q->elevator->kobj); | ||
| 946 | goto fail_init; | 932 | goto fail_init; |
| 947 | } | ||
| 948 | 933 | ||
| 949 | if (registered) { | 934 | if (registered) { |
| 950 | err = elv_register_queue(q); | 935 | err = elv_register_queue(q); |
diff --git a/block/noop-iosched.c b/block/noop-iosched.c index 5d1bf70e33d5..3de89d4690f3 100644 --- a/block/noop-iosched.c +++ b/block/noop-iosched.c | |||
| @@ -59,16 +59,27 @@ noop_latter_request(struct request_queue *q, struct request *rq) | |||
| 59 | return list_entry(rq->queuelist.next, struct request, queuelist); | 59 | return list_entry(rq->queuelist.next, struct request, queuelist); |
| 60 | } | 60 | } |
| 61 | 61 | ||
| 62 | static int noop_init_queue(struct request_queue *q) | 62 | static int noop_init_queue(struct request_queue *q, struct elevator_type *e) |
| 63 | { | 63 | { |
| 64 | struct noop_data *nd; | 64 | struct noop_data *nd; |
| 65 | struct elevator_queue *eq; | ||
| 66 | |||
| 67 | eq = elevator_alloc(q, e); | ||
| 68 | if (!eq) | ||
| 69 | return -ENOMEM; | ||
| 65 | 70 | ||
| 66 | nd = kmalloc_node(sizeof(*nd), GFP_KERNEL, q->node); | 71 | nd = kmalloc_node(sizeof(*nd), GFP_KERNEL, q->node); |
| 67 | if (!nd) | 72 | if (!nd) { |
| 73 | kobject_put(&eq->kobj); | ||
| 68 | return -ENOMEM; | 74 | return -ENOMEM; |
| 75 | } | ||
| 76 | eq->elevator_data = nd; | ||
| 69 | 77 | ||
| 70 | INIT_LIST_HEAD(&nd->queue); | 78 | INIT_LIST_HEAD(&nd->queue); |
| 71 | q->elevator->elevator_data = nd; | 79 | |
| 80 | spin_lock_irq(q->queue_lock); | ||
| 81 | q->elevator = eq; | ||
| 82 | spin_unlock_irq(q->queue_lock); | ||
| 72 | return 0; | 83 | return 0; |
| 73 | } | 84 | } |
| 74 | 85 | ||
diff --git a/include/linux/elevator.h b/include/linux/elevator.h index acd0312d46fb..306dd8cd0b6f 100644 --- a/include/linux/elevator.h +++ b/include/linux/elevator.h | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | #ifdef CONFIG_BLOCK | 7 | #ifdef CONFIG_BLOCK |
| 8 | 8 | ||
| 9 | struct io_cq; | 9 | struct io_cq; |
| 10 | struct elevator_type; | ||
| 10 | 11 | ||
| 11 | typedef int (elevator_merge_fn) (struct request_queue *, struct request **, | 12 | typedef int (elevator_merge_fn) (struct request_queue *, struct request **, |
| 12 | struct bio *); | 13 | struct bio *); |
| @@ -35,7 +36,8 @@ typedef void (elevator_put_req_fn) (struct request *); | |||
| 35 | typedef void (elevator_activate_req_fn) (struct request_queue *, struct request *); | 36 | typedef void (elevator_activate_req_fn) (struct request_queue *, struct request *); |
| 36 | typedef void (elevator_deactivate_req_fn) (struct request_queue *, struct request *); | 37 | typedef void (elevator_deactivate_req_fn) (struct request_queue *, struct request *); |
| 37 | 38 | ||
| 38 | typedef int (elevator_init_fn) (struct request_queue *); | 39 | typedef int (elevator_init_fn) (struct request_queue *, |
| 40 | struct elevator_type *e); | ||
| 39 | typedef void (elevator_exit_fn) (struct elevator_queue *); | 41 | typedef void (elevator_exit_fn) (struct elevator_queue *); |
| 40 | 42 | ||
| 41 | struct elevator_ops | 43 | struct elevator_ops |
| @@ -155,6 +157,8 @@ extern int elevator_init(struct request_queue *, char *); | |||
| 155 | extern void elevator_exit(struct elevator_queue *); | 157 | extern void elevator_exit(struct elevator_queue *); |
| 156 | extern int elevator_change(struct request_queue *, const char *); | 158 | extern int elevator_change(struct request_queue *, const char *); |
| 157 | extern bool elv_rq_merge_ok(struct request *, struct bio *); | 159 | extern bool elv_rq_merge_ok(struct request *, struct bio *); |
| 160 | extern struct elevator_queue *elevator_alloc(struct request_queue *, | ||
| 161 | struct elevator_type *); | ||
| 158 | 162 | ||
| 159 | /* | 163 | /* |
| 160 | * Helper functions. | 164 | * Helper functions. |
