您的位置:首页 > 运维架构

Mathopd源码分析——启动

2008-12-23 23:08 155 查看
本文较为详细的分析了Mathopd的启动过程。如果将此启动过程一般化,便可以作为我们开发Linux下服务器程序的蓝本。

要看Mathopd的启动过程,当然应先从main函数看起,看看做了哪几个大的步骤。如果你对本文感兴趣,阅读时最好参考源代码。

1. 处理命令行参数

2. 打开/dev/null设备

3. 读入配置信息(这很重要,我们以后将着重分析)

4. 启动配置文件中指定的所有服务器,并侦听连接

5. 根据配置信息设置根目录

6. 根据配置信息,设置程序运行的uid和gid。

7. 打开pid文件

8. 初始化日志模块

9. 根据命令行参数,决定是否让程序后台运行。

10. 设置信号处理函数

11. 向pid文件中登记pid

12. 初始化一些缓存区

13. 进入处理http协议的主函数。

我们来详细的看看各个步骤:

1. 处理命令行参数

while ((c = getopt(argc, argv, "ndvf:t")) != EOF) {

//...

}

getopt是Unix/Linux中解析命令行参数的函数。关于getopt的用法我这里不做详细的说明。关于getopt的详细介绍请参考本文后的相关链接

-n: 将系统放在前台运行。系统默认是在后台运行守护进程(服务器程序一般作为后台守护进程执行),如果有该参数,系统将不会放到后台运行。这个参数在调试的时候很有用处。

-d: 系统以调试模式启动。

-v: 给出系统版本。若有该参数,系统不会启动服务,而是打印出版本号后直接退出。

-f <filename>: 指定配置文件路径。若有该参数,系统将从指定文件加载配置信息,若无该参数系统从标准输入中接受配置信息。

-t: 如果有该选项,系统的调试信息既会输出到日志文件,也输出到标准错误上,这个选项在调试时很有用处。

2. 打开/dev/null

null_fd = open(devnull, O_RDWR);

/dev/null是Unix-Like操作系统中的一个特殊的文件,它就像一个“黑洞”,丢弃所有向它写入的数据(但返回值为成功)。

打开此文件的目的是为以后将标准输入,标准输出和标准错误重定向到该“黑洞”做好准备工作;

一个后台运行的守护进程,不可能用到这三个标准的描述符,因为这三个描述符均和终端有关,而后台的守护进程没有一个终端作为他的父进程。

3. 读入配置信息

message = config(config_filename);

if (message)

die(0, "%s", message);

config函数完成了整个系统的配置,config模块是Mathopd设计的比较优雅的一个模块,他应用了编译原理中词法分析中的自动状态机的理论,使一个复杂的解析过程,变的清晰易懂。配置模块在以后的文章中会进行详细的分析。

这个函数和我所见过的一般的函数设计风格有所不同。一般的函数设计是返回一个预先定义好的错误码来表示是何种错误。而这个函数的返回的是一个指向一个常量字符的指针来表示错误。其实这样的设计可以简化代码编写,使代码看起来更加优美。作者预先定义好了很多全局的常量的字符串用来表示错误。这样做既可以判断是何种错误,又可以很方便的使用打印语句打印出是错误信息(不用书写冗长的错误码和错误字符串的映射代码)。下面是一个错误字符串的定义。

static const char e_memory[] = "out of memory";

4. 启动服务器,并侦听连接

s = servers;

while (s) {

startup_server(s);

s = s->next;

}

Mathopd的“服务器们”是一个全局链表。在读入配置信息的同时完成了这个链表的建立。很容易读懂,我们遍历链表,并对每一个服务器对象进行startup_server。不要以为startup_server很神秘,它是一种我们很熟悉的代码,几乎所有的关于C的socket编程的服务器范例都会有讲解。这里我再不厌其烦的重复一遍,权当是回顾一下socket服务器编程。

a). 创建socket

s->fd = socket(AF_INET, SOCK_STREAM, 0);

b). 设置socket选项

这个倒是值得一提!

SO_REUSEADDR : 这个选项在编写服务器程序时一般都要设置。如果没有设置该选项会导致两个问题:1.服务器的快速起停,会引起地址被占用的错误。2. 在一个服务器上绑定不同ip相同端口会引起地址被占用的错误。显然,这两个问题对HTTP服务器来说是不允许存在的。我在参考链接中给出了我检索的关于SO_REUSEADDR 选项的说明

FD_CLOEXEC : 这个我不太明白

O_NONBLOCK : 文件描述符设置为非阻塞,前面介绍过,这里的socket采用的是I/O多路复用的同步非阻塞模式。

c). 绑定地址和端口

bind(s->fd, (struct sockaddr *) &sa, sizeof sa);

d). 开始侦听

listen(s->fd, s->backlog);

6. 打开pid文件

这为登记pid做准备,登记pid的步骤一定要放在将程序变成后台服务之后,因为我们登记的是后台服务器的进程pid,它和当前运行的进程pid不同,当前运行的进程是后台服务器进程的父进程

7. 初始化日志模块

为记录日志做准备,如果要在标准错误上显示日志,这里要将标志错误从定向。为什么要重定向呢?因为我们马上就要将/dev/null重定向到标准错误了。到那时,我们向标准错误中写日志,就不会再有任何反应了。

8. 后台运行

关于将程序变为后台守护进程运行的理论比较复杂,这里不再叙述。但我在参考链接中将会给出这一理论的详细说明

9. 设置信号处理函数

程序成为后台守护进程后,就没有终端与之交互了。在这里我们依赖信号来与服务器交互。信号处理函数,就是我们与服务器交互的函数。

10. 登记pid

这里就是我们登记后台服务进程的pid最佳时机了

11. 初始化缓冲区

这里其实就是创建一些系统需要的资源,如连接、输入输出缓冲区等。资源的个数及大小由系统配置决定。

12. 进入处理http协议的主函数

完成启动后,我们就要干正经事了。

参考链接

1. IBM DeveloperWorks的文章一直都很经典:使用 getopt() 进行命令行处理

2. 维基百科中关于/dev/null的详细说明。

3. 维基百科中关于有限自动机(DFA)的介绍

4. 关于SO_REUSEADDR的两篇博文。SO_REUSEADDR例解socket中的SO_REUSEADDR

4. 编写守护进程的理论结合实际:Linux守护进程的编程方法

阅读全文

类别:Mathopd 查看评论
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: