Commit 83005276 authored by Fabio M. De Francesco's avatar Fabio M. De Francesco Committed by Al Viro

fs/sysv: Replace kmap() with kmap_local_page()

kmap() is being deprecated in favor of kmap_local_page().

There are two main problems with kmap(): (1) It comes with an overhead as
the mapping space is restricted and protected by a global lock for
synchronization and (2) it also requires global TLB invalidation when the
kmap’s pool wraps and it might block when the mapping space is fully
utilized until a slot becomes available.

With kmap_local_page() the mappings are per thread, CPU local, can take
page faults, and can be called from any context (including interrupts).
It is faster than kmap() in kernels with HIGHMEM enabled. Furthermore,
the tasks can be preempted and, when they are scheduled to run again, the
kernel virtual addresses are restored and still valid.

Since kmap_local_page() would not break the strict rules of local mappings
(i.e., the thread locality and the stack based nesting), this function can
be easily and safely replace the deprecated API.

Therefore, replace kmap() with kmap_local_page() in fs/sysv. kunmap_local()
requires the mapping address, so return that address from dir_get_page()
to be used in dir_put_page().
Suggested-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
Suggested-by: default avatarIra Weiny <ira.weiny@intel.com>
Signed-off-by: default avatarFabio M. De Francesco <fmdefrancesco@gmail.com>
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent c26ddc49
......@@ -28,9 +28,9 @@ const struct file_operations sysv_dir_operations = {
.fsync = generic_file_fsync,
};
inline void dir_put_page(struct page *page)
inline void dir_put_page(struct page *page, void *page_addr)
{
kunmap(page);
kunmap_local((void *)((unsigned long)page_addr & PAGE_MASK));
put_page(page);
}
......@@ -57,15 +57,21 @@ static int sysv_handle_dirsync(struct inode *dir)
return err;
}
/*
* Calls to dir_get_page()/dir_put_page() must be nested according to the
* rules documented in mm/highmem.rst.
*
* NOTE: sysv_find_entry() and sysv_dotdot() act as calls to dir_get_page()
* and must be treated accordingly for nesting purposes.
*/
static void *dir_get_page(struct inode *dir, unsigned long n, struct page **p)
{
struct address_space *mapping = dir->i_mapping;
struct page *page = read_mapping_page(mapping, n, NULL);
if (IS_ERR(page))
return ERR_CAST(page);
kmap(page);
*p = page;
return page_address(page);
return kmap_local_page(page);
}
static int sysv_readdir(struct file *file, struct dir_context *ctx)
......@@ -103,11 +109,11 @@ static int sysv_readdir(struct file *file, struct dir_context *ctx)
if (!dir_emit(ctx, name, strnlen(name,SYSV_NAMELEN),
fs16_to_cpu(SYSV_SB(sb), de->inode),
DT_UNKNOWN)) {
dir_put_page(page);
dir_put_page(page, kaddr);
return 0;
}
}
dir_put_page(page);
dir_put_page(page, kaddr);
}
return 0;
}
......@@ -130,6 +136,11 @@ static inline int namecompare(int len, int maxlen,
* returns the cache buffer in which the entry was found, and the entry
* itself (as a parameter - res_dir). It does NOT read the inode of the
* entry - you'll have to do that yourself if you want to.
*
* On Success dir_put_page() should be called on *res_page.
*
* sysv_find_entry() acts as a call to dir_get_page() and must be treated
* accordingly for nesting purposes.
*/
struct sysv_dir_entry *sysv_find_entry(struct dentry *dentry, struct page **res_page)
{
......@@ -161,7 +172,7 @@ struct sysv_dir_entry *sysv_find_entry(struct dentry *dentry, struct page **res_
name, de->name))
goto found;
}
dir_put_page(page);
dir_put_page(page, kaddr);
}
if (++n >= npages)
......@@ -204,7 +215,7 @@ int sysv_add_link(struct dentry *dentry, struct inode *inode)
goto out_page;
de++;
}
dir_put_page(page);
dir_put_page(page, kaddr);
}
BUG();
return -EINVAL;
......@@ -223,7 +234,7 @@ int sysv_add_link(struct dentry *dentry, struct inode *inode)
mark_inode_dirty(dir);
err = sysv_handle_dirsync(dir);
out_page:
dir_put_page(page);
dir_put_page(page, kaddr);
return err;
out_unlock:
unlock_page(page);
......@@ -241,7 +252,7 @@ int sysv_delete_entry(struct sysv_dir_entry *de, struct page *page)
BUG_ON(err);
de->inode = 0;
dir_commit_chunk(page, pos, SYSV_DIRSIZE);
dir_put_page(page);
dir_put_page(page, de);
inode->i_ctime = inode->i_mtime = current_time(inode);
mark_inode_dirty(inode);
return sysv_handle_dirsync(inode);
......@@ -261,9 +272,7 @@ int sysv_make_empty(struct inode *inode, struct inode *dir)
unlock_page(page);
goto fail;
}
kmap(page);
base = (char*)page_address(page);
base = kmap_local_page(page);
memset(base, 0, PAGE_SIZE);
de = (struct sysv_dir_entry *) base;
......@@ -273,7 +282,7 @@ int sysv_make_empty(struct inode *inode, struct inode *dir)
de->inode = cpu_to_fs16(SYSV_SB(inode->i_sb), dir->i_ino);
strcpy(de->name,"..");
kunmap(page);
kunmap_local(base);
dir_commit_chunk(page, 0, 2 * SYSV_DIRSIZE);
err = sysv_handle_dirsync(inode);
fail:
......@@ -289,10 +298,10 @@ int sysv_empty_dir(struct inode * inode)
struct super_block *sb = inode->i_sb;
struct page *page = NULL;
unsigned long i, npages = dir_pages(inode);
char *kaddr;
for (i = 0; i < npages; i++) {
char *kaddr;
struct sysv_dir_entry * de;
struct sysv_dir_entry *de;
kaddr = dir_get_page(inode, i, &page);
if (IS_ERR(kaddr))
......@@ -316,12 +325,12 @@ int sysv_empty_dir(struct inode * inode)
if (de->name[1] != '.' || de->name[2])
goto not_empty;
}
dir_put_page(page);
dir_put_page(page, kaddr);
}
return 1;
not_empty:
dir_put_page(page);
dir_put_page(page, kaddr);
return 0;
}
......@@ -338,12 +347,19 @@ void sysv_set_link(struct sysv_dir_entry *de, struct page *page,
BUG_ON(err);
de->inode = cpu_to_fs16(SYSV_SB(inode->i_sb), inode->i_ino);
dir_commit_chunk(page, pos, SYSV_DIRSIZE);
dir_put_page(page);
dir_put_page(page, de);
dir->i_mtime = dir->i_ctime = current_time(dir);
mark_inode_dirty(dir);
sysv_handle_dirsync(inode);
}
/*
* Calls to dir_get_page()/dir_put_page() must be nested according to the
* rules documented in mm/highmem.rst.
*
* sysv_dotdot() acts as a call to dir_get_page() and must be treated
* accordingly for nesting purposes.
*/
struct sysv_dir_entry *sysv_dotdot(struct inode *dir, struct page **p)
{
struct sysv_dir_entry *de = dir_get_page(dir, 0, p);
......@@ -362,7 +378,7 @@ ino_t sysv_inode_by_name(struct dentry *dentry)
if (de) {
res = fs16_to_cpu(SYSV_SB(dentry->d_sb), de->inode);
dir_put_page(page);
dir_put_page(page, de);
}
return res;
}
......@@ -251,9 +251,9 @@ static int sysv_rename(struct user_namespace *mnt_userns, struct inode *old_dir,
out_dir:
if (dir_de)
dir_put_page(dir_page);
dir_put_page(dir_page, dir_de);
out_old:
dir_put_page(old_page);
dir_put_page(old_page, old_de);
out:
return err;
}
......
......@@ -148,7 +148,7 @@ extern void sysv_destroy_icache(void);
/* dir.c */
extern void dir_put_page(struct page *page);
extern void dir_put_page(struct page *page, void *vaddr);
extern struct sysv_dir_entry *sysv_find_entry(struct dentry *, struct page **);
extern int sysv_add_link(struct dentry *, struct inode *);
extern int sysv_delete_entry(struct sysv_dir_entry *, struct page *);
......
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