Commit 996d5b4d authored by Matthew Wilcox's avatar Matthew Wilcox Committed by Dominique Martinet

9p: Use a slab for allocating requests

Replace the custom batch allocation with a slab.  Use an IDR to store
pointers to the active requests instead of an array.  We don't try to
handle P9_NOTAG specially; the IDR will happily shrink all the way back
once the TVERSION call has completed.

Link: http://lkml.kernel.org/r/20180711210225.19730-6-willy@infradead.orgSigned-off-by: default avatarMatthew Wilcox <willy@infradead.org>
Cc: Eric Van Hensbergen <ericvh@gmail.com>
Cc: Ron Minnich <rminnich@sandia.gov>
Cc: Latchesar Ionkov <lucho@ionkov.net>
Signed-off-by: default avatarDominique Martinet <dominique.martinet@cea.fr>
parent 62e39417
...@@ -64,22 +64,15 @@ enum p9_trans_status { ...@@ -64,22 +64,15 @@ enum p9_trans_status {
/** /**
* enum p9_req_status_t - status of a request * enum p9_req_status_t - status of a request
* @REQ_STATUS_IDLE: request slot unused
* @REQ_STATUS_ALLOC: request has been allocated but not sent * @REQ_STATUS_ALLOC: request has been allocated but not sent
* @REQ_STATUS_UNSENT: request waiting to be sent * @REQ_STATUS_UNSENT: request waiting to be sent
* @REQ_STATUS_SENT: request sent to server * @REQ_STATUS_SENT: request sent to server
* @REQ_STATUS_RCVD: response received from server * @REQ_STATUS_RCVD: response received from server
* @REQ_STATUS_FLSHD: request has been flushed * @REQ_STATUS_FLSHD: request has been flushed
* @REQ_STATUS_ERROR: request encountered an error on the client side * @REQ_STATUS_ERROR: request encountered an error on the client side
*
* The @REQ_STATUS_IDLE state is used to mark a request slot as unused
* but use is actually tracked by the idpool structure which handles tag
* id allocation.
*
*/ */
enum p9_req_status_t { enum p9_req_status_t {
REQ_STATUS_IDLE,
REQ_STATUS_ALLOC, REQ_STATUS_ALLOC,
REQ_STATUS_UNSENT, REQ_STATUS_UNSENT,
REQ_STATUS_SENT, REQ_STATUS_SENT,
...@@ -92,24 +85,12 @@ enum p9_req_status_t { ...@@ -92,24 +85,12 @@ enum p9_req_status_t {
* struct p9_req_t - request slots * struct p9_req_t - request slots
* @status: status of this request slot * @status: status of this request slot
* @t_err: transport error * @t_err: transport error
* @flush_tag: tag of request being flushed (for flush requests)
* @wq: wait_queue for the client to block on for this request * @wq: wait_queue for the client to block on for this request
* @tc: the request fcall structure * @tc: the request fcall structure
* @rc: the response fcall structure * @rc: the response fcall structure
* @aux: transport specific data (provided for trans_fd migration) * @aux: transport specific data (provided for trans_fd migration)
* @req_list: link for higher level objects to chain requests * @req_list: link for higher level objects to chain requests
*
* Transport use an array to track outstanding requests
* instead of a list. While this may incurr overhead during initial
* allocation or expansion, it makes request lookup much easier as the
* tag id is a index into an array. (We use tag+1 so that we can accommodate
* the -1 tag for the T_VERSION request).
* This also has the nice effect of only having to allocate wait_queues
* once, instead of constantly allocating and freeing them. Its possible
* other resources could benefit from this scheme as well.
*
*/ */
struct p9_req_t { struct p9_req_t {
int status; int status;
int t_err; int t_err;
...@@ -117,40 +98,26 @@ struct p9_req_t { ...@@ -117,40 +98,26 @@ struct p9_req_t {
struct p9_fcall *tc; struct p9_fcall *tc;
struct p9_fcall *rc; struct p9_fcall *rc;
void *aux; void *aux;
struct list_head req_list; struct list_head req_list;
}; };
/** /**
* struct p9_client - per client instance state * struct p9_client - per client instance state
* @lock: protect @fidlist * @lock: protect @fids and @reqs
* @msize: maximum data size negotiated by protocol * @msize: maximum data size negotiated by protocol
* @dotu: extension flags negotiated by protocol
* @proto_version: 9P protocol version to use * @proto_version: 9P protocol version to use
* @trans_mod: module API instantiated with this client * @trans_mod: module API instantiated with this client
* @status: connection state
* @trans: tranport instance state and API * @trans: tranport instance state and API
* @fids: All active FID handles * @fids: All active FID handles
* @tagpool - transaction id accounting for session * @reqs: All active requests.
* @reqs - 2D array of requests * @name: node name used as client id
* @max_tag - current maximum tag id allocated
* @name - node name used as client id
* *
* The client structure is used to keep track of various per-client * The client structure is used to keep track of various per-client
* state that has been instantiated. * state that has been instantiated.
* In order to minimize per-transaction overhead we use a
* simple array to lookup requests instead of a hash table
* or linked list. In order to support larger number of
* transactions, we make this a 2D array, allocating new rows
* when we need to grow the total number of the transactions.
*
* Each row is 256 requests and we'll support up to 256 rows for
* a total of 64k concurrent requests per session.
*
* Bugs: duplicated data and potentially unnecessary elements.
*/ */
struct p9_client { struct p9_client {
spinlock_t lock; /* protect client structure */ spinlock_t lock;
unsigned int msize; unsigned int msize;
unsigned char proto_version; unsigned char proto_version;
struct p9_trans_module *trans_mod; struct p9_trans_module *trans_mod;
...@@ -170,10 +137,7 @@ struct p9_client { ...@@ -170,10 +137,7 @@ struct p9_client {
} trans_opts; } trans_opts;
struct idr fids; struct idr fids;
struct idr reqs;
struct p9_idpool *tagpool;
struct p9_req_t *reqs[P9_ROW_MAXTAG];
int max_tag;
char name[__NEW_UTS_LEN + 1]; char name[__NEW_UTS_LEN + 1];
}; };
...@@ -279,4 +243,7 @@ struct p9_fid *p9_client_xattrwalk(struct p9_fid *, const char *, u64 *); ...@@ -279,4 +243,7 @@ struct p9_fid *p9_client_xattrwalk(struct p9_fid *, const char *, u64 *);
int p9_client_xattrcreate(struct p9_fid *, const char *, u64, int); int p9_client_xattrcreate(struct p9_fid *, const char *, u64, int);
int p9_client_readlink(struct p9_fid *fid, char **target); int p9_client_readlink(struct p9_fid *fid, char **target);
int p9_client_init(void);
void p9_client_exit(void);
#endif /* NET_9P_CLIENT_H */ #endif /* NET_9P_CLIENT_H */
...@@ -242,132 +242,102 @@ static struct p9_fcall *p9_fcall_alloc(int alloc_msize) ...@@ -242,132 +242,102 @@ static struct p9_fcall *p9_fcall_alloc(int alloc_msize)
return fc; return fc;
} }
static struct kmem_cache *p9_req_cache;
/** /**
* p9_tag_alloc - lookup/allocate a request by tag * p9_req_alloc - Allocate a new request.
* @c: client session to lookup tag within * @c: Client session.
* @tag: numeric id for transaction * @type: Transaction type.
* * @max_size: Maximum packet size for this request.
* this is a simple array lookup, but will grow the
* request_slots as necessary to accommodate transaction
* ids which did not previously have a slot.
*
* this code relies on the client spinlock to manage locks, its
* possible we should switch to something else, but I'd rather
* stick with something low-overhead for the common case.
* *
* Context: Process context.
* Return: Pointer to new request.
*/ */
static struct p9_req_t * static struct p9_req_t *
p9_tag_alloc(struct p9_client *c, u16 tag, unsigned int max_size) p9_tag_alloc(struct p9_client *c, int8_t type, unsigned int max_size)
{ {
unsigned long flags; struct p9_req_t *req = kmem_cache_alloc(p9_req_cache, GFP_NOFS);
int row, col;
struct p9_req_t *req;
int alloc_msize = min(c->msize, max_size); int alloc_msize = min(c->msize, max_size);
int tag;
/* This looks up the original request by tag so we know which if (!req)
* buffer to read the data into */ return NULL;
tag++;
if (tag >= c->max_tag) {
spin_lock_irqsave(&c->lock, flags);
/* check again since original check was outside of lock */
while (tag >= c->max_tag) {
row = (tag / P9_ROW_MAXTAG);
c->reqs[row] = kcalloc(P9_ROW_MAXTAG,
sizeof(struct p9_req_t), GFP_ATOMIC);
if (!c->reqs[row]) {
pr_err("Couldn't grow tag array\n");
spin_unlock_irqrestore(&c->lock, flags);
return ERR_PTR(-ENOMEM);
}
for (col = 0; col < P9_ROW_MAXTAG; col++) {
req = &c->reqs[row][col];
req->status = REQ_STATUS_IDLE;
init_waitqueue_head(&req->wq);
}
c->max_tag += P9_ROW_MAXTAG;
}
spin_unlock_irqrestore(&c->lock, flags);
}
row = tag / P9_ROW_MAXTAG;
col = tag % P9_ROW_MAXTAG;
req = &c->reqs[row][col]; req->tc = p9_fcall_alloc(alloc_msize);
if (!req->tc) req->rc = p9_fcall_alloc(alloc_msize);
req->tc = p9_fcall_alloc(alloc_msize);
if (!req->rc)
req->rc = p9_fcall_alloc(alloc_msize);
if (!req->tc || !req->rc) if (!req->tc || !req->rc)
goto grow_failed; goto free;
p9pdu_reset(req->tc); p9pdu_reset(req->tc);
p9pdu_reset(req->rc); p9pdu_reset(req->rc);
req->tc->tag = tag-1;
req->status = REQ_STATUS_ALLOC; req->status = REQ_STATUS_ALLOC;
init_waitqueue_head(&req->wq);
INIT_LIST_HEAD(&req->req_list);
idr_preload(GFP_NOFS);
spin_lock_irq(&c->lock);
if (type == P9_TVERSION)
tag = idr_alloc(&c->reqs, req, P9_NOTAG, P9_NOTAG + 1,
GFP_NOWAIT);
else
tag = idr_alloc(&c->reqs, req, 0, P9_NOTAG, GFP_NOWAIT);
req->tc->tag = tag;
spin_unlock_irq(&c->lock);
idr_preload_end();
if (tag < 0)
goto free;
return req; return req;
grow_failed: free:
pr_err("Couldn't grow tag array\n");
kfree(req->tc); kfree(req->tc);
kfree(req->rc); kfree(req->rc);
req->tc = req->rc = NULL; kmem_cache_free(p9_req_cache, req);
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
} }
/** /**
* p9_tag_lookup - lookup a request by tag * p9_tag_lookup - Look up a request by tag.
* @c: client session to lookup tag within * @c: Client session.
* @tag: numeric id for transaction * @tag: Transaction ID.
* *
* Context: Any context.
* Return: A request, or %NULL if there is no request with that tag.
*/ */
struct p9_req_t *p9_tag_lookup(struct p9_client *c, u16 tag) struct p9_req_t *p9_tag_lookup(struct p9_client *c, u16 tag)
{ {
int row, col; struct p9_req_t *req;
/* This looks up the original request by tag so we know which
* buffer to read the data into */
tag++;
if (tag >= c->max_tag)
return NULL;
row = tag / P9_ROW_MAXTAG; rcu_read_lock();
col = tag % P9_ROW_MAXTAG; req = idr_find(&c->reqs, tag);
/* There's no refcount on the req; a malicious server could cause
* us to dereference a NULL pointer
*/
rcu_read_unlock();
return &c->reqs[row][col]; return req;
} }
EXPORT_SYMBOL(p9_tag_lookup); EXPORT_SYMBOL(p9_tag_lookup);
/** /**
* p9_tag_init - setup tags structure and contents * p9_free_req - Free a request.
* @c: v9fs client struct * @c: Client session.
* * @r: Request to free.
* This initializes the tags structure for each client instance.
* *
* Context: Any context.
*/ */
static void p9_free_req(struct p9_client *c, struct p9_req_t *r)
static int p9_tag_init(struct p9_client *c)
{ {
int err = 0; unsigned long flags;
u16 tag = r->tc->tag;
c->tagpool = p9_idpool_create(); p9_debug(P9_DEBUG_MUX, "clnt %p req %p tag: %d\n", c, r, tag);
if (IS_ERR(c->tagpool)) { spin_lock_irqsave(&c->lock, flags);
err = PTR_ERR(c->tagpool); idr_remove(&c->reqs, tag);
goto error; spin_unlock_irqrestore(&c->lock, flags);
} kfree(r->tc);
err = p9_idpool_get(c->tagpool); /* reserve tag 0 */ kfree(r->rc);
if (err < 0) { kmem_cache_free(p9_req_cache, r);
p9_idpool_destroy(c->tagpool);
goto error;
}
c->max_tag = 0;
error:
return err;
} }
/** /**
...@@ -379,52 +349,15 @@ static int p9_tag_init(struct p9_client *c) ...@@ -379,52 +349,15 @@ static int p9_tag_init(struct p9_client *c)
*/ */
static void p9_tag_cleanup(struct p9_client *c) static void p9_tag_cleanup(struct p9_client *c)
{ {
int row, col; struct p9_req_t *req;
int id;
/* check to insure all requests are idle */
for (row = 0; row < (c->max_tag/P9_ROW_MAXTAG); row++) {
for (col = 0; col < P9_ROW_MAXTAG; col++) {
if (c->reqs[row][col].status != REQ_STATUS_IDLE) {
p9_debug(P9_DEBUG_MUX,
"Attempting to cleanup non-free tag %d,%d\n",
row, col);
/* TODO: delay execution of cleanup */
return;
}
}
}
if (c->tagpool) {
p9_idpool_put(0, c->tagpool); /* free reserved tag 0 */
p9_idpool_destroy(c->tagpool);
}
/* free requests associated with tags */ rcu_read_lock();
for (row = 0; row < (c->max_tag/P9_ROW_MAXTAG); row++) { idr_for_each_entry(&c->reqs, req, id) {
for (col = 0; col < P9_ROW_MAXTAG; col++) { pr_info("Tag %d still in use\n", id);
kfree(c->reqs[row][col].tc); p9_free_req(c, req);
kfree(c->reqs[row][col].rc);
}
kfree(c->reqs[row]);
} }
c->max_tag = 0; rcu_read_unlock();
}
/**
* p9_free_req - free a request and clean-up as necessary
* c: client state
* r: request to release
*
*/
static void p9_free_req(struct p9_client *c, struct p9_req_t *r)
{
int tag = r->tc->tag;
p9_debug(P9_DEBUG_MUX, "clnt %p req %p tag: %d\n", c, r, tag);
r->status = REQ_STATUS_IDLE;
if (tag != P9_NOTAG && p9_idpool_check(tag, c->tagpool))
p9_idpool_put(tag, c->tagpool);
} }
/** /**
...@@ -698,7 +631,7 @@ static struct p9_req_t *p9_client_prepare_req(struct p9_client *c, ...@@ -698,7 +631,7 @@ static struct p9_req_t *p9_client_prepare_req(struct p9_client *c,
int8_t type, int req_size, int8_t type, int req_size,
const char *fmt, va_list ap) const char *fmt, va_list ap)
{ {
int tag, err; int err;
struct p9_req_t *req; struct p9_req_t *req;
p9_debug(P9_DEBUG_MUX, "client %p op %d\n", c, type); p9_debug(P9_DEBUG_MUX, "client %p op %d\n", c, type);
...@@ -711,24 +644,17 @@ static struct p9_req_t *p9_client_prepare_req(struct p9_client *c, ...@@ -711,24 +644,17 @@ static struct p9_req_t *p9_client_prepare_req(struct p9_client *c,
if ((c->status == BeginDisconnect) && (type != P9_TCLUNK)) if ((c->status == BeginDisconnect) && (type != P9_TCLUNK))
return ERR_PTR(-EIO); return ERR_PTR(-EIO);
tag = P9_NOTAG; req = p9_tag_alloc(c, type, req_size);
if (type != P9_TVERSION) {
tag = p9_idpool_get(c->tagpool);
if (tag < 0)
return ERR_PTR(-ENOMEM);
}
req = p9_tag_alloc(c, tag, req_size);
if (IS_ERR(req)) if (IS_ERR(req))
return req; return req;
/* marshall the data */ /* marshall the data */
p9pdu_prepare(req->tc, tag, type); p9pdu_prepare(req->tc, req->tc->tag, type);
err = p9pdu_vwritef(req->tc, c->proto_version, fmt, ap); err = p9pdu_vwritef(req->tc, c->proto_version, fmt, ap);
if (err) if (err)
goto reterr; goto reterr;
p9pdu_finalize(c, req->tc); p9pdu_finalize(c, req->tc);
trace_9p_client_req(c, type, tag); trace_9p_client_req(c, type, req->tc->tag);
return req; return req;
reterr: reterr:
p9_free_req(c, req); p9_free_req(c, req);
...@@ -1026,14 +952,11 @@ struct p9_client *p9_client_create(const char *dev_name, char *options) ...@@ -1026,14 +952,11 @@ struct p9_client *p9_client_create(const char *dev_name, char *options)
spin_lock_init(&clnt->lock); spin_lock_init(&clnt->lock);
idr_init(&clnt->fids); idr_init(&clnt->fids);
idr_init(&clnt->reqs);
err = p9_tag_init(clnt);
if (err < 0)
goto free_client;
err = parse_opts(options, clnt); err = parse_opts(options, clnt);
if (err < 0) if (err < 0)
goto destroy_tagpool; goto free_client;
if (!clnt->trans_mod) if (!clnt->trans_mod)
clnt->trans_mod = v9fs_get_default_trans(); clnt->trans_mod = v9fs_get_default_trans();
...@@ -1042,7 +965,7 @@ struct p9_client *p9_client_create(const char *dev_name, char *options) ...@@ -1042,7 +965,7 @@ struct p9_client *p9_client_create(const char *dev_name, char *options)
err = -EPROTONOSUPPORT; err = -EPROTONOSUPPORT;
p9_debug(P9_DEBUG_ERROR, p9_debug(P9_DEBUG_ERROR,
"No transport defined or default transport\n"); "No transport defined or default transport\n");
goto destroy_tagpool; goto free_client;
} }
p9_debug(P9_DEBUG_MUX, "clnt %p trans %p msize %d protocol %d\n", p9_debug(P9_DEBUG_MUX, "clnt %p trans %p msize %d protocol %d\n",
...@@ -1065,8 +988,6 @@ struct p9_client *p9_client_create(const char *dev_name, char *options) ...@@ -1065,8 +988,6 @@ struct p9_client *p9_client_create(const char *dev_name, char *options)
clnt->trans_mod->close(clnt); clnt->trans_mod->close(clnt);
put_trans: put_trans:
v9fs_put_trans(clnt->trans_mod); v9fs_put_trans(clnt->trans_mod);
destroy_tagpool:
p9_idpool_destroy(clnt->tagpool);
free_client: free_client:
kfree(clnt); kfree(clnt);
return ERR_PTR(err); return ERR_PTR(err);
...@@ -2282,3 +2203,14 @@ int p9_client_readlink(struct p9_fid *fid, char **target) ...@@ -2282,3 +2203,14 @@ int p9_client_readlink(struct p9_fid *fid, char **target)
return err; return err;
} }
EXPORT_SYMBOL(p9_client_readlink); EXPORT_SYMBOL(p9_client_readlink);
int __init p9_client_init(void)
{
p9_req_cache = KMEM_CACHE(p9_req_t, 0);
return p9_req_cache ? 0 : -ENOMEM;
}
void __exit p9_client_exit(void)
{
kmem_cache_destroy(p9_req_cache);
}
...@@ -171,11 +171,17 @@ void v9fs_put_trans(struct p9_trans_module *m) ...@@ -171,11 +171,17 @@ void v9fs_put_trans(struct p9_trans_module *m)
*/ */
static int __init init_p9(void) static int __init init_p9(void)
{ {
int ret;
ret = p9_client_init();
if (ret)
return ret;
p9_error_init(); p9_error_init();
pr_info("Installing 9P2000 support\n"); pr_info("Installing 9P2000 support\n");
p9_trans_fd_init(); p9_trans_fd_init();
return 0; return ret;
} }
/** /**
...@@ -188,6 +194,7 @@ static void __exit exit_p9(void) ...@@ -188,6 +194,7 @@ static void __exit exit_p9(void)
pr_info("Unloading 9P2000 support\n"); pr_info("Unloading 9P2000 support\n");
p9_trans_fd_exit(); p9_trans_fd_exit();
p9_client_exit();
} }
module_init(init_p9) module_init(init_p9)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment