ngx_open_cached_file

📅 2026/6/30 6:53:53
ngx_open_cached_file
1 定义ngx_open_cached_file 函数 定义在 src/core/ngx_open_file_cache.c2 作用ngx_open_cached_file 是 Nginx 文件缓存的核心执行函数。 它的设计目标是 在高并发场景下 通过 缓存 文件元数据和文件描述符 减少不必要的系统调用stat、open从而提升静态文件服务性能。 同时它需要保证缓存的一致性当文件被修改或删除时能及时感知 并支持多 worker 进程间的安全访问。3 详解1 函数签名ngx_int_tngx_open_cached_file(ngx_open_file_cache_t*cache,ngx_str_t*name,ngx_open_file_info_t*of,ngx_pool_t*pool)1. 返回值类型ngx_int_t用于统一表示操作状态码。NGX_OK文件操作成功NGX_ERROR文件操作失败。具体的错误原因记录在 of-err 中简单的二值返回值成功/失败让调用者的主逻辑清晰而丰富的错误信息则通过 of 输出参数传递。这种分离设计使得错误处理可以非常细致而正常路径的代码保持简洁。返回值仅表示 “调用是否正常完成”而不直接等于“文件是否存在”。调用者必须结合 of-err 和返回值一起判断。这避免了返回值语义过载2. 函数名ngx_open_cached_file前缀ngx_Nginx 函数的标准前缀表明它属于 Nginx 内部实现。open_cached_file精确描述了函数的功能——通过缓存机制来“打开”一个文件。“打开”是一个广义概念根据of-test_only的设置它可能只获取文件元数据stat也可能真正打开文件open并返回文件描述符。“缓存”意味着如果文件信息已经在缓存中且有效则直接返回缓存数据避免重复的系统调用。整体语义该函数是 Nginx 文件访问的统一入口它将文件系统操作与内置的缓存策略无缝结合对调用者屏蔽了缓存命中、失效、更新的复杂逻辑。无论调用者需要“仅测试文件是否存在”还是“真正打开文件供读取”都可以通过这个函数完成。3. 第一个参数ngx_open_file_cache_t *cache要使用的缓存管理对象管理着 文件缓存 所需的所有数据结构和管理信息函数内部会使用cache中的红黑树进行文件查找当调用者传入 NULL 时表示完全不使用缓存。函数会退化为每次都直接进行系统调用stat 或 open。这为那些不需要缓存或只是临时测试文件是否存在的场景提供了轻量路径。缓存作为可选加速层函数接收一个ngx_open_file_cache_t *cache参数该参数可以为NULL。这一设计将缓存机制 与核心文件访问逻辑解耦调用者可以选择使用缓存传入有效cache指针也可以完全绕过缓存传入NULL。这带来的好处是对于不需要缓存的临时文件访问例如仅测试文件是否存在的test_only模式可以避免额外的缓存管理开销。每个location可以通过open_file_cache指令独立配置自己的缓存实例互不干扰。函数内部根据cache是否为NULL分为两大分支1 无缓存分支直接调用底层系统调用或 Nginx 封装的 I/O 函数并注册清理回调来管理文件描述符的生命周期。2 有缓存分支通过红黑树查找缓存条目并根据缓存条目状态和配置决定是直接复用、重新验证还是重新创建。4. 第二个参数ngx_str_t *name表示要访问的文件完整路径。5. 第三个参数ngx_open_file_info_t *of这是函数最复杂的参数同时承担输入和输出的角色。作为输入调用者→函数 —— 传递控制信息告诉函数 如何 执行文件操作。作为输出函数→调用者 —— 传递执行结果报告 操作完成后 文件的信息。这种将“请求参数”和“响应数据”封装在同一个结构体中的设计是 Nginx 中常见的“请求-响应”模式既避免了函数参数过多又保证了接口的稳定性和可扩展性。输入侧的重要字段包括test_only若为 1仅通过stat获取文件信息不真正打开文件。这用于索引模块快速扫描多个索引文件如index.html、index.php时避免浪费文件描述符。valid缓存条目的有效期秒。超过有效期的条目需要重新验证。min_uses文件至少需要被访问多少次才能保留在缓存中用于淘汰不常用文件。errors是否缓存错误状态如文件不存在。read_ahead、directio与文件传输优化相关的 I/O 策略。输出侧的重要字段包括fd如果文件被实际打开存储有效的文件描述符。err操作失败时的系统错误码如ENOENT。uniq通常为 inode、mtime、size文件的唯一标识、修改时间、大小。is_dir、is_file、is_link、is_exec文件类型标志。6. 第四个参数ngx_pool_t *pool要使用的内存池2 逻辑流程1 无缓存模式 直接访问并注册清理 当调用者没有配置文件缓存即 open_file_cache off; 或未配置时 进入此分支。该分支完全不使用缓存,每次都直接进行系统调用。 1-1 仅测试文件是否存在 test_only 为真 调用 ngx_file_info_wrapper获取文件元数据不打开文件不注册清理。 如果失败返回 NGX_FILE_ERROR 直接返回 NGX_ERROR错误信息已记录在 of-err 中 如果成功将 fi 中的文件信息提取出来 填充到 of 的相应字段然后返回 NGX_OK。 此时 不会 打开文件不会占用文件描述符。 这主要用于索引模块快速测试多个索引文件 1-2 需要真正打开文件 test_only 为假需要真正打开文件。 首先 注册一个清理回调。 然后调用 实际打开文件并获取信息。 如果成功且文件非目录则将文件描述符等信息填入清理回调数据 确保请求结束时自动关闭文件描述符。 这里将文件描述符的生命周期与请求的内存池绑定 无需手动关闭框架保证在任何情况下正常结束、异常跳转文件描述符都会被正确释放。 2 有缓存模式 注册缓存清理回调。 计算哈希查找文件 如果找到 更新访问统计处理描述符关闭情况。 检查缓存是否有效 有效则直接返回缓存数据 无效则重新获取文件状态对比新旧状态决定更新、添加事件或创建新条目。 如果未找到 直接打开/获取文件信息然后创建新条目。 在合适的时机注册文件事件保持缓存一致性。 在返回前更新访问时间、调整 LRU 队列、设置清理回调。 3 失败清理 任何失败路径都通过 failed 标签进行统一的资源清理确保不泄漏文件描述符和内存。{time_tnow;uint32_thash;ngx_int_trc;ngx_file_info_tfi;ngx_pool_cleanup_t*cln;ngx_cached_open_file_t*file;ngx_pool_cleanup_file_t*clnf;ngx_open_file_cache_cleanup_t*ofcln;局部变量声明of-fdNGX_INVALID_FILE;of-err0;初始化of的输出字段1 无缓存模式if(cacheNULL){1-1 仅测试文件是否存在if(of-test_only){if(ngx_file_info_wrapper(name,of,fi,pool-log)NGX_FILE_ERROR){returnNGX_ERROR;}of-uniqngx_file_uniq(fi);of-mtimengx_file_mtime(fi);of-sizengx_file_size(fi);of-fs_sizengx_file_fs_size(fi);of-is_dirngx_is_dir(fi);of-is_filengx_is_file(fi);of-is_linkngx_is_link(fi);of-is_execngx_is_exec(fi);returnNGX_OK;}获取文件元数据ngx_file_info_wrapper失败,直接返回 NGX_ERROR表示操作失败成功,提取文件信息,填充到 of 的相应字段,作为输出结果返回 NGX_OK表示操作成功1-2 需要真正打开文件clnngx_pool_cleanup_add(pool,sizeof(ngx_pool_cleanup_file_t));if(clnNULL){returnNGX_ERROR;}rcngx_open_and_stat_file(name,of,pool-log);if(rcNGX_OK!of-is_dir){cln-handlerngx_pool_cleanup_file;clnfcln-data;clnf-fdof-fd;clnf-namename-data;clnf-logpool-log;}returnrc;}实际打开文件首先在请求的内存池 pool 中注册一个清理回调 ngx_pool_cleanup_file。这个回调会在请求结束时自动关闭文件描述符防止资源泄漏。调用 ngx_open_and_stat_file(name, of, pool-log)该函数会真正调用 open 系统调用打开文件并同时获取文件的元数据fstat。结果成功/失败、错误码存入 of。如果打开成功且文件不是目录则把清理回调的函数指针设为 ngx_pool_cleanup_file并将文件描述符、文件名、日志对象存入清理数据这样请求结束时就会自动关闭这个文件描述符。最后返回 ngx_open_and_stat_file 的结果NGX_OK 或 NGX_ERROR。2 有缓存模式clnngx_pool_cleanup_add(pool,sizeof(ngx_open_file_cache_cleanup_t));if(clnNULL){returnNGX_ERROR;}nowngx_time();hashngx_crc32_long(name-data,name-len);filengx_open_file_lookup(cache,name,hash);if(file){file-uses;ngx_queue_remove(file-queue);if(file-fdNGX_INVALID_FILEfile-err0!file-is_dir){/* file was not used often enough to keep open */rcngx_open_and_stat_file(name,of,pool-log);if(rc!NGX_OK(of-err0||!of-errors)){gotofailed;}gotoadd_event;}if(file-use_event||(file-eventNULL(of-uniq0||of-uniqfile-uniq)now-file-createdof-valid#if(NGX_HAVE_OPENAT)of-disable_symlinksfile-disable_symlinksof-disable_symlinks_fromfile-disable_symlinks_from#endif)){if(file-err0){of-fdfile-fd;of-uniqfile-uniq;of-mtimefile-mtime;of-sizefile-size;of-is_dirfile-is_dir;of-is_filefile-is_file;of-is_linkfile-is_link;of-is_execfile-is_exec;of-is_directiofile-is_directio;if(!file-is_dir){file-count;ngx_open_file_add_event(cache,file,of,pool-log);}}else{of-errfile-err;#if(NGX_HAVE_OPENAT)of-failedfile-disable_symlinks?ngx_openat_file_n:ngx_open_file_n;#elseof-failedngx_open_file_n;#endif}gotofound;}ngx_log_debug4(NGX_LOG_DEBUG_CORE,pool-log,0,retest open file: %s, fd:%d, c:%d, e:%d,file-name,file-fd,file-count,file-err);if(file-is_dir){/* * chances that directory became file are very small * so test_dir flag allows to use a single syscall * in ngx_file_info() instead of three syscalls */of-test_dir1;}of-fdfile-fd;of-uniqfile-uniq;rcngx_open_and_stat_file(name,of,pool-log);if(rc!NGX_OK(of-err0||!of-errors)){gotofailed;}if(of-is_dir){if(file-is_dir||file-err){gotoupdate;}/* file became directory */}elseif(of-err0){/* file */if(file-is_dir||file-err){gotoadd_event;}if(of-uniqfile-uniq){if(file-event){file-use_event1;}of-is_directiofile-is_directio;gotoupdate;}/* file was changed */}else{/* error to cache */if(file-err||file-is_dir){gotoupdate;}/* file was removed, etc. */}if(file-count0){ngx_open_file_del_event(file);if(ngx_close_file(file-fd)NGX_FILE_ERROR){ngx_log_error(NGX_LOG_ALERT,pool-log,ngx_errno,ngx_close_file_n \%V\ failed,name);}gotoadd_event;}ngx_rbtree_delete(cache-rbtree,file-node);cache-current--;file-close1;gotocreate;}/* not found */rcngx_open_and_stat_file(name,of,pool-log);if(rc!NGX_OK(of-err0||!of-errors)){gotofailed;}create:if(cache-currentcache-max){ngx_expire_old_cached_files(cache,0,pool-log);}filengx_alloc(sizeof(ngx_cached_open_file_t),pool-log);if(fileNULL){gotofailed;}file-namengx_alloc(name-len1,pool-log);if(file-nameNULL){ngx_free(file);fileNULL;gotofailed;}ngx_cpystrn(file-name,name-data,name-len1);file-node.keyhash;ngx_rbtree_insert(cache-rbtree,file-node);cache-current;file-uses1;file-count0;file-use_event0;file-eventNULL;add_event:ngx_open_file_add_event(cache,file,of,pool-log);update:file-fdof-fd;file-errof-err;#if(NGX_HAVE_OPENAT)file-disable_symlinksof-disable_symlinks;file-disable_symlinks_fromof-disable_symlinks_from;#endifif(of-err0){file-uniqof-uniq;file-mtimeof-mtime;file-sizeof-size;file-close0;file-is_dirof-is_dir;file-is_fileof-is_file;file-is_linkof-is_link;file-is_execof-is_exec;file-is_directioof-is_directio;if(!of-is_dir){file-count;}}file-creatednow;found:file-accessednow;ngx_queue_insert_head(cache-expire_queue,file-queue);ngx_log_debug5(NGX_LOG_DEBUG_CORE,pool-log,0,cached open file: %s, fd:%d, c:%d, e:%d, u:%d,file-name,file-fd,file-count,file-err,file-uses);if(of-err0){if(!of-is_dir){cln-handlerngx_open_file_cleanup;ofclncln-data;ofcln-cachecache;ofcln-filefile;ofcln-min_usesof-min_uses;ofcln-logpool-log;}returnNGX_OK;}returnNGX_ERROR;3 失败清理failed:if(file){ngx_rbtree_delete(cache-rbtree,file-node);cache-current--;if(file-count0){if(file-fd!NGX_INVALID_FILE){if(ngx_close_file(file-fd)NGX_FILE_ERROR){ngx_log_error(NGX_LOG_ALERT,pool-log,ngx_errno,ngx_close_file_n \%s\ failed,file-name);}}ngx_free(file-name);ngx_free(file);}else{file-close1;}}if(of-fd!NGX_INVALID_FILE){if(ngx_close_file(of-fd)NGX_FILE_ERROR){ngx_log_error(NGX_LOG_ALERT,pool-log,ngx_errno,ngx_close_file_n \%V\ failed,name);}}returnNGX_ERROR;}