存档

2011年5月 的存档

Apache MPM Prefork设计方法浅析

五月 16th, {2011 2 条评论 9,765 人阅读过  

最近几天翻阅了apache的MPM(Multi-Processing Module)机制相关的代码,虽然还有很多细节没有搞明白,但对apache的服务器模型有了一个大体的概念,对于不同的操作系统,apache提供了不同的默认MPM模型,下表是不同操作系统默认的MPM模型:

BeOS beos
Netware mpm_netware
OS/2 mpmt_os2
Unix prefork
Windows mpm_winnt

Unix平台则对应着prefork模型,prefork从名字上看意思是预先生成子进程,所以这种模型大致上是怎么工作的我们心里差不多有些认识了,prefork是一种很重要的服务器程序设计模型,对应的还有prethread,prefork一般应用在Unix平台上,因为在服务器启动时需要预告fork出一些空闲的子进程,由它们共同监听客户端的请求,这样来实现快速高并发的特性,这种机制之所以不适合Windows等平台,是因为在Windows等平台上进程的代价太高。

apache的进程管理中有一个叫做scoreboard(记分牌)的概念,主进程在进入MPM循环以前会先在进程池中创建一个scoreboard对象,该对象定义如下:

typedef struct {
    global_score *global;
    process_score *parent;
    worker_score **servers;
    lb_score     *balancers;
} scoreboard;

global_score保存主进程的状态,process_score则是一个数组插槽,每个插槽保存一个子进程的状态,worker_score则是一个二维数组插槽,用来保存每个子进程创建的线程状态,根据这个结构主进程可以对子进程以及相关的线程进行管理,apache按照最大化的原则来分配内存,比如会按配置中允许最多的进程数目来为parent分配内存空间。

MPM初始化还有一个很重要的方面是创建一个进程锁,fork()出来的子进程与父进程并不共享内存空间,多进程之于多线程的优势在于多进程可以省去多线程进行线程同步的开销,而这里创建的进程锁,主要作用是为了给accept()加锁,为了避免thundering herd问题。apache实现了五种类型的进程锁,使用flock()或fcntl()实现的文件锁,Posix信号量或System V信号量,以及使用pthread线程库实现的互斥锁。我理解的是文件锁的效率会低于其它类型的锁,因为文件锁要涉及到文件系统的IO操作。我只阅读了跟pthread相关的代码,和一般的多进程程序实现方式一致,因为子进程与父进程以及子进程之间不共享内存空间的,所以不可能像多线程程序一样将互斥锁定义为全局变量 ,因此使用共享内存机制,将互斥锁变量存放到共享内存里面,并设置共享属性。然后便可以使用该互斥锁对子进程中的accept()过程进行加锁。

初始化最后需要开始创建预定个数的子进程,调用startup_children()函数创建指定个数的子进程,该函数会检查scoreboard的空闲插槽,在空闲插槽上调用make_child()函数来在该插槽位置处创建一个子进程,该函数设置scoreboard中进程的状态,并fork()一个子进程,将子进程的pid写入到scoreboard对应的插槽处,子进程创建之后设置SIGHUP和SIGTERM信号,这两个信号对应的回调函数均为clean_child_exit()函数,该函数销毁内存池然后退出子进程。

make_child()函数执行成功后进行child_main()函数,即子进程的主循环。该函数看起来比较复杂,其实做的事情也很简单,首先是创建相关的内存池,对进程锁进行初始化(对于pthread进程锁对应是一个空函数,即无需进行初始化),将socket描述符加入到pollset中,这里的pollset也是apache抽象出来的概念,它的实现可以是kqueue/port/epoll/poll/select,具体采用哪种方式也是配置可选的。这里是我不太明白的地方,经常看到评论说nginx效率高于apache,当问起nginx效率高于apache的主要原因时,得到的答案很多都是nginx采用kqueue和epoll实现了高并发,其实感觉这个理由并不充分,我们可以看到apache同样也实现了kqueue和epoll的多路复用,如果这因为这个的话那apache没有理由会比nginx效率低多少的,另外也看到有说apache的进程管理机制占用内存过高,而且时常需要进行进程切换从而占用了CPU时间,这个说法可以接受,现在非常想去读下nginx的源码,想看看它到底是采用了什么样的机制带来了它如此之多的好评,下一步就可始阅读下nginx的源码,对比着apache,探索一些高并发服务器设计的最优方法。

子进程进入主循环之后会调用accept()方法,这个方法是需要进行加锁的,之后创建一个新的连接对象,并调用HOOK函数对连接进行处理,HOOK机制是apache模块化很重要的一种机制,在主程序中调用HOOK函数,具体的实现由具体的模块来定义。

父进程在创建完子进程之后也进行主循环,监控活动子进程的数目,并通过一定的调度使用子进程数目维护一个平衡,父进程使用waitpid()函数来检测子进程的退出情况,如果有进程退出,则创建一个新的进程来替代已结束的进程从而维持总数的一个平衡。

当然apache还有平稳启动机制,关于平衡启动的代码我暂时略过了,没有细读,以后有时间再回过头来仔细研究。

分类: C/C++ 标签:

Openfetion for ubuntu messaging menu开发手记

五月 3rd, {2011 7 条评论 11,643 人阅读过  

自从ubuntu11.04发布之后Openfetion就遇到了一个比较麻烦的问题,把Openfetion飞信最小化到托盘之后就找不到了,没用过ubuntu11.04,不过据说它的unity桌面貌似没有status icon这回事,所以把Openfetion塞进Messaging Menu也成了一个很重要的任务,在这里把开发过程和大家分享。

首先要感谢@YunQiangSu提供的关于ubuntu messaging menu的资料,是在ubuntu wiki上关于Messaging Menu的介绍,链接在此:https://wiki.ubuntu.com/MessagingMenu/,这篇文章是关于Messaging Menu的一个介绍,以及它的行为和样式的一个指南,虽然没有涉及到具体的开发细则,但也不失为一个很重要的参考。很惭愧地说,在这之前我甚至不知道在ubuntu右上角看到的那个信封到底叫什么名字,后来知道它原来是一个Indicator,ubuntu的status icon区域很多软件都是用libappindicator来实现的,所以它们的行为和其它的发行版不太一致,比如Dropbox和Transimission左键点击Status Icon就可以弹出菜单,而在Slackware里面就只能用右键才能弹出菜单,这就是它们的不同,一个是Indicator,一个是普通的Gnome Status Icon,而这里面提到的右上角的那个信封便是Indiactor的一种,名字叫做Messaging Menu。好吧,我相信很多ubuntu用户会过来鄙视我的,写下这些给那些和我一样的小白扫扫盲,有误请指正。

我们可以把软件安装到Messaging Menu里面,这样即使软件没有启动也可以在Messaging Menu里面找到该软件,并可以从那里启动该软件,方法很简单。下面是pidgin的做法:

mkdir -p debian/pidgin/usr/share/indicators/messages/applications
echo /usr/share/applications/pidgin.desktop > \
		 debian/pidgin/usr/share/indicators/messages/applications/pidgin

这两句话的功能一眼就看地出来,无须解释了。

Messaging Menu相关的开发需要用到libindicate这个库,首先得确保在系统里面安装了这个库的开发版本:

sudo apt-get install libindicate-dev

首先我们需要获取默认IndicateServer对象的引用,并对其进行初始化:

IndicateServer *server = indicate_server_ref_default();
/* 这句话给软件分类,主要是保存位置不同 */
indicate_server_set_type(server, "message.openfetion");
/* 这句话比较重要,它会让已安装在Messaging Menu里面的软件项前面带一个箭头,
 * 表示该软件当前正在运行,效果图见下面 */
indicate_server_set_desktop_file(server, DESKTOP_DIR"openfetion.desktop");
indicate_server_show(server);
g_signal_connect(G_OBJECT(server), INDICATE_SERVER_SIGNAL_SERVER_DISPLAY, 
		G_CALLBACK(server_display), fxmain);

当收到好友发送过来的消息时可以在该项后面显示哪个好友发送过来的消息,以及显示未读消息的条数,效果图如下:

这时候就需要给当前的这个IndicateServer对象添加一个IndicateIndicator对象,并为该对象设置相关的属性,如name(即显示Indicator上显示的名字),count(未读消息的数目),time(消息发送的时间),icon(像如发送消息好友的头像),draw-attention(这个属性可以随便设置一个非空字符串,以使Indicator高亮提示用户有消息到达,同理设置空字符串可取消高亮)。
上面的几个属性除了time和icon之外都可以用indicate_indicator_set_property()函数来搞定,该函数设置的属性值都是字符串,time是一个时间值,需要使用indicate_indicator_set_property_time()函数来进行设置,至于icon属性的设置需要用到另外一个库libindicate-gtk,这个库仅提供了两个函数,其中一个便是indicate_indicator_set_proper_icon()用来设置icon属性,属性值是一个GdkPixbuf对象。下面看Openfetion上收到一条消息时Messaging Menu所对的动作:

/* no indicator found, create one :) */
indicator = indicate_indicator_new();
/* add it to the global indicator list */
indicators = g_slist_append(indicators, indicator);
indicate_server_add_indicator(server, indicator);
 
indicate_indicator_set_property(indicator, INDICATE_INDICATOR_MESSAGES_PROP_COUNT, "0");
indicate_indicator_set_property(indicator, "sid", sid);
/* set icon */
snprintf(portrait, sizeof(portrait) - 1, "%s/%s.jpg", fxmain->user->config->iconPath, sid);
pixbuf = gdk_pixbuf_new_from_file(portrait, NULL);
indicate_indicator_set_property_icon(indicator, INDICATE_INDICATOR_MESSAGES_PROP_ICON, pixbuf);
g_object_unref(pixbuf);
 
g_signal_connect(G_OBJECT(indicator), INDICATE_INDICATOR_SIGNAL_DISPLAY,
		G_CALLBACK(message_display), NULL);
 
indicate_indicator_show(indicator);

首先我们需要确定在Messaging Menu中是否已经有该用户对应的Indicator存在,如果有就没有必要再创建一个新的造成重复,而检测重复性这个问题费了我不小的力气,libindicate没有提供获取列表的API,但查了一下它的源码,发现它确实实现了一个获取列表的函数,它把它定义为虚函数,推荐用户从这个类继承,我对GTK的C语言面向对象的机制不太了解,不知道该怎么去继承,但既然有这个函数就可以想办法拿来调用,能获取到Indicator列表,但这个列表简直让我抓狂,一个GArray列表,里面存放的是Indicator的id,一个毫无用处的数字,通过它根本获取不到Indicator对象,更不用说Indicator对象的属性了,因此我只能用自己的方法来实现了,其实很简单,就是每创建一个Indicator对象就把它塞到一个GSList里面,创建之前搜索该链表,如果其中有indicator的sid值与要创建的sid相同,则表示已存在,无需再重新创建,把它的count值加1即可。

有一点需要注意的是libindicate-gtk新旧版本的pc文件名是不同的(感谢@riku的测试),在10.04上是旧版pc文件名是indicate-gtk.pc,因此编译选项可以是

pkg-config --cflags --libs indicate-gtk

而在ubuntu 11.04中文件名是indicate-gtk-0.5.pc,编译选项便是:

pkg-config --cflags --libs indicate-gtk-0.5

我在CMakeLists.txt做了一些处理来让它自动识别这两个版本的不同。

分类: C/C++ 标签: ,