mirror of
https://github.com/versity/scoutfs.git
synced 2026-07-20 15:02:21 +00:00
7b18bce2e2
Now that we have a fixed small block size we don't need our own code for tracking contiguous memory for blocks that are larger than the page size. We can use buffer heads which support block sizes smaller than the page size. Our block API remains to enforce transactions, cheksumming, cow, and eventually invalidating and retrying reads of stale bloks. We set the logical blocksize of the bdev buffer cache to our fixed block size. We use a private bh state bit to indicate that the contents of a block have had their checksum verified. We use a small structure stored at b_private to track dirty blocks so that we can control when they're written. The btree block traversal code uses the buffer_head lock to serialize access to btree block contents now that the block rwsem has gone away. This isn't great but works for now. Not being able to relocate blocks in the buffer cache (really fragments of pages in the bdev page cache.. blkno determines memory location) means that the cow path always has to copy. Callers are easily translated: use struct buffer_head instead of scoutfs_block and use a little helper instead of dereferencing ->data directly. I took the opportunity to clean up some of the inconsistent block function names. Now more of the functions follow the scoutfs_block_*() pattern. Signed-off-by: Zach Brown <zab@versity.com>
181 lines
5.0 KiB
C
181 lines
5.0 KiB
C
/*
|
|
* Copyright (C) 2016 Versity Software, Inc. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public
|
|
* License v2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/atomic.h>
|
|
|
|
#include "super.h"
|
|
#include "block.h"
|
|
#include "trans.h"
|
|
#include "buddy.h"
|
|
#include "scoutfs_trace.h"
|
|
|
|
/*
|
|
* scoutfs metadata blocks are written in atomic transactions.
|
|
*
|
|
* Writers hold transactions to dirty blocks. The transaction can't be
|
|
* written until these active writers release the transaction. We don't
|
|
* track the relationships between dirty blocks so there's only ever one
|
|
* transaction being built.
|
|
*
|
|
* The copy of the on-disk super block in the fs sb info has its header
|
|
* sequence advanced so that new dirty blocks inherit this dirty
|
|
* sequence number. It's only advanced once all those dirty blocks are
|
|
* reachable after having first written them all out and then the new
|
|
* super with that seq. It's first incremented at mount.
|
|
*
|
|
* Unfortunately writers can nest. We don't bother trying to special
|
|
* case holding a transaction that you're already holding because that
|
|
* requires per-task storage. We just let anyone hold transactions
|
|
* regardless of waiters waiting to write, which risks waiters waiting a
|
|
* very long time.
|
|
*/
|
|
|
|
/*
|
|
* It's critical that this not try to perform IO if there's nothing
|
|
* dirty. The sync at unmount can have this work scheduled after sync
|
|
* returns and the unmount path starts to tear down supers and block
|
|
* devices. We have to safely detect that there's nothing to do using
|
|
* nothing in the vfs.
|
|
*/
|
|
void scoutfs_trans_write_func(struct work_struct *work)
|
|
{
|
|
struct scoutfs_sb_info *sbi = container_of(work, struct scoutfs_sb_info,
|
|
trans_write_work);
|
|
struct super_block *sb = sbi->sb;
|
|
bool advance = false;
|
|
int ret = 0;
|
|
|
|
wait_event(sbi->trans_hold_wq,
|
|
atomic_cmpxchg(&sbi->trans_holds, 0, -1) == 0);
|
|
|
|
/* XXX probably want to write out dirty pages in inodes */
|
|
|
|
ret = scoutfs_block_write_dirty(sb);
|
|
if (ret > 0) {
|
|
ret = scoutfs_write_dirty_super(sb);
|
|
if (!ret)
|
|
advance = 1;
|
|
}
|
|
|
|
spin_lock(&sbi->trans_write_lock);
|
|
if (advance)
|
|
scoutfs_advance_dirty_super(sb);
|
|
sbi->trans_write_count++;
|
|
sbi->trans_write_ret = ret;
|
|
spin_unlock(&sbi->trans_write_lock);
|
|
wake_up(&sbi->trans_write_wq);
|
|
|
|
atomic_set(&sbi->trans_holds, 0);
|
|
wake_up(&sbi->trans_hold_wq);
|
|
}
|
|
|
|
struct write_attempt {
|
|
u64 seq;
|
|
u64 count;
|
|
int ret;
|
|
};
|
|
|
|
/* this is called as a wait_event() condition so it can't change task state */
|
|
static int write_attempted(struct scoutfs_sb_info *sbi,
|
|
struct write_attempt *attempt)
|
|
{
|
|
int done = 1;
|
|
|
|
spin_lock(&sbi->trans_write_lock);
|
|
if (le64_to_cpu(sbi->super.hdr.seq) > attempt->seq)
|
|
attempt->ret = 0;
|
|
else if (sbi->trans_write_count > attempt->count)
|
|
attempt->ret = sbi->trans_write_ret;
|
|
else
|
|
done = 0;
|
|
spin_unlock(&sbi->trans_write_lock);
|
|
|
|
return done;
|
|
}
|
|
|
|
/*
|
|
* sync records the current dirty seq and write count and waits for
|
|
* either to change. If there's nothing to write or the write returned
|
|
* an error then only the write count advances and sets the appropriate
|
|
* return code.
|
|
*/
|
|
int scoutfs_sync_fs(struct super_block *sb, int wait)
|
|
{
|
|
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
|
struct write_attempt attempt;
|
|
int ret;
|
|
|
|
if (!wait) {
|
|
schedule_work(&sbi->trans_write_work);
|
|
return 0;
|
|
}
|
|
|
|
spin_lock(&sbi->trans_write_lock);
|
|
attempt.seq = le64_to_cpu(sbi->super.hdr.seq);
|
|
attempt.count = sbi->trans_write_count;
|
|
spin_unlock(&sbi->trans_write_lock);
|
|
|
|
schedule_work(&sbi->trans_write_work);
|
|
|
|
ret = wait_event_interruptible(sbi->trans_write_wq,
|
|
write_attempted(sbi, &attempt));
|
|
if (ret == 0)
|
|
ret = attempt.ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int scoutfs_hold_trans(struct super_block *sb)
|
|
{
|
|
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
|
|
|
return wait_event_interruptible(sbi->trans_hold_wq,
|
|
atomic_add_unless(&sbi->trans_holds, 1, -1));
|
|
}
|
|
|
|
void scoutfs_release_trans(struct super_block *sb)
|
|
{
|
|
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
|
|
|
if (atomic_sub_return(1, &sbi->trans_holds) == 0)
|
|
wake_up(&sbi->trans_hold_wq);
|
|
}
|
|
|
|
int scoutfs_setup_trans(struct super_block *sb)
|
|
{
|
|
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
|
|
|
sbi->trans_write_workq = alloc_workqueue("scoutfs_trans", 0, 1);
|
|
if (!sbi->trans_write_workq)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* kill_sb calls sync before getting here so we know that dirty data
|
|
* should be in flight. We just have to wait for it to quiesce.
|
|
*/
|
|
void scoutfs_shutdown_trans(struct super_block *sb)
|
|
{
|
|
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
|
|
|
if (sbi->trans_write_workq) {
|
|
flush_work(&sbi->trans_write_work);
|
|
destroy_workqueue(sbi->trans_write_workq);
|
|
}
|
|
}
|