从 Java SocketChannel 到 TCP 挥手:一次 close() 的完整调用链与引用计数解析

在 Java 里写下一行 channel.close(),我们通常不会去想它背后发生了什么。但这行代码要穿过 Java 状态机、JNI 边界、glibc 的系统调用包装、Linux 内核的 fdtable 管理、struct file 引用计数、Socket 抽象层和 TCP 协议栈,最终才让一个连接进入四次挥手。本文以一组横跨 Java、JNI、glibc 与 Linux 内核的源码为线索,把这条链路一环一环串起来,并重点澄清两个容易混淆的问题:file->f_op->release 到底是不是 inet_release?struct file 的引用计数又是在哪里被初始化的?


一、Java 层:关闭不是"立刻关掉"

Java NIO 的 SocketChannelImpl 内部维护一个状态机(ST_OPENED → ST_CLOSING → ST_CLOSED),关闭动作被拆成了两步:先把状态置为 ST_CLOSING,再尝试真正关闭。真正的关闭条件是"没有任何 I/O 正在进行,且没有注册到 Selector 上":

java

复制代码
/**
 * Closes the socket if there are no I/O operations in progress and the
 * channel is not registered with a Selector.
 */
private boolean tryClose() throws IOException {
    assert Thread.holdsLock(stateLock) && state == ST_CLOSING;

    // 三个条件同时满足才真正关闭:
    //   1. 没有读者线程正在使用该 fd
    //   2. 没有写者线程正在使用该 fd
    //   3. 没有注册在 Selector 上
    // 否则直接返回 false,由最后一个退出的 I/O 操作负责收尾
    if ((readerThread == 0) && (writerThread == 0) && !isRegistered()) {
        state = ST_CLOSED;
        nd.close(fd);      // 调用 NativeDispatcher 关闭文件描述符
        return true;
    } else {
        return false;
    }
}

这种设计避免了"一个线程还在 read() 另一个线程就把 fd 关了"的经典问题。nd 是 NativeDispatcher,对 Socket 来说通常是 SocketDispatcher(继承自 FileDispatcherImpl)。注意 FileDispatcherImpl.close0 的注释里写着 "Shared with SocketDispatcher and DatagramDispatcher but NOT used by FileDispatcherImpl"------这说明这个 close0 是给 Socket 和 Datagram 用的实现,普通文件的关闭走的是另一条路径。


二、JNI 与 glibc:进入内核前的最后一站

JNI 实现只做两件事:把 Java 的 FileDescriptor 对象转成整数 fd,然后调用 libc 的 close():

c

复制代码
JNIEXPORT void JNICALL
Java_sun_nio_ch_FileDispatcherImpl_close0(JNIEnv *env, jclass clazz, jobject fdo)
{
    jint fd = fdval(env, fdo);        // 从 FileDescriptor 对象中取出 int fd
    closeFileDescriptor(env, fd);
}

static void closeFileDescriptor(JNIEnv *env, int fd) {
    if (fd != -1) {                   // fd 已被关闭则直接跳过,防止重复 close
        int result = close(fd);       // 调用 glibc 的 close
        if (result < 0)
            JNU_ThrowIOExceptionWithLastError(env, "Close failed");
    }
}

glibc 里的 close 是一个弱别名,最终落到 __close:

c

复制代码
/* Close the file descriptor FD.  */
int
__close (int fd)
{
  return SYSCALL_CANCEL (close, fd);   // 发起 close 系统调用
}
libc_hidden_def (__close)
strong_alias (__close, __libc_close)
weak_alias (__close, close)            // 用户程序里的 close 就是它

SYSCALL_CANCEL 负责在发起系统调用前处理信号取消点等细节。到这一步,执行流正式从用户态切换到内核态。


三、内核 close:先摘 fd,再放 file

内核侧的 close 系统调用实现如下,注意那段 "Careful here!" 的注释:

c

复制代码
/*
 * Careful here! We test whether the file pointer is NULL before
 * releasing the fd. This ensures that one clone task can't release
 * an fd while another clone is opening it.
 */
SYSCALL_DEFINE1(close, unsigned int, fd)
{
    int retval;
    struct file *file;

    // 1. 从当前进程的 fdtable 中摘下 struct file,并清空该 fd 槽位
    file = file_close_fd(fd);
    if (!file)
        return -EBADF;                    // fd 无效

    // 2. 刷新文件(对 socket 而言通常无实际动作,但保留 VFS 语义)
    retval = filp_flush(file, current->files);

    /*
     * We're returning to user space. Don't bother
     * with any delayed fput() cases.
     */
    // 3. 同步减少引用计数,若归零则立即执行 __fput
    __fput_sync(file);

    /* can't restart close syscall because file table entry was cleared */
    // 4. 系统调用不可重启,把可重启错误码统一转成 -EINTR
    if (unlikely(retval == -ERESTARTSYS ||
                 retval == -ERESTARTNOINTR ||
                 retval == -ERESTARTNOHAND ||
                 retval == -ERESTART_RESTARTBLOCK))
        retval = -EINTR;

    return retval;
}

3.1 file_close_fd:为什么先摘 fd 再放 file

c

复制代码
struct file *file_close_fd(unsigned int fd)
{
    struct files_struct *files = current->files;
    struct file *file;

    spin_lock(&files->file_lock);
    file = file_close_fd_locked(files, fd);
    spin_unlock(&files->file_lock);

    return file;
}

struct file *file_close_fd_locked(struct files_struct *files, unsigned fd)
{
    struct fdtable *fdt = files_fdtable(files);
    struct file *file;

    lockdep_assert_held(&files->file_lock);

    if (fd >= fdt->max_fds)
        return NULL;

    fd = array_index_nospec(fd, fdt->max_fds);   // 防御 Spectre 的索引消毒
    file = fdt->fd[fd];
    if (file) {
        rcu_assign_pointer(fdt->fd[fd], NULL);   // ★ 先清空 fd 槽位
        __put_unused_fd(files, fd);              // ★ 再把 fd 号标记为可复用
    }
    return file;
}

close 的顺序是"先清 fd 槽位,再释放 file ",这一点对应注释里的 "one clone task can't release an fd while another clone is opening it"。clone 出来的线程共享 files_struct,如果先释放 file 再清 fd 槽位,另一个线程可能在这段窗口里通过 open() 复用同一个 fd 号,造成 fd 号与 struct file 对象生命周期错位。先摘槽位,就把 fd 号的生命周期与 struct file 的生命周期解耦了。

__put_unused_fd 还做了一件小事:把 files->next_fd 回退到较小的值,让下一次 open 优先复用低编号 fd。

c

复制代码
static void __put_unused_fd(struct files_struct *files, unsigned int fd)
{
    struct fdtable *fdt = files_fdtable(files);
    __clear_open_fd(fd, fdt);           // 清除 open_fds / full_fds_bits 位图
    if (fd < files->next_fd)
        files->next_fd = fd;            // 下次 open 优先复用这个较小的 fd 号
}

3.2 __fput_sync:为什么不走延迟路径

c

复制代码
/*
 * synchronous analog of fput(); for kernel threads that might be needed
 * in some umount() ... Use only if you really need it ...
 */
void __fput_sync(struct file *file)
{
    if (atomic_long_dec_and_test(&file->f_count))   // ★ 引用计数减一,归零才真正释放
        __fput(file);
}

普通的 fput() 会把 __fput 推迟到 task_work 里执行(所谓"延迟 fput"),而 close 系统调用即将返回用户态,没必要绕这一圈,于是直接用 __fput_sync。注意 atomic_long_dec_and_test 的语义:只有当 f_count 减到 0 时才返回 true。这意味着如果这个 file 还有别的引用(例如 dup() 出来的另一个 fd,或者 epoll 内部持有的引用),这里就不会触发 __fput,真正的释放留给最后一个持有者。


四、__fput:VFS 层清理与 release 调用

c

复制代码
/* the real guts of fput() - releasing the last reference to file */
static void __fput(struct file *file)
{
    struct dentry *dentry = file->f_path.dentry;
    struct vfsmount *mnt = file->f_path.mnt;
    struct inode *inode = file->f_inode;
    fmode_t mode = file->f_mode;

    if (unlikely(!(file->f_mode & FMODE_OPENED)))
        goto out;                        // 未真正完成 open 的 file 直接释放

    might_sleep();

    fsnotify_close(file);
    /*
     * The function eventpoll_release() should be the first called
     * in the file cleanup chain.
     */
    eventpoll_release(file);             // ★ 必须最先调用:从 epoll 中摘除引用
    locks_remove_file(file);             // 清理文件锁
    ima_file_free(file);                 // IMA 完整性度量清理

    if (unlikely(file->f_flags & FASYNC)) {
        if (file->f_op->fasync)
            file->f_op->fasync(-1, file, 0);   // 取消异步通知
    }

    // ★ 核心:调用文件操作集的 release
    if (file->f_op->release)
        file->f_op->release(inode, file);

    if (unlikely(S_ISCHR(inode->i_mode) && inode->i_cdev != NULL &&
                 !(mode & FMODE_PATH))) {
        cdev_put(inode->i_cdev);
    }
    fops_put(file->f_op);
    put_pid(file->f_owner.pid);
    put_file_access(file);
    dput(dentry);                        // 放掉 dentry 引用
    if (unlikely(mode & FMODE_NEED_UNMOUNT))
        dissolve_on_fput(mnt);
    mntput(mnt);                         // 放掉 vfsmount 引用
out:
    file_free(file);                     // 释放 struct file 本身
}

__fput 是整个旅程的枢纽:在它之前,大家操作的都是"fd 编号"和"struct file 指针";在它之后,真正属于该文件类型的释放逻辑才开始执行。eventpoll_release 被要求最先调用,是因为如果 file 还被 epoll 引用着,release 回调里触发的任何操作都可能踩到已经失效的 epoll 数据结构。


五、socket_file_ops:f_op->release 究竟是谁

__fput 里那行 file->f_op->release(inode, file),对 Socket 文件描述符来说,调用的是谁?用户提供的源码给出了决定性证据:

c

复制代码
static const struct file_operations socket_file_ops = {
    .owner =        THIS_MODULE,
    .llseek =       no_llseek,
    .read_iter =    sock_read_iter,
    .write_iter =   sock_write_iter,
    .poll =         sock_poll,
    .unlocked_ioctl = sock_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl = compat_sock_ioctl,
#endif
    .uring_cmd =    io_uring_cmd_sock,
    .mmap =         sock_mmap,
    .release =      sock_close,          // ★ 就是它!
    .fasync =       sock_fasync,
    .splice_write = splice_to_socket,
    .splice_read =  sock_splice_read,
    .splice_eof =   sock_splice_eof,
    .show_fdinfo =  sock_show_fdinfo,
};

当 socket 被创建时,alloc_file_pseudo → alloc_file 会把 socket_file_ops 赋给 file->f_op。因此,__fput 里调的 release 是 sock_close ,而不是 inet_release。

这里要澄清一个常见的误解。很多人以为 file->private_data 存着 struct socket,所以 sock_close 会走 file->private_data。实际上:

  • file->private_data 确实指向 struct socket,但它主要服务于 sock_poll、sock_ioctl、sock_mmap、sock_fasync 这些需要快速拿到 socket 的操作;

  • sock_close 走的是 SOCKET_I(inode) ,即从 inode 反推出 struct socket(struct socket 内嵌在 socket_alloc 中,SOCKET_I 用 container_of 零成本转换)。选择 inode 路径是因为 __sock_release 需要 inode_lock 来保护 socket 的释放过程。


六、Socket 抽象层与协议层:inet_release 终于登场

sock_close 调用 __sock_release,后者在持有 inode_lock 的情况下调用 sock->ops->release(sock)。sock->ops 是 struct proto_ops,对 INET 流式套接字来说是 inet_stream_ops,其 .release 正是 inet_release:

c

复制代码
int inet_release(struct socket *sock)
{
    struct sock *sk = sock->sk;

    if (sk) {
        long timeout;

        if (!sk->sk_kern_sock)
            BPF_CGROUP_RUN_PROG_INET_SOCK_RELEASE(sk);   // cgroup BPF 释放钩子

        /* Applications forget to leave groups before exiting */
        ip_mc_drop_socket(sk);                           // 清理多播组成员关系

        /*
         * If linger is set, we don't return until the close is complete.
         * ...
         * If the close is due to the process exiting, we never linger.
         */
        timeout = 0;
        if (sock_flag(sk, SOCK_LINGER) &&
            !(current->flags & PF_EXITING))              // 进程正在退出时不 linger
            timeout = sk->sk_lingertime;

        sk->sk_prot->close(sk, timeout);                 // ★ 进入协议层,如 tcp_close
        sock->sk = NULL;
    }
    return 0;
}
EXPORT_SYMBOL(inet_release);

到这里,TCP 状态机才真正开始执行四次挥手。sk->sk_prot->close 对 TCP 来说是 tcp_close,它负责把连接从 ESTABLISHED 推进到 FIN_WAIT_1,发出 FIN 包,并处理 SO_LINGER 语义。


七、引用计数从哪里来:alloc_file_pseudo 到底做了什么

前面反复出现 f_count 的递减,那么 f_count 是在哪里被初始化的?很多人看到 alloc_file_pseudo 里有一串 mntget、d_instantiate、ihold,就以为其中某一行在给 f_count 加一。其实不是。

c

复制代码
struct file *alloc_file_pseudo(struct inode *inode, struct vfsmount *mnt,
                                const char *name, int flags,
                                const struct file_operations *fops)
{
    struct qstr this = QSTR_INIT(name, strlen(name));
    struct path path;
    struct file *file;

    path.dentry = d_alloc_pseudo(mnt->mnt_sb, &this);  // 分配匿名 dentry
    if (!path.dentry)
        return ERR_PTR(-ENOMEM);
    path.mnt = mntget(mnt);                             // ★ 增加的是 vfsmount 的 mnt_count
    d_instantiate(path.dentry, inode);                  // 把 inode 挂到 dentry 上(不增加 i_count)
    file = alloc_file(&path, flags, fops);              // ★ f_count 在内部被初始化为 1
    if (IS_ERR(file)) {
        ihold(inode);                                   // ★ 失败路径补偿 inode 引用
        path_put(&path);                                // 释放 dentry + vfsmount
    }
    return file;
}

mntget 增加的是 vfsmount->mnt_count,与 file->f_count 无关;ihold 只在失败路径上补 inode 引用;d_instantiate 不增加 inode 引用。那 f_count 在哪?

c

复制代码
static struct file *alloc_file(const struct path *path, int flags,
                               const struct file_operations *fop)
{
    struct file *file;

    // ★ 新 file 对象在这里被分配,f_count 在内部被初始化为 1
    file = alloc_empty_file(flags, current_cred());
    if (IS_ERR(file))
        return file;

    file->f_path = *path;
    file->f_inode = path->dentry->d_inode;
    file->f_mapping = path->dentry->d_inode->i_mapping;
    // ... 省略若干字段填充 ...
    file->f_mode |= FMODE_OPENED;
    file->f_op = fop;                                    // ★ 绑定 socket_file_ops

    // ★ 注意:这是 inode 的"读计数",不是 file 的 f_count
    if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
        i_readcount_inc(path->dentry->d_inode);

    return file;
}

真正的初始化发生在 fs/file_table.c 的 __alloc_file() 里:

c

复制代码
static struct file *__alloc_file(int flags, const struct cred *cred)
{
    struct file *f;
    int error;

    f = kmem_cache_zalloc(filp_cachep, GFP_KERNEL);   // 从 slab 分配,清零
    if (unlikely(!f))
        return ERR_PTR(-ENOMEM);

    f->f_cred = get_cred(cred);                        // 增加 cred 引用
    error = security_file_alloc(f);
    if (unlikely(error)) {
        put_cred(f->f_cred);
        kmem_cache_free(filp_cachep, f);
        return ERR_PTR(error);
    }

    // ★★★ 就是这一行:新对象的引用计数被初始化为 1
    atomic_long_set(&f->f_count, 1);

    spin_lock_init(&f->f_lock);
    // ...
    return f;
}

f_count 是"新分配对象自带一个引用"被初始化为 1 的,而不是通过 get_file() 增加出来的。 这也解释了 __fput_sync 里那句 atomic_long_dec_and_test 配对的是什么------正是 __alloc_file 里这个初值 1。close 放掉的,就是 open/alloc 时新对象自带的那一个引用。

需要额外说明的是,"对已有对象增加引用"的函数是 get_file(),它定义在 include/linux/fs.h:

c

复制代码
/**
 * get_file - 增加文件的引用计数
 */
static inline struct file *get_file(struct file *f)
{
    atomic_long_inc(&f->f_count);   // 原子增加引用计数
    return f;
}

alloc_file_pseudo 之所以不调用它,是因为对象刚刚从 slab 里分配出来、尚未对外发布,直接 set(..., 1) 更清晰,也避免了对"0 引用对象"调用 get_file() 的争议(这正是内核社区讨论 get_file_rcu 的原因)。

把整个引用计数生命周期归纳一下:

操作 语义 源码位置
atomic_long_set(&f->f_count, 1) 新分配对象,持有者天然拥有 1 个引用 fs/file_table.c 的 __alloc_file()
get_file(f) / get_file_rcu(f) 对已有对象额外获取一个引用 include/linux/fs.h
fput(f) / __fput_sync(f) 放掉一个引用,归零时执行 __fput() fs/file_table.c

八、完整调用链

把前面的内容串起来,一次 Java SocketChannel.close() 的完整调用链如下:

text

复制代码
Java 层
  SocketChannelImpl.close() / tryClose()
    └─ nd.close(fd)
JNI 层
    └─ FileDispatcherImpl.close0(FileDescriptor)
        └─ Java_sun_nio_ch_FileDispatcherImpl_close0
            └─ closeFileDescriptor → close(fd)
glibc 层
                └─ __close → SYSCALL_CANCEL(close, fd)
内核 VFS 层
                    └─ SYSCALL_DEFINE1(close, fd)
                        ├─ file_close_fd(fd)              // 先摘 fd 槽位
                        ├─ filp_flush(file, ...)
                        └─ __fput_sync(file)              // f_count 减一
                            └─ __fput(file)               // 归零才进入
                                └─ file->f_op->release(inode, file)
Socket VFS 层(file_operations)
                                    └─ sock_close(inode, file)
                                        └─ __sock_release(SOCKET_I(inode), inode)
Socket 协议抽象层(proto_ops)
                                            └─ inet_release(sock)
协议实现层(proto)
                                                └─ tcp_close(sk, timeout)

与之对称的"获取"路径是:

text

复制代码
socket() 系统调用
  └─ sock_create → __sock_create
      └─ sock_alloc_file(sock, ...)
          └─ alloc_file_pseudo(inode, mnt, ..., &socket_file_ops)
              ├─ mntget(mnt)                    // mnt_count++
              ├─ d_instantiate(dentry, inode)
              └─ alloc_file(&path, flags, &socket_file_ops)
                  └─ alloc_empty_file → __alloc_file
                      └─ atomic_long_set(&f->f_count, 1)   // ★ f_count 初值

九、几个容易混淆的点

  1. file->f_op->release ≠ inet_release 。前者是 VFS 层的 socket_file_ops.release,即 sock_close;后者是 struct proto_ops.release,由 __sock_release 调用。两者之间隔着 sock_close → __sock_release 两层。

  2. file->private_data 确实是 struct socket ,但 sock_close 不用它,而是通过 SOCKET_I(inode) 获取。private_data 服务于 poll/ioctl/mmap/fasync 等操作。

  3. close 先摘 fd 再放 file 。这是为了在共享 files_struct 的多线程(clone)场景下避免 fd 号被复用导致的竞态,注释里的 "Careful here!" 说的正是这件事。

  4. __fput_sync 与 fput 的区别 。前者同步执行 __fput,适合 close 这种即将返回用户态、不需要延迟处理的场景;后者通过 task_work 延迟执行,避免在原子上下文中做可能睡眠的清理。

  5. alloc_file_pseudo 里没有"增加 f_count"的语句 。mntget 加的是 mount 计数,ihold 是失败路径补 inode 引用,i_readcount_inc 是 inode 读计数。f_count 的初值 1 来自 __alloc_file() 里的 atomic_long_set(&f->f_count, 1)。

  6. FMODE_OPENED 检查 。__fput 开头判断 file->f_mode & FMODE_OPENED,只有真正完成 open 的 file 才走完整释放流程,否则直接 file_free。

  7. eventpoll_release 必须最先调用 。如果 fd 还被 epoll 引用着,必须先摘除,否则后续 release 回调可能踩到已经失效的 epoll 结构。


十、结语

一行 SocketChannel.close(),背后是 Java 状态机、JNI 边界、glibc 系统调用包装、VFS fdtable 操作、struct file 引用计数、socket_file_ops、proto_ops、proto 四层抽象依次接力。理解这条链路,不仅能回答"f_op->release 到底是哪个函数""f_count 在哪里被初始化"这类具体问题,更能帮助我们在排查 fd 泄漏、epoll 悬挂、TCP 连接未正常挥手、SO_LINGER 行为异常等问题时,准确判断问题发生在哪一层。

下次在 Java 里写下 channel.close() 时,不妨想一想:这一行代码,已经替你走过了从用户态到内核协议栈的整段旅程。

#源码

cpp 复制代码
/**
     * Closes the socket if there are no I/O operations in progress and the
     * channel is not registered with a Selector.
     */
    private boolean tryClose() throws IOException {
        assert Thread.holdsLock(stateLock) && state == ST_CLOSING;
        if ((readerThread == 0) && (writerThread == 0) && !isRegistered()) {
            state = ST_CLOSED;
            nd.close(fd);
            return true;
        } else {
            return false;
        }
    }

void close(FileDescriptor fd) throws IOException {
        FileDispatcherImpl.close0(fd);
    }

// Shared with SocketDispatcher and DatagramDispatcher but
    // NOT used by FileDispatcherImpl
    static native void close0(FileDescriptor fd) throws IOException;

JNIEXPORT void JNICALL
Java_sun_nio_ch_FileDispatcherImpl_close0(JNIEnv *env, jclass clazz, jobject fdo)
{
    jint fd = fdval(env, fdo);
    closeFileDescriptor(env, fd);
}

static void closeFileDescriptor(JNIEnv *env, int fd) {
    if (fd != -1) {
        int result = close(fd);
        if (result < 0)
            JNU_ThrowIOExceptionWithLastError(env, "Close failed");
    }
}

JNIEXPORT void JNICALL
Java_sun_nio_ch_FileDispatcherImpl_close0(JNIEnv *env, jclass clazz, jobject fdo)
{
    jint fd = fdval(env, fdo);
    closeFileDescriptor(env, fd);
}

// instance method close0 for FileDescriptor
JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_close0(JNIEnv *env, jobject this) {
    fileDescriptorClose(env, this);
}

/* Close the file descriptor FD.  */
int
__close (int fd)
{
  return SYSCALL_CANCEL (close, fd);
}
libc_hidden_def (__close)
strong_alias (__close, __libc_close)
weak_alias (__close, close)

/*
 * Careful here! We test whether the file pointer is NULL before
 * releasing the fd. This ensures that one clone task can't release
 * an fd while another clone is opening it.
 */
SYSCALL_DEFINE1(close, unsigned int, fd)
{
	int retval;
	struct file *file;

	file = file_close_fd(fd);
	if (!file)
		return -EBADF;

	retval = filp_flush(file, current->files);

	/*
	 * We're returning to user space. Don't bother
	 * with any delayed fput() cases.
	 */
	__fput_sync(file);

	/* can't restart close syscall because file table entry was cleared */
	if (unlikely(retval == -ERESTARTSYS ||
		     retval == -ERESTARTNOINTR ||
		     retval == -ERESTARTNOHAND ||
		     retval == -ERESTART_RESTARTBLOCK))
		retval = -EINTR;

	return retval;
}

/*
 * synchronous analog of fput(); for kernel threads that might be needed
 * in some umount() (and thus can't use flush_delayed_fput() without
 * risking deadlocks), need to wait for completion of __fput() and know
 * for this specific struct file it won't involve anything that would
 * need them.  Use only if you really need it - at the very least,
 * don't blindly convert fput() by kernel thread to that.
 */
void __fput_sync(struct file *file)
{
	if (atomic_long_dec_and_test(&file->f_count))
		__fput(file);
}

/**
 * file_close_fd - return file associated with fd
 * @fd: file descriptor to retrieve file for
 *
 * Doesn't take a separate reference count.
 *
 * Returns: The file associated with @fd (NULL if @fd is not open)
 */
struct file *file_close_fd(unsigned int fd)
{
	struct files_struct *files = current->files;
	struct file *file;

	spin_lock(&files->file_lock);
	file = file_close_fd_locked(files, fd);
	spin_unlock(&files->file_lock);

	return file;
}

/**
 * file_close_fd_locked - return file associated with fd
 * @files: file struct to retrieve file from
 * @fd: file descriptor to retrieve file for
 *
 * Doesn't take a separate reference count.
 *
 * Context: files_lock must be held.
 *
 * Returns: The file associated with @fd (NULL if @fd is not open)
 */
struct file *file_close_fd_locked(struct files_struct *files, unsigned fd)
{
	struct fdtable *fdt = files_fdtable(files);
	struct file *file;

	lockdep_assert_held(&files->file_lock);

	if (fd >= fdt->max_fds)
		return NULL;

	fd = array_index_nospec(fd, fdt->max_fds);
	file = fdt->fd[fd];
	if (file) {
		rcu_assign_pointer(fdt->fd[fd], NULL);
		__put_unused_fd(files, fd);
	}
	return file;
}

static void __put_unused_fd(struct files_struct *files, unsigned int fd)
{
	struct fdtable *fdt = files_fdtable(files);
	__clear_open_fd(fd, fdt);
	if (fd < files->next_fd)
		files->next_fd = fd;
}

static inline void __clear_open_fd(unsigned int fd, struct fdtable *fdt)
{
	__clear_bit(fd, fdt->open_fds);
	__clear_bit(fd / BITS_PER_LONG, fdt->full_fds_bits);
}

/* the real guts of fput() - releasing the last reference to file
 */
static void __fput(struct file *file)
{
	struct dentry *dentry = file->f_path.dentry;
	struct vfsmount *mnt = file->f_path.mnt;
	struct inode *inode = file->f_inode;
	fmode_t mode = file->f_mode;

	if (unlikely(!(file->f_mode & FMODE_OPENED)))
		goto out;

	might_sleep();

	fsnotify_close(file);
	/*
	 * The function eventpoll_release() should be the first called
	 * in the file cleanup chain.
	 */
	eventpoll_release(file);
	locks_remove_file(file);

	ima_file_free(file);
	if (unlikely(file->f_flags & FASYNC)) {
		if (file->f_op->fasync)
			file->f_op->fasync(-1, file, 0);
	}
	if (file->f_op->release)
		file->f_op->release(inode, file);
	if (unlikely(S_ISCHR(inode->i_mode) && inode->i_cdev != NULL &&
		     !(mode & FMODE_PATH))) {
		cdev_put(inode->i_cdev);
	}
	fops_put(file->f_op);
	put_pid(file->f_owner.pid);
	put_file_access(file);
	dput(dentry);
	if (unlikely(mode & FMODE_NEED_UNMOUNT))
		dissolve_on_fput(mnt);
	mntput(mnt);
out:
	file_free(file);
}

/*
 *	The peer socket should always be NULL (or else). When we call this
 *	function we are destroying the object and from then on nobody
 *	should refer to it.
 */
int inet_release(struct socket *sock)
{
	struct sock *sk = sock->sk;

	if (sk) {
		long timeout;

		if (!sk->sk_kern_sock)
			BPF_CGROUP_RUN_PROG_INET_SOCK_RELEASE(sk);

		/* Applications forget to leave groups before exiting */
		ip_mc_drop_socket(sk);

		/* If linger is set, we don't return until the close
		 * is complete.  Otherwise we return immediately. The
		 * actually closing is done the same either way.
		 *
		 * If the close is due to the process exiting, we never
		 * linger..
		 */
		timeout = 0;
		if (sock_flag(sk, SOCK_LINGER) &&
		    !(current->flags & PF_EXITING))
			timeout = sk->sk_lingertime;
		sk->sk_prot->close(sk, timeout);
		sock->sk = NULL;
	}
	return 0;
}
EXPORT_SYMBOL(inet_release);

/*
 *	Socket files have a set of 'special' operations as well as the generic file ones. These don't appear
 *	in the operation structures but are done directly via the socketcall() multiplexor.
 */

static const struct file_operations socket_file_ops = {
	.owner =	THIS_MODULE,
	.llseek =	no_llseek,
	.read_iter =	sock_read_iter,
	.write_iter =	sock_write_iter,
	.poll =		sock_poll,
	.unlocked_ioctl = sock_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = compat_sock_ioctl,
#endif
	.uring_cmd =    io_uring_cmd_sock,
	.mmap =		sock_mmap,
	.release =	sock_close,
	.fasync =	sock_fasync,
	.splice_write = splice_to_socket,
	.splice_read =	sock_splice_read,
	.splice_eof =	sock_splice_eof,
	.show_fdinfo =	sock_show_fdinfo,
};

struct file *alloc_file_pseudo(struct inode *inode, struct vfsmount *mnt,
				const char *name, int flags,
				const struct file_operations *fops)
{
	struct qstr this = QSTR_INIT(name, strlen(name));
	struct path path;
	struct file *file;

	path.dentry = d_alloc_pseudo(mnt->mnt_sb, &this);
	if (!path.dentry)
		return ERR_PTR(-ENOMEM);
	path.mnt = mntget(mnt);
	d_instantiate(path.dentry, inode);
	file = alloc_file(&path, flags, fops);
	if (IS_ERR(file)) {
		ihold(inode);
		path_put(&path);
	}
	return file;
}
EXPORT_SYMBOL(alloc_file_pseudo);


/**
 * alloc_file - allocate and initialize a 'struct file'
 *
 * @path: the (dentry, vfsmount) pair for the new file
 * @flags: O_... flags with which the new file will be opened
 * @fop: the 'struct file_operations' for the new file
 */
static struct file *alloc_file(const struct path *path, int flags,
		const struct file_operations *fop)
{
	struct file *file;

	file = alloc_empty_file(flags, current_cred());
	if (IS_ERR(file))
		return file;

	file->f_path = *path;
	file->f_inode = path->dentry->d_inode;
	file->f_mapping = path->dentry->d_inode->i_mapping;
	file->f_wb_err = filemap_sample_wb_err(file->f_mapping);
	file->f_sb_err = file_sample_sb_err(file);
	if (fop->llseek)
		file->f_mode |= FMODE_LSEEK;
	if ((file->f_mode & FMODE_READ) &&
	     likely(fop->read || fop->read_iter))
		file->f_mode |= FMODE_CAN_READ;
	if ((file->f_mode & FMODE_WRITE) &&
	     likely(fop->write || fop->write_iter))
		file->f_mode |= FMODE_CAN_WRITE;
	file->f_iocb_flags = iocb_flags(file);
	file->f_mode |= FMODE_OPENED;
	file->f_op = fop;
	if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
		i_readcount_inc(path->dentry->d_inode);
	return file;
}
相关推荐
damoluomu1 小时前
国产 Java CMS 的协议和坑,讲清楚
java·开发语言
Wang's Blog1 小时前
Java 项目实战: 交友项目 - 黑名单:分页查询黑名单
java·开发语言·交友
Smoothcloud润云1 小时前
GPU算力平台跑知识库问答,模型回答总跑偏?先排查检索链路
人工智能·python·深度学习·gpu算力·gpu
mklpo1471 小时前
python3:如何按照实际执行需求导入模块,而不是启动时统一导入?(Python3.15)。2026-10-20
开发语言·人工智能·python
蚰蜒螟1 小时前
从 Java write 到磁盘扇区:一次写入操作的完整内核穿越之旅
java·开发语言
en.en..1 小时前
volatile 关键字整理(嵌入式 C 标准)
java·开发语言
captain3762 小时前
Spring IoC和DI(2)
java·后端·spring
Shaoshing2 小时前
高可用与核心概念
java·mq
小羊没烦恼!2 小时前
深入浅出SQL Server中的死锁
java·开发语言·前端·数据库·后端·spring·c#