mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-27 11:19:37 +00:00
![Tejun Heo](/assets/img/avatar_default.png)
Plug merge calls two elevator callbacks outside queue lock - elevator_allow_merge_fn() and elevator_bio_merged_fn(). Although attempt_plug_merge() suggests that elevator is guaranteed to be there through the existing request on the plug list, nothing prevents plug merge from calling into dying or initializing elevator. For regular merges, bypass ensures elvpriv count to reach zero, which in turn prevents merges as all !ELVPRIV requests get REQ_SOFTBARRIER from forced back insertion. Plug merge doesn't check ELVPRIV, and, as the requests haven't gone through elevator insertion yet, it doesn't have SOFTBARRIER set allowing merges on a bypassed queue. This, for example, leads to the following crash during elevator switch. BUG: unable to handle kernel NULL pointer dereference at 0000000000000008 IP: [<ffffffff813b34e9>] cfq_allow_merge+0x49/0xa0 PGD 112cbc067 PUD 115d5c067 PMD 0 Oops: 0000 [#1] PREEMPT SMP CPU 1 Modules linked in: deadline_iosched Pid: 819, comm: dd Not tainted 3.3.0-rc2-work+ #76 Bochs Bochs RIP: 0010:[<ffffffff813b34e9>] [<ffffffff813b34e9>] cfq_allow_merge+0x49/0xa0 RSP: 0018:ffff8801143a38f8 EFLAGS: 00010297 RAX: 0000000000000000 RBX: ffff88011817ce28 RCX: ffff880116eb6cc0 RDX: 0000000000000000 RSI: ffff880118056e20 RDI: ffff8801199512f8 RBP: ffff8801143a3908 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000001 R11: 0000000000000000 R12: ffff880118195708 R13: ffff880118052aa0 R14: ffff8801143a3d50 R15: ffff880118195708 FS: 00007f19f82cb700(0000) GS:ffff88011fc80000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 000000008005003b CR2: 0000000000000008 CR3: 0000000112c6a000 CR4: 00000000000006e0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400 Process dd (pid: 819, threadinfo ffff8801143a2000, task ffff880116eb6cc0) Stack: ffff88011817ce28 ffff880118195708 ffff8801143a3928 ffffffff81391bba ffff88011817ce28 ffff880118195708 ffff8801143a3948 ffffffff81391bf1 ffff88011817ce28 0000000000000000 ffff8801143a39a8 ffffffff81398e3e Call Trace: [<ffffffff81391bba>] elv_rq_merge_ok+0x4a/0x60 [<ffffffff81391bf1>] elv_try_merge+0x21/0x40 [<ffffffff81398e3e>] blk_queue_bio+0x8e/0x390 [<ffffffff81396a5a>] generic_make_request+0xca/0x100 [<ffffffff81396b04>] submit_bio+0x74/0x100 [<ffffffff811d45c2>] __blockdev_direct_IO+0x1ce2/0x3450 [<ffffffff811d0dc7>] blkdev_direct_IO+0x57/0x60 [<ffffffff811460b5>] generic_file_aio_read+0x6d5/0x760 [<ffffffff811986b2>] do_sync_read+0xe2/0x120 [<ffffffff81199345>] vfs_read+0xc5/0x180 [<ffffffff81199501>] sys_read+0x51/0x90 [<ffffffff81aeac12>] system_call_fastpath+0x16/0x1b There are multiple ways to fix this including making plug merge check ELVPRIV; however, * Calling into elevator outside queue lock is confusing and error-prone. * Requests on plug list aren't known to the elevator. They aren't on the elevator yet, so there's no elevator specific state to update. * Given the nature of plug merges - collecting bio's for the same purpose from the same issuer - elevator specific restrictions aren't applicable. So, simply don't call into elevator methods from plug merge by moving elv_bio_merged() from bio_attempt_*_merge() to blk_queue_bio(), and using blk_try_merge() in attempt_plug_merge(). This is based on Jens' patch to skip elevator_allow_merge_fn() from plug merge. Note that this makes per-cgroup merged stats skip plug merging. Signed-off-by: Tejun Heo <tj@kernel.org> LKML-Reference: <4F16F3CA.90904@kernel.dk> Original-patch-by: Jens Axboe <axboe@kernel.dk> Signed-off-by: Jens Axboe <axboe@kernel.dk>
208 lines
6.8 KiB
C
208 lines
6.8 KiB
C
#ifndef _LINUX_ELEVATOR_H
|
|
#define _LINUX_ELEVATOR_H
|
|
|
|
#include <linux/percpu.h>
|
|
|
|
#ifdef CONFIG_BLOCK
|
|
|
|
struct io_cq;
|
|
|
|
typedef int (elevator_merge_fn) (struct request_queue *, struct request **,
|
|
struct bio *);
|
|
|
|
typedef void (elevator_merge_req_fn) (struct request_queue *, struct request *, struct request *);
|
|
|
|
typedef void (elevator_merged_fn) (struct request_queue *, struct request *, int);
|
|
|
|
typedef int (elevator_allow_merge_fn) (struct request_queue *, struct request *, struct bio *);
|
|
|
|
typedef void (elevator_bio_merged_fn) (struct request_queue *,
|
|
struct request *, struct bio *);
|
|
|
|
typedef int (elevator_dispatch_fn) (struct request_queue *, int);
|
|
|
|
typedef void (elevator_add_req_fn) (struct request_queue *, struct request *);
|
|
typedef struct request *(elevator_request_list_fn) (struct request_queue *, struct request *);
|
|
typedef void (elevator_completed_req_fn) (struct request_queue *, struct request *);
|
|
typedef int (elevator_may_queue_fn) (struct request_queue *, int);
|
|
|
|
typedef void (elevator_init_icq_fn) (struct io_cq *);
|
|
typedef void (elevator_exit_icq_fn) (struct io_cq *);
|
|
typedef int (elevator_set_req_fn) (struct request_queue *, struct request *, gfp_t);
|
|
typedef void (elevator_put_req_fn) (struct request *);
|
|
typedef void (elevator_activate_req_fn) (struct request_queue *, struct request *);
|
|
typedef void (elevator_deactivate_req_fn) (struct request_queue *, struct request *);
|
|
|
|
typedef void *(elevator_init_fn) (struct request_queue *);
|
|
typedef void (elevator_exit_fn) (struct elevator_queue *);
|
|
|
|
struct elevator_ops
|
|
{
|
|
elevator_merge_fn *elevator_merge_fn;
|
|
elevator_merged_fn *elevator_merged_fn;
|
|
elevator_merge_req_fn *elevator_merge_req_fn;
|
|
elevator_allow_merge_fn *elevator_allow_merge_fn;
|
|
elevator_bio_merged_fn *elevator_bio_merged_fn;
|
|
|
|
elevator_dispatch_fn *elevator_dispatch_fn;
|
|
elevator_add_req_fn *elevator_add_req_fn;
|
|
elevator_activate_req_fn *elevator_activate_req_fn;
|
|
elevator_deactivate_req_fn *elevator_deactivate_req_fn;
|
|
|
|
elevator_completed_req_fn *elevator_completed_req_fn;
|
|
|
|
elevator_request_list_fn *elevator_former_req_fn;
|
|
elevator_request_list_fn *elevator_latter_req_fn;
|
|
|
|
elevator_init_icq_fn *elevator_init_icq_fn; /* see iocontext.h */
|
|
elevator_exit_icq_fn *elevator_exit_icq_fn; /* ditto */
|
|
|
|
elevator_set_req_fn *elevator_set_req_fn;
|
|
elevator_put_req_fn *elevator_put_req_fn;
|
|
|
|
elevator_may_queue_fn *elevator_may_queue_fn;
|
|
|
|
elevator_init_fn *elevator_init_fn;
|
|
elevator_exit_fn *elevator_exit_fn;
|
|
};
|
|
|
|
#define ELV_NAME_MAX (16)
|
|
|
|
struct elv_fs_entry {
|
|
struct attribute attr;
|
|
ssize_t (*show)(struct elevator_queue *, char *);
|
|
ssize_t (*store)(struct elevator_queue *, const char *, size_t);
|
|
};
|
|
|
|
/*
|
|
* identifies an elevator type, such as AS or deadline
|
|
*/
|
|
struct elevator_type
|
|
{
|
|
/* managed by elevator core */
|
|
struct kmem_cache *icq_cache;
|
|
|
|
/* fields provided by elevator implementation */
|
|
struct elevator_ops ops;
|
|
size_t icq_size; /* see iocontext.h */
|
|
size_t icq_align; /* ditto */
|
|
struct elv_fs_entry *elevator_attrs;
|
|
char elevator_name[ELV_NAME_MAX];
|
|
struct module *elevator_owner;
|
|
|
|
/* managed by elevator core */
|
|
char icq_cache_name[ELV_NAME_MAX + 5]; /* elvname + "_io_cq" */
|
|
struct list_head list;
|
|
};
|
|
|
|
/*
|
|
* each queue has an elevator_queue associated with it
|
|
*/
|
|
struct elevator_queue
|
|
{
|
|
struct elevator_type *type;
|
|
void *elevator_data;
|
|
struct kobject kobj;
|
|
struct mutex sysfs_lock;
|
|
struct hlist_head *hash;
|
|
unsigned int registered:1;
|
|
};
|
|
|
|
/*
|
|
* block elevator interface
|
|
*/
|
|
extern void elv_dispatch_sort(struct request_queue *, struct request *);
|
|
extern void elv_dispatch_add_tail(struct request_queue *, struct request *);
|
|
extern void elv_add_request(struct request_queue *, struct request *, int);
|
|
extern void __elv_add_request(struct request_queue *, struct request *, int);
|
|
extern int elv_merge(struct request_queue *, struct request **, struct bio *);
|
|
extern void elv_merge_requests(struct request_queue *, struct request *,
|
|
struct request *);
|
|
extern void elv_merged_request(struct request_queue *, struct request *, int);
|
|
extern void elv_bio_merged(struct request_queue *q, struct request *,
|
|
struct bio *);
|
|
extern void elv_requeue_request(struct request_queue *, struct request *);
|
|
extern struct request *elv_former_request(struct request_queue *, struct request *);
|
|
extern struct request *elv_latter_request(struct request_queue *, struct request *);
|
|
extern int elv_register_queue(struct request_queue *q);
|
|
extern void elv_unregister_queue(struct request_queue *q);
|
|
extern int elv_may_queue(struct request_queue *, int);
|
|
extern void elv_abort_queue(struct request_queue *);
|
|
extern void elv_completed_request(struct request_queue *, struct request *);
|
|
extern int elv_set_request(struct request_queue *, struct request *, gfp_t);
|
|
extern void elv_put_request(struct request_queue *, struct request *);
|
|
extern void elv_drain_elevator(struct request_queue *);
|
|
|
|
/*
|
|
* io scheduler registration
|
|
*/
|
|
extern int elv_register(struct elevator_type *);
|
|
extern void elv_unregister(struct elevator_type *);
|
|
|
|
/*
|
|
* io scheduler sysfs switching
|
|
*/
|
|
extern ssize_t elv_iosched_show(struct request_queue *, char *);
|
|
extern ssize_t elv_iosched_store(struct request_queue *, const char *, size_t);
|
|
|
|
extern int elevator_init(struct request_queue *, char *);
|
|
extern void elevator_exit(struct elevator_queue *);
|
|
extern int elevator_change(struct request_queue *, const char *);
|
|
extern bool elv_rq_merge_ok(struct request *, struct bio *);
|
|
|
|
/*
|
|
* Helper functions.
|
|
*/
|
|
extern struct request *elv_rb_former_request(struct request_queue *, struct request *);
|
|
extern struct request *elv_rb_latter_request(struct request_queue *, struct request *);
|
|
|
|
/*
|
|
* rb support functions.
|
|
*/
|
|
extern void elv_rb_add(struct rb_root *, struct request *);
|
|
extern void elv_rb_del(struct rb_root *, struct request *);
|
|
extern struct request *elv_rb_find(struct rb_root *, sector_t);
|
|
|
|
/*
|
|
* Return values from elevator merger
|
|
*/
|
|
#define ELEVATOR_NO_MERGE 0
|
|
#define ELEVATOR_FRONT_MERGE 1
|
|
#define ELEVATOR_BACK_MERGE 2
|
|
|
|
/*
|
|
* Insertion selection
|
|
*/
|
|
#define ELEVATOR_INSERT_FRONT 1
|
|
#define ELEVATOR_INSERT_BACK 2
|
|
#define ELEVATOR_INSERT_SORT 3
|
|
#define ELEVATOR_INSERT_REQUEUE 4
|
|
#define ELEVATOR_INSERT_FLUSH 5
|
|
#define ELEVATOR_INSERT_SORT_MERGE 6
|
|
|
|
/*
|
|
* return values from elevator_may_queue_fn
|
|
*/
|
|
enum {
|
|
ELV_MQUEUE_MAY,
|
|
ELV_MQUEUE_NO,
|
|
ELV_MQUEUE_MUST,
|
|
};
|
|
|
|
#define rq_end_sector(rq) (blk_rq_pos(rq) + blk_rq_sectors(rq))
|
|
#define rb_entry_rq(node) rb_entry((node), struct request, rb_node)
|
|
|
|
/*
|
|
* Hack to reuse the csd.list list_head as the fifo time holder while
|
|
* the request is in the io scheduler. Saves an unsigned long in rq.
|
|
*/
|
|
#define rq_fifo_time(rq) ((unsigned long) (rq)->csd.list.next)
|
|
#define rq_set_fifo_time(rq,exp) ((rq)->csd.list.next = (void *) (exp))
|
|
#define rq_entry_fifo(ptr) list_entry((ptr), struct request, queuelist)
|
|
#define rq_fifo_clear(rq) do { \
|
|
list_del_init(&(rq)->queuelist); \
|
|
INIT_LIST_HEAD(&(rq)->csd.list); \
|
|
} while (0)
|
|
|
|
#endif /* CONFIG_BLOCK */
|
|
#endif
|