一起读读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 在这个平台上:

时间: 2024-10-11 18:00:50

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

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

调试 lock 的用法: 使用这个方法,我们能够捕获以下两种的lock的错误: unlocking a lock that we don't actually hold re-locking a non-recursive lock 在之前的分析,我们知道它的其中一部分是通过 evthread_lock_debugging_enabled_ 这变量来进行的.但具体怎么样,来一起深挖一下 Interface void evthread_enable_lock_debugging(void); #d

第一章:设置无缓冲

1 #用select, 要先select一个句柄, 用完后记得select回原来的 2 open FILE, ">log.txt"; 3 select FILE; 4 $| = 1; 5 #$|为true时设置FILE为无缓冲 6 print FILE "The log file data"; 7 #print "The log file data"; 8 select STDOUT; 9 #上面的打印不会经过缓冲而直接写进文件log.tx

构建之法第一章

本章为概论,主要讲解计算机科学的领域.软件工程和计算机科学的关系.软件的特性.软件工程的定义与组成部分等内容. 一.软件: 程序=数据结构+算法 软件=程序+软件工程 软件工程的核心部分: 1.构建管理 2.源代码管理 3.软件设计 4.软件测试 5.项目管理 软件开发有玩具阶段.业余爱好阶段.探索阶段.成熟的产业阶段等四个阶段. 二.软件工程: 软件具有复杂性.不可见性.易变性.服从性.非连续性等. 计算机科学与软件工程的区别(侧重点) 计算机科学: 1.发现和研究长期的.客观的真理 2.理想

第一章随笔&&思考题

<构建之法>已经到手了,但是还没开始看,只翻看了前几页,和传统的教材区别很大,没有大篇幅的理论,比教材看起来有意思.第一章中还提到了‘bug’的由来,第一次听说,感觉挺有意思. 第一周上课老师提问什么是软件,同学们认为软件就是程序,我也是这么想的.但是为什么他要叫软件,而不叫程序呢?对于程序,比较通用的定义是:程序=算法+数据结构.但是算法和数据结构组合起来能构成软件吗?答案是不够的.所以<软件工程概论>中,将软件定义为程序.数据及其相关文档的完整集合(软件=程序+数据+文档).程

使用Micrisoft.net设计方案 第一章 企业解决方案中构建设计模式

第一章企业解决方案中构建设计模式 我们知道的系统总是由简单到复杂,而不是直接去设计一个复杂系统.如果直接去设计一个复杂系统,结果最终会导致失败.在设计系统的时候,先设计一个能够正常工作的系统,然后在此基础上逐步扩展.而一个好的企业设计方案就是由一些短小.简单.可靠.有效的并能够解决问题的机制组成.由这些短小精悍的机制进行组合,形成复杂的系统.而这些机制就设计模式.设计模式就是能够记录这些机制的一些描述. 企业级业务解决方案一般是复杂.性能要好.可扩展性好以及容易维护和可伸缩性强,而设计模式可以帮

经典中的博弈:第一章 C++的Hello,World!

经典中的博弈:第一章 C++的Hello,World! 摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢! "程序设计要通过编写程序的实践来学习"-Brian Kernighan 1.1 程序 何为程序?简单的说,就是为了使计算机能够做事,你需要在繁琐的细节中告诉它怎么做.对于怎么做的描述就是程序.编程是书写和测试怎么做的过程.维基百科上说,一个程序就像一个用汉语(程序设计语言)写下的红烧肉菜谱(程序),用于指

重温《STL源码剖析》笔记 第一章

源码之前,了无秘密. --侯杰 经典的书,确实每看一遍都能重新收获一遍: 第一章:STL简介 STL的设计思维:对象的耦合性极低,复用性极高,符合开发封闭原则的程序库. STL的价值:1.带给我们一套极具实用价值的零部件,以及一个整合的组织. 2.带给我们一个高层次的以泛型思维为基础的.系统化的.条理分明的“软件组件分类学”. 在STL接口之下,任何组件都有最大的独立性,并以所谓迭代器胶合起来,或以配接器互相配接,或以所 谓仿函数动态选择某种策略. STL六大组件:1.容器(containers

Orange&#39;s 自己动手写操作系统 第一章 十分钟完成的操作系统 U盘启动 全记录

材料: 1 nasm:编译汇编源代码,网上很多地方有下 2  WinHex:作为windows系统中的写U盘工具,需要是正版(full version)才有写的权限,推荐:http://down.liangchan.net/WinHex_16.7.rar 步骤: 1 编译得到引导程序的机器代码.用命令行编译汇编源代码:name boot.asm -o boot.bin,其中boot.bin文件产生在命令行的当前目录中. 2 将引导程序写入到U盘引导盘的第一个扇区的第一个字节处(后),即主引导区.

《大道至简》第一章JAVA语言伪代码

第一章写了编程的精义详细写出了编程是简单的.举愚公移山的例子,既写出了我们中华文化源远流长,博大精深,千百年前就有了编程的思想,也引出了结构概念,虽我之死,有 存焉",这里描述了可能存在的分支结构,即"IF"条件判断,以及子子孙孙无穷匮也等循环结构,等编程思想.关于我会不会写程序的问题书里面也做了详细介绍!除了先天智障或后天懒惰者,都是可以学会写程序的,也许会给学编程的学生增加了很大的信心. 下面是源代码................... import.java.大道至简