diff options
| author | Gerald Schaefer <geraldsc@de.ibm.com> | 2005-06-04 18:43:33 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-06-04 20:13:00 -0400 |
| commit | f26d583e41aedad8159acf9533fa287d7209dfbf (patch) | |
| tree | 215ef8ae71cd2b6bb6fec824d1db4a9f39db97ca | |
| parent | 595bf2aacae96d0f87352a1ff5476b79e52e212f (diff) | |
[PATCH] s390: deadlock in appldata
The system might hang when using appldata_mem with high I/O traffic and a
large number of devices. The spinlocks bdev_lock and swaplock are acquired
via calls to si_meminfo() and si_swapinfo() from a tasklet, i.e. interrupt
context, which can lead to a deadlock. Replace tasklet with work queue.
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
| -rw-r--r-- | arch/s390/appldata/appldata_base.c | 72 | ||||
| -rw-r--r-- | arch/s390/appldata/appldata_mem.c | 2 | ||||
| -rw-r--r-- | arch/s390/appldata/appldata_net_sum.c | 2 | ||||
| -rw-r--r-- | arch/s390/appldata/appldata_os.c | 4 |
4 files changed, 45 insertions, 35 deletions
diff --git a/arch/s390/appldata/appldata_base.c b/arch/s390/appldata/appldata_base.c index 01ae1964c938..c067435bae45 100644 --- a/arch/s390/appldata/appldata_base.c +++ b/arch/s390/appldata/appldata_base.c | |||
| @@ -28,6 +28,7 @@ | |||
| 28 | //#include <linux/kernel_stat.h> | 28 | //#include <linux/kernel_stat.h> |
| 29 | #include <linux/notifier.h> | 29 | #include <linux/notifier.h> |
| 30 | #include <linux/cpu.h> | 30 | #include <linux/cpu.h> |
| 31 | #include <linux/workqueue.h> | ||
| 31 | 32 | ||
| 32 | #include "appldata.h" | 33 | #include "appldata.h" |
| 33 | 34 | ||
| @@ -133,9 +134,12 @@ static int appldata_interval = APPLDATA_CPU_INTERVAL; | |||
| 133 | static int appldata_timer_active; | 134 | static int appldata_timer_active; |
| 134 | 135 | ||
| 135 | /* | 136 | /* |
| 136 | * Tasklet | 137 | * Work queue |
| 137 | */ | 138 | */ |
| 138 | static struct tasklet_struct appldata_tasklet_struct; | 139 | static struct workqueue_struct *appldata_wq; |
| 140 | static void appldata_work_fn(void *data); | ||
| 141 | static DECLARE_WORK(appldata_work, appldata_work_fn, NULL); | ||
| 142 | |||
| 139 | 143 | ||
| 140 | /* | 144 | /* |
| 141 | * Ops list | 145 | * Ops list |
| @@ -144,11 +148,11 @@ static DEFINE_SPINLOCK(appldata_ops_lock); | |||
| 144 | static LIST_HEAD(appldata_ops_list); | 148 | static LIST_HEAD(appldata_ops_list); |
| 145 | 149 | ||
| 146 | 150 | ||
| 147 | /************************* timer, tasklet, DIAG ******************************/ | 151 | /*************************** timer, work, DIAG *******************************/ |
| 148 | /* | 152 | /* |
| 149 | * appldata_timer_function() | 153 | * appldata_timer_function() |
| 150 | * | 154 | * |
| 151 | * schedule tasklet and reschedule timer | 155 | * schedule work and reschedule timer |
| 152 | */ | 156 | */ |
| 153 | static void appldata_timer_function(unsigned long data, struct pt_regs *regs) | 157 | static void appldata_timer_function(unsigned long data, struct pt_regs *regs) |
| 154 | { | 158 | { |
| @@ -157,22 +161,22 @@ static void appldata_timer_function(unsigned long data, struct pt_regs *regs) | |||
| 157 | atomic_read(&appldata_expire_count)); | 161 | atomic_read(&appldata_expire_count)); |
| 158 | if (atomic_dec_and_test(&appldata_expire_count)) { | 162 | if (atomic_dec_and_test(&appldata_expire_count)) { |
| 159 | atomic_set(&appldata_expire_count, num_online_cpus()); | 163 | atomic_set(&appldata_expire_count, num_online_cpus()); |
| 160 | tasklet_schedule((struct tasklet_struct *) data); | 164 | queue_work(appldata_wq, (struct work_struct *) data); |
| 161 | } | 165 | } |
| 162 | } | 166 | } |
| 163 | 167 | ||
| 164 | /* | 168 | /* |
| 165 | * appldata_tasklet_function() | 169 | * appldata_work_fn() |
| 166 | * | 170 | * |
| 167 | * call data gathering function for each (active) module | 171 | * call data gathering function for each (active) module |
| 168 | */ | 172 | */ |
| 169 | static void appldata_tasklet_function(unsigned long data) | 173 | static void appldata_work_fn(void *data) |
| 170 | { | 174 | { |
| 171 | struct list_head *lh; | 175 | struct list_head *lh; |
| 172 | struct appldata_ops *ops; | 176 | struct appldata_ops *ops; |
| 173 | int i; | 177 | int i; |
| 174 | 178 | ||
| 175 | P_DEBUG(" -= Tasklet =-\n"); | 179 | P_DEBUG(" -= Work Queue =-\n"); |
| 176 | i = 0; | 180 | i = 0; |
| 177 | spin_lock(&appldata_ops_lock); | 181 | spin_lock(&appldata_ops_lock); |
| 178 | list_for_each(lh, &appldata_ops_list) { | 182 | list_for_each(lh, &appldata_ops_list) { |
| @@ -231,7 +235,7 @@ static int appldata_diag(char record_nr, u16 function, unsigned long buffer, | |||
| 231 | : "=d" (ry) : "d" (&(appldata_parameter_list)) : "cc"); | 235 | : "=d" (ry) : "d" (&(appldata_parameter_list)) : "cc"); |
| 232 | return (int) ry; | 236 | return (int) ry; |
| 233 | } | 237 | } |
| 234 | /********************** timer, tasklet, DIAG <END> ***************************/ | 238 | /************************ timer, work, DIAG <END> ****************************/ |
| 235 | 239 | ||
| 236 | 240 | ||
| 237 | /****************************** /proc stuff **********************************/ | 241 | /****************************** /proc stuff **********************************/ |
| @@ -411,7 +415,7 @@ appldata_generic_handler(ctl_table *ctl, int write, struct file *filp, | |||
| 411 | struct list_head *lh; | 415 | struct list_head *lh; |
| 412 | 416 | ||
| 413 | found = 0; | 417 | found = 0; |
| 414 | spin_lock_bh(&appldata_ops_lock); | 418 | spin_lock(&appldata_ops_lock); |
| 415 | list_for_each(lh, &appldata_ops_list) { | 419 | list_for_each(lh, &appldata_ops_list) { |
| 416 | tmp_ops = list_entry(lh, struct appldata_ops, list); | 420 | tmp_ops = list_entry(lh, struct appldata_ops, list); |
| 417 | if (&tmp_ops->ctl_table[2] == ctl) { | 421 | if (&tmp_ops->ctl_table[2] == ctl) { |
| @@ -419,15 +423,15 @@ appldata_generic_handler(ctl_table *ctl, int write, struct file *filp, | |||
| 419 | } | 423 | } |
| 420 | } | 424 | } |
| 421 | if (!found) { | 425 | if (!found) { |
| 422 | spin_unlock_bh(&appldata_ops_lock); | 426 | spin_unlock(&appldata_ops_lock); |
| 423 | return -ENODEV; | 427 | return -ENODEV; |
| 424 | } | 428 | } |
| 425 | ops = ctl->data; | 429 | ops = ctl->data; |
| 426 | if (!try_module_get(ops->owner)) { // protect this function | 430 | if (!try_module_get(ops->owner)) { // protect this function |
| 427 | spin_unlock_bh(&appldata_ops_lock); | 431 | spin_unlock(&appldata_ops_lock); |
| 428 | return -ENODEV; | 432 | return -ENODEV; |
| 429 | } | 433 | } |
| 430 | spin_unlock_bh(&appldata_ops_lock); | 434 | spin_unlock(&appldata_ops_lock); |
| 431 | 435 | ||
| 432 | if (!*lenp || *ppos) { | 436 | if (!*lenp || *ppos) { |
| 433 | *lenp = 0; | 437 | *lenp = 0; |
| @@ -451,10 +455,11 @@ appldata_generic_handler(ctl_table *ctl, int write, struct file *filp, | |||
| 451 | return -EFAULT; | 455 | return -EFAULT; |
| 452 | } | 456 | } |
| 453 | 457 | ||
| 454 | spin_lock_bh(&appldata_ops_lock); | 458 | spin_lock(&appldata_ops_lock); |
| 455 | if ((buf[0] == '1') && (ops->active == 0)) { | 459 | if ((buf[0] == '1') && (ops->active == 0)) { |
| 456 | if (!try_module_get(ops->owner)) { // protect tasklet | 460 | // protect work queue callback |
| 457 | spin_unlock_bh(&appldata_ops_lock); | 461 | if (!try_module_get(ops->owner)) { |
| 462 | spin_unlock(&appldata_ops_lock); | ||
| 458 | module_put(ops->owner); | 463 | module_put(ops->owner); |
| 459 | return -ENODEV; | 464 | return -ENODEV; |
| 460 | } | 465 | } |
| @@ -485,7 +490,7 @@ appldata_generic_handler(ctl_table *ctl, int write, struct file *filp, | |||
| 485 | } | 490 | } |
| 486 | module_put(ops->owner); | 491 | module_put(ops->owner); |
| 487 | } | 492 | } |
| 488 | spin_unlock_bh(&appldata_ops_lock); | 493 | spin_unlock(&appldata_ops_lock); |
| 489 | out: | 494 | out: |
| 490 | *lenp = len; | 495 | *lenp = len; |
| 491 | *ppos += len; | 496 | *ppos += len; |
| @@ -529,7 +534,7 @@ int appldata_register_ops(struct appldata_ops *ops) | |||
| 529 | } | 534 | } |
| 530 | memset(ops->ctl_table, 0, 4*sizeof(struct ctl_table)); | 535 | memset(ops->ctl_table, 0, 4*sizeof(struct ctl_table)); |
| 531 | 536 | ||
| 532 | spin_lock_bh(&appldata_ops_lock); | 537 | spin_lock(&appldata_ops_lock); |
| 533 | list_for_each(lh, &appldata_ops_list) { | 538 | list_for_each(lh, &appldata_ops_list) { |
| 534 | tmp_ops = list_entry(lh, struct appldata_ops, list); | 539 | tmp_ops = list_entry(lh, struct appldata_ops, list); |
| 535 | P_DEBUG("register_ops loop: %i) name = %s, ctl = %i\n", | 540 | P_DEBUG("register_ops loop: %i) name = %s, ctl = %i\n", |
| @@ -541,18 +546,18 @@ int appldata_register_ops(struct appldata_ops *ops) | |||
| 541 | APPLDATA_PROC_NAME_LENGTH) == 0) { | 546 | APPLDATA_PROC_NAME_LENGTH) == 0) { |
| 542 | P_ERROR("Name \"%s\" already registered!\n", ops->name); | 547 | P_ERROR("Name \"%s\" already registered!\n", ops->name); |
| 543 | kfree(ops->ctl_table); | 548 | kfree(ops->ctl_table); |
| 544 | spin_unlock_bh(&appldata_ops_lock); | 549 | spin_unlock(&appldata_ops_lock); |
| 545 | return -EBUSY; | 550 | return -EBUSY; |
| 546 | } | 551 | } |
| 547 | if (tmp_ops->ctl_nr == ops->ctl_nr) { | 552 | if (tmp_ops->ctl_nr == ops->ctl_nr) { |
| 548 | P_ERROR("ctl_nr %i already registered!\n", ops->ctl_nr); | 553 | P_ERROR("ctl_nr %i already registered!\n", ops->ctl_nr); |
| 549 | kfree(ops->ctl_table); | 554 | kfree(ops->ctl_table); |
| 550 | spin_unlock_bh(&appldata_ops_lock); | 555 | spin_unlock(&appldata_ops_lock); |
| 551 | return -EBUSY; | 556 | return -EBUSY; |
| 552 | } | 557 | } |
| 553 | } | 558 | } |
| 554 | list_add(&ops->list, &appldata_ops_list); | 559 | list_add(&ops->list, &appldata_ops_list); |
| 555 | spin_unlock_bh(&appldata_ops_lock); | 560 | spin_unlock(&appldata_ops_lock); |
| 556 | 561 | ||
| 557 | ops->ctl_table[0].ctl_name = CTL_APPLDATA; | 562 | ops->ctl_table[0].ctl_name = CTL_APPLDATA; |
| 558 | ops->ctl_table[0].procname = appldata_proc_name; | 563 | ops->ctl_table[0].procname = appldata_proc_name; |
| @@ -583,12 +588,12 @@ int appldata_register_ops(struct appldata_ops *ops) | |||
| 583 | */ | 588 | */ |
| 584 | void appldata_unregister_ops(struct appldata_ops *ops) | 589 | void appldata_unregister_ops(struct appldata_ops *ops) |
| 585 | { | 590 | { |
| 586 | spin_lock_bh(&appldata_ops_lock); | 591 | spin_lock(&appldata_ops_lock); |
| 587 | unregister_sysctl_table(ops->sysctl_header); | 592 | unregister_sysctl_table(ops->sysctl_header); |
| 588 | list_del(&ops->list); | 593 | list_del(&ops->list); |
| 589 | kfree(ops->ctl_table); | 594 | kfree(ops->ctl_table); |
| 590 | ops->ctl_table = NULL; | 595 | ops->ctl_table = NULL; |
| 591 | spin_unlock_bh(&appldata_ops_lock); | 596 | spin_unlock(&appldata_ops_lock); |
| 592 | P_INFO("%s-ops unregistered!\n", ops->name); | 597 | P_INFO("%s-ops unregistered!\n", ops->name); |
| 593 | } | 598 | } |
| 594 | /********************** module-ops management <END> **************************/ | 599 | /********************** module-ops management <END> **************************/ |
| @@ -602,7 +607,7 @@ appldata_online_cpu(int cpu) | |||
| 602 | init_virt_timer(&per_cpu(appldata_timer, cpu)); | 607 | init_virt_timer(&per_cpu(appldata_timer, cpu)); |
| 603 | per_cpu(appldata_timer, cpu).function = appldata_timer_function; | 608 | per_cpu(appldata_timer, cpu).function = appldata_timer_function; |
| 604 | per_cpu(appldata_timer, cpu).data = (unsigned long) | 609 | per_cpu(appldata_timer, cpu).data = (unsigned long) |
| 605 | &appldata_tasklet_struct; | 610 | &appldata_work; |
| 606 | atomic_inc(&appldata_expire_count); | 611 | atomic_inc(&appldata_expire_count); |
| 607 | spin_lock(&appldata_timer_lock); | 612 | spin_lock(&appldata_timer_lock); |
| 608 | __appldata_vtimer_setup(APPLDATA_MOD_TIMER); | 613 | __appldata_vtimer_setup(APPLDATA_MOD_TIMER); |
| @@ -615,7 +620,7 @@ appldata_offline_cpu(int cpu) | |||
| 615 | del_virt_timer(&per_cpu(appldata_timer, cpu)); | 620 | del_virt_timer(&per_cpu(appldata_timer, cpu)); |
| 616 | if (atomic_dec_and_test(&appldata_expire_count)) { | 621 | if (atomic_dec_and_test(&appldata_expire_count)) { |
| 617 | atomic_set(&appldata_expire_count, num_online_cpus()); | 622 | atomic_set(&appldata_expire_count, num_online_cpus()); |
| 618 | tasklet_schedule(&appldata_tasklet_struct); | 623 | queue_work(appldata_wq, &appldata_work); |
| 619 | } | 624 | } |
| 620 | spin_lock(&appldata_timer_lock); | 625 | spin_lock(&appldata_timer_lock); |
| 621 | __appldata_vtimer_setup(APPLDATA_MOD_TIMER); | 626 | __appldata_vtimer_setup(APPLDATA_MOD_TIMER); |
| @@ -648,7 +653,7 @@ static struct notifier_block __devinitdata appldata_nb = { | |||
| 648 | /* | 653 | /* |
| 649 | * appldata_init() | 654 | * appldata_init() |
| 650 | * | 655 | * |
| 651 | * init timer and tasklet, register /proc entries | 656 | * init timer, register /proc entries |
| 652 | */ | 657 | */ |
| 653 | static int __init appldata_init(void) | 658 | static int __init appldata_init(void) |
| 654 | { | 659 | { |
| @@ -657,6 +662,12 @@ static int __init appldata_init(void) | |||
| 657 | P_DEBUG("sizeof(parameter_list) = %lu\n", | 662 | P_DEBUG("sizeof(parameter_list) = %lu\n", |
| 658 | sizeof(struct appldata_parameter_list)); | 663 | sizeof(struct appldata_parameter_list)); |
| 659 | 664 | ||
| 665 | appldata_wq = create_singlethread_workqueue("appldata"); | ||
| 666 | if (!appldata_wq) { | ||
| 667 | P_ERROR("Could not create work queue\n"); | ||
| 668 | return -ENOMEM; | ||
| 669 | } | ||
| 670 | |||
| 660 | for_each_online_cpu(i) | 671 | for_each_online_cpu(i) |
| 661 | appldata_online_cpu(i); | 672 | appldata_online_cpu(i); |
| 662 | 673 | ||
| @@ -670,7 +681,6 @@ static int __init appldata_init(void) | |||
| 670 | appldata_table[1].de->owner = THIS_MODULE; | 681 | appldata_table[1].de->owner = THIS_MODULE; |
| 671 | #endif | 682 | #endif |
| 672 | 683 | ||
| 673 | tasklet_init(&appldata_tasklet_struct, appldata_tasklet_function, 0); | ||
| 674 | P_DEBUG("Base interface initialized.\n"); | 684 | P_DEBUG("Base interface initialized.\n"); |
| 675 | return 0; | 685 | return 0; |
| 676 | } | 686 | } |
| @@ -678,7 +688,7 @@ static int __init appldata_init(void) | |||
| 678 | /* | 688 | /* |
| 679 | * appldata_exit() | 689 | * appldata_exit() |
| 680 | * | 690 | * |
| 681 | * stop timer and tasklet, unregister /proc entries | 691 | * stop timer, unregister /proc entries |
| 682 | */ | 692 | */ |
| 683 | static void __exit appldata_exit(void) | 693 | static void __exit appldata_exit(void) |
| 684 | { | 694 | { |
| @@ -690,7 +700,7 @@ static void __exit appldata_exit(void) | |||
| 690 | /* | 700 | /* |
| 691 | * ops list should be empty, but just in case something went wrong... | 701 | * ops list should be empty, but just in case something went wrong... |
| 692 | */ | 702 | */ |
| 693 | spin_lock_bh(&appldata_ops_lock); | 703 | spin_lock(&appldata_ops_lock); |
| 694 | list_for_each(lh, &appldata_ops_list) { | 704 | list_for_each(lh, &appldata_ops_list) { |
| 695 | ops = list_entry(lh, struct appldata_ops, list); | 705 | ops = list_entry(lh, struct appldata_ops, list); |
| 696 | rc = appldata_diag(ops->record_nr, APPLDATA_STOP_REC, | 706 | rc = appldata_diag(ops->record_nr, APPLDATA_STOP_REC, |
| @@ -700,7 +710,7 @@ static void __exit appldata_exit(void) | |||
| 700 | "return code: %d\n", ops->name, rc); | 710 | "return code: %d\n", ops->name, rc); |
| 701 | } | 711 | } |
| 702 | } | 712 | } |
| 703 | spin_unlock_bh(&appldata_ops_lock); | 713 | spin_unlock(&appldata_ops_lock); |
| 704 | 714 | ||
| 705 | for_each_online_cpu(i) | 715 | for_each_online_cpu(i) |
| 706 | appldata_offline_cpu(i); | 716 | appldata_offline_cpu(i); |
| @@ -709,7 +719,7 @@ static void __exit appldata_exit(void) | |||
| 709 | 719 | ||
| 710 | unregister_sysctl_table(appldata_sysctl_header); | 720 | unregister_sysctl_table(appldata_sysctl_header); |
| 711 | 721 | ||
| 712 | tasklet_kill(&appldata_tasklet_struct); | 722 | destroy_workqueue(appldata_wq); |
| 713 | P_DEBUG("... module unloaded!\n"); | 723 | P_DEBUG("... module unloaded!\n"); |
| 714 | } | 724 | } |
| 715 | /**************************** init / exit <END> ******************************/ | 725 | /**************************** init / exit <END> ******************************/ |
diff --git a/arch/s390/appldata/appldata_mem.c b/arch/s390/appldata/appldata_mem.c index 462ee9a84e76..f0e2fbed3d4c 100644 --- a/arch/s390/appldata/appldata_mem.c +++ b/arch/s390/appldata/appldata_mem.c | |||
| @@ -68,7 +68,7 @@ struct appldata_mem_data { | |||
| 68 | u64 pgmajfault; /* page faults (major only) */ | 68 | u64 pgmajfault; /* page faults (major only) */ |
| 69 | // <-- New in 2.6 | 69 | // <-- New in 2.6 |
| 70 | 70 | ||
| 71 | } appldata_mem_data; | 71 | } __attribute__((packed)) appldata_mem_data; |
| 72 | 72 | ||
| 73 | 73 | ||
| 74 | static inline void appldata_debug_print(struct appldata_mem_data *mem_data) | 74 | static inline void appldata_debug_print(struct appldata_mem_data *mem_data) |
diff --git a/arch/s390/appldata/appldata_net_sum.c b/arch/s390/appldata/appldata_net_sum.c index dd61638d3027..2a4c7432db4a 100644 --- a/arch/s390/appldata/appldata_net_sum.c +++ b/arch/s390/appldata/appldata_net_sum.c | |||
| @@ -57,7 +57,7 @@ struct appldata_net_sum_data { | |||
| 57 | u64 rx_dropped; /* no space in linux buffers */ | 57 | u64 rx_dropped; /* no space in linux buffers */ |
| 58 | u64 tx_dropped; /* no space available in linux */ | 58 | u64 tx_dropped; /* no space available in linux */ |
| 59 | u64 collisions; /* collisions while transmitting */ | 59 | u64 collisions; /* collisions while transmitting */ |
| 60 | } appldata_net_sum_data; | 60 | } __attribute__((packed)) appldata_net_sum_data; |
| 61 | 61 | ||
| 62 | 62 | ||
| 63 | static inline void appldata_print_debug(struct appldata_net_sum_data *net_data) | 63 | static inline void appldata_print_debug(struct appldata_net_sum_data *net_data) |
diff --git a/arch/s390/appldata/appldata_os.c b/arch/s390/appldata/appldata_os.c index b83f07484551..e0a476bf4fd6 100644 --- a/arch/s390/appldata/appldata_os.c +++ b/arch/s390/appldata/appldata_os.c | |||
| @@ -49,7 +49,7 @@ struct appldata_os_per_cpu { | |||
| 49 | u32 per_cpu_softirq; /* ... spent in softirqs */ | 49 | u32 per_cpu_softirq; /* ... spent in softirqs */ |
| 50 | u32 per_cpu_iowait; /* ... spent while waiting for I/O */ | 50 | u32 per_cpu_iowait; /* ... spent while waiting for I/O */ |
| 51 | // <-- New in 2.6 | 51 | // <-- New in 2.6 |
| 52 | }; | 52 | } __attribute__((packed)); |
| 53 | 53 | ||
| 54 | struct appldata_os_data { | 54 | struct appldata_os_data { |
| 55 | u64 timestamp; | 55 | u64 timestamp; |
| @@ -75,7 +75,7 @@ struct appldata_os_data { | |||
| 75 | 75 | ||
| 76 | /* per cpu data */ | 76 | /* per cpu data */ |
| 77 | struct appldata_os_per_cpu os_cpu[0]; | 77 | struct appldata_os_per_cpu os_cpu[0]; |
| 78 | }; | 78 | } __attribute__((packed)); |
| 79 | 79 | ||
| 80 | static struct appldata_os_data *appldata_os_data; | 80 | static struct appldata_os_data *appldata_os_data; |
| 81 | 81 | ||
