佐须之男 发布的文章

wifidog源码分析Lighttpd1.4.20源码分析之fdevent系统(2)-----初始化(2)

接着上文的struct ev_map结构,继续朝下走,

if (srv->srvconf.event_handler->used == 0)
    {
        srv->event_handler = event_handlers[0].et;
    }
    else
    {
        for (i = 0; event_handlers[i].name; i++)
        {
            if (0 == strcmp(event_handlers[i].name, srv->srvconf.event_handler->ptr))
            {
                srv->event_handler = event_handlers[i].et;
                break;
            }
        }
    }

srv->srvconf.event_handler->used为0说明配置文件中没有配置所使用的多路IO系统。

跑个题,在配置文件中是如下进行设置的:

## set the event-handler (read the performance
##section in the manual)
server.event-handler = "freebsd-kqueue" # needed on OS X

所设置的值就是上面定义的那个数组中元素的第二个成员(字符串)的值。
如果配置文件中没有配置,那么就使用上面定义的数组的第一个元素的值。这也就是为什么定义的时候那个排序很有意思的原因。
如果配置文件中有配置,那就按配置文件的来喽。(上面的代码删除了处理出错的部分)
从congif_set_defaults()函数回来,继续在main函数中走。
下面的语句设置最大描述符数量:

if (srv->event_handler == FDEVENT_HANDLER_SELECT)
    {
        /*
         * select的硬限制。减去200是为了增加安全性,防止出现段错误。
         */
        srv->max_fds = FD_SETSIZE - 200;
    }
    else
    {
        srv->max_fds = 4096;
    }

由于select()函数一次能处理的fd数量有限制,所以要特殊处理。FD_SETSIZT在select.h中定义。其他情况最大描述符数量都设置为4096.
后面还有一个设置最大fd数量的地方:
//根据实际设置情况,重新设置max_fds。

if (srv->event_handler == FDEVENT_HANDLER_SELECT)
{
    srv->max_fds = rlim.rlim_cur < FD_SETSIZE - 200 ?
rlim.rlim_cur : FD_SETSIZE - 200;
}
else
{
    srv->max_fds = rlim.rlim_cur;
}

这个是根据当前用户的限制来的。
当程序产生子进程后,在子进程中执行的第一条语句就是初始化fdevent系统:

if (NULL == (srv->ev = fdevent_init(srv->max_fds + 1,
srv->event_handler)))
    {
        log_error_write(srv, __FILE__, __LINE__, "s",
"fdevent_init failed");
        return -1;
    }

进入fdevent_init()函数:

/**
 * 初始化文件描述符事件数组fdevent
 */
fdevents *fdevent_init(size_t maxfds, fdevent_handler_t type)
{
    fdevents *ev;

    //内存被初始化为0
    ev = calloc(1, sizeof(*ev));

    //分配数组
    ev->fdarray = calloc(maxfds, sizeof(*ev->fdarray));
    ev->maxfds = maxfds;

    //根据设定的多路IO的类型进行初始化。
    switch (type)
    {
    case FDEVENT_HANDLER_POLL:
        if (0 != fdevent_poll_init(ev))
        {
            fprintf(stderr, "%s.%d: event-handler poll failed\n", __FILE__, __LINE__);
            return NULL;
        }
        break;
    case FDEVENT_HANDLER_SELECT:
        if (0 != fdevent_select_init(ev))
        {
            fprintf(stderr, "%s.%d: event-handler select failed\n", __FILE__, __LINE__);
            return NULL;
        }
        break;
    case FDEVENT_HANDLER_LINUX_RTSIG:
        if (0 != fdevent_linux_rtsig_init(ev))
        {
            fprintf(stderr, "%s.%d: event-handler linux-rtsig failed, try to set server.event-handler = \"poll\" or \"select\"\n",
                    __FILE__, __LINE__);
            return NULL;
        }
        break;
    case FDEVENT_HANDLER_LINUX_SYSEPOLL:
        if (0 != fdevent_linux_sysepoll_init(ev))
        {
            fprintf(stderr, "%s.%d: event-handler linux-sysepoll failed, try to set server.event-handler = \"poll\" or \"select\"\n",
                    __FILE__, __LINE__);
            return NULL;
        }
        break;
    case FDEVENT_HANDLER_SOLARIS_DEVPOLL:
        if (0 != fdevent_solaris_devpoll_init(ev))
        {
            fprintf(stderr, "%s.%d: event-handler solaris-devpoll failed, try to set server.event-handler = \"poll\" or \"select\"\n",
                    __FILE__, __LINE__);
            return NULL;
        }
        break;
    case FDEVENT_HANDLER_FREEBSD_KQUEUE:
        if (0 != fdevent_freebsd_kqueue_init(ev))
        {
            fprintf(stderr, "%s.%d: event-handler freebsd-kqueue failed, try to set server.event-handler = \"poll\" or \"select\"\n",
                    __FILE__, __LINE__);
            return NULL;
        }
        break;
    default:
        fprintf(stderr, "%s.%d: event-handler is unknown, try to set server.event-handler = \"poll\" or \"select\"\n",
                __FILE__, __LINE__);
        return NULL;
    }

    return ev;
}

fdevent_init()函数根据fdevent_handler_t的值调用相应的初始化函数。我们前面已经假设系统使用epoll,那么进入

fdevent_linux_sysepoll_init()函数:
int fdevent_linux_sysepoll_init(fdevents * ev)
{
    ev->type = FDEVENT_HANDLER_LINUX_SYSEPOLL;
#define SET(x) \
    ev->x = fdevent_linux_sysepoll_##x;
    SET(free);
    SET(poll);
    SET(event_del);
    SET(event_add);
    SET(event_next_fdndx);
    SET(event_get_fd);
    SET(event_get_revent);
    //创建epoll
    if (-1 == (ev->epoll_fd = epoll_create(ev->maxfds)))
    {
        fprintf(stderr, "%s.%d: epoll_create failed (%s), try 
to set server.event-handler = \"poll\" or \"select\"\n",
                __FILE__, __LINE__, strerror(errno));
        return -1;
    }
    //设置epoll_fd为运行exec()函数时关闭。
    if (-1 == fcntl(ev->epoll_fd, F_SETFD, FD_CLOEXEC))
    {
        fprintf(stderr,    "%s.%d: epoll_create failed (%s), try 
to set server.event-handler = \"poll\" or \"select\"\n",
                __FILE__, __LINE__, strerror(errno));
        close(ev->epoll_fd);
        return -1;
    }
    //创建fd事件数组。在epoll_wait函数中使用。存储发生了IO事件的fd和
//对应的IO事件。
    ev->epoll_events = malloc(ev->maxfds * 
sizeof(*ev->epoll_events));
    return 0;
}

函数中首先通过SET宏对fdevents结构体中的函数指针赋值。然后创建epoll,最后做一些设置。
至此fdevent的初始化工作已经全部完成了。
额,没想到初始化讲了这么多。还是等下一篇再讲使用吧。。。

本文章由 http://www.wifidog.pro/2015/04/20/wifidog%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90Lighttpd%E5%88%9D%E5%A7%8B%E5%8C%96-2.html 整理编辑,转载请注明出处

wifidog源码分析Lighttpd1.4.20源码分析之fdevent系统(2)-----初始化(1)

前面讲了lighttpd的fdevents结构体以及fdevent系统的对外接口,这一篇将解析一下fdevent系统初始化。
C程序在进行真正的编译之前都要进行预编译,那么,我们就先来看看fdevent系统中的一些宏。首先是fdevent.h开头的一些宏:

#if defined(HAVE_EPOLL_CTL) && defined(HAVE_SYS_EPOLL_H)
# if defined HAVE_STDINT_H
#  include <stdint.h>
# endif
# define USE_LINUX_EPOLL
# include <sys/epoll.h>
 #endif



 #if defined HAVE_POLL && (defined(HAVE_SYS_POLL_H) || defined(HAVE_POLL_H))
# define USE_POLL
# ifdef HAVE_POLL_H
#  include <poll.h>
# else
#  include <sys/poll.h>
# endif
# if defined HAVE_SIGTIMEDWAIT && defined(__linux__)
#  define USE_LINUX_SIGIO
#  include <signal.h>
# endif
 #endif

 #if defined HAVE_SELECT
# ifdef __WIN32
#  include <winsock2.h>
# endif
# define USE_SELECT
# ifdef HAVE_SYS_SELECT_H
#  include <sys/select.h>
# endif
 #endif

 #if defined HAVE_SYS_DEVPOLL_H && defined(__sun)
# define USE_SOLARIS_DEVPOLL
# include <sys/devpoll.h>
 #endif

 #if defined HAVE_SYS_EVENT_H && defined HAVE_KQUEUE
# define USE_FREEBSD_KQUEUE
# include <sys/event.h>
 #endif

 #if defined HAVE_SYS_PORT_H && defined HAVE_PORT_CREATE
# define USE_SOLARIS_PORT
# include <sys/port.h>
 #endif

通过上面的宏判断系统中是否有对应的多路IO系统,如果有,就定义对应的USE_XXX宏,用来方便后面程序的盘算。
预编译完这些宏以后,对于当前系统中有的多路IO系统,就会有对应的USE_XXX符号被定义。预编译器接着运行,将那些不需要的代码都忽略。由于fdevent.h中对所有可能的多路IO系统都定义了初始化函数:

int fdevent_select_init(fdevents * ev);
 int fdevent_poll_init(fdevents * ev);
 int fdevent_linux_rtsig_init(fdevents * ev);
 int fdevent_linux_sysepoll_init(fdevents * ev);
 int fdevent_solaris_devpoll_init(fdevents * ev);
 int fdevent_freebsd_kqueue_init(fdevents * ev);

因此,对于系统中没有的多路IO系统对应的初始化函数,预编译结束后,这些初始化函数被定义为报错函数。如epoll对应的为:

int fdevent_linux_sysepoll_init(fdevents * ev)
{
    UNUSED(ev);
    fprintf(stderr,    "%s.%d: linux-sysepoll not supported,
 try to set server.event-handler = \"poll\" or \"select\"\n",
             __FILE__, __LINE__);
    return -1;
}

等预编译器执行完后,进行真正的编译。这里,我们假设系统中只有epoll。
还是从server.c中的main函数开始。
首先,我们要看一看在配置中,有关fdevent的设置。进入configfile.c文件中的config_set_defaults()函数。函数的一开始就有这么一个定义:

struct ev_map
    {
        fdevent_handler_t et;
        const char *name;
    } event_handlers[] =
    {
        /*
         * - poll is most reliable - select works everywhere -
* linux-* are experimental
         */
#ifdef USE_POLL
        {FDEVENT_HANDLER_POLL, "poll"},
#endif
#ifdef USE_SELECT
        {FDEVENT_HANDLER_SELECT, "select"},
#endif
#ifdef USE_LINUX_EPOLL
        {FDEVENT_HANDLER_LINUX_SYSEPOLL, "linux-sysepoll"},
#endif
#ifdef USE_LINUX_SIGIO
        {FDEVENT_HANDLER_LINUX_RTSIG, "linux-rtsig"},
#endif
#ifdef USE_SOLARIS_DEVPOLL
        {FDEVENT_HANDLER_SOLARIS_DEVPOLL, "solaris-devpoll"},
#endif
#ifdef USE_FREEBSD_KQUEUE
        {FDEVENT_HANDLER_FREEBSD_KQUEUE, "freebsd-kqueue"},
        {FDEVENT_HANDLER_FREEBSD_KQUEUE, "kqueue"},
#endif
        {FDEVENT_HANDLER_UNSET, NULL}
    };

这个结构体定义了多路IO系统的类型和名称。那些宏的作用就不多说了。上面的语句定义了一个struct ev_map类型的数组。数组的内容是当前系统中存在的多路IO系统的类型和名称。这里排序很有意思,从注释中可以看出,poll排在最前因为最可靠,select其次因为支持最广泛,epoll第三因为是最好的。

本文章由 http://www.wifidog.pro/2015/04/20/wifidog%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90lighttpd%E5%88%9D%E5%A7%8B%E5%8C%96-1.html 整理编辑,转载请注明出处

wifidog源码分析Lighttpd1.4.20源码分析之fdevent系统(1)---fdevents结构体和fdevent系统对外接口

前面讲了lighttpd的插件系统,这一篇将看一看lighttpd中的fdevent系统。fdevent系统主要是处理各种IO事件,在web服务器中,主要就是向socket写数据和从socket读数据。通常,web服务器是IO密集型程序,这就要求在数据的读写上,web服务器必须能够具有很好的性能,不会因为某个socket的阻塞而致使其他socket也被阻塞,否则会大大降低服务器的性能。因此,大部分的web服务器都采用非阻塞IO进行数据的读写。lighttpd通过fdevent系统,采用类似OO中面向对象的方式将对IO事件的处理进行封装,对于不同的IO系统,提供一个统一的接口。
lighttpd采用了所谓的Reactor模式,也就是非阻塞IO加多路复用(non-blocking IO + IO multiplexing)。在多路复用上,lighttpd通过fdevent将各种不同的实现进行封装。lighttpd使用的多路IO有如下几个:
1.png

下面看一下fdevent.h中fdevents结构体,这个结构体相当于是一个虚基类,其中的函数指针是纯虚函数。对于每种实现,则相当于是继承了这个基类并实现了其中的纯虚函数,也就是给函数指针赋一个函数地址值。下面是代码:

typedef struct fdevents
{
    fdevent_handler_t type; //多路IO类型
     fdnode **fdarray;     //文件描述符数组
     size_t maxfds;         //最大的文件描述符数

#ifdef USE_LINUX_SIGIO
    int in_sigio;
    int signum;
    sigset_t sigset;
    siginfo_t siginfo;
    bitset *sigbset;
 #endif

#ifdef USE_LINUX_EPOLL
    int epoll_fd;
    struct epoll_event *epoll_events;
 #endif

#ifdef USE_POLL
    struct pollfd *pollfds;     //描述符及其状态的结构体数组
     size_t size;                     //数组中数据的个数
     size_t used;                     //数组的大小
    //用于存储pollfds中为使用的位置。
    //由于可能的删除操作,会是pollfds中存在空档,将这些空档
    //的索引存在unused中,便于下次插入操作时直接使用这些空档
    //减少空间的浪费。
     buffer_int unused;     
 #endif

#ifdef USE_SELECT
    //三个文件描述符集合
     fd_set select_read;             //可读,对应FDEVENT_IN
     fd_set select_write;         //可写,对应FDEVENT_OUT
     fd_set select_error;         //处于异常条件,对应FDEVENT_ERR
    //由于select函数会修改上面的三个集合,
    //因此,在这里保存一个初始的副本。
     fd_set select_set_read;
    fd_set select_set_write;
    fd_set select_set_error;
    int select_max_fd;             //最大的文件描述符数。
 #endif

#ifdef USE_SOLARIS_DEVPOLL
    int devpoll_fd;
    struct pollfd *devpollfds;
 #endif

#ifdef USE_FREEBSD_KQUEUE
    int kq_fd;
    struct kevent *kq_results;
    bitset *kq_bevents;
 #endif

#ifdef USE_SOLARIS_PORT
    int port_fd;
 #endif

    //统一的操作接口,与后面的函数声明对应。
     int (*reset) (struct fdevents * ev);
    void (*free) (struct fdevents * ev);
    int (*event_add) (struct fdevents * ev, int fde_ndx, int fd, int events);
    int (*event_del) (struct fdevents * ev, int fde_ndx, int fd);
    int (*event_get_revent) (struct fdevents * ev, size_t ndx);
    int (*event_get_fd) (struct fdevents * ev, size_t ndx);
    int (*event_next_fdndx) (struct fdevents * ev, int ndx);
    int (*poll) (struct fdevents * ev, int timeout_ms);
    int (*fcntl_set) (struct fdevents * ev, int fd);
} fdevents;

可以看到这个结构体中使用很多宏,这是为了在编译的时候去掉那些没有使用到的变量,根据当前所使用的多路IO系统,对这个结构体进行定制。
结构体的第一个成员是一个枚举类型fdevent_handler_t,定义如下:

typedef enum
{
    FDEVENT_HANDLER_UNSET,                 //未定义
     FDEVENT_HANDLER_SELECT,             //select
     FDEVENT_HANDLER_POLL,                 //poll
     FDEVENT_HANDLER_LINUX_RTSIG,         //rtsig
     FDEVENT_HANDLER_LINUX_SYSEPOLL,     //sysepoll
     FDEVENT_HANDLER_SOLARIS_DEVPOLL,     //devpoll
     FDEVENT_HANDLER_FREEBSD_KQUEUE,     //kqueue
     FDEVENT_HANDLER_SOLARIS_PORT         //port
 } fdevent_handler_t;

这个枚举类型标记了所有可能用到的多路IO系统。
结构体中的第二个成员fdnode **fdarray;,是一个fdnode类型变量的数组。fdnode的定义如下:

typedef struct _fdnode
{
    fdevent_handler handler; //处理函数指针
    void *ctx;                //文件描述符的context
    int fd;                  //文件描述符
    struct _fdnode *prev, *next; //指针
} fdnode;

fdevent_handler handler是一个函数指针,用来存储这个描述符处理函数的地址。关于这个地址,后面的文章中将会有详细的介绍,其定义为typedef handler_t(*fdevent_handler) (void *srv, void *ctx, int revents);。从最后两个变量可以看出,这应该是一个链表的节点,但是,这个结构体是以数组的形式存储的,也就是fdevents中的fdarray变量,这样可以提高查询的效率。
后面由宏包裹的变量就是对于各个不同的多路IO系统定义的变量。我们着重看一看linux下的epoll所使用的变量:

#ifdef USE_LINUX_EPOLL
     int epoll_fd; //epoll_create返回的描述符
     struct epoll_event *epoll_events;//保存fd及对应的事件
#endif

这里要说明一下poll使用的变量buffer_init unused。这个变量的类型的定义如下:

typedef struct
{
    int *ptr;         //位置索引数组。
    size_t used;     //数组中数据个数。
    size_t size;     //数组长度。
} buffer_int;

其实就是一个int数组,只不过封装了一下,增加了两个属性。unused数组中存放的是pollfds的下标值。在后面的实现中我们可以看到,pollfds是一个struct pollfd类型数组,注意,不是这个类型的指针的数组。这个数组的大小是根据fdevents中的maxfds的值定的,并且在初始化的时候数组的空间也一次性分配好。由于对pollfds数组有删除元素的操作,因此,会在数组中留下“洞”,而ununsd就是存储这些“洞”的下标值,便于在插入元素时,快速的找到位置。这是一个很有用的技巧,在对数据进行反复的删除插入元素操作时,可以提高效率。大多数情况下使用栈链表来存储这些可用空间的下标,栈顶指向链表头。
其余的变量读者可自行分析。
接下来看看这些函数指针。这些函数指针对应与结构体定义后面的一系列函数声明。从名字中可以轻易的看出对应关系。

/*
 * 重置和释放fdevent系统。
 */
int fdevent_reset(fdevents * ev);
void fdevent_free(fdevents * ev);
/*
 * 将fd增加到fd event系统中。events是要对fd要监听的事件。
 * fde_ndx是fd对应的fdnode在ev->fdarray中的下标值的指针。
 * 如果fde_ndx==NULL,则表示在fd event系统中增加fd。如果不为NULL,则表示这个
 * fd已经在系统中存在,这个函数的功能就变为将对fd监听的事件变为events。
 */
int fdevent_event_add(fdevents * ev, int *fde_ndx, int fd, int events);
/*
 * 从fd event系统中删除fd。 fde_ndx的内容和上面的一致。
 */
int fdevent_event_del(fdevents * ev, int *fde_ndx, int fd);
/*
 * 返回ndx对应的fd所发生的事件。
 * 这里的ndx和上面的fde_ndx不一样,这个ndx是ev->epoll_events中epoll_event结构体的下标。
 * 第一次调用的时候,通常ndx为-1。
 * 这个ndx和其对应的fd没有关系。而fde_ndx等于其对应的fd。
 */
int fdevent_event_get_revent(fdevents * ev, size_t ndx);
/*
 * 返回ndx对应的fd。
 */
int fdevent_event_get_fd(fdevents * ev, size_t ndx);
/*
 * 返回下一个发生IO事件的fd。
 */
int fdevent_event_next_fdndx(fdevents * ev, int ndx);
/*
 * 开始等待IO事件。timeout_ms是超时限制。
 */
int fdevent_poll(fdevents * ev, int timeout_ms);
/**
 * 设置fd的状态,通常是设置为运行exec在子进程中关闭和非阻塞。
 */
int fdevent_fcntl_set(fdevents * ev, int fd);

在fdevent.c文件中,这些函数的实现基本上都是简单的调用fdevents结构体中对应的函数指针。对于lighttpd,通过调用上面的这些函数完成IO事件的处理,对于具体到底是谁处理了这些事件,lighttpd并不知道,也不关心。
剩下的函数声明:

/*
 * 返回fd对应的事件处理函数地址。也就是fdnode中handler的值。
 */
fdevent_handler fdevent_get_handler(fdevents * ev, int fd);
/*
 * 返回fd对应的环境。也就是fdnode中ctx的值。
 */
void *fdevent_get_context(fdevents * ev, int fd);

/*
 * 注册和取消注册fd。
 * 就是生成一个fdnode,然后保存在ev->fdarray中。或者删除之。
 */
int fdevent_register(fdevents * ev, int fd, fdevent_handler handler, void *ctx);
int fdevent_unregister(fdevents * ev, int fd);
/**
 * 初始化各种多路IO。
 */
int fdevent_select_init(fdevents * ev);
int fdevent_poll_init(fdevents * ev);
int fdevent_linux_rtsig_init(fdevents * ev);
int fdevent_linux_sysepoll_init(fdevents * ev);
int fdevent_solaris_devpoll_init(fdevents * ev);
int fdevent_freebsd_kqueue_init(fdevents * ev);

下面总结一下:
文件fdevent.h中声明的一系列函数就是fdevent系统对外的接口,这相当与类的公有函数。lighttpd通过调用这些函数来实现IO事件的处理。在这些函数的具体实现仅仅是简单的调用了fdevents结构体中的函数指针。而这写函数指针所对应的函数分别定义在以fdevent_开头的.c文件中。从这些文件的名字可以看出其所对应的IO系统。在这些文件中,函数大多是static,这就行当与类的私有函数,起到隐藏具体实现的效果。后面的问装中我们会具体的分析一个多路IO系统的使用。
lighttpd作者对fdevent系统封装相当的出彩,对于理解在C中使用面向对象的方式编程具有很好的帮助。
下一篇中将看一看fdevent系统的初始化和使用。

本文章由 http://www.wifidog.pro/2015/04/20/wifidog%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90lighttpd%E5%AF%B9%E5%A4%96%E6%8E%A5%E5%8F%A3.html 整理编辑,转载请注明出处

Tomato开发视频教程-Tomato 系统目录介绍

本视频教程简单讲解了,Tomato 一些常用目录的功能,大家可以从视频内容中学习到比如porc目录的作用,dev目录,如何如何备份cfe等。大家在观看教程的过程中,遇到一些不理解的地方可以在帖子里回复。

土豆视频地址,在条件允许的情况下,请大家选择超清模式。

优酷视频地址,在条件允许的情况下,请大家选择超清模式或1080P。

百度网盘,视频教程原文件下载地址,无损视频适合收藏 http://pan.baidu.com/s/1dD5ylFF

顺便多提一句各位网友自己或者朋友有需要定制路由器系统的项目,可以记下我的联系方式181-1435-4589 (QQ和手机) ,你也可以在你的博客或者个人网站上增加一个www.router.tw 的友链。大家的支持是我进步和奋斗的动力。

本文章由 http://www.wifidog.pro/2015/04/17/tomato-%E5%BC%80%E5%8F%91.html 整理编辑,转载请注明出处