nginx 进程间通信

进程间通信(IPC)


一:父进程与子进程间通信

nginx是master-worker服务器模型,master负责接收外部信号,并给子进程发送信号,比如:重启,二进制文件替换等。

在这里采用的是高级进程间通信方式:unix域套接字,socketpair

nginx细节:

子进程只保留自己的channel[1],并把其他子进程的channel[1]给关闭了,避免本进程误接收到发给其他进程的信号。

父进程独占所有进程的channel[0]:    fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid)。

channel只用于父进程给子进程发送信号,并没有子进程给父进程发送信号的需求。


注:

unix域套接字是套接字和管道的结合物。

相比较网络ipc:因为unix域套接字效率要高,它只是复制数据,并不进行协议处理,网络报头,数据检验等操作,只是数据的复制。

相比较管道:因为管道pipe两次才能实现全双工,使得代码复杂。两者区别见:点击打开链接

Socketpair与其他IPC相比最大的不同是它可以在进程间传递描述符


二:子进程间通信

子进程通讯主要有两个地方:

一个是子进程间的accpet锁,解决epoll_wait造成的惊群。

一个是cache模块和limit模块,这些模块需要子进程访问共同的存储区,并对共同存储器区的访问也需要加锁控制。

锁在nginx里有两种实现方式,一个是共享内存地址作为信号量lock,一个是文件锁,如果操作系统支持原子操作,首先考虑使用共享内存信号量,因为信号量比文件锁耗时少,但是不得不说文件锁使用还是相当方便的。

子进程要访问的共同的存储区则使用了共享内存实现。


1.子进程accept  解决惊群

子进程用同步来实现,同步一般是为了同步多个进程(线程)的活动,来允许进程(线程)间共享数据。

锁其实就是多个进程间共享一个变量,来控制进程的行为,nginx实现同步锁的时候有两种方式:共享内存原子锁;文件锁。

先看一下创建锁的函数:

ngx_int_t ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name)

这个函数有三个参数,mtx是锁的数据结构,addr提供了共享内存的地址,name提供了文件的名称。

typedef struct {
//如果操作系统支持原子操作(NGX_HAVE_ATOMIC_OPS),那么使用共享内存锁,
//如果操作系统不支持院原子操作,那么使用文件锁
//支持原子操作方式的时候,因为使用的共享内存锁,这时候锁就是一个信号量
#if (NGX_HAVE_ATOMIC_OPS)
    ngx_atomic_t  *lock;
#if (NGX_HAVE_POSIX_SEM)
    ngx_atomic_t  *wait;
    ngx_uint_t     semaphore;
    sem_t          sem;
#endif
#else
    ngx_fd_t       fd;
    u_char        *name;
#endif
    ngx_uint_t     spin;
} ngx_shmtx_t;

下面分别看一下共享内存锁和文件锁:

1)在操作系统支持原子操作的情况下,采用了共享内存,锁就是共享内存的地址

共享内存主要有四种:

①内存映射文件:实名文件

②匿名内存映射

③posix共享内存区

④system V共享内存区

nginx中主要用了第②种和第④种

代码展示只选了源代码的部分:


#include <ngx_config.h>
#include <ngx_core.h>

#if (NGX_HAVE_MAP_ANON)

ngx_int_t
ngx_shm_alloc(ngx_shm_t *shm)
{
    shm->addr = (u_char *) mmap(NULL, shm->size,
                                PROT_READ|PROT_WRITE,
                                MAP_ANON|MAP_SHARED, -1, 0);
}

#elif (NGX_HAVE_MAP_DEVZERO)

ngx_int_t
ngx_shm_alloc(ngx_shm_t *shm)
{
    ngx_fd_t  fd;

    fd = open("/dev/zero", O_RDWR);

    shm->addr = (u_char *) mmap(NULL, shm->size, PROT_READ|PROT_WRITE,
                                MAP_SHARED, fd, 0);
}

#elif (NGX_HAVE_SYSVSHM)

#include <sys/ipc.h>
#include <sys/shm.h>

ngx_int_t
ngx_shm_alloc(ngx_shm_t *shm)
{
    int  id;
    id = shmget(IPC_PRIVATE, shm->size, (SHM_R|SHM_W|IPC_CREAT));
    shm->addr = shmat(id, NULL, 0);
}

#endif


共享锁准备好之后,就是锁的创建,加锁,解锁操作:


ngx_int_t
ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name)
{
    mtx->lock = &addr->lock;

    if (mtx->spin == (ngx_uint_t) -1) {
        return NGX_OK;
    }

    mtx->spin = 2048;

#if (NGX_HAVE_POSIX_SEM)

    mtx->wait = &addr->wait;

    if (sem_init(&mtx->sem, 1, 0) == -1) {
        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,
                      "sem_init() failed");
    } else {
        mtx->semaphore = 1;
    }

#endif

    return NGX_OK;
}


ngx_uint_t
ngx_shmtx_trylock(ngx_shmtx_t *mtx)
{
    return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));
}


void
ngx_shmtx_unlock(ngx_shmtx_t *mtx)
{
    if (mtx->spin != (ngx_uint_t) -1) {
        ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx unlock");
    }

    if (ngx_atomic_cmp_set(mtx->lock, ngx_pid, 0)) {
        ngx_shmtx_wakeup(mtx);
    }
}

 

 

2)不支持原子操作的情况下,采用了文件上锁:posix fcntl,锁就是此文件的权限锁。

对于文件上锁,只要准备好文件名字name即可,nginx默认的文件路径为:logs/nginx.lock.accpt

代码展示只选了源代码的部分:

 

ngx_int_t
ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name)
{
    if (mtx->name) {

        if (ngx_strcmp(name, mtx->name) == 0) {
            mtx->name = name;
            return NGX_OK;
        }

        ngx_shmtx_destroy(mtx);
    }

    mtx->fd = ngx_open_file(name, NGX_FILE_RDWR, NGX_FILE_CREATE_OR_OPEN,
                            NGX_FILE_DEFAULT_ACCESS);

 }


ngx_uint_t
ngx_shmtx_trylock(ngx_shmtx_t *mtx)
{
    ngx_err_t  err;

    err = ngx_trylock_fd(mtx->fd);
}


void
ngx_shmtx_unlock(ngx_shmtx_t *mtx)
{
    ngx_err_t  err;

    err = ngx_unlock_fd(mtx->fd);
}


 

ngx_err_t
ngx_trylock_fd(ngx_fd_t fd)
{
    struct flock  fl;

    ngx_memzero(&fl, sizeof(struct flock));
    fl.l_type = F_WRLCK;
    fl.l_whence = SEEK_SET;

    if (fcntl(fd, F_SETLK, &fl) == -1) {
        return ngx_errno;
    }

    return 0;
}

ngx_err_t
ngx_unlock_fd(ngx_fd_t fd)
{
    struct flock  fl;

    ngx_memzero(&fl, sizeof(struct flock));
    fl.l_type = F_UNLCK;
    fl.l_whence = SEEK_SET;

    if (fcntl(fd, F_SETLK, &fl) == -1) {
        return  ngx_errno;
    }

    return 0;
}


 

三:子进程之间的共享资源

比如 访问控制 limit模块

比如proxy模块中的cache


对于进程的共享资源,nginx采用的是共享内存的方式。

其中共享内存 采用了ngx_shm_zone_s 结构。

struct ngx_shm_zone_s {
    void                     *data;
    ngx_shm_t                 shm;//共享内存存储结构
    ngx_shm_zone_init_pt      init;
    void                     *tag;
};

typedef struct {
    u_char      *addr;//指向共享内存地址
    size_t       size;
    ngx_str_t    name;
    ngx_log_t   *log;
    ngx_uint_t   exists;   /* unsigned  exists:1;  */
} ngx_shm_t;

ngx_shm_t 结构中的addr是共享内存的地址,共享内存的起始位置被结构化为:ngx_slab_pool_t,后续的空间都用来存储数据,ngx_slab_pool_t是用来组织共享内存的。

通过ngx_slab_pool_t名字可以看出,共享内存的使用采用的是slab算法 ,详见nginx slab算法

ngx_shared_memory_add

typedef struct {
    ngx_shmtx_sh_t    lock;

    size_t            min_size;
    size_t            min_shift;

    ngx_slab_page_t  *pages;
    ngx_slab_page_t   free;

    u_char           *start;
    u_char           *end;

    ngx_shmtx_t       mutex;

    u_char           *log_ctx;
    u_char            zero;

    void             *data;
    void             *addr;
} ngx_slab_pool_t;


nginx共享内存用在了若干个地方,那个nginx是怎么管理所有的共享内存的呢?

struct ngx_cycle_s {
   
    ......
  
    ngx_list_t                shared_memory;//这是一个list,存储了所有的共享内存。

    ......
};


因为只是说一下共享内存的情况,所以摘取了部分代码,并且忽略了存在oldcycle中共享内存的情况。详解见代码

ngx_cycle_t *
ngx_init_cycle(ngx_cycle_t *old_cycle)
{
 ......

 
//初始化共享内存的list链表
    if (ngx_list_init(&cycle->shared_memory, pool, n, sizeof(ngx_shm_zone_t))
        != NGX_OK)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }

//ngx_conf_parse 会依次解析配置文件。那么调用模块指令的handler,在使用共享内存模块的handler中会将其共享内存 ngx_shm_zone_s加入到链表中,后面会有代码展示


    if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
        environ = senv;
        ngx_destroy_cycle_pools(&conf);
        return NULL;
    }

    //未完,待续,先看一下 ngx_conf_parse 发生了什么。此函数中后面的代码解析见后

}

解析配置文件时候,根据不同的指令,去调用对应模块的hanlder,可以发现新建共享内存的包括以下几个模块:

fastcgi module 的cache;

proxy module 的cache;

scgi module 的cache;

uwsgi module 的cache;

ngx_http_file_cache: 这个是以上四个模块公用的一个共享内存;

limit_conn_zone模块两个共享内存:limit_conn_zone 和limit_zone

limit_req模块两个共享内存:limit_req_zone 和 limit_req


在以上几个模块相应指令的的handler都调用了:

shm_zone = ngx_shared_memory_add(cf, &name, size,&ngx_http_limit_req_module);

ngx_shared_memory_add函数做的主要就是初始化共享内存区,并加入cycle->shared_memory的链表里


举例:limit_conn_zone:

配置指令:limit_conn_zone $binary_remote_addr zone=ip_zone:10m;

这个指令的hanler是ngx_http_limit_conn_zone函数,其中也调用了ngx_shared_memory_add。

static char *
ngx_http_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    u_char                     *p;
    ssize_t                     size;
    ngx_str_t                  *value, name, s;
    ngx_uint_t                  i;
    ngx_shm_zone_t             *shm_zone;
    ngx_http_limit_conn_ctx_t  *ctx;

    value = cf->args->elts;

    ctx = NULL;
    size = 0;
    name.len = 0;

    for (i = 1; i < cf->args->nelts; i++) {

        if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {

            name.data = value[i].data + 5;

            p = (u_char *) ngx_strchr(name.data, ':');

            if (p == NULL) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "invalid zone size \"%V\"", &value[i]);
                return NGX_CONF_ERROR;
            }

            name.len = p - name.data;

            s.data = p + 1;
            s.len = value[i].data + value[i].len - s.data;

            size = ngx_parse_size(&s);

            if (size == NGX_ERROR) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "invalid zone size \"%V\"", &value[i]);
                return NGX_CONF_ERROR;
            }

            if (size < (ssize_t) (8 * ngx_pagesize)) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "zone \"%V\" is too small", &value[i]);
                return NGX_CONF_ERROR;
            }

            continue;
        }

        if (value[i].data[0] == '$') {

            value[i].len--;
            value[i].data++;

            ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_ctx_t));
            if (ctx == NULL) {
                return NGX_CONF_ERROR;
            }

            ctx->index = ngx_http_get_variable_index(cf, &value[i]);
            if (ctx->index == NGX_ERROR) {
                return NGX_CONF_ERROR;
            }

            ctx->var = value[i];

            continue;
        }

        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "invalid parameter \"%V\"", &value[i]);
        return NGX_CONF_ERROR;
    }

    if (name.len == 0) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "\"%V\" must have \"zone\" parameter",
                           &cmd->name);
        return NGX_CONF_ERROR;
    }

    if (ctx == NULL) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "no variable is defined for %V \"%V\"",
                           &cmd->name, &name);
        return NGX_CONF_ERROR;
    }

//name和size都是根据配置指令来获取的,按照上面的配置:则name就是ip_zone,size就是10m
 
    shm_zone = ngx_shared_memory_add(cf, &name, size,
                                     &ngx_http_limit_conn_module);
    if (shm_zone == NULL) {
        return NGX_CONF_ERROR;
    }

    if (shm_zone->data) {
        ctx = shm_zone->data;

        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "%V \"%V\" is already bound to variable \"%V\"",
                           &cmd->name, &name, &ctx->var);
        return NGX_CONF_ERROR;
    }
//设置init函数和data  

    shm_zone->init = ngx_http_limit_conn_init_zone;
    shm_zone->data = ctx;

    return NGX_CONF_OK;
}


接下来看看函数ngx_shared_memory_add

ngx_shm_zone_t *
ngx_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name, size_t size, void *tag)
{
    ngx_uint_t        i;
    ngx_shm_zone_t   *shm_zone;
    ngx_list_part_t  *part;

    part = &cf->cycle->shared_memory.part;
    shm_zone = part->elts;
//先查找同名的共享内存区存在吗?如果存在,就返回存在的。
    for (i = 0; /* void */ ; i++) {
        
        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }
            part = part->next;
            shm_zone = part->elts;
            i = 0;
        }

        if (name->len != shm_zone[i].shm.name.len) {
            continue;
        }

        if (ngx_strncmp(name->data, shm_zone[i].shm.name.data, name->len)
            != 0)
        {
            continue;
        }

        if (size && size != shm_zone[i].shm.size) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                            "the size %uz of shared memory zone \"%V\" "
                            "conflicts with already declared size %uz",
                            size, &shm_zone[i].shm.name, shm_zone[i].shm.size);
            return NULL;
        }

        if (tag != shm_zone[i].tag) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                            "the shared memory zone \"%V\" is "
                            "already declared for a different use",
                            &shm_zone[i].shm.name);
            return NULL;
        }

        return &shm_zone[i];
    }
//如果不存在,创建,加入队列。
    shm_zone = ngx_list_push(&cf->cycle->shared_memory);

    if (shm_zone == NULL) {
        return NULL;
    }

    shm_zone->data = NULL;
    shm_zone->shm.log = cf->cycle->log;
    shm_zone->shm.size = size;
    shm_zone->shm.name = *name;
    shm_zone->shm.exists = 0;
    shm_zone->init = NULL;
    shm_zone->tag = tag;

    return shm_zone;
}


解析完所有配置,自然就完成了上面的步骤,接下来回到ngx_init_cycle中


ngx_cycle_t *
ngx_init_cycle(ngx_cycle_t *old_cycle)
{
......

 //紧接前面解析配置文件代码


//依次初始化链表中的共享内存
    /* create shared memory */

    part = &cycle->shared_memory.part;
    shm_zone = part->elts;

    for (i = 0; /* void */ ; i++) {

        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }
            part = part->next;
            shm_zone = part->elts;
            i = 0;
        }

        if (shm_zone[i].shm.size == 0) {
            ngx_log_error(NGX_LOG_EMERG, log, 0,
                          "zero size shared memory zone \"%V\"",
                          &shm_zone[i].shm.name);
            goto failed;
        }

        shm_zone[i].shm.log = cycle->log;

    ......

 //进行内存映射,获取共享内存地址
       if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) {
            goto failed;
        }
//初始化共享内存,进行slab算法结构分配
        if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) {
            goto failed;
        }

//调用各模块特定的init函数
        if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {
            goto failed;
        }

        continue;
    }

}


看一下函数ngx_init_zone_pool,这就是整个共享内存区结构化开始


static ngx_int_t
ngx_init_zone_pool(ngx_cycle_t *cycle, ngx_shm_zone_t *zn)
{
    u_char           *file;
    ngx_slab_pool_t  *sp;

    sp = (ngx_slab_pool_t *) zn->shm.addr;

    if (zn->shm.exists) {

        if (sp == sp->addr) {
            return NGX_OK;
        }

        ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
                      "shared zone \"%V\" has no equal addresses: %p vs %p",
                      &zn->shm.name, sp->addr, sp);
        return NGX_ERROR;
    }

    sp->end = zn->shm.addr + zn->shm.size;
    sp->min_shift = 3;
    sp->addr = zn->shm.addr;

#if (NGX_HAVE_ATOMIC_OPS)

    file = NULL;

#else

    file = ngx_pnalloc(cycle->pool, cycle->lock_file.len + zn->shm.name.len);
    if (file == NULL) {
        return NGX_ERROR;
    }

    (void) ngx_sprintf(file, "%V%V%Z", &cycle->lock_file, &zn->shm.name);

#endif
//创建个同步锁
    if (ngx_shmtx_create(&sp->mutex, &sp->lock, file) != NGX_OK) {
        return NGX_ERROR;
    }
    sp->size
//进行slab ,这样以后内存分配就按照slab算法来进行了
    ngx_slab_init(sp);

    return NGX_OK;
}


好吧,nginx涉及到的进程间通信就这些了,写的有点乱,啊哈哈哈


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值