首页 > 代码库 > 一起读读libevent的源代码:Libevent 第一章 设置libevent

一起读读libevent的源代码:Libevent 第一章 设置libevent

某人曾提醒我要多读源代码,我就选了libevent 2.1.8稳定版的源代码来读。

读了一会,纯看源代码里面的东西,还挺无聊的。所以我就开始,便看他们的编程教程:

http://www.wangafu.net/~nickm/libevent-book/

然后每遇到实现,我就跑去源代码中看别人怎么做到的。

这样还是比较有趣的,一个一个小目标的去做,直到这个事情是为什么而做。

我之前,已经把编程的指导粗略看过一边,也是边犯困边看,再开始看源代码,昨天睡了十次八次,才看了很小的一部分。

这样太慢了,而且很无聊。所以换了这样的方式,省去细枝末节,按功能来进行逐个翻看。

下面是我个人做这个事的笔记   

ps:这里面前面代码里面有数字,是代码的数字,可以参考我之前的设置LXR来查看这些源代码,会比较方便。

 


 

如何设置log messag:

可以通过以下的接口:

Interface
#define EVENT_LOG_DEBUG 0
#define EVENT_LOG_MSG   1
#define EVENT_LOG_WARN  2
#define EVENT_LOG_ERR   3

typedef void (*event_log_cb)(int severity, const char *msg);

void event_set_log_callback(event_log_cb cb);

上面的宏定义,是一个severity的等级,下面的是写日记的回调函数。下面是将这个回调函数设置到eventbase里面的。让我们来看看这些函数都是怎么写的。

0719 /**
0720   A callback function used to intercept Libevent‘s log messages.
0721 
0722   @see event_set_log_callback
0723  */
0724 typedef void (*event_log_cb)(int severity, const char *msg);

这是它原本的定义,只是一个原型的定义。它在/include/event2/event.h和/log.c中,都会被引用到:

在头文件event.h的引用,也是一个函数原型:

0725 /**
0726   Redirect Libevent‘s log messages.
0727 
0728   @param cb a function taking two arguments: an integer severity between
0729      EVENT_LOG_DEBUG and EVENT_LOG_ERR, and a string.  If cb is NULL,
0730      then the default log is used.
0731 
0732   NOTE: The function you provide *must not* call any other libevent
0733   functionality.  Doing so can produce undefined behavior.
0734   */
0735 EVENT2_EXPORT_SYMBOL
0736 void event_set_log_callback(event_log_cb cb);

在log.c当中,有两处的引用:

0219 static event_log_cb log_fn = NULL;

0221 void
0222 event_set_log_callback(event_log_cb cb)
0223 {
0224     log_fn = cb;
0225 }

这里的代码也很明显,就是设置了一个函数,我继续深挖,它这个函数long_fn,是如何决定它作为记录的函数的?

挖下还是会有的,就在同一个文件当中,有一个 event_log 的函数,用于决定运用哪个函数:我们设置的log_fn 还是 fprintf 

0227 static void
0228 event_log(int severity, const char *msg)
0229 {
0230     if (log_fn)
0231         log_fn(severity, msg);
0232     else {
0233         const char *severity_str;
0234         switch (severity) {
0235         case EVENT_LOG_DEBUG:
0236             severity_str = "debug";
0237             break;
0238         case EVENT_LOG_MSG:
0239             severity_str = "msg";
0240             break;
0241         case EVENT_LOG_WARN:
0242             severity_str = "warn";
0243             break;
0244         case EVENT_LOG_ERR:
0245             severity_str = "err";
0246             break;
0247         default:
0248             severity_str = "???";
0249             break;
0250         }
0251         (void)fprintf(stderr, "[%s] %s\n", severity_str, msg);
0252     }
0253 }

要注意的是,在一个用户提供的 eent_log_cb 的回调函数里面,调用libevent的函数,是不安全的。比如说,你要在回调函数里面将错误信息写socket,就不要使用libevent的buffervent这些功能,会产生怪异的,并且难以调试的错误。

This restriction may be removed for some functions in a future version of Libevent.

 


 

 

 

默认情况下,debug logs 是不会启用的。需要通过以下接口,才能够打开他们:

Interface
#define EVENT_DBG_NONE 0
#define EVENT_DBG_ALL 0xffffffffu

void event_enable_debug_logging(ev_uint32_t which);

这个函数的用法,就是使用 event_enable_debug_logging 这个函数,设置调试的等级为上面的两个等级之间的其中一个。可以是没有调试信息 EVENT_DBG_NONE 或者是所有的它i傲视信息 EVENT_DBG_ALL

event_enable_debug_logging 函数的定义是在/log.c里面:

0085 event_enable_debug_logging(ev_uint32_t which)
0086 {
0087 #ifdef EVENT_DEBUG_LOGGING_ENABLED
0088     event_debug_logging_mask_ = which;
0089 #endif
0090 }

0044 #if !defined(EVENT__DISABLE_DEBUG_MODE) || defined(USE_DEBUG)
0045 #define EVENT_DEBUG_LOGGING_ENABLED
0046 #endif

0073 ev_uint32_t event_debug_logging_mask_ = DEFAULT_MASK;

0065 #ifdef EVENT_DEBUG_LOGGING_ENABLED
0066 #ifdef USE_DEBUG
0067 #define DEFAULT_MASK EVENT_DBG_ALL
0068 #else
0069 #define DEFAULT_MASK 0
0070 #endif

 

在这里,event_enable_debug_logging 的函数的行为,是根据 EVENT_DEBUG_LOGGING_ENABLED 来进行抉择的。

event_debug_logging_mask_ 也是根据 EVENT_DEBUG_LOGGING_ENABLED 来进行决定DEFAULT_MASK 是0 还是所有。

EVENT_DEBUG_LOGGING_ENABLED 的定义, 是根据 EVENT__DISABLE_DEBUG_MODE 宏和 USE_DEBUG宏来决定的。

这两个宏,在配置文件中是没有的,那么,只要我们在编译的时候,不设置EVENT__DISABLE_DEBUG_MODE 或者是自己设置了USER_DEBUG,就可以有这些功能,也就是,默认是会有这些功能的。

 


 

 

如何处理致命的错误:

可以使用自己的错误处理函数来处理致命的错误:

Interface
typedef void (*event_fatal_cb)(int err);
void event_set_fatal_callback(event_fatal_cb cb);

首先,你需要定义一个新的函数,这个函数是Libevent出现致命错误的时候,进行调用的,函数的原型就是 event_fatal_cb。然后再使用event_set_fatal_callback来进行设置。

首先来看一下:event_fatal_cb 的定义和引用的情况:

// /include/event2/event.h

0738 /**
0739    A function to be called if Libevent encounters a fatal internal error.
0740 
0741    @see event_set_fatal_callback
0742  */
0743 typedef void (*event_fatal_cb)(int err);

0745 /**
0746  Override Libevent‘s behavior in the event of a fatal internal error.
0747 
0748  By default, Libevent will call exit(1) if a programming error makes it
0749  impossible to continue correct operation.  This function allows you to supply
0750  another callback instead.  Note that if the function is ever invoked,
0751  something is wrong with your program, or with Libevent: any subsequent calls
0752  to Libevent may result in undefined behavior.
0753 
0754  Libevent will (almost) always log an EVENT_LOG_ERR message before calling
0755  this function; look at the last log message to see why Libevent has died.
0756  */
0757 EVENT2_EXPORT_SYMBOL
0758 void event_set_fatal_callback(event_fatal_cb cb);

// /log.c
0063 static event_fatal_cb fatal_fn = NULL;

0092 void
0093 event_set_fatal_callback(event_fatal_cb cb)
0094 {
0095     fatal_fn = cb;
0096 }

但是, 在这里, 默认行为就是不处理。

0063 static event_fatal_cb fatal_fn = NULL;

 


 

 

Memory management

默认情况下,Libevent使用C语言库的内存分配函数来进行分配。可以用下面的接口来进行自定义:

// //mm-internal.h

0035 #ifndef EVENT__DISABLE_MM_REPLACEMENT
0036 /* Internal use only: Memory allocation functions. We give them nice short
0037  * mm_names for our own use, but make sure that the symbols have longer names
0038  * so they don‘t conflict with other libraries (like, say, libmm). */
0039 
0040 /** Allocate uninitialized memory.
0041  *
0042  * @return On success, return a pointer to sz newly allocated bytes.
0043  *     On failure, set errno to ENOMEM and return NULL.
0044  *     If the argument sz is 0, simply return NULL.
0045  */
0046 void *event_mm_malloc_(size_t sz);
0047 
0048 /** Allocate memory initialized to zero.
0049  *
0050  * @return On success, return a pointer to (count * size) newly allocated
0051  *     bytes, initialized to zero.
0052  *     On failure, or if the product would result in an integer overflow,
0053  *     set errno to ENOMEM and return NULL.
0054  *     If either arguments are 0, simply return NULL.
0055  */
0056 void *event_mm_calloc_(size_t count, size_t size);
0057 
0058 /** Duplicate a string.
0059  *
0060  * @return On success, return a pointer to a newly allocated duplicate
0061  *     of a string.
0062  *     Set errno to ENOMEM and return NULL if a memory allocation error
0063  *     occurs (or would occur) in the process.
0064  *     If the argument str is NULL, set errno to EINVAL and return NULL.
0065  */
0066 char *event_mm_strdup_(const char *str);
0067 
0068 void *event_mm_realloc_(void *p, size_t sz);
0069 void event_mm_free_(void *p);
0070 #define mm_malloc(sz) event_mm_malloc_(sz)
0071 #define mm_calloc(count, size) event_mm_calloc_((count), (size))
0072 #define mm_strdup(s) event_mm_strdup_(s)
0073 #define mm_realloc(p, sz) event_mm_realloc_((p), (sz))
0074 #define mm_free(p) event_mm_free_(p)
0075 #else
0076 #define mm_malloc(sz) malloc(sz)
0077 #define mm_calloc(n, sz) calloc((n), (sz))
0078 #define mm_strdup(s) strdup(s)
0079 #define mm_realloc(p, sz) realloc((p), (sz))
0080 #define mm_free(p) free(p)
0081 #endif

取决于 EVENT__DISABLE_MM_REPLACEMENT 的定义,这个定义可以在 /WIN32-Code/nmake/event2/event-config.h下面

/* Define if libevent should not allow replacing the mm functions */
/* #undef EVENT__DISABLE_MM_REPLACEMENT */

定义了这个,就会在建造的时候,无法代替这些内存分配函数。

如果没有定义,那么就会定义下面这几个函数的接口: 

void *event_mm_malloc_(size_t sz);
void *event_mm_calloc_(size_t count, size_t size);
char *event_mm_strdup_(const char *str);
void *event_mm_realloc_(void *p, size_t sz);
void event_mm_free_(void *p);

这些函数的定义,也都在/event.c下面

malloc

3432 void *
3433 event_mm_malloc_(size_t sz)
3434 {
3435     if (sz == 0)
3436         return NULL;
3437 
3438     if (mm_malloc_fn_)
3439         return mm_malloc_fn_(sz);
3440     else
3441         return malloc(sz);
3442 }

 

calloc函数

3444 void *
3445 event_mm_calloc_(size_t count, size_t size)
3446 {
3447     if (count == 0 || size == 0)
3448         return NULL;
3449 
3450     if (mm_malloc_fn_) {
3451         size_t sz = count * size;
3452         void *p = NULL;
3453         if (count > EV_SIZE_MAX / size)
3454             goto error;
3455         p = mm_malloc_fn_(sz);
3456         if (p)
3457             return memset(p, 0, sz);
3458     } else {
3459         void *p = calloc(count, size);
3460 #ifdef _WIN32
3461         /* Windows calloc doesn‘t reliably set ENOMEM */
3462         if (p == NULL)
3463             goto error;
3464 #endif
3465         return p;
3466     }
3467 
3468 error:
3469     errno = ENOMEM;
3470     return NULL;
3471 }

 

strdup的函数,作用是复制一个字符串

3473 char *
3474 event_mm_strdup_(const char *str)
3475 {
3476     if (!str) {
3477         errno = EINVAL;
3478         return NULL;
3479     }
3480 
3481     if (mm_malloc_fn_) {
3482         size_t ln = strlen(str);
3483         void *p = NULL;
3484         if (ln == EV_SIZE_MAX)
3485             goto error;
3486         p = mm_malloc_fn_(ln+1);
3487         if (p)
3488             return memcpy(p, str, ln+1);
3489     } else
3490 #ifdef _WIN32
3491         return _strdup(str);
3492 #else
3493         return strdup(str);
3494 #endif
3495 
3496 error:
3497     errno = ENOMEM;
3498     return NULL;
3499 }

 

这两个函数realloc和free函数

3501 void *
3502 event_mm_realloc_(void *ptr, size_t sz)
3503 {
3504     if (mm_realloc_fn_)
3505         return mm_realloc_fn_(ptr, sz);
3506     else
3507         return realloc(ptr, sz);
3508 }
3509 

3510 void
3511 event_mm_free_(void *ptr)
3512 {
3513     if (mm_free_fn_)
3514         mm_free_fn_(ptr);
3515     else
3516         free(ptr);
3517 }

 

 

不定义的话, 就可以替换 ,使用下面的接口来进行替换

Interface
void event_set_mem_functions(void *(*malloc_fn)(size_t sz),
                             void *(*realloc_fn)(void *ptr, size_t sz),
                             void (*free_fn)(void *ptr));

 

在上面的实现的基础上,如何实现这个函数,就会比较简单了。只要设置代面里面的 mm_*_fn 就好了

3519 void
3520 event_set_mem_functions(void *(*malloc_fn)(size_t sz),
3521             void *(*realloc_fn)(void *ptr, size_t sz),
3522             void (*free_fn)(void *ptr))
3523 {
3524     mm_malloc_fn_ = malloc_fn;
3525     mm_realloc_fn_ = realloc_fn;
3526     mm_free_fn_ = free_fn;
3527 }

要注意的是,内存分享函数,是会影响所有需要allocate,resize,或free的函数功能的。因此,必须要在任何这些Libevent函数之前。来进行设置。否则,就会一半是用默认的,一半用自己的。

 

自定义的分配函数,必须是要返回内存的alignment得和C的标准库里面的一样。

 

你的分配函数必须要正确处理realloc(NULL, sz) 正确,处理方式就是malloc(sz)。

 

必须要处理realloc(ptr, 0) 的方式为 free(ptr)

 

free函数不需要处理free(NULL)

 

不需要处理malloc(0)

 

内存分配函数必须是threadsafe的

 

如果替换了malloc,也请使用替换的free来进行释放,因为Libevent会使用这些函数来进行分配和回收。

 


 

 

Locks and threading

多线程的程序,多个线程不可能总是安全的去访问同一个数据。

Libevent 结构一般可以用三种方式来在多个线程中进行工作:

1、一些结构体固有的单个进程的:也就是说,它们都是不能够被多个进程来进行同时访问的

2、一些结构体是可选的locked的:你可以告诉Libevent,哪些object是需要多个进程访问。

3、一些结构体总是被锁上的,因为Libevent总是访问使用锁来进行访问它的。

 

为了能够在Libevent进行上所,你必须告诉Libevent使用哪个Lock函数。这必须在使用任何函数之前先设置好这个。

 

如果你使用的是pthreads库,那么你就是幸运的。因为在那个库里面,有与定义的函数,可以用来设置Libevent来使用right pthreads。

 

接口如下:

Interface
#ifdef _EVENT_HAVE_PTHREADS
int evthread_use_pthreads(void);
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED
#endif

 

这些函数的定义和说明如下:

// /include/event2/thread.h

0209 /** Sets up Libevent for use with Pthreads locking and thread ID functions.
0210     Unavailable if Libevent is not build for use with pthreads.  Requires
0211     libraries to link against Libevent_pthreads as well as Libevent.
0212 
0213     @return 0 on success, -1 on failure. */
0214 EVENT2_EXPORT_SYMBOL
0215 int evthread_use_pthreads(void);
// /evthread_pthread.c

0163 int
0164 evthread_use_pthreads(void)
0165 {
0166     struct evthread_lock_callbacks cbs = {
0167         EVTHREAD_LOCK_API_VERSION,
0168         EVTHREAD_LOCKTYPE_RECURSIVE,
0169         evthread_posix_lock_alloc,
0170         evthread_posix_lock_free,
0171         evthread_posix_lock,
0172         evthread_posix_unlock
0173     };
0174     struct evthread_condition_callbacks cond_cbs = {
0175         EVTHREAD_CONDITION_API_VERSION,
0176         evthread_posix_cond_alloc,
0177         evthread_posix_cond_free,
0178         evthread_posix_cond_signal,
0179         evthread_posix_cond_wait
0180     };
0181     /* Set ourselves up to get recursive locks. */
0182     if (pthread_mutexattr_init(&attr_recursive))
0183         return -1;
0184     if (pthread_mutexattr_settype(&attr_recursive, PTHREAD_MUTEX_RECURSIVE))
0185         return -1;
0186 
0187     evthread_set_lock_callbacks(&cbs);
0188     evthread_set_condition_callbacks(&cond_cbs);
0189     evthread_set_id_callback(evthread_posix_get_id);
0190     return 0;
0191 }

在这个 evthread_use_pthreads 函数当中它做了如下的事情:

struct evthread_lock_callbacks 这个结构体,是用来描述一个多线程库的接口的,这会被用于进行上锁:

0091 /** This structure describes the interface a threading library uses for
0092  * locking.   It‘s used to tell evthread_set_lock_callbacks() how to use
0093  * locking on this platform.
0094  */
0095 struct evthread_lock_callbacks {
0096     /** The current version of the locking API.  Set this to
0097      * EVTHREAD_LOCK_API_VERSION */
0098     int lock_api_version;
0099     /** Which kinds of locks does this version of the locking API
0100      * support?  A bitfield of EVTHREAD_LOCKTYPE_RECURSIVE and
0101      * EVTHREAD_LOCKTYPE_READWRITE.
0102      *
0103      * (Note that RECURSIVE locks are currently mandatory, and
0104      * READWRITE locks are not currently used.)
0105      **/
0106     unsigned supported_locktypes;
0107     /** Function to allocate and initialize new lock of type ‘locktype‘.
0108      * Returns NULL on failure. */
0109     void *(*alloc)(unsigned locktype);
0110     /** Funtion to release all storage held in ‘lock‘, which was created
0111      * with type ‘locktype‘. */
0112     void (*free)(void *lock, unsigned locktype);
0113     /** Acquire an already-allocated lock at ‘lock‘ with mode ‘mode‘.
0114      * Returns 0 on success, and nonzero on failure. */
0115     int (*lock)(unsigned mode, void *lock);
0116     /** Release a lock at ‘lock‘ using mode ‘mode‘.  Returns 0 on success,
0117      * and nonzero on failure. */
0118     int (*unlock)(unsigned mode, void *lock);
0119 };

 

另外一个结构体是struct evthread_condition_callbacks, 这个结构体是用来描写一个线程库的接口用来作条件变量,这是用来告诉 evthread_set_condition_callbacks 要怎么使用locking 在这个平台上:

 

一起读读libevent的源代码:Libevent 第一章 设置libevent