注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

widebright的个人空间

// 编程和生活

 
 
 

日志

 
 

Nginx的多进程模型和锁同步(简单那源码阅读)  

2012-04-23 17:52:14|  分类: 程序设计 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
http://lxr.evanmiller.org/http/ident?v=nginx-1.1.12&i=ngx_spinlock 这里可以搜索

nginx / src / core/nginx.c
nginx / src / os / unix / ngx_process_cycle.c 
nginx / src / event / ngx_event.c        

http://trac.nginx.org/nginx/browser/nginx/trunk/src/core/nginx.c
http://trac.nginx.org/nginx/browser/nginx/trunk/src/os/unix/ngx_process_cycle.c
http://trac.nginx.org/nginx/browser/nginx/trunk/src/event/ngx_event.c


// 多进程时,一个master ,多个worker 。 xxxx_cycle 函数就是循环体,循环调用事件处理处理函数。
main 
    ngx_master_process_cycle
         ngx_start_worker_processes
                ngx_spawn_process(-> 
                ngx_worker_process_cycle 
                        ngx_create_thread  
                        ngx_worker_thread_cycle     //线程
                        ngx_process_events_and_timers

//单进程时  没有所谓的master 和worker概念 直接调用事件处理函数。
   ngx_single_process_cycle
         ngx_process_events_and_timers

unix平台的线程函数
ngx_worker_thread_cycle
      ngx_mutex_lock(ngx_posted_events_mutex);
      ngx_cond_wait(thr->cv, ngx_posted_events_mutex)
      ngx_event_thread_process_posted

win32平台的线程函数
ngx_worker_thread
      ngx_process_events_and_timers


ngx_process_events_and_timers
        ngx_trylock_accept_mutex(cycle )
        ngx_process_events
        if (ngx_accept_mutex_held) {    ///各个子进程竞争内存锁,拿到锁的就尝试去accept一个新的连接,当然还有一些为了保持进程间连接分布均匀的计算等。 父进程打开了一个socket连接并

监听之后,就启动工作子进程。各个子进程都是accept同一个socket fd的,所有需要锁的同步。看后面的的说明。
             ngx_shmtx_unlock(&ngx_accept_mutex);
        }
       ngx_wakeup_worker_thread
       ngx_event_process_posted



http://trac.nginx.org/nginx/browser/nginx/trunk/src/event/ngx_event_accept.c
 ngx_int_t
 ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
 {
     if (ngx_shmtx_trylock(&ngx_accept_mutex)) {
                            ngx_accept_mutex_held = 1;



进程之间同步使用共享内存锁或者文件锁。
http://trac.nginx.org/nginx/browser/nginx/trunk/src/core/ngx_shmtx.h
http://trac.nginx.org/nginx/browser/nginx/trunk/src/core/ngx_shmtx.c
typedef struct {
#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;



 线程之间同步可以使用spinlock,但好像都没看到nginx里面用到这个
http://trac.nginx.org/nginx/browser/nginx/trunk/src/core/ngx_spinlock.c


=================================
参考文章。
Nginx源码分析-事件循环
http://www.tbdata.org/archives/1267

Nginx多进程并发连接处理模型
http://blog.dccmx.com/2011/02/nginx-conn-handling/

nginx的进程模型
http://simohayha.iteye.com/blog/467940
nginx中锁的设计以及惊群的处理
http://simohayha.iteye.com/blog/658012

nginx-0.8.6的shmtx代码分析
http://songjinshan.com/blog/index.php/nginx-0-8-6%E7%9A%84shmtx%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/





=========================================
关于如果多进程之间如何accept同一个套接字,我还猜测需要额外的共享的,想起fork系统调用好像子进程是会自动继承父进程打开的文件描述符的,应该就简单多了。但还是要把父进程打开的

socket的 fd传给子进程才行。


UNIX网络编程第1卷.pdf 一书的
第27章  客户-服务器程序其他设计方法
对这种多进程来accept 同一个连接的用法进行了详细解释,还给出了示例代码,比较了各种多进程,多线程,文件锁和共享内存锁的进程公布,父进程做accept再把fd传给子进程和子进程自己

accept的各种模型的性能做了测试和比较。感觉nginx的代码跟这个很类似啊,什么锁啊,进程模型啊,连接的进程间分布啊等概念,写这个代码的人应该看过这个书的吧。确实经典书来的啊!!


-------------------
fork产生的子进程会继承父进程打开的文件描述符
http://linux.die.net/man/2/fork
The child inherits copies of the parent's set of open file descriptors. Each file descriptor in the child refers to the same open file description (see open(2)) as the corresponding file 

descriptor in the parent. This means that the two descriptors share open file status flags, current file offset, and signal-driven I/O attributes (see the description of F_SETOWN and 

F_SETSIG in fcntl(2)).

-------------------------
多个进程都accept同一socket fd时候,有新连接了,不会唤醒所有的进程。惊群 ( thundering herd)问题,听说linux没有这个问题了。看看linux的accept函数的实现

(http://lxr.linux.no/linux+v3.3.3/net/socket.c#L1506)
http://lxr.linux.no/linux+v3.3.3/net/ipv4/tcp_ipv4.c
http://lxr.linux.no/linux+v3.3.3/net/ipv4/inet_connection_sock.c#L269

---------------------
2610struct proto tcp_prot = {
2611        .name                   = "TCP",
2612        .owner                  = THIS_MODULE,
2613        .close                  = tcp_close,
2614        .connect                = tcp_v4_connect,
2615        .disconnect             = tcp_disconnect,
2616        .accept                 = inet_csk_accept,

----------------------------
 266/*
 267 * This will accept the next outstanding connection.
 268 */
 269struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)

里面调用
lock_sock(sk);

 218/*
 219 * Wait for an incoming connection, avoid race conditions. This must be called
 220 * with the socket locked.
 221 */
 222static int inet_csk_wait_for_connect(struct sock *sk, long timeo)

 release_sock(sk);
来等待新的连接
--------------------------

 218/*
 219 * Wait for an incoming connection, avoid race conditions. This must be called
 220 * with the socket locked.
 221 */
 222static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
 223{
 224        struct inet_connection_sock *icsk = inet_csk(sk);
 225        DEFINE_WAIT(wait);
 226        int err;
 227
 228        /*
 229         * True wake-one mechanism for incoming connections: only  注释写的很清楚了。
 230         * one process gets woken up, not the 'whole herd'.
 231         * Since we do not 'race & poll' for established sockets
 232         * anymore, the common case will execute the loop only once.
 233         *
 234         * Subtle issue: "add_wait_queue_exclusive()" will be added
 235         * after any current non-exclusive waiters, and we know that
 236         * it will always _stay_ after any new non-exclusive waiters
 237         * because all non-exclusive waiters are added at the
 238         * beginning of the wait-queue. As such, it's ok to "drop"
 239         * our exclusiveness temporarily when we get woken up without
 240         * having to remove and re-insert us on the wait queue.
 241         */
 242        for (;;) {
 243                prepare_to_wait_exclusive(sk_sleep(sk), &wait,
 244                                          TASK_INTERRUPTIBLE);
 245                release_sock(sk);
 246                if (reqsk_queue_empty(&icsk->icsk_accept_queue))
 247                        timeo = schedule_timeout(timeo);
 248                lock_sock(sk);
 249                err = 0;
 250                if (!reqsk_queue_empty(&icsk->icsk_accept_queue))
 251                        break;
 252                err = -EINVAL;
 253                if (sk->sk_state != TCP_LISTEN)
 254                        break;
 255                err = sock_intr_errno(timeo);
 256                if (signal_pending(current))
 257                        break;
 258                err = -EAGAIN;
 259                if (!timeo)
 260                        break;
 261        }
 262        finish_wait(sk_sleep(sk), &wait);
 263        return err;
 264}
 265


------------------
保证连接在所有子进程的分布是均匀的。书上说是由于内核调度器,基本还是均匀的。但nginx还是做了一些统计来保证吧。

多个进程都在同一个文件描述符上面select调用时的select冲突问题。

在不同进程调用accepte同一个socket fd的原子性问题,使用文件锁来保护accept,还有使用共享内存上的互斥锁。nginx上面的这两种锁都有实现。根据书上的测试通过 字节流管道的进程间通讯来传递新连接描述符的办法比基于锁然后各个子进程自己accept的实现更耗时。

========================================



  评论这张
 
阅读(1309)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017