1 定义 ngx_write_file 函数 定义在 ./nginx-1.24.0/src/os/unix/ngx_files.cssize_t ngx_write_file ( ngx_file_t * file, u_char* buf, size_t size, off_t offset) { ssize_t n, written; ngx_err_t err; ngx_log_debug4 ( NGX_LOG_DEBUG_CORE, file-> log, 0 , "write: %d, %p, %uz, %O" , file-> fd, buf, size, offset) ; written= 0 ; # if ( NGX_HAVE_PWRITE) for ( ; ; ) { n= pwrite ( file-> fd, buf+ written, size, offset) ; if ( n== - 1 ) { err= ngx_errno; if ( err== NGX_EINTR) { ngx_log_debug0 ( NGX_LOG_DEBUG_CORE, file-> log, err, "pwrite() was interrupted" ) ; continue ; } ngx_log_error ( NGX_LOG_CRIT, file-> log, err, "pwrite() \"%s\" failed" , file-> name. data) ; return NGX_ERROR; } file-> offset+= n; written+= n; if ( ( size_t ) n== size) { return written; } offset+= n; size-= n; } # else if ( file-> sys_offset!= offset) { if ( lseek ( file-> fd, offset, SEEK_SET ) == - 1 ) { ngx_log_error ( NGX_LOG_CRIT, file-> log, ngx_errno, "lseek() \"%s\" failed" , file-> name. data) ; return NGX_ERROR; } file-> sys_offset= offset; } for ( ; ; ) { n= write ( file-> fd, buf+ written, size) ; if ( n== - 1 ) { err= ngx_errno; if ( err== NGX_EINTR) { ngx_log_debug0 ( NGX_LOG_DEBUG_CORE, file-> log, err, "write() was interrupted" ) ; continue ; } ngx_log_error ( NGX_LOG_CRIT, file-> log, err, "write() \"%s\" failed" , file-> name. data) ; return NGX_ERROR; } file-> sys_offset+= n; file-> offset+= n; written+= n; if ( ( size_t ) n== size) { return written; } size-= n; } # endif } `ngx_write_file` 函数 用于向文件写入指定长度的数据,支持从指定偏移量开始写入。 它内部循环处理部分写入和 `EINTR` 信号中断,保证数据完整写入; 若系统支持 `pwrite` 则直接使用(不改变文件位置), 否则通过 `lseek` 定位后调用 `write`,并维护文件位置缓存。 写入成功时返回实际写入字节数, 失败时返回 `NGX_ERROR`。2 详解 1 函数签名 ssize_t ngx_write_file ( ngx_file_t * file, u_char* buf, size_t size, off_t offset) 返回值类型:ssize_t 成功时:返回实际写入文件的字节数(非负整数)。 这个值可能等于请求的 size,也可能小于 size (但在该函数实现中,除非出错,否则会循环直到写完,所以通常等于 size)。 失败时:返回 NGX_ERROR参数: ngx_file_t *file 指向 nginx 封装的文件结构体。 u_char *buf 指向要写入的数据缓冲区的起始地址 size_t size 指定期望写入的字节数 off_t offset 指定写入操作的起始位置(绝对偏移)。 函数会将数据写入文件的该偏移处,而不受当前文件指针的影响。2 逻辑流程 1 局部变量 2 文件写入 2-1 支持 pwrite 2-2 不支持 pwrite1 局部变量{ ssize_t n, written; ngx_err_t err; written= 0 ; n 临时存储每次系统调用(pwrite 或 write)的返回值。 在循环中,n 表示本次实际写入的字节数。 written 累积已成功写入的总字节数 初始化为 02 文件写入2-1 支持 pwrite# if ( NGX_HAVE_PWRITE) for ( ; ; ) { n= pwrite ( file-> fd, buf+ written, size, offset) ; if ( n== - 1 ) { err= ngx_errno; if ( err== NGX_EINTR) { ngx_log_debug0 ( NGX_LOG_DEBUG_CORE, file-> log, err, "pwrite() was interrupted" ) ; continue ; } ngx_log_error ( NGX_LOG_CRIT, file-> log, err, "pwrite() \"%s\" failed" , file-> name. data) ; return NGX_ERROR; } file-> offset+= n; written+= n; if ( ( size_t ) n== size) { return written; } offset+= n; size-= n; } #1 NGX_HAVE_PWRITE 宏定义 判断系统是否支持 pwrite 系统调用。若支持,则编译此分支代码 pwrite 可以在指定偏移量写入数据,且不改变文件当前偏移量, 避免多线程/多进程写同一文件描述符时产生竞争#2 无限循环,用于处理可能的部分写入和 EINTR 中断, 直到所有数据写入完成或遇到不可恢复的错误。 pwrite 不保证一次写入全部数据,且可能被信号中断(EINTR),因此需要循环重试。#3 调用 pwrite 系统调用, 从 buf + written 处开始写入 size 字节到文件描述符 file->fd 的 offset 偏移位置。 参数详解: file->fd:文件描述符。 buf + written:当前待写入数据的起始地址(written 记录已写入的字节数)。 size:本次期望写入的字节数(循环中逐渐减少)。 offset:本次写入的绝对偏移量(循环中逐渐增加)。 返回值:成功时返回实际写入的字节数(ssize_t),失败时返回 -1 并设置 errno。#4 判断系统调用是否失败。 获取当前的系统错误码,保存到局部变量 err 中 判断错误码是否为 EINTR(系统调用被信号中断)。 EINTR 是可恢复的临时错误,应该重试而不是报错。 记录调试日志,表明 pwrite 被中断,但会重试 记录严重错误日志,指明 pwrite 失败,并输出文件名。 对于其他错误(非 EINTR),记录错误后函数将返回失败。#5 更新 nginx 内部维护的文件偏移量 file->offset,加上本次实际写入的字节数。 file->offset 用于 nginx 上层模块跟踪当前文件位置,保持一致性。 累积已写入的总字节数到局部变量 written。 用于计算剩余待写数据,并最终作为返回值。 判断本次实际写入的字节数是否等于本次期望写入的字节数(即 size) 如果本次写入完成了所有请求的字节,则函数返回累积写入的总字节数。 所有数据已成功写入,正常退出。#6 更新下一次 pwrite 的偏移量,加上本次写入的字节数。 因为还有剩余数据未写入,需要将后续数据写入到紧接的位置。 减少剩余待写入的字节数。 调整循环条件,继续写入剩余部分。 循环结束,回到 for (;;) 继续下一次迭代2-2 不支持 pwrite# else if ( file-> sys_offset!= offset) { if ( lseek ( file-> fd, offset, SEEK_SET ) == - 1 ) { ngx_log_error ( NGX_LOG_CRIT, file-> log, ngx_errno, "lseek() \"%s\" failed" , file-> name. data) ; return NGX_ERROR; } file-> sys_offset= offset; } for ( ; ; ) { n= write ( file-> fd, buf+ written, size) ; if ( n== - 1 ) { err= ngx_errno; if ( err== NGX_EINTR) { ngx_log_debug0 ( NGX_LOG_DEBUG_CORE, file-> log, err, "write() was interrupted" ) ; continue ; } ngx_log_error ( NGX_LOG_CRIT, file-> log, err, "write() \"%s\" failed" , file-> name. data) ; return NGX_ERROR; } file-> sys_offset+= n; file-> offset+= n; written+= n; if ( ( size_t ) n== size) { return written; } size-= n; } # endif } #1 当系统不支持 pwrite 时, nginx 使用 lseek + write 的组合来实现偏移量写入, 并缓存文件系统偏移量以减少 lseek 调用。#2 检查缓存的文件系统偏移量 file->sys_offset 是否与目标写入偏移量 offset 一致 file->sys_offset 记录了当前文件指针的实际位置。 如果位置不符,则需要先 lseek 定位到目标偏移,避免每次写入都进行 lseek 系统调用(性能优化)。#3 调用 lseek 将文件指针移动到 offset 处(绝对定位)。 返回值:成功时返回新偏移量;失败时返回 -1。 将缓存的文件系统偏移量更新为当前文件指针位置(即 offset)。#4 无限循环,用于处理部分写入和 EINTR。 调用 write 系统调用,从 buf + written 处写入 size 字节到当前文件指针位置。 注意:与 pwrite 不同,write 不指定偏移量,而是使用当前文件位置,并自动更新位置。