RE: [PATCH] exfat: optimize dir-cache

Sungjong Seo
2020-05-26T02:36:59Z

> Optimize directory access based on exfat_entry_set_cache.
> - Hold bh instead of copied d-entry.
> - Modify bh->data directly instead of the copied d-entry.
> - Write back the retained bh instead of rescanning the d-entry-set.
> And
> - Remove unused cache related definitions.
>
> Signed-off-by: Tetsuhiro Kohada
> <kohada.tetsuhiro@dc.mitsubishielectric.co.jp>
> ---
> fs/exfat/dir.c | 197 +++++++++++++++++---------------------------
> fs/exfat/exfat_fs.h | 27 +++---
> fs/exfat/file.c | 15 ++--
> fs/exfat/inode.c | 53 +++++-------
> fs/exfat/namei.c | 14 ++--
> 5 files changed, 124 insertions(+), 182 deletions(-)
[snip]
>
> - es->entries[0].dentry.file.checksum = cpu_to_le16(chksum);
> +void exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync)
> +{
> + int i;
>
> - while (num_entries) {
> - /* write per sector base */
> - remaining_byte_in_sector = (1 << sb->s_blocksize_bits) -
off;
> - copy_entries = min_t(int,
> - EXFAT_B_TO_DEN(remaining_byte_in_sector),
> - num_entries);
> - bh = sb_bread(sb, sec);
> - if (!bh)
> - goto err_out;
> - memcpy(bh->b_data + off,
> - (unsigned char *)&es->entries[0] + buf_off,
> - EXFAT_DEN_TO_B(copy_entries));
> - exfat_update_bh(sb, bh, sync);
> - brelse(bh);
> - num_entries -= copy_entries;
> -
> - if (num_entries) {
> - /* get next sector */
> - if (exfat_is_last_sector_in_cluster(sbi, sec)) {
> - clu = exfat_sector_to_cluster(sbi, sec);
> - if (es->alloc_flag == ALLOC_NO_FAT_CHAIN)
> - clu++;
> - else if (exfat_get_next_cluster(sb, &clu))
> - goto err_out;
> - sec = exfat_cluster_to_sector(sbi, clu);
> - } else {
> - sec++;
> - }
> - off = 0;
> - buf_off += EXFAT_DEN_TO_B(copy_entries);
> - }
> + for (i = 0; i < es->num_bh; i++) {
> + if (es->modified)
> + exfat_update_bh(es->sb, es->bh[i], sync);

Overall, it looks good to me.
However, if "sync" is set, it looks better to return the result of
exfat_update_bh().
Of course, a tiny modification for exfat_update_bh() is also required.

> + brelse(es->bh[i]);
> }
> -
> - return 0;
> -err_out:
> - return -EIO;
> + kfree(es);
> }
>
> static int exfat_walk_fat_chain(struct super_block *sb, @@ -820,34
> +786,40 @@ static bool exfat_validate_entry(unsigned int type,
> }
> }
>
> +struct exfat_dentry *exfat_get_dentry_cached(
> + struct exfat_entry_set_cache *es, int num) {
> + int off = es->start_off + num * DENTRY_SIZE;
> + struct buffer_head *bh = es->bh[EXFAT_B_TO_BLK(off, es->sb)];
> + char *p = bh->b_data + EXFAT_BLK_OFFSET(off, es->sb);

In order to prevent illegal accesses to bh and dentries, it would be better
to check validation for num and bh.

> +
> + return (struct exfat_dentry *)p;
> +}
> +
> /*
> * Returns a set of dentries for a file or dir.
> *
> - * Note that this is a copy (dump) of dentries so that user should
> - * call write_entry_set() to apply changes made in this entry set
> - * to the real device.
> + * Note It provides a direct pointer to bh->data via
> exfat_get_dentry_cached().
> + * User should call exfat_get_dentry_set() after setting 'modified' to
> + apply
> + * changes made in this entry set to the real device.
> *
> * in:
[snip]
> /* check if the given file ID is opened */ @@ -153,12 +151,15 @@
> int __exfat_truncate(struct inode *inode, loff_t new_size)
> /* update the directory entry */
> if (!evict) {
> struct timespec64 ts;
> + struct exfat_dentry *ep, *ep2;
> + struct exfat_entry_set_cache *es;
>
> es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry,
> - ES_ALL_ENTRIES, &ep);
> + ES_ALL_ENTRIES);
> if (!es)
> return -EIO;
> - ep2 = ep + 1;
> + ep = exfat_get_dentry_cached(es, 0);
> + ep2 = exfat_get_dentry_cached(es, 1);
>
> ts = current_time(inode);
> exfat_set_entry_time(sbi, &ts,
> @@ -185,10 +186,8 @@ int __exfat_truncate(struct inode *inode, loff_t
> new_size)
> ep2->dentry.stream.start_clu = EXFAT_FREE_CLUSTER;
> }
>
> - if (exfat_update_dir_chksum_with_entry_set(sb, es,
> - inode_needs_sync(inode)))
> - return -EIO;
> - kfree(es);
> + exfat_update_dir_chksum_with_entry_set(es);
> + exfat_free_dentry_set(es, inode_needs_sync(inode));

It would be better to return the result of exfat_free_dentry_set().

> }
>
> /* cut off from the FAT chain */
> diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c index
> 3f367d081cd6..ef7cf7a6d187 100644
> --- a/fs/exfat/inode.c
> +++ b/fs/exfat/inode.c
> @@ -19,7 +19,6 @@
>
> static int __exfat_write_inode(struct inode *inode, int sync) {
> - int ret = -EIO;
> unsigned long long on_disk_size;
> struct exfat_dentry *ep, *ep2;
> struct exfat_entry_set_cache *es = NULL; @@ -43,11 +42,11 @@ static
> int __exfat_write_inode(struct inode *inode, int sync)
> exfat_set_vol_flags(sb, VOL_DIRTY);
>
> /* get the directory entry of given file or directory */
> - es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES,
> - &ep);
> + es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry,
> ES_ALL_ENTRIES);
> if (!es)
> return -EIO;
> - ep2 = ep + 1;
> + ep = exfat_get_dentry_cached(es, 0);
> + ep2 = exfat_get_dentry_cached(es, 1);
>
> ep->dentry.file.attr = cpu_to_le16(exfat_make_attr(inode));
>
> @@ -77,9 +76,9 @@ static int __exfat_write_inode(struct inode *inode, int
> sync)
> ep2->dentry.stream.valid_size = cpu_to_le64(on_disk_size);
> ep2->dentry.stream.size = ep2->dentry.stream.valid_size;
>
> - ret = exfat_update_dir_chksum_with_entry_set(sb, es, sync);
> - kfree(es);
> - return ret;
> + exfat_update_dir_chksum_with_entry_set(es);
> + exfat_free_dentry_set(es, sync);

Ditto

> + return 0;
> }
>
> int exfat_write_inode(struct inode *inode, struct writeback_control *wbc)
> @@ -110,8 +109,6 @@ static int exfat_map_cluster(struct inode *inode,
> unsigned int clu_offset,
> int ret, modified = false;
> unsigned int last_clu;
> struct exfat_chain new_clu;
> - struct exfat_dentry *ep;
> - struct exfat_entry_set_cache *es = NULL;
> struct super_block *sb = inode->i_sb;
> struct exfat_sb_info *sbi = EXFAT_SB(sb);
> struct exfat_inode_info *ei = EXFAT_I(inode); @@ -222,34 +219,28 @@
> static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
> num_clusters += num_to_be_allocated;
> *clu = new_clu.dir;
>
[snip]
> -
> - if (exfat_update_dir_chksum_with_entry_set(sb, es,
> - inode_needs_sync(inode)))
> - return -EIO;
> - kfree(es);
> + ep->dentry.stream.flags = ei->flags;
> + ep->dentry.stream.start_clu =
> + cpu_to_le32(ei->start_clu);
> + ep->dentry.stream.valid_size =
> + cpu_to_le64(i_size_read(inode));
> + ep->dentry.stream.size =
> + ep->dentry.stream.valid_size;
> +
> + exfat_update_dir_chksum_with_entry_set(es);
> + exfat_free_dentry_set(es, inode_needs_sync(inode));

Ditto

>
> } /* end of if != DIR_DELETED */
>
[snip]
> --
> 2.25.0