Skip to content

Commit 46ed60d

Browse files
committed
netfs: Implement unbuffered/DIO vs buffered I/O locking
Borrow NFS's direct-vs-buffered I/O locking into netfslib. Similar code is also used in ceph. Modify it to have the correct checker annotations for i_rwsem lock acquisition/release and to return -ERESTARTSYS if waits are interrupted. Signed-off-by: David Howells <dhowells@redhat.com> Reviewed-by: Jeff Layton <jlayton@kernel.org> cc: linux-cachefs@redhat.com cc: linux-fsdevel@vger.kernel.org cc: linux-mm@kvack.org
1 parent c1ec4d7 commit 46ed60d

3 files changed

Lines changed: 227 additions & 0 deletions

File tree

fs/netfs/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ netfs-y := \
44
buffered_read.o \
55
io.o \
66
iterator.o \
7+
locking.o \
78
main.o \
89
misc.o \
910
objects.o

fs/netfs/locking.c

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* I/O and data path helper functionality.
4+
*
5+
* Borrowed from NFS Copyright (c) 2016 Trond Myklebust
6+
*/
7+
8+
#include <linux/kernel.h>
9+
#include <linux/netfs.h>
10+
#include "internal.h"
11+
12+
/*
13+
* inode_dio_wait_interruptible - wait for outstanding DIO requests to finish
14+
* @inode: inode to wait for
15+
*
16+
* Waits for all pending direct I/O requests to finish so that we can
17+
* proceed with a truncate or equivalent operation.
18+
*
19+
* Must be called under a lock that serializes taking new references
20+
* to i_dio_count, usually by inode->i_mutex.
21+
*/
22+
static int inode_dio_wait_interruptible(struct inode *inode)
23+
{
24+
if (!atomic_read(&inode->i_dio_count))
25+
return 0;
26+
27+
wait_queue_head_t *wq = bit_waitqueue(&inode->i_state, __I_DIO_WAKEUP);
28+
DEFINE_WAIT_BIT(q, &inode->i_state, __I_DIO_WAKEUP);
29+
30+
for (;;) {
31+
prepare_to_wait(wq, &q.wq_entry, TASK_INTERRUPTIBLE);
32+
if (!atomic_read(&inode->i_dio_count))
33+
break;
34+
if (signal_pending(current))
35+
break;
36+
schedule();
37+
}
38+
finish_wait(wq, &q.wq_entry);
39+
40+
return atomic_read(&inode->i_dio_count) ? -ERESTARTSYS : 0;
41+
}
42+
43+
/* Call with exclusively locked inode->i_rwsem */
44+
static int netfs_block_o_direct(struct netfs_inode *ictx)
45+
{
46+
if (!test_bit(NETFS_ICTX_ODIRECT, &ictx->flags))
47+
return 0;
48+
clear_bit(NETFS_ICTX_ODIRECT, &ictx->flags);
49+
return inode_dio_wait_interruptible(&ictx->inode);
50+
}
51+
52+
/**
53+
* netfs_start_io_read - declare the file is being used for buffered reads
54+
* @inode: file inode
55+
*
56+
* Declare that a buffered read operation is about to start, and ensure
57+
* that we block all direct I/O.
58+
* On exit, the function ensures that the NETFS_ICTX_ODIRECT flag is unset,
59+
* and holds a shared lock on inode->i_rwsem to ensure that the flag
60+
* cannot be changed.
61+
* In practice, this means that buffered read operations are allowed to
62+
* execute in parallel, thanks to the shared lock, whereas direct I/O
63+
* operations need to wait to grab an exclusive lock in order to set
64+
* NETFS_ICTX_ODIRECT.
65+
* Note that buffered writes and truncates both take a write lock on
66+
* inode->i_rwsem, meaning that those are serialised w.r.t. the reads.
67+
*/
68+
int netfs_start_io_read(struct inode *inode)
69+
__acquires(inode->i_rwsem)
70+
{
71+
struct netfs_inode *ictx = netfs_inode(inode);
72+
73+
/* Be an optimist! */
74+
if (down_read_interruptible(&inode->i_rwsem) < 0)
75+
return -ERESTARTSYS;
76+
if (test_bit(NETFS_ICTX_ODIRECT, &ictx->flags) == 0)
77+
return 0;
78+
up_read(&inode->i_rwsem);
79+
80+
/* Slow path.... */
81+
if (down_write_killable(&inode->i_rwsem) < 0)
82+
return -ERESTARTSYS;
83+
if (netfs_block_o_direct(ictx) < 0) {
84+
up_write(&inode->i_rwsem);
85+
return -ERESTARTSYS;
86+
}
87+
downgrade_write(&inode->i_rwsem);
88+
return 0;
89+
}
90+
EXPORT_SYMBOL(netfs_start_io_read);
91+
92+
/**
93+
* netfs_end_io_read - declare that the buffered read operation is done
94+
* @inode: file inode
95+
*
96+
* Declare that a buffered read operation is done, and release the shared
97+
* lock on inode->i_rwsem.
98+
*/
99+
void netfs_end_io_read(struct inode *inode)
100+
__releases(inode->i_rwsem)
101+
{
102+
up_read(&inode->i_rwsem);
103+
}
104+
EXPORT_SYMBOL(netfs_end_io_read);
105+
106+
/**
107+
* netfs_start_io_write - declare the file is being used for buffered writes
108+
* @inode: file inode
109+
*
110+
* Declare that a buffered read operation is about to start, and ensure
111+
* that we block all direct I/O.
112+
*/
113+
int netfs_start_io_write(struct inode *inode)
114+
__acquires(inode->i_rwsem)
115+
{
116+
struct netfs_inode *ictx = netfs_inode(inode);
117+
118+
if (down_write_killable(&inode->i_rwsem) < 0)
119+
return -ERESTARTSYS;
120+
if (netfs_block_o_direct(ictx) < 0) {
121+
up_write(&inode->i_rwsem);
122+
return -ERESTARTSYS;
123+
}
124+
return 0;
125+
}
126+
EXPORT_SYMBOL(netfs_start_io_write);
127+
128+
/**
129+
* netfs_end_io_write - declare that the buffered write operation is done
130+
* @inode: file inode
131+
*
132+
* Declare that a buffered write operation is done, and release the
133+
* lock on inode->i_rwsem.
134+
*/
135+
void netfs_end_io_write(struct inode *inode)
136+
__releases(inode->i_rwsem)
137+
{
138+
up_write(&inode->i_rwsem);
139+
}
140+
EXPORT_SYMBOL(netfs_end_io_write);
141+
142+
/* Call with exclusively locked inode->i_rwsem */
143+
static int netfs_block_buffered(struct inode *inode)
144+
{
145+
struct netfs_inode *ictx = netfs_inode(inode);
146+
int ret;
147+
148+
if (!test_bit(NETFS_ICTX_ODIRECT, &ictx->flags)) {
149+
set_bit(NETFS_ICTX_ODIRECT, &ictx->flags);
150+
if (inode->i_mapping->nrpages != 0) {
151+
unmap_mapping_range(inode->i_mapping, 0, 0, 0);
152+
ret = filemap_fdatawait(inode->i_mapping);
153+
if (ret < 0) {
154+
clear_bit(NETFS_ICTX_ODIRECT, &ictx->flags);
155+
return ret;
156+
}
157+
}
158+
}
159+
return 0;
160+
}
161+
162+
/**
163+
* netfs_start_io_direct - declare the file is being used for direct i/o
164+
* @inode: file inode
165+
*
166+
* Declare that a direct I/O operation is about to start, and ensure
167+
* that we block all buffered I/O.
168+
* On exit, the function ensures that the NETFS_ICTX_ODIRECT flag is set,
169+
* and holds a shared lock on inode->i_rwsem to ensure that the flag
170+
* cannot be changed.
171+
* In practice, this means that direct I/O operations are allowed to
172+
* execute in parallel, thanks to the shared lock, whereas buffered I/O
173+
* operations need to wait to grab an exclusive lock in order to clear
174+
* NETFS_ICTX_ODIRECT.
175+
* Note that buffered writes and truncates both take a write lock on
176+
* inode->i_rwsem, meaning that those are serialised w.r.t. O_DIRECT.
177+
*/
178+
int netfs_start_io_direct(struct inode *inode)
179+
__acquires(inode->i_rwsem)
180+
{
181+
struct netfs_inode *ictx = netfs_inode(inode);
182+
int ret;
183+
184+
/* Be an optimist! */
185+
if (down_read_interruptible(&inode->i_rwsem) < 0)
186+
return -ERESTARTSYS;
187+
if (test_bit(NETFS_ICTX_ODIRECT, &ictx->flags) != 0)
188+
return 0;
189+
up_read(&inode->i_rwsem);
190+
191+
/* Slow path.... */
192+
if (down_write_killable(&inode->i_rwsem) < 0)
193+
return -ERESTARTSYS;
194+
ret = netfs_block_buffered(inode);
195+
if (ret < 0) {
196+
up_write(&inode->i_rwsem);
197+
return ret;
198+
}
199+
downgrade_write(&inode->i_rwsem);
200+
return 0;
201+
}
202+
EXPORT_SYMBOL(netfs_start_io_direct);
203+
204+
/**
205+
* netfs_end_io_direct - declare that the direct i/o operation is done
206+
* @inode: file inode
207+
*
208+
* Declare that a direct I/O operation is done, and release the shared
209+
* lock on inode->i_rwsem.
210+
*/
211+
void netfs_end_io_direct(struct inode *inode)
212+
__releases(inode->i_rwsem)
213+
{
214+
up_read(&inode->i_rwsem);
215+
}
216+
EXPORT_SYMBOL(netfs_end_io_direct);

include/linux/netfs.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ struct netfs_inode {
129129
struct fscache_cookie *cache;
130130
#endif
131131
loff_t remote_i_size; /* Size of the remote file */
132+
unsigned long flags;
133+
#define NETFS_ICTX_ODIRECT 0 /* The file has DIO in progress */
132134
};
133135

134136
/*
@@ -310,6 +312,13 @@ ssize_t netfs_extract_user_iter(struct iov_iter *orig, size_t orig_len,
310312
struct iov_iter *new,
311313
iov_iter_extraction_t extraction_flags);
312314

315+
int netfs_start_io_read(struct inode *inode);
316+
void netfs_end_io_read(struct inode *inode);
317+
int netfs_start_io_write(struct inode *inode);
318+
void netfs_end_io_write(struct inode *inode);
319+
int netfs_start_io_direct(struct inode *inode);
320+
void netfs_end_io_direct(struct inode *inode);
321+
313322
/**
314323
* netfs_inode - Get the netfs inode context from the inode
315324
* @inode: The inode to query
@@ -335,6 +344,7 @@ static inline void netfs_inode_init(struct netfs_inode *ctx,
335344
{
336345
ctx->ops = ops;
337346
ctx->remote_i_size = i_size_read(&ctx->inode);
347+
ctx->flags = 0;
338348
#if IS_ENABLED(CONFIG_FSCACHE)
339349
ctx->cache = NULL;
340350
#endif

0 commit comments

Comments
 (0)