如何在文件句柄结构中实现引用计数?

问题描述

我有一个作业,要求实现一些与文件相关的syscall以及实现它们所需的结构(即文件表和文件句柄)。

这是我对结构的实现:

// File Handle -------------------------

struct filehandle {
    int flags;
    off_t offset;
    unsigned int refcount;
    struct vnode *vnode_ptr;
    struct lock *offset_lock;
    struct spinlock refcount_spinlock;

    // flags:
    //     bit 0: O_RDONLY or O_WRONLY
    //     bit 1: O_RDWR
    //     bit 2: is_seekable
};

struct filehandle *filehandle_create(void);
void filehandle_destroy(struct filehandle *);

void filehandle_incref(struct filehandle *);
void filehandle_decref(struct filehandle *);

// File Table --------------------------

struct filetable {
    struct filehandle **table;
    struct lock **fh_locks;
    struct rwlock *table_lock;    
    int search_start_index;
    int capacity;
};

struct filetable *filetable_create(void);
void filetable_destroy(struct filetable *);

void filetable_incref_all(struct filetable *);
void filetable_decref_all(struct filetable *);

int get_min_available_fd(struct filetable *,int *);
void reclaim_fd(struct filetable *,int);

void filetable_lock_handle(struct filetable *,int);
void filetable_unlock_handle(struct filetable *,int);
void filetable_lock(struct filetable *);
void filetable_unlock(struct filetable *);

在结构filehandle中,我有一个用于更改引用计数的自旋锁。但是我相信这里有问题。

这是sys_close系统调用的实现:

void sys_close_nocheck(int fd)
{
    struct filehandle **handles = curproc->ft->table;

    filehandle_decref(handles[fd]);
    
    // Todo accessing the refcount 
    // without acquiring the lock.
    if (handles[fd]->refcount == 0) {
        filehandle_destroy(handles[fd]);
    }

    handles[fd] = NULL;
}

int sys_close(int fd)
{
    // when to return EIO?..
    
    struct filetable *ft  = curproc->ft;

    if (fd < 0 || fd >= ft->capacity) {
        return EBADF;
    }

    filetable_lock_handle(ft,fd);

    if (ft->table[fd] == NULL) {
        filetable_unlock_handle(ft,fd);
        return EBADF;
    }

    sys_close_nocheck(fd);

    filetable_unlock_handle(ft,fd);

    return 0;
}

这是sys_dup2函数的实现:

int sys_dup2(int oldfd,int newfd,int *ret_fd)
{
    // when to return EMFILE or ENFILE?...

    struct filetable *ft = curproc->ft;

    if (oldfd < 0 || newfd < 0 || 
        oldfd >= ft->capacity || newfd >= ft->capacity) {
        return EBADF;
    }

    filetable_lock_handle(ft,oldfd);

    if (ft->table[oldfd] == NULL) {
        filetable_unlock_handle(ft,oldfd);
        return EBADF;
    }
        
    *ret_fd = newfd;

    if (oldfd == newfd) {
        filetable_unlock_handle(ft,oldfd);
        return 0;
    }

    filetable_lock_handle(ft,newfd);

    if (ft->table[newfd] != NULL) {
        sys_close_nocheck(newfd);
    }

    struct filehandle *fh = ft->table[oldfd];

    filehandle_incref(fh);

    ft->table[newfd] = fh;

    filetable_unlock_handle(ft,oldfd);
    filetable_unlock_handle(ft,newfd);

    return 0;
}

我要在refcount函数中递增sys_dup2,并在sys_close中递减。但是请注意,在sys_close中,我正在检查refcount是否为零,如果为零,则销毁文件句柄。但这是在没有获取自旋锁的情况下完成的,这意味着有可能在refcount的条件评估为true之后,可能会调度线程并且可以继续进行另一个sys_dup2调用,复制filehandle和之后,带有sys_close的线程被调度,并销毁filehandle,这使该代码不可靠。

由于两个原因,我无法在检查之前一直按住旋转锁:

1-因为它是文件句柄的一部分,并且在销毁之前无法获取

2-如果条件为true并且调用函数filehandle_destroy,则效率低下,应使用睡眠锁代替自旋锁。但是我相信我也不应该使用睡眠锁,因为在大多数情况下,该锁只能保护一行代码(例如filehandle->refcount++)。

该设计应如何处理,以使其牢固并不会因某些不良行为而受到影响?

如果有人需要查看功能的实现,则为:

struct filehandle *filehandle_create()
{
    struct filehandle *fh;
    fh = kmalloc(sizeof(*fh));

    if (fh == NULL) {
        return NULL;
    }

    fh->offset_lock = lock_create("offset_lock");
    if (fh->offset_lock == NULL) {
        kfree(fh);
        return NULL;
    }

    spinlock_init(&(fh->refcount_spinlock));

    fh->refcount = 1;
    fh->offset = 0;
    fh->vnode_ptr = NULL;
    fh->flags = 0;

    return fh;
}

void filehandle_destroy(struct filehandle *fh)
{   
    //Todo check the cleaning and freeing.

    KASSERT(fh != NULL);
    KASSERT(fh->offset_lock != NULL);

    if (fh->vnode_ptr != NULL) {
        vfs_close(fh->vnode_ptr);
    }

    lock_destroy(fh->offset_lock);
    spinlock_cleanup(&(fh->refcount_spinlock));

    kfree(fh);
}

void filehandle_incref(struct filehandle *fh)
{
    spinlock_acquire(&(fh->refcount_spinlock));
    fh->refcount++;
    spinlock_release(&(fh->refcount_spinlock));
}

void filehandle_decref(struct filehandle *fh)
{
    spinlock_acquire(&(fh->refcount_spinlock));
    KASSERT(fh->refcount > 0);
    fh->refcount--;
    spinlock_release(&(fh->refcount_spinlock));
}

struct filetable *filetable_create()
{
    struct filetable *ft;
    ft = kmalloc(sizeof(*ft));

    if (ft == NULL) {
        return NULL;
    }

    ft->capacity = FILETABLE_SIZE;

    ft->table = kmalloc(ft->capacity * sizeof(struct filehandle *));
    if (ft->table == NULL) {
        kfree(ft);
        return NULL;
    }

    ft->fh_locks = kmalloc(ft->capacity * sizeof(struct lock *));
    if (ft->table == NULL) {
        kfree(ft->table);
        kfree(ft);
        return NULL;
    }

    // Todo use custom names for easier debugging.
    ft->table_lock = rwlock_create("table_lock");
    if (ft->table_lock == NULL) {
        kfree(ft->table);
        kfree(ft->fh_locks);
        kfree(ft);
        return NULL;
    }

    for (int i = 0; i < ft->capacity; i++) {
        // Todo use custom names for easier debugging.
        ft->fh_locks[i] = lock_create("fh_lock");
        if (ft->fh_locks[i] == NULL) {

            for (int j = 0; j < i; j++) {
                lock_destroy(ft->fh_locks[i]);
            }

            rwlock_destroy(ft->table_lock);
            kfree(ft->table);
            kfree(ft->fh_locks);
            kfree(ft);
            return NULL;
        }
    }

    //Todo may use a function here (i.e. bzero)
    for (int i = 0; i < ft->capacity; i++) {
        ft->table[i] = NULL;
    }

    ft->search_start_index = 0;

    return ft;
}

void filetable_destroy(struct filetable *ft)
{
    KASSERT(ft != NULL);

    if (ft->table != NULL) {
        kfree(ft->table);
    }

    rwlock_destroy(ft->table_lock);

    if (ft->fh_locks != NULL) {
        for (int i = 0; i < ft->capacity; i++) {
            lock_destroy(ft->fh_locks[i]);
        }
        kfree(ft->fh_locks);
    }

    kfree(ft);
}

void filetable_incref_all(struct filetable *ft)
{
    for (int i = 0; i < ft->capacity; i++) {
        if (ft->table[i] != NULL) {
            ft->table[i]->refcount++;
        }
    }
}

void filetable_decref_all(struct filetable *ft)
{
    for (int i = 0; i < ft->capacity; i++) {
        if (ft->table[i] != NULL) {
            KASSERT(ft->table[i]->refcount > 0);
            ft->table[i]->refcount--;
        }
    }
}

int get_min_available_fd(struct filetable *ft,int *fd)
{
    for (int i = ft->search_start_index; i < ft->capacity; i++) {
        if (ft->table[i] == NULL) {
            ft->search_start_index = i + 1;
            *fd = i;
            return 0;
        }
    }

    return EMFILE;
}

void reclaim_fd(struct filetable *ft,int fd)
{
    KASSERT(fd >= 0);

    if (fd < ft->search_start_index) {
        ft->search_start_index = fd;
    }
}

void filetable_lock_handle(struct filetable *ft,int fd)
{
    // no check for the fd since this is called from 
    // a trusted source.

    // it's fine if the thread is descheduled after only
    // acquiring the rwlock and not the sleep lock since
    // the rwlock does nothing except for indicating that
    // there are threads using the table currently. if 
    // this thread is trying to lock a fh that is already
    // locked,it'll sleep,but no one will be able to 
    // acquire the global rw lock until all locks are 
    // released (including this one that is being waited
    // for). that means that the order in which the 
    // locks are acquired matters.

    rwlock_acquire_read(ft->table_lock);
    lock_acquire(ft->fh_locks[fd]);
}

void filetable_unlock_handle(struct filetable *ft,int fd)
{
    // does the order matter here?
    lock_release(ft->fh_locks[fd]);
    rwlock_release_read(ft->table_lock);
}

void filetable_lock(struct filetable *ft)
{
    rwlock_acquire_write(ft->table_lock);
}

void filetable_unlock(struct filetable *ft)
{
    rwlock_release_write(ft->table_lock);
}

编辑:

在@Tsyvarev的帮助下,我达到了一个我认为可能很好同步的地步。

sys_close

void sys_close_nocheck(int fd)
{
    struct filehandle **handles = curproc->ft->table;

    // will return 0 if the refcount is
    // 0 after decrementing it.
    if (!filehandle_decref(handles[fd])) {
        filehandle_destroy(handles[fd]);
    }

    handles[fd] = NULL;
}

int sys_close(int fd)
{
    // when to return EIO?..
    
    struct filetable *ft  = curproc->ft;

    if (fd < 0 || fd >= ft->capacity) {
        return EBADF;
    }

    filetable_lock_handle(ft,fd);

    return 0;
}

sys_dup2

int sys_dup2(int oldfd,int *ret_fd)
{
    // when to return EMFILE or ENFILE?...

    struct filetable *ft = curproc->ft;

    if (oldfd < 0 || newfd < 0 || 
        oldfd >= ft->capacity || newfd >= ft->capacity) {
        return EBADF;
    }

    bool are_the_same = false;

    if (oldfd < newfd) {
        filetable_lock_handle(ft,oldfd);
        filetable_lock_handle(ft,newfd);
    } else if (newfd < oldfd) {
        filetable_lock_handle(ft,newfd);
        filetable_lock_handle(ft,oldfd);
    } else {
        filetable_lock_handle(ft,oldfd);
        are_the_same = true;
    }

    struct filehandle *fh = ft->table[oldfd];

    // filehandle_incref would return 0 in 
    // case the refcount was 0 when trying
    // to increment it. if that's the case,// then this filehandle is in the middle
    // of being destroyed. Equivelent for 
    // having a NULL in this slot.
    if (fh == NULL || !filehandle_incref(fh)) {
        filetable_unlock_handle(ft,oldfd);
        if (!are_the_same) {
            filetable_unlock_handle(ft,newfd);
        }
        return EBADF;
    }
        
    *ret_fd = newfd;

    if (are_the_same) {
        filetable_unlock_handle(ft,oldfd);
        return 0;
    }

    if (ft->table[newfd] != NULL) {
        sys_close_nocheck(newfd);
    }

    ft->table[newfd] = fh;

    filetable_unlock_handle(ft,newfd);

    return 0;
}

filehandle_increffilehandle_decref

bool filehandle_incref(struct filehandle *fh)
{
    spinlock_acquire(&(fh->refcount_spinlock));

    if (fh->refcount == 0) {
        spinlock_release(&(fh->refcount_spinlock));
        return false;
    }

    fh->refcount++;

    spinlock_release(&(fh->refcount_spinlock));

    return true;
}

bool filehandle_decref(struct filehandle *fh)
{
    spinlock_acquire(&(fh->refcount_spinlock));

    KASSERT(fh->refcount > 0);

    fh->refcount--;
    bool

    result = (bool)fh->refcount;

    spinlock_release(&(fh->refcount_spinlock));

    return result;
}

我不确定情况是否如此。现在同步良好了吗?

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)