Nginx学习之七-模块ngx_epoll_module详解(epoll机制在nginx中的实现)

本文详细介绍了Nginx中ngx_epoll_module模块的实现原理,包括配置项解析、核心方法ngx_epoll_init、ngx_epoll_add_event及ngx_epoll_process_events的具体实现过程,同时探讨了解决过期事件问题的instance标志位机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文结合实际代码介绍事件驱动模块接口与epoll用法是如何结合起来发挥作用的。Nginx中ngx_epoll_module模块基于epoll实现了Nginx的事件驱动。

模块定义(src/event/ngx_epoll_module.c):
ngx_module_t  ngx_epoll_module = {
    NGX_MODULE_V1,
    &ngx_epoll_module_ctx,               /* module context */
    ngx_epoll_commands,                  /* module directives */
    NGX_EVENT_MODULE,                    /* module type */
    NULL,                                /* init master */
    NULL,                                /* init module */
    NULL,                                /* init process */
    NULL,                                /* init thread */
    NULL,                                /* exit thread */
    NULL,                                /* exit process */
    NULL,                                /* exit master */
    NGX_MODULE_V1_PADDING
};

首先看ngx_epoll_module模块对哪些配置项感兴趣,ngx_epoll_commands数组定义如下:
static ngx_command_t  ngx_epoll_commands[] = {
    //这个配置项表示调用一次epoll_wait最多可以返回的事件数
    { ngx_string("epoll_events"),
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,//采用预设的配置项解析方法
      0,
      offsetof(ngx_epoll_conf_t, events),
      NULL },

    //指明在开启异步I/O且使用io_setup系统调用初始化异步I/O上下文环境时,初始分配的异步I/O事件个数
    { ngx_string("worker_aio_requests"),
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      0,
      offsetof(ngx_epoll_conf_t, aio_requests),
      NULL },

      ngx_null_command
};

存储配置项的结构体ngx_epoll_conf_t:
ngx_event_module_t  ngx_epoll_module_ctx = {
    &epoll_name,
    ngx_epoll_create_conf,               /* create configuration */
    ngx_epoll_init_conf,                 /* init configuration */

    {
        ngx_epoll_add_event,             /* add an event */
        ngx_epoll_del_event,             /* delete an event */
        ngx_epoll_add_event,             /* enable an event */
        ngx_epoll_del_event,             /* disable an event */
        ngx_epoll_add_connection,        /* add an connection */
        ngx_epoll_del_connection,        /* delete an connection */
        NULL,                            /* process the changes */
        ngx_epoll_process_events,        /* process the events */
        ngx_epoll_init,                  /* init the events */
        ngx_epoll_done,                  /* done the events */
    }
};

类型ngx_event_module_t(src/event/ngx_event.h):
typedef struct {
    ngx_str_t              *name;//事件模块名称

    void                 *(*create_conf)(ngx_cycle_t *cycle);//解析配置项前,这个回调方法用户创建存储配置项参数的结构体
    char                 *(*init_conf)(ngx_cycle_t *cycle, void *conf);//解析配置项完成后,这个方法被调用用以综合处理当前事件模块感兴趣的全部配置项

    ngx_event_actions_t     actions;
} ngx_event_module_t;

其中actions对应着事件驱动机制下,每个事件模块需要实现的10个抽象方法:
typedef struct {
    ngx_int_t  (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);//添加事件
    ngx_int_t  (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);//删除事件

    //启用事件和禁用事件,目前事件框架不会调用这两个方法
    ngx_int_t  (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    ngx_int_t  (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

    ngx_int_t  (*add_conn)(ngx_connection_t *c);//添加连接,意味着这个连接上的读写事件都添加到事件驱动机制中了
    ngx_int_t  (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);

    //仅在多线程环境下调用,暂时Nginx不支持
    ngx_int_t  (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait);
    //在正常的工作循环中,将通过调用这个方法来处理时间事件。这个方法仅在方法ngx_process_events_and_timers中调用,它是处理、分发事件的核心
    ngx_int_t  (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
                   ngx_uint_t flags);

    ngx_int_t  (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);//初始化事件驱动模块
    void       (*done)(ngx_cycle_t *cycle);//退出事件驱动模块前调用的方法
} ngx_event_actions_t;

下面看几个重要的方法。
实现init接口的方法ngx_epoll_init,它做了两件事情:
(1)调用epoll_create方法创建epoll对象
(2)创建event_list数组,用于进行epoll_wait调用时传递内核对象
源代码:
static int                  ep = -1;//epoll对象描述符
static struct epoll_event  *event_list;//链表
static ngx_uint_t           nevents;//链表应该分配的大小

static ngx_int_t
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
    ngx_epoll_conf_t  *epcf;//存储配置项的结构体

    epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);//获取解析所得的配置项的值

    if (ep == -1) {
        //创建epoll对象,在很多Linux内核版本中,并不处理这个函数的参数
        ep = epoll_create(cycle->connection_n / 2);

        //创建失败
        if (ep == -1) {
            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
                          "epoll_create() failed");
            return NGX_ERROR;
        }

#if (NGX_HAVE_FILE_AIO)
        //异步I/O
        ngx_epoll_aio_init(cycle, epcf);

#endif
    }

    //解析配置项所得的epoll_wait一次可最多返回的时间个数较大
    if (nevents < epcf->events) {
        if (event_list) {
            //销毁原来的存储返回事件的链表
            ngx_free(event_list);
        }

        //重新分配链表空间
        event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,
                               cycle->log);
        if (event_list == NULL) {
            return NGX_ERROR;
        }
    }

    //更新参数
    nevents = epcf->events;

    ngx_io = ngx_os_io;

    //Nginx时间框架处理事件时封装的接口
    ngx_event_actions = ngx_epoll_module_ctx.actions;

#if (NGX_HAVE_CLEAR_EVENT)
    //默认采用E模式使用epoll,NGX_HAVE_CLEAR_EVENT宏实际上就是在告诉nginx使用ET模式
    ngx_event_flags = NGX_USE_CLEAR_EVENT
#else
    ngx_event_flags = NGX_USE_LEVEL_EVENT
#endif
                      |NGX_USE_GREEDY_EVENT
                      |NGX_USE_EPOLL_EVENT;

    return NGX_OK;
}

下面以ngx_epoll_add_event方法为例看他们是如何调用epoll_ctl向epoll中添加事件的。
static ngx_int_t
ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
{
    int                  op;
    uint32_t             events, prev;
    ngx_event_t         *e;
    ngx_connection_t    *c;
    struct epoll_event   ee;

    //每个事件的data成员存放着其对应的ngx_connection_t连接
    c = ev->data;

    //下面会根据event参数确定当前事件是读事件还是写事件,这会决定events是加上EPOLLIN标志还是EPOLLOUT标志位
    events = (uint32_t) event;

    //关于下面这段代码的解释:需要结合后面的依据active标志位确定是否为活跃事件的代码来看
    /*首先,
    man epoll:
    Q:  What  happens  if you register the same file descriptor on an epoll  instance twice?
 
    A: You will probably get EEXIST.  However, it is  possible  to  add  a   duplicate  (dup(2),  dup2(2),  fcntl(2)  F_DUPFD) descriptor to the  same epoll instance.  This can be a useful technique for  filtering   events,  if the duplicate file descriptors are registered with different events masks.*/

    /*所以nginx这里就是为了避免这种情况,当要在epoll中加入对一个fd读事件(即NGX_READ_EVENT)的监听时,nginx先看一下与这个fd相关的写事件的状态,即e=c->write,如果此时e->active为1,说明该fd之前已经以NGX_WRITE_EVENT方式被加到epoll中了,此时只需要使用mod方式,将我们的需求加进去,否则才使用add方式,将该fd注册到epoll中。反之处理NGX_WRITE_EVENT时道理是一样的。*/
    
    if (event == NGX_READ_EVENT) {
        e = c->write;
        prev = EPOLLOUT;
#if (NGX_READ_EVENT != EPOLLIN)
        events = EPOLLIN;
#endif

    } else {
        e = c->read;
        prev = EPOLLIN;
#if (NGX_WRITE_EVENT != EPOLLOUT)
        events = EPOLLOUT;
#endif
    }

    //依据active标志位确定是否为活跃事件
    if (e->active) {
        op = EPOLL_CTL_MOD;//是活跃事件,修改事件
        events |= prev;

    } else {
        op = EPOLL_CTL_ADD;//不是活跃事件,添加事件
    }

    ee.events = events | (uint32_t) flags;//设定events标志位
    //ptr存储的是ngx_connection_t连接,instance是过期事件标志位
    ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);

    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
                   "epoll add event: fd:%d op:%d ev:%08XD",
                   c->fd, op, ee.events);

    if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
        ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
                      "epoll_ctl(%d, %d) failed", op, c->fd);
        return NGX_ERROR;
    }

    ev->active = 1;
#if 0
    ev->oneshot = (flags & NGX_ONESHOT_EVENT) ? 1 : 0;
#endif

    return NGX_OK;
}

ngx_epoll_process_events是实现了收集、分发事件的process_events接口的方法,代码如下:
static ngx_int_t
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
    int                events;
    uint32_t           revents;
    ngx_int_t          instance, i;
    ngx_uint_t         level;
    ngx_err_t          err;
    ngx_event_t       *rev, *wev, **queue;
    ngx_connection_t  *c;

    /* NGX_TIMER_INFINITE == INFTIM */

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "epoll timer: %M", timer);

    //调用epoll_wait获取事件
    events = epoll_wait(ep, event_list, (int) nevents, timer);

    err = (events == -1) ? ngx_errno : 0;

    //更新时间
    if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
        ngx_time_update();
    }

    //epoll_wait出错处理
    if (err) {
        if (err == NGX_EINTR) {

            if (ngx_event_timer_alarm) {
                ngx_event_timer_alarm = 0;
                return NGX_OK;
            }

            level = NGX_LOG_INFO;

        } else {
            level = NGX_LOG_ALERT;
        }

        ngx_log_error(level, cycle->log, err, "epoll_wait() failed");
        return NGX_ERROR;
    }

    //本次调用没有事件发生
    if (events == 0) {
        if (timer != NGX_TIMER_INFINITE) {
            return NGX_OK;
        }

        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
                      "epoll_wait() returned no events without timeout");
        return NGX_ERROR;
    }

    ngx_mutex_lock(ngx_posted_events_mutex);

    //遍历本次epoll_wait返回的所有事件
    for (i = 0; i < events; i++) {
        //获取连接ngx_connection_t的地址
        c = event_list[i].data.ptr;

        //连接的地址最后一位具有特殊意义:用于存储instance变量,将其取出来
        instance = (uintptr_t) c & 1;
        //无论是32位还是64位机器,其地址最后一位一定是0,获取真正地址
        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
        //取出读事件
        rev = c->read;

        //判断读事件是否为过期事件
        if (c->fd == -1 || rev->instance != instance) {

            /*
             * the stale event from a file descriptor
             * that was just closed in this iteration
             */

            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                           "epoll: stale event %p", c);
            continue;
        }

        //取出事件类型
        revents = event_list[i].events;

        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                       "epoll: fd:%d ev:%04XD d:%p",
                       c->fd, revents, event_list[i].data.ptr);

        if (revents & (EPOLLERR|EPOLLHUP)) {
            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                           "epoll_wait() error on fd:%d ev:%04XD",
                           c->fd, revents);
        }

#if 0
        if (revents & ~(EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP)) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
                          "strange epoll_wait() events fd:%d ev:%04XD",
                          c->fd, revents);
        }
#endif

        if ((revents & (EPOLLERR|EPOLLHUP))
             && (revents & (EPOLLIN|EPOLLOUT)) == 0)
        {
            /*
             * if the error events were returned without EPOLLIN or EPOLLOUT,
             * then add these flags to handle the events at least in one
             * active handler
             */

            revents |= EPOLLIN|EPOLLOUT;
        }

        //是读事件且该事件是活跃的
        if ((revents & EPOLLIN) && rev->active) {

            if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {
                rev->posted_ready = 1;

            } else {
                rev->ready = 1;
            }

            //事件需要延后处理
            if (flags & NGX_POST_EVENTS) {
                /*如果要在post队列中延后处理该事件,首先要判断它是新连接时间还是普通事件
                以确定是把它加入到ngx_posted_accept_events队列或者ngx_posted_events队列中。*/
                queue = (ngx_event_t **) (rev->accept ?
                               &ngx_posted_accept_events : &ngx_posted_events);
                //将该事件添加到相应的延后队列中
                ngx_locked_post_event(rev, queue);

            } else {
                //立即调用事件回调方法来处理这个事件
                rev->handler(rev);
            }
        }

        wev = c->write;

        if ((revents & EPOLLOUT) && wev->active) {

            if (c->fd == -1 || wev->instance != instance) {

                /*
                 * the stale event from a file descriptor
                 * that was just closed in this iteration
                 */

                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                               "epoll: stale event %p", c);
                continue;
            }

            if (flags & NGX_POST_THREAD_EVENTS) {
                wev->posted_ready = 1;

            } else {
                wev->ready = 1;
            }

            if (flags & NGX_POST_EVENTS) {
                ngx_locked_post_event(wev, &ngx_posted_events);

            } else {
                wev->handler(wev);
            }
        }
    }

    ngx_mutex_unlock(ngx_posted_events_mutex);

    return NGX_OK;
}

下面解释一下上面的代码中出现的过期事件。
举例,假设epoll_wait一次返回三个事件,在处理第一个事件的过程中,由于业务的需要关闭了一个连接,而这个连接恰好对应第三个事件。这样,在处理到第三个事件时,这个事件就是过期事件了,一旦处理就会出错。
那么,怎么解决这个问题呢?把关闭的这个连接的fd套接字置为-1?这样并不能解决所有问题。原因是ngx_connection_t的复用。
假设第三个事件对应的ngx_connection_t连接中的fd套接字原先是10,处理第一个事件时把这个连接的套接字关闭了,同时置为-1,并且调用ngx_free_connection将这个连接归还给连接池。在ngx_process_events方法的循环中开始i处理第二个事件,恰好第二个事件是建立新连接事件,调用ngx_get_connection从连接池中取出的连接非常可能是刚刚释放的第三个事件对应的连接。由于套接字10刚刚被释放,linux内核非常有可能把刚刚释放的套接字10又分配给新建立的连接。因此,在循环中处理第三个事件的时候,这个时间就是过期的了!它对应的事件是关闭的连接而不是新建立的连接。
如何解决这个问题呢?用instance标志位。当调用ngx_get_connection从连接池中获取一个新连接时,instance标志位会被置反。下面看函数ngx_get_connection中对应的代码(src/core/ngx_connection.c):
ngx_connection_t *
ngx_get_connection(ngx_socket_t s, ngx_log_t *log)
{
    ngx_uint_t         instance;
    ngx_event_t       *rev, *wev;
    ngx_connection_t  *c;

    /* disable warning: Win32 SOCKET is u_int while UNIX socket is int */

    if (ngx_cycle->files && (ngx_uint_t) s >= ngx_cycle->files_n) {
        ngx_log_error(NGX_LOG_ALERT, log, 0,
                      "the new socket has number %d, "
                      "but only %ui files are available",
                      s, ngx_cycle->files_n);
        return NULL;
    }

    /* ngx_mutex_lock */

    //从连接池中获取一个连接
    c = ngx_cycle->free_connections;

    if (c == NULL) {
        ngx_drain_connections();
        c = ngx_cycle->free_connections;
    }

    if (c == NULL) {
        ngx_log_error(NGX_LOG_ALERT, log, 0,
                      "%ui worker_connections are not enough",
                      ngx_cycle->connection_n);

        /* ngx_mutex_unlock */

        return NULL;
    }

    ngx_cycle->free_connections = c->data;
    ngx_cycle->free_connection_n--;

    /* ngx_mutex_unlock */

    if (ngx_cycle->files) {
        ngx_cycle->files[s] = c;
    }

    //获取读事件和写事件
    rev = c->read;
    wev = c->write;

    ngx_memzero(c, sizeof(ngx_connection_t));

    c->read = rev;
    c->write = wev;
    c->fd = s;
    c->log = log;

    //获取instance标志位
    instance = rev->instance;

    ngx_memzero(rev, sizeof(ngx_event_t));
    ngx_memzero(wev, sizeof(ngx_event_t));

    //instance标志位置反
    rev->instance = !instance;
    wev->instance = !instance;

    rev->index = NGX_INVALID_INDEX;
    wev->index = NGX_INVALID_INDEX;

    rev->data = c;
    wev->data = c;

    wev->write = 1;

    return c;
}

这样,就在不增加任何成本的情况下解决了服务器开发时出现的过期事件问题。

实现done接口的ngx_epoll_done方法,在nginx退出服务时会得到调用。主要完成的工作是关闭epoll描述符ep,同时释放event_list数组。




参考资料:
《深入理解Nginx》





[root@VM-24-11-opencloudos nginx-1.24.0]# pcre-config --version 8.35 [root@VM-24-11-opencloudos nginx-1.24.0]# ./configure --prefix=/www/server/nginx --add-module=/www/server/nginx/src/ngx_devel_kit --add-module=/www/server/nginx/src/lua_nginx_module --add-module=/www/server/nginx/src/ngx_cache_purge --with-openssl=/www/server/nginx/src/openssl --with-pcre=pcre-8.43 --with-http_v2_module --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module --with-http_stub_status_module --with-http_ssl_module --with-http_image_filter_module --with-http_gzip_static_module --with-http_gunzip_module --with-ipv6 --with-http_sub_module --with-http_flv_module --with-http_addition_module --with-http_realip_module --with-http_mp4_module --add-module=/www/server/nginx/src/ngx_http_substitutions_filter_module-master --with-ld-opt=-Wl,-E --with-cc-opt=-Wno-error --with-http_dav_module --add-module=/www/server/nginx/src/nginx-dav-ext-module --add-module=/usr/local/fastdfs-nginx-module/src checking for OS + Linux 6.6.47-12.oc9.x86_64 x86_64 checking for C compiler ... found + using GNU C compiler + gcc version: 12.3.1 20230912 (OpenCloudOS 12.3.1.3-1) (Tencent Compiler 12.3.1.3) checking for gcc -pipe switch ... found checking for --with-ld-opt="-Wl,-E" ... found checking for -Wl,-E switch ... found checking for gcc builtin atomic operations ... found checking for C99 variadic macros ... found checking for gcc variadic macros ... found checking for gcc builtin 64 bit byteswap ... found checking for unistd.h ... found checking for inttypes.h ... found checking for limits.h ... found checking for sys/filio.h ... not found checking for sys/param.h ... found checking for sys/mount.h ... found checking for sys/statvfs.h ... found checking for crypt.h ... found checking for Linux specific features checking for epoll ... found checking for EPOLLRDHUP ... found checking for EPOLLEXCLUSIVE ... found checking for eventfd() ... found checking for O_PATH ... found checking for sendfile() ... found checking for sendfile64() ... found checking for sys/prctl.h ... found checking for prctl(PR_SET_DUMPABLE) ... found checking for prctl(PR_SET_KEEPCAPS) ... found checking for capabilities ... found checking for crypt_r() ... found checking for sys/vfs.h ... found checking for UDP_SEGMENT ... found checking for nobody group ... found checking for poll() ... found checking for /dev/poll ... not found checking for kqueue ... not found checking for crypt() ... not found checking for crypt() in libcrypt ... found checking for F_READAHEAD ... not found checking for posix_fadvise() ... found checking for O_DIRECT ... found checking for F_NOCACHE ... not found checking for directio() ... not found checking for statfs() ... found checking for statvfs() ... found checking for dlopen() ... found checking for sched_yield() ... found checking for sched_setaffinity() ... found checking for SO_SETFIB ... not found checking for SO_REUSEPORT ... found checking for SO_ACCEPTFILTER ... not found checking for SO_BINDANY ... not found checking for IP_TRANSPARENT ... found checking for IP_BINDANY ... not found checking for IP_BIND_ADDRESS_NO_PORT ... found checking for IP_RECVDSTADDR ... not found checking for IP_SENDSRCADDR ... not found checking for IP_PKTINFO ... found checking for IPV6_RECVPKTINFO ... found checking for TCP_DEFER_ACCEPT ... found checking for TCP_KEEPIDLE ... found checking for TCP_FASTOPEN ... found checking for TCP_INFO ... found checking for accept4() ... found checking for int size ... 4 bytes checking for long size ... 8 bytes checking for long long size ... 8 bytes checking for void * size ... 8 bytes checking for uint32_t ... found checking for uint64_t ... found checking for sig_atomic_t ... found checking for sig_atomic_t size ... 4 bytes checking for socklen_t ... found checking for in_addr_t ... found checking for in_port_t ... found checking for rlim_t ... found checking for uintptr_t ... uintptr_t found checking for system byte ordering ... little endian checking for size_t size ... 8 bytes checking for off_t size ... 8 bytes checking for time_t size ... 8 bytes checking for AF_INET6 ... found checking for setproctitle() ... not found checking for pread() ... found checking for pwrite() ... found checking for pwritev() ... found checking for strerrordesc_np() ... found checking for localtime_r() ... found checking for clock_gettime(CLOCK_MONOTONIC) ... found checking for posix_memalign() ... found checking for memalign() ... found checking for mmap(MAP_ANON|MAP_SHARED) ... found checking for mmap("/dev/zero", MAP_SHARED) ... found checking for System V shared memory ... found checking for POSIX semaphores ... found checking for struct msghdr.msg_control ... found checking for ioctl(FIONBIO) ... found checking for ioctl(FIONREAD) ... found checking for struct tm.tm_gmtoff ... found checking for struct dirent.d_namlen ... not found checking for struct dirent.d_type ... found checking for sysconf(_SC_NPROCESSORS_ONLN) ... found checking for sysconf(_SC_LEVEL1_DCACHE_LINESIZE) ... found checking for openat(), fstatat() ... found checking for getaddrinfo() ... found configuring additional modules adding module in /www/server/nginx/src/ngx_devel_kit + ngx_devel_kit was configured adding module in /www/server/nginx/src/lua_nginx_module checking for LuaJIT library in /usr/local/lib and /usr/local/include/luajit-2.1 (specified by the LUAJIT_LIB and LUAJIT_INC env, with -ldl) ... found checking for LuaJIT 2.x ... found checking for Lua language 5.1 ... found checking for LuaJIT has FFI ... found checking for export symbols by default (-E) ... found checking for export symbols by default (--export-all-symbols) ... not found checking for SO_PASSCRED ... found checking for SA_RESTART ... found checking for malloc_trim ... found checking for pipe2 ... found checking for signalfd ... found checking for execvpe ... found + ngx_http_lua_module was configured adding module in /www/server/nginx/src/ngx_cache_purge + ngx_http_cache_purge_module was configured adding module in /www/server/nginx/src/ngx_http_substitutions_filter_module-master + ngx_http_subs_filter_module was configured adding module in /www/server/nginx/src/nginx-dav-ext-module + ngx_http_dav_ext_module was configured adding module in /usr/local/fastdfs-nginx-module/src + ngx_http_fastdfs_module was configured checking for zlib library ... found checking for libxslt ... found checking for libexslt ... found checking for GD library ... found checking for GD WebP support ... found creating objs/Makefile Configuration summary + using PCRE library: pcre-8.43 + using OpenSSL library: /www/server/nginx/src/openssl + using system zlib library nginx path prefix: "/www/server/nginx" nginx binary file: "/www/server/nginx/sbin/nginx" nginx modules path: "/www/server/nginx/modules" nginx configuration prefix: "/www/server/nginx/conf" nginx configuration file: "/www/server/nginx/conf/nginx.conf" nginx pid file: "/www/server/nginx/logs/nginx.pid" nginx error log file: "/www/server/nginx/logs/error.log" nginx http access log file: "/www/server/nginx/logs/access.log" nginx http client request body temporary files: "client_body_temp" nginx http proxy temporary files: "proxy_temp" nginx http fastcgi temporary files: "fastcgi_temp" nginx http uwsgi temporary files: "uwsgi_temp" nginx http scgi temporary files: "scgi_temp" ./configure: warning: the "--with-ipv6" option is deprecated [root@VM-24-11-opencloudos nginx-1.24.0]# make make -f objs/Makefile make[1]: Entering directory '/www/server/nginx/src/nginx-1.24.0' cd pcre-8.43 \ && if [ -f Makefile ]; then make distclean; fi \ && CC="cc" CFLAGS="-O2 -fomit-frame-pointer -pipe " \ ./configure --disable-shared /bin/sh: line 1: cd: pcre-8.43: No such file or directory make[1]: *** [objs/Makefile:2256: pcre-8.43/Makefile] Error 1 make[1]: Leaving directory '/www/server/nginx/src/nginx-1.24.0' make: *** [Makefile:10: build] Error 2
最新发布
06-19
<think>我们面对的问题是在执行`make`命令时出现错误:```cdpcre-8.43\&&if[-fMakefile];thenmakedistclean;fi\&&CC="cc"CFLAGS="-O2-fomit-frame-pointer-pipe"\./configure--disable-shared/bin/sh:line1:cd:pcre-8.43:Nosuchfileordirectory```错误信息表明在进入`pcre-8.43`目录时失败,因为该目录不存在。回顾用户提供的配置命令:```./configure--prefix=/www/server/nginx--add-module=/www/server/nginx/src/ngx_devel_kit--add-module=/www/server/nginx/src/lua_nginx_module--add-module=/www/server/nginx/src/ngx_cache_purge--with-openssl=/www/server/nginx/src/openssl--with-pcre=pcre-8.43--with-http_v2_module--with-stream--with-stream_ssl_module--with-stream_ssl_preread_module--with-http_stub_status_module--with-http_ssl_module--with-http_image_filter_module--with-http_gzip_static_module--with-http_gunzip_module--with-ipv6--with-http_sub_module--with-http_flv_module--with-http_addition_module--with-http_realip_module--with-http_mp4_module--add-module=/www/server/nginx/src/ngx_http_substitutions_filter_module-master--with-ld-opt=-Wl,-E--with-cc-opt=-Wno-error--with-http_dav_module--add-module=/www/server/nginx/src/nginx-dav-ext-module--add-module=/usr/local/fastdfs-nginx-module/src```特别注意`--with-pcre=pcre-8.43`选项。这个选项告诉Nginx的configure脚本,PCRE库的源代码位于当前目录下的`pcre-8.43`目录中。但是,错误信息表明在Nginx源代码目录下并没有这个目录。根据引用[1]和引用[2]的内容,我们需要确保PCRE库已经正确安装,并且在配置Nginx时指定正确的PCRE源码路径。###解决方案:####步骤1:确认PCRE源码包的存在首先,我们需要确认PCRE的源码包是否已经下载并解压到指定的位置。根据用户之前的操作,他可能将PCRE-8.43的源码包放在了`/usr/local/src`目录下。我们可以检查一下:```bashls/usr/local/src/pcre-8.43```如果该目录存在,那么我们需要在配置Nginx时指定完整的路径,而不是相对路径。####步骤2:重新配置Nginx在配置Nginx时,将`--with-pcre`参数的值改为PCRE源码的绝对路径。例如,如果PCRE源码在`/usr/local/src/pcre-8.43`,则配置命令应改为:```bash./configure...--with-pcre=/usr/local/src/pcre-8.43...```注意:其他配置选项保持不变,只需修改`--with-pcre`的值为绝对路径。####步骤3:重新编译重新运行`make`命令。```bashmake```如果之前已经执行过`make`并且失败了,建议先执行`makeclean`来清理之前的编译文件。###详细步骤:1.**确认PCRE源码目录**:如果PCRE源码不在`/usr/local/src/pcre-8.43`,那么需要下载并解压。根据引用[1]中的步骤:```bashcd/usr/local/srcwgethttps://sourceforge.net/projects/pcre/files/pcre/8.43/pcre-8.43.tar.gztar-zxvfpcre-8.43.tar.gz```2.**重新配置Nginx**:进入Nginx源码目录(`/www/server/nginx/src/nginx-1.24.0`),重新运行配置命令,并将`--with-pcre`参数指向绝对路径`/usr/local/src/pcre-8.43`:```bashcd/www/server/nginx/src/nginx-1.24.0./configure--prefix=/www/server/nginx\--add-module=/www/server/nginx/src/ngx_devel_kit\--add-module=/www/server/nginx/src/lua_nginx_module\--add-module=/www/server/nginx/src/ngx_cache_purge\--with-openssl=/www/server/nginx/src/openssl\--with-pcre=/usr/local/src/pcre-8.43\#修改为绝对路径...[其他参数保持不变]...```3.**编译并安装**:```bashmakemakeinstall```###注意事项:-确保PCRE的版本正确,这里是8.43。-如果之前配置过,最好先执行`makeclean`,然后再重新配置和编译。-在配置PCRE时,建议启用UTF-8支持(如引用[1]中提到的`./configure--enable-utf8`),但这一步在Nginx配置中不需要,因为Nginx会使用PCRE的源码进行编译,所以我们应该在PCRE的源码目录中先配置并编译安装PCRE吗?实际上,在Nginx配置中指定PCRE源码路径时,Nginx会在编译过程中自动编译PCRE,因此不需要单独安装PCRE。但是,如果我们已经单独安装了PCRE,也可以使用`--with-pcre`指向已安装的库(但注意,这里要求指向源码目录,而不是安装目录)。###引用说明:根据引用[1],用户提到安装PCRE-8.43时,需要先下载并解压,然后进行配置(包括`--enable-utf8`选项)和编译安装。但根据Nginx的配置要求,我们只需要提供PCRE的源码目录,Nginx会在编译过程中编译PCRE。因此,我们不需要预先编译安装PCRE,只需要提供源码目录即可。###相关问题:1.如何为Nginx添加第三方模块?2.在编译Nginx时如何解决OpenSSL路径问题?3.如何启用Nginx的HTTP/2支持?4.如何配置Nginx的Lua模块?5.如何解决Nginx编译过程中的zlib依赖问题?</think>### 解决 Nginx 编译时 PCRE 目录缺失错误 #### 🔍 问题分析 编译错误 `cd: pcre-8.43: No such file or directory` 表明: 1. `--with-pcre=pcre-8.43` 参数指定了相对路径,但实际文件不存在 2. PCRE 库未正确解压到 Nginx 源码目录 3. 路径权限问题导致无法访问(需检查 `/usr/local/src` 权限) #### ✅ 解决方案(逐步操作) ##### 步骤 1:验证并准备 PCRE 源码 ```bash # 检查源码位置(根据引用[1]应在 /usr/local/src) ls -l /usr/local/src/pcre-8.43* # 若不存在则重新下载解压(引用[1]方法) cd /usr/local/src sudo wget https://sourceforge.net/projects/pcre/files/pcre/8.43/pcre-8.43.tar.gz sudo tar -zxvf pcre-8.43.tar.gz sudo chmod -R 755 pcre-8.43 # 确保权限(引用[1]) ``` ##### 步骤 2:修正 Nginx 配置路径 ```bash cd /www/server/nginx/src/nginx-1.24.0 # 修改 --with-pcre 为绝对路径(关键!) ./configure \ --prefix=/www/server/nginx \ --with-pcre=/usr/local/src/pcre-8.43 \ # 修改这里 --with-openssl=/www/server/nginx/src/openssl \ ...其他参数保持原样... ``` ##### 步骤 3:重新编译安装 ```bash # 清理旧编译文件 make clean # 重新编译(引用[3]依赖检查) make -j$(nproc) # 安装 sudo make install ``` ##### 步骤 4:验证安装 ```bash sudo /www/server/nginx/sbin/nginx -t # 成功应显示: configuration file test is successful ``` #### ⚠️ 关键注意事项 1. **路径必须为绝对路径** `--with-pcre` 必须指向 **PCRE 源码目录** 而非压缩包或安装目录(引用[1][2]) 2. **UTF-8 支持** 配置 PCRE 时需启用 UTF-8(引用[1]): ```bash cd /usr/local/src/pcre-8.43 ./configure --enable-utf8 # 必须选项 ``` 3. **依赖完整性检查** 确保安装编译依赖(引用[3][4]): ```bash sudo yum -y install make zlib-devel gcc-c++ openssl-devel ``` #### 🔧 故障排查流程图 ```mermaid graph TD A[编译错误] --> B{错误类型} B --> |目录不存在| C[检查 --with-pcre 路径] B --> |权限问题| D[chmod -R 755 pcre-8.43] C --> E[使用绝对路径 /usr/local/src/pcre-8.43] D --> F[确认 /usr/local/src 可访问] E --> G[重新configure] F --> G G --> H[make clean && make] ``` #### 📚 相关问题 1. 如何为 Nginx 添加 Brotli 压缩支持? 2. PCRE JIT 编译对 Nginx 性能有何影响?[^1] 3. 如何在 OpenCloudOS 上配置 Nginx 系统服务? 4. 如何解决 `fatal error: pcre.h` 头文件缺失问题?[^2] 5. Nginx 动态模块加载 vs 静态编译有何区别? > 引用说明:PCRE 路径必须使用绝对路径[^1],UTF-8 支持是必须选项[^1],依赖完整性检查可避免后续错误[^3][^4]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

没有昵称阿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值