Linux内核中do_exit()函数如何释放未关闭的文件描述符

ChatGPT4o 使用 ChatGPT 4o 回答

问题:

linux内核中的do_exit()函数,是如何将没有被用户主动close的fd的相关资源调用task_work_run()函数进行释放的?

回答:

在 Linux 内核中,当一个进程退出时,do_exit() 函数负责清理和释放与该进程相关的资源,包括文件描述符(file descriptors, FDs)。do_exit() 函数会调用一系列的清理函数来确保所有资源都被正确释放。

以下是 do_exit() 函数如何处理未被用户主动关闭的文件描述符的详细过程:

  1. 调用 exit_files() 函数: 在 do_exit() 函数中,会调用 exit_files() 函数来处理文件描述符的清理工作。exit_files() 函数的定义如下:

    void exit_files(struct task_struct *tsk)
    {
        struct files_struct *files = tsk->files;
    
        if (files) {
            tsk->files = NULL;
            task_lock(tsk);
            tsk->files = NULL;
            task_unlock(tsk);
            put_files_struct(files);
        }
    }
    

    这里的 tsk 是指当前进程的 task_struct 结构体。tsk->files 是指向该进程的文件描述符表的指针。

  2. 调用 put_files_struct() 函数: 在 exit_files() 函数中,put_files_struct(files) 被调用来释放文件描述符表。put_files_struct() 函数的定义如下:

    void put_files_struct(struct files_struct *files)
    {
        if (atomic_dec_and_test(&files->count))
            __put_files_struct(files);
    }
    

    这里使用了原子操作来减少文件描述符表的引用计数,如果引用计数变为零,则调用 __put_files_struct() 函数。

  3. 调用 __put_files_struct() 函数: __put_files_struct() 函数负责实际的清理工作:

    void __put_files_struct(struct files_struct *files)
    {
        int i;
        struct fdtable *fdt;
    
        fdt = files_fdtable(files);
        for (i = 0; i < fdt->max_fds; i++) {
            struct file *f = fdt->fd[i];
            if (f) {
                fdt->fd[i] = NULL;
                filp_close(f, files);
            }
        }
        free_fdtable(fdt);
        kmem_cache_free(files_cachep, files);
    }
    

    这个函数遍历文件描述符表中的每一个文件描述符,并调用 filp_close() 函数来关闭每一个打开的文件描述符。

  4. 调用 filp_close() 函数: filp_close() 函数负责关闭单个文件描述符:

    int filp_close(struct file *filp, fl_owner_t id)
    {
        int retval = 0;
    
        if (!file_count(filp))
            return 0;
    
        if (filp->f_op->flush)
            retval = filp->f_op->flush(filp, id);
        dnotify_flush(filp, id);
        locks_remove_posix(filp, id);
        fput(filp);
    
        return retval;
    }
    

    这个函数会调用文件操作结构体中的 flush 方法(如果存在),然后调用 fput() 函数来减少文件对象的引用计数。

  5. 调用 fput() 函数: fput() 函数负责最终的文件对象释放:

    void fput(struct file *file)
    {
        if (atomic_long_dec_and_test(&file->f_count))
            __fput(file);
    }
    

    如果文件对象的引用计数变为零,则调用 __fput() 函数。

  6. 调用 __fput() 函数: __fput() 函数负责实际的文件对象释放:

    void __fput(struct file *file)
    {
        struct dentry *dentry = file->f_path.dentry;
        struct vfsmount *mnt = file->f_path.mnt;
    
        if (file->f_op->release)
            file->f_op->release(inode, file);
        dput(dentry);
        mntput(mnt);
        kmem_cache_free(filp_cachep, file);
    }
    

    这个函数会调用文件操作结构体中的 release 方法(如果存在),然后释放与文件对象相关的其他资源。

通过上述步骤,do_exit() 函数确保了所有未被用户主动关闭的文件描述符在进程退出时都被正确关闭和释放。task_work_run() 函数在这个过程中并没有直接参与文件描述符的清理工作。