`
liuzhaomin
  • 浏览: 207689 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Apache中的进程剖析(1)

阅读更多

//转载请注明来源:http://blog.csdn.net/tingya
//版权声明:
//本书是《Apache源代码全景分析》的草稿部分,
//读者可以自由浏览和打印
//未经本文允许,不得以任何形式出现在盈利印刷品中,否则将追究法律责任!!!

6.1 Apache进程概述

对于大负载的服务器而言,系统内部的并发处理以及进程之间的通信处理非常的重要。良好的设计可以使服务器的效率和稳定性加倍提升。Apache是服务器设计的一个典范,尤其是它的进程和线程处理,一直为人称道。本章我们详细分析APR中对进程和线程的封装。在下一章,我们将分析进程和线程间的通信。这两张综合起来构成了进程和线程的大部分内容。

APR中所有进程封装相关的源代码都保存在$(APR_HOME)/threadproc目录下,其相应头文件则为$(APR_HOME)/include/apr_thread_proc.h

尽管都称之为进程,但是不同的操作系统对进程的实现却是不同的。由于APR支持五种大的操作系统平台,OS/2BeOSUnixWindow32以及NetWare,因此进程的封装也细分为五种。我们仅仅讨论WindowUnix平台上的进程和线程的封装细节。本章我们首先分别讨论UnixWindow上的进程,继而讨论UnixWindow上的线程。

<chsdate w:st="on" isrocdate="False" islunardate="False" day="30" month="12" year="1899"><span lang="EN-US"><font face="Verdana">6.1.1</font></span></chsdate>进程数据结构描述

尽管APR提供了五种进程的封装实现,但是Apache中统一使用数据结构apr_proc_t来描述某个进程:

typedef struct apr_proc_t {

pid_t pid;

apr_file_t *in;

apr_file_t *out;

apr_file_t *err;

#if defined(WIN32)

HANDLE hproc;

#endif

} apr_proc_t;

上述的进程数据结构非常容易理解,pid描述的是进程id号,对于Unix平台,该变量可以用getpid()函数的值进行填充,而对于Window平台,则用PROCESS_INFORMATION结构中的dwProcessId进行填充。inouterr则是与该进程关联的输入,输出以及错误描述符,正常情况下这三个对应标准输入输出stdinstdoutstderr。不过如果你愿意,你可以进行重定向。使用的最多的就是使用管道进行重定向。具体的细节部分,我们在后面的部分描述。

对于Window平台而言,描述一个进程,除了需要一个DWORD类型的全局进程ID号之外,还需要一个更重要的进程句柄。两者结合在一起才能完整地描述一个Window进程,因此对于Window平台而言,apr_proc_t中还需要额外的hproc成员描述进程句柄。

<chsdate w:st="on" isrocdate="False" islunardate="False" day="30" month="12" year="1899"><span lang="EN-US"><font face="Verdana">6.1.2</font></span></chsdate>进程属性描述

与进程相关联的另外一个最重要的数据结构应该就是进程属性描述数据结构apr_procattr_t,每一个进程都会关联一个apr_procattr_t结构,该结构是个大杂烩,凡是跟进程相关的属性都可以塞到该结构中。不过由于UnixWindow中的进程属性描述存在一定的差异,因此我们分开描述

<chsdate w:st="on" isrocdate="False" islunardate="False" day="30" month="12" year="1899"><span lang="EN-US">6.1.2</span></chsdate>.1Unix平台

Unix下的进程属性结构定义在srclib\apr\include\arch\unix\apr_arch_threadproc.h中,如下:

struct apr_procattr_t {

/*Part 1*/

apr_pool_t *pool;

/*Part 2*/

apr_file_t *parent_in;

apr_file_t *child_in;

apr_file_t *parent_out;

apr_file_t *child_out;

apr_file_t *parent_err;

apr_file_t *child_err;

/*Part 3*/

char *currdir;

apr_int32_t cmdtype;

apr_int32_t detached;

/*Part 4*/

struct rlimit *limit_cpu;

struct rlimit *limit_mem;

struct rlimit *limit_nproc;

struct rlimit *limit_nofile;

/*Part 5*/

apr_child_errfn_t *errfn;

apr_int32_t errchk;

/*Part 6*/

apr_uid_t uid;

apr_gid_t gid;

};

apr_procattr_t结构中描述的进程信息包括六个部分:

第一部分

pool描述了apr_procattr_t结构的关联内存池,apr_procattr_t结构分配所需要的内存都来自pool

第二部分

部分描述了父进程和子进程之间通信的管道文件。 六个描述符对应三个管道,分别用于父进程和子进程之间的通信:一个管道用于从父进程写入数据,从子进程中读出;一个管道用于从子进程写入数据,从父进程中读出;一个管道负责子进程写入错误信息通知父进程。父进程或者子进程通常只使用六个描述符中的三个,而其余三个则关闭;至于关闭哪三个则由进程的角色决定:对于父进程,通常关闭child_XXX,保留parent_XXX,而对于子进程则通常关闭parent_XXX,保留child_XXX

它们之间的管道通信示意图如下描述:

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 344.25pt; HEIGHT: 260.25pt" type="#_x0000_t75"><imagedata o:title="" src="file:///C:%5CDOCUME~1%5CADMINI~1%5CLOCALS~1%5CTemp%5Cmsohtml1%5C01%5Cclip_image001.png"></imagedata></shape>

为什么需要三对管道而不是一对,两对或者四对?这是跟前面的apr_proc_t结构中的描述符对应的,正如前面所说,每一个进程都维持三个描述符,正常情况下为stdinstdoutstderr,如果需要进行父子进程通信,那么我们就可以很快地将上面的管道描述符重定向到对应进程的描述符中。这就是为什么要建立三对的原因。

尽管如此,上面三对管道与进程并没有必然的联系。进程是否创建并不影响上面三对管道的创建,不过通常情况下总是先创建上面的管道,然后创建进程,然后再进程重定向。apr_procattr_t结构中的六个描述符一旦确定,那么APR就可以使用apr_procattr_io_set函数创建三对管道:

APR_DECLARE(apr_status_t) apr_procattr_io_set(apr_procattr_t *attr,

apr_int32_t in,

apr_int32_t out,

apr_int32_t err)

attr是管道的进程的描述结构,创建后的三对管道通过其返回出去。inouterr都是整数类型。in用以标示是否需要创建上图中的“in管道,即从父进程到子进程的通信管道;out则标记是否需要创建“out管道,即使从子进程到父进程的管道;err则标记是否需要创建“err管道。这三个标记可能的取值包括下面五种:

1)APR_NO_PIPE:该标记意味着不需要创建对应的管道,因此如果仅仅需要实现父进程到子进程的通信,那么outerr都可以设置为APR_NO_PIPE。如果标记为不是该值,则必须创建管道,至于管道的属性则由具体的标记决定。

2)APR_FULL_BLOCK:如果是该标记,则意味着必须创建管道,并且管道的两端都是阻塞的。默认情况下,管道创建后就是阻塞的,因此这个标记处理最简单,不需要做额外的工作。

3)APR_FULL_NOBLOCK:创建管道并同时将管道对应的两个描述符都设置为非阻塞。将管道设置为非阻塞我们在管道部分描述过,请参考apr_file_pipe_timeout_set函数。

4)APR_PARENT_BLOCK:仅仅父进程端的管道描述符设置为阻塞,这意味着子进程的管道描述符必须设置为非阻塞。

5)APR_CHILD_BLOCK:仅仅子进程的管道描述符设置为阻塞,而父进程则设置为非阻塞。

下面是对in管道部分的处理,outerr部分类似:

apr_status_t status;

if (in != 0) {

if ((status = apr_file_pipe_create(&attr->child_in, &attr->parent_in,

attr->pool)) != APR_SUCCESS) {

return status;

}

switch (in) {

case APR_FULL_BLOCK:

break;

case APR_PARENT_BLOCK:

apr_file_pipe_timeout_set(attr->child_in, 0);

break;

case APR_CHILD_BLOCK:

apr_file_pipe_timeout_set(attr->parent_in, 0);

break;

default:

apr_file_pipe_timeout_set(attr->child_in, 0);

apr_file_pipe_timeout_set(attr->parent_in, 0);

}

}

下面是一个典型的创建双向通信管道的示例代码:

apr_status_trv;

apr_procattr_t*attr;

rv = apr_pool_create(&p, NULL);

rv = apr_procattr_create(&attr, p);

rv = apr_procattr_io_set(attr, APR_FULL_BLOCK, APR_FULL_BLOCK,APR_NO_PIPE);

除了可以使用apr_procattr_io_set函数进行批量的一次性创建管道之外,APR中还可以使用apr_procattr_child_XXX_set函数单独创建三个管道中的某一个,比如如果创建in管道,则函数名称为apr_procattr_child_in_set,其定义如下:

APR_DECLARE(apr_status_t) apr_procattr_child_in_set(apr_procattr_t *attr,

apr_file_t *child_in,

apr_file_t *parent_in)

{

apr_status_t rv = APR_SUCCESS;

if (attr->child_in == NULL && attr->parent_in == NULL)

rv = apr_file_pipe_create(&attr->child_in, &attr->parent_in, attr->pool);

if (child_in != NULL && rv == APR_SUCCESS)

rv = apr_file_dup2(attr->child_in, child_in, attr->pool);

if (parent_in != NULL && rv == APR_SUCCESS)

rv = apr_file_dup2(attr->parent_in, parent_in, attr->pool);

return rv;

}

该过程可以创建管道,但是它的更重要的责任并不仅仅限于此。它隐含的另外一个重要的功能就是实现管道的重定向。

第三部分

这部分描述了进程的一些常规属性。

currdir标识新进程启动时的工作路径(执行路径),默认时为和父进程相同;

cmdtype标识新的子进程将执行什么类型的任务,APR中使用枚举类型apr_cmd_type_e总共定义了五种任务类型,默认为APR_PROGRAM

typedef enum {

APR_SHELLCMD, /**< use the shell to invoke the program */

APR_SHELLCMD_ENV /**< use the shell to invoke the program replicating our environment*/

APR_PROGRAM, /**< invoke the program directly, no copied env */

APR_PROGRAM_ENV, /**< invoke the program, replicating our environment */

APR_PROGRAM_PATH, /**< find program on PATH, use our environment */

} apr_cmdtype_e;

为了能够深入理解APR中为什么将程序划分为这几种类型,我们有必要详细了解Unix中是如何执行一个新的应用程序的。Unix中执行一个新的程序通常使用exec函数实现。当进程调用exec的时候,调用进程将被新的执行程序完全替代。新程序从main开始执行。由于exec函数的执行并不创建新的进程,因此新程序的进程号仍然是原有的进程号。exec只是用另外一个程序替换了当前进程的正文、数据、堆栈。

Unix中提供六个exec函数的变种供使用,APR中应用程序的类型正是根据这六种类型划分的。

#include <unistd.h>

int execl(const char * pathname, const char * arg 0, ... /* (char *) 0 */);

int execv(const char * pathname, char *const argv [] );

int execle(const char * pathname, const char * arg 0, .../* (char *)0, char *const envp [] */);

int execve(const char * pathname, char *const argv [], char *const envp [] );

int execlp(const char * filename, const char * arg 0, ... /* (char *) 0 */);

int execvp(const char * filename, char *const argv [] );

上面的六个函数都能执行一个新的应用程序,不过它们之间的差异主要在三方面:

1)、应用程序参数的传递方法的差别

exec允许使用两种途径进行应用程序的参数传递。或者每一个命令行参数都单独作为函数的参数,或者先构造一个参数数组,将所有的命令行参数保存在该数组中,然后用数组的形式传递给函数。前一种的参数格式通常如下:

char *arg0, char *arg1, char *arg2,…,char *argn, (char*)0

最后一个实际的命令行参数后面必须跟一个(char*)0空指针,这个不能省略,而且也不能写为整数0,必须将其转换为字符指针。ASCII0的字符正是空字符\0

后一种用法的参数通常就是直接一个char* argv[]数组而已,因此如果用这种策略传递的话,通常先将所有的参数生成字符串数组,然后再将该数组传入,在后面的使用示例中可以看到。

函数名称中通过lv来进行这两个格式的区分,l意味着参数为列表,使用第一个方法传递;v意味着参数为向量,使用第二种途径传递。因此execlexecleexeclp属于前一种传递策略,execvexecveexecvp属于后一种传递策略。不过APR中仅仅使用向量传递的方法,因此APR中只是用到了execvexecveexecvp

2)、环境变量表传递的差别

Unix中每个进程都将收到两份表,一份就是参数表,另一份就是环境变量表。在最原先的main函数中参数是三个而不是我们现在所看到的两个:

int main(int argc, char *argv[], char *envp[] ) /*原始的main函数参数*/

第三个参数就是必须传递的环境变量表,通常情况下它是一个字符指针数组,其中每一个指针包含一个以NULL结尾字符串的地址。不过ANSI C规定了main只能有两个参数,因此现在的做法通常是使用全局环境变量environ进行传递,比如:

<shape id="_x0000_i1026" style="WIDTH: 358.5pt; HEIGHT: 123pt" type="#_x0000_t75"><imagedata o:title="" src="file:///C:%5CDOCUME~1%5CADMINI~1%5CLOCALS~1%5CTemp%5Cmsohtml1%5C01%5Cclip_image003.png"></imagedata></shape>

上面函数的第二个差异就是对环境参数表的传递的差异。一种策略就是传递默认的系统全局环境参数表environ,这种情况下该参数列表将是隐含的,因此不需要在参数列表中明确列出。另一种策略就是传递自定义的环境参数表,此时通过environ行不通,因此必须通过函数参数明确传递。上面的六个函数中,名称最后为e的则表明可以传递自定义的环境变量数组,比如execleexecve

应用程序类型APR_SHELLCMD_ENVAPR_PROGRAM_ENV都是需要传递自定义的环境变量参数,因此毫无疑问,对于这两种程序类型肯定是调用execve(execle是传递参数列表,APR中不使用这种策略)

3)、启动应用程序路径的差异

前面的四个程序都必须使用路径名称明确指定需要执行的程序的路径和名称,而且路径必须是绝对路径。而后两者则不一定,它们仅仅使用文件名称指名启动的程序。如果该名称是绝对路径名称(/开始),那么该名称被视为路径。如果给出的仅仅是文件名或者一个相对的文件名称,那么程序将在PATH和当前目录下查找对应的文件。

APR中不支持使用程序名称启动程序的策略,毕竟在PATH指定的目录下进行搜索与直接给定程序路径相比显然要耗费时间的多。

下面是几个函数的使用示例:

1. execl(“/bin/ls”,”ls”,”-al”,”/etc/passwd”,(char * )0);

2. execlp(“ls”,”ls”,”-al”,”/etc/passwd”,(char *)0);

3. char * argv[ ]={“ls”,”-al”,”/etc/passwd”,(char*) }};

execv(“/bin/ls”,argv);

4. char * argv[ ]={“ls”,”-al”,”/etc/passwd”,(char *)0};

char * envp[ ]={“PATH=/bin”,0}

execve(“/bin/ls”,argv,envp);

5. char * argv[ ] ={ “ls”,”-al”,”/etc/passwd”,0};

execvp(“ls”,argv);

6. char * envp[ ]={“PATH=/bin”,0}

execve(“/bin/ls”,”ls”,”-al”,”/etc/passwd”,(char * )0,envp);

因此尽管UNIX提供了六种调用接口,不过APR中仅仅使用了三种而已:execvexecveexecvp。根据前面的分析,我们可以看到上面五种任务的区别,如下表所示:

任务类型

含义

环境变量传递方式

执行路径方式

APR_SHELLCMD

执行普通SHELL命令

自定义环境变量传递方式

APR_SHELLCMD_ENV

执行普通SHELL命令

默认隐含传递方式

APR_PROGRAM

普通应用程序

自定义环境变量传递方式

APR_PROGRAM_ENV

<span

分享到:
评论

相关推荐

    重启apache进程脚本

    这个脚本的主要功能是检测当前系统中 Apache 进程的数量,如果数量超过 60 个,则重启 Apache 服务器,以释放系统资源。同时,如果系统中可用内存小于 20%,则清理系统缓存,释放更多的内存资源。 下面我们来详细...

    Apache工作机制分析

    - **多处理模块(MPM)**:决定Apache如何处理客户端请求,包括使用进程或线程模型来处理并发连接。 - **可移植运行库(APR)**:提供一个平台独立的接口,使得Apache能够在多种操作系统上运行。 #### Apache+...

    Apache Server源代码分析(PDF)

    7. **错误处理和异常恢复**:Apache如何处理错误,如404找不到文件或500内部服务器错误,以及如何优雅地恢复异常状态,都是源代码分析中的重要部分。 8. **并发和线程模型**:Apache的多路复用模型允许高效地处理...

    Apache日志分析手册

    6. 查看进程和端口连接:手册中提到了使用ps和netstat命令来查看Apache进程和80端口的TCP连接数。这些命令有助于了解Apache服务的运行状态。 7. 分析日志中的异常行为:比如,通过过滤日志来查看特定IP地址在特定...

    Apache2中文使用手册

    1. **安装Apache2**:在不同的操作系统上安装Apache2的步骤略有不同。在Windows上,可以使用提供的`apache2.exe`文件进行安装,而在Linux或Unix系统中,通常通过包管理器如`apt`或`yum`来安装。安装过程中需要注意...

    Apache源代码全景分析

    - **进程间通信**:在多进程模型中,Apache使用管道、信号和共享内存等机制实现进程间的通信与协调。 - **请求处理流程**:Apache接收到请求后,会经历接收、解析、路由、执行模块、响应等步骤,最后返回结果给...

    apache架构设计原理剖析

    1. 连接管理(监听):Apache监听指定端口的连接请求,一旦有新的连接到来,它会创建一个新的进程或线程来处理该连接。 2. 请求读取:Apache接收并解析HTTP请求,包括请求行、头和数据。 3. 找到表示形式:根据请求...

    apache中文帮助文档

    1. **Apache安装与配置**:在不同的操作系统(如Linux、Windows、macOS)上安装Apache,以及配置httpd.conf文件以定制服务器的行为,包括端口设置、目录权限和虚拟主机。 2. **模块管理**:Apache通过模块化设计来...

    apache源代码解析--基于Apache0.6.5

    在 Apache 0.6.5 版本中,守护进程的创建和管理是非常重要的。 知识点4: Apache 生命周期 Apache 生命周期是指 Apache 服务器从启动到关闭的整个过程。在这个过程中,Apache 服务器会经历多个阶段,包括初始化、...

    Apache Server源代码分析

    1. **主程序(Main Program)**:Apache服务器的启动点,通常在`httpd`或`apache2`可执行文件中。这部分代码负责初始化进程环境,加载配置文件,以及创建并管理子进程。 2. **模块系统(Module System)**:Apache...

    Apache官方中文文档教程

    1. **安装与配置**:Apache的安装通常涉及下载源代码或预编译的二进制包,然后按照官方指导进行编译和安装。配置文件`httpd.conf`是核心,其中包含了服务器的所有设置,如端口、目录权限、模块启用等。 2. **模块...

    apache中文手册

    1. **安装与配置**:Apache的安装过程可能因操作系统而异,手册会指导用户下载正确的版本,并解释如何正确配置httpd.conf等核心配置文件,以满足特定的服务器需求,如设置监听端口、虚拟主机和文档根目录。...

    Apache中文手册20

    1. **Apache安装与配置**:介绍了如何在各种操作系统(如Windows、Linux、Unix等)上安装Apache,并详细解释了配置文件httpd.conf中的各项设置,包括端口设置、虚拟主机配置、日志文件管理等。 2. **模块管理**:...

    Apache中文手册

    1. **模块化架构**:Apache以其模块化的结构著称,允许用户根据需求添加或移除功能模块。例如,mod_rewrite模块用于URL重写,mod_security则提供了Web应用防火墙功能。 2. **配置文件**:Apache的配置主要通过httpd...

    APACHE 2.2 中文参考手册

    2. **多线程支持**:通过MPM(Multi-Processing Module)机制,Apache 2.2 支持多线程和多进程模型,提高了并发处理能力。 3. **更好的性能**:相比之前的版本,Apache 2.2 在性能上有所提升,优化了内存使用,降低...

    apache 配置中文详解

    虚拟主机设置部分定义了 Apache 服务器进程中的不同 IP 地址和主机名。在这个部分,我们可以设置虚拟主机的各种参数,如 ServerName、ServerAdmin 和 DocumentRoot 等。 配置文件命名和路径 配置文件的命名和路径...

    Apache2.22源代码

    1. **性能优化**:通过分析源代码,开发者可以找出性能瓶颈,优化内存使用、减少CPU占用。 2. **安全增强**:源代码研究有助于发现潜在的安全漏洞,并及时修复,提高服务器的安全性。 3. **功能定制**:根据业务需求...

    apache中文文档

    1. **安装与配置**:解释如何在不同的操作系统上安装Apache,包括设置端口号、虚拟主机、服务器根目录等。 2. **核心功能**:介绍Apache的核心特性,如多进程模型(MPM)、请求处理机制、模块加载等。 3. **安全...

    apache启动不了的解决办法

    1. Apache 服务安装不完整 在安装 Apache 服务器时,如果服务没有正确安装,可能会导致 Apache 服务器无法启动。解决办法是重新安装 Apache 服务,并确保服务正确安装。 2. 端口被占用 如果Apache服务器无法启动...

    APACHE COOKBOOK中文版

    1. **Apache基础**:介绍Apache的历史、版本、基本概念和架构,以及如何安装Apache服务器。 2. **配置基础**:详细讲解httpd.conf和其他相关配置文件的结构,以及如何修改这些文件来定制Apache服务器的行为。 3. *...

Global site tag (gtag.js) - Google Analytics