apache2 进程/线程模型

本文介绍了Apache2在Windows下的多路处理模块(MPM)的工作原理,包括主进程和子进程的作用,子进程内部线程的启动与终止过程,并解释了如何通过配置参数优化Apache2的性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

apache2 有两种进程:主进程( master_main)和子进程( child_main )。

子进程又派生了三种线程:

1、control thread  控制线程。是主线程。负责创建 accept thread, worker threads ,接收和执行 master  process 的控制指令。
 * events.

2、listener threads  负责处理监听
3、worker threads  负责处理数据交互

 

 // c:\httpd-2.2.17-win32\server\mpm\winnt\mpm_winnt.c
 master_main()

apache2 使用 apr_proc_create 来创建一个新的进程。在 windows 下,是调用了 CreateProcessAsUserW 函数。

mpm_winnt.c 是专门针对Windows NT优化的MPM(多路处理模块),它使用一个单独的父进程产生一个单独的子进程,在这个子进程中轮流产生多个线程来处理请求。也就是说 mpm_winnt只能启动父子两个进程, 不能像Linux下那样同时启动多个进程。 mpm_winnt主要通过ThreadsPerChild和MaxRequestsPerChild两个参数来优化Apache,下面详细来说明一下。ThreadsPerChild 这个参数用于设置每个进程的线程数, 子进程在启动时建立这些线程后就不再建立新的线程了. 一方面因为mpm_winnt不能启动多个进程, 所以这个数值要足够大,以便可以处理可能的请求高峰; 另一方面该参数以服务器的响应速度为准的, 数目太大的反而会变慢。因此需要综合均衡一个合理的数值。
mpm_winnt上的默认值是64, 最大值是1920. 这里建议设置为100-500之间,服务器性能高的话值大一些,反之值小一些。

 

/***********************************************************************
 * master_main()
 * master_main() runs in the parent process.  It creates the child
 * process which handles HTTP requests then waits on one of three
 * events:
 *
 * restart_event
 * -------------
 * The restart event causes master_main to start a new child process and
 * tells the old child process to exit (by setting the child_exit_event).
 * The restart event is set as a result of one of the following:
 * 1. An apache -k restart command on the command line
 * 2. A command received from Windows service manager which gets
 *    translated into an ap_signal_parent(SIGNAL_PARENT_RESTART)
 *    call by code in service.c.
 * 3. The child process calling ap_signal_parent(SIGNAL_PARENT_RESTART)
 *    as a result of hitting MaxRequestsPerChild.
 *
 * shutdown_event
 * --------------
 * The shutdown event causes master_main to tell the child process to
 * exit and that the server is shutting down. The shutdown event is
 * set as a result of one of the following:
 * 1. An apache -k shutdown command on the command line
 * 2. A command received from Windows service manager which gets
 *    translated into an ap_signal_parent(SIGNAL_PARENT_SHUTDOWN)
 *    call by code in service.c.
 *
 * child process handle
 * --------------------
 * The child process handle will be signaled if the child process
 * exits for any reason. In a normal running server, the signaling
 * of this event means that the child process has exited prematurely
 * due to a seg fault or other irrecoverable error. For server
 * robustness, master_main will restart the child process under this
 * condtion.
 *
 * master_main uses the child_exit_event to signal the child process
 * to exit.
 **********************************************************************/

 线程的启动

    if ((parent_pid != my_pid) || one_process){
        
// The child process or in one_process (debug) mode
        ...
        child_main(pconf); ...
    }
    
else{        
        
// A real-honest to goodness parent 
        ...
        restart 
= master_main(ap_server_conf, shutdown_event, restart_event);
        ...
    }

 

 以上,one_process 是对程序启动的命令行参数 ONE_PROCESS 的判断。这个命令行参数在启动时被保存在全局变量 ap_server_config_defines 中。要想以单进程模式运行(没有控制台)httpd,有两种办法:一种是在命令行下使用  -DONE_PROCESS。另一种是在启动后的钩子中直接将变量 写入 ap_server_config_defines。

    // ap_hook_post_config( ) 钩子中插入以下代码, 
    char **new_start_arg;
    new_start_arg 
= (char **)apr_array_push(ap_server_config_defines);
    
*new_start_arg = "ONE_PROCESS";

 

需要注意 *new_start_arg 所指向的字串的生命周期。本例中指向一个静态字符串,这个字符串在进程生命周期内有效。如果指向一个栈变量,则可能成为野指针。

 

void child_main(apr_pool_t *pconf)
{
    ...
    
for (i = 0; i < ap_threads_per_child; i++) {
        ...
        child_handles[i] 
= (HANDLE) _beginthreadex(NULL, (unsigned)ap_thread_stacksize,
                                                       worker_main, (
void *) i, 0&tid);
        ... 
    }
    create_listener_thread();
   ...
}

 

 

static void create_listener_thread()
{
 ...
    
/* Now start a thread per listener */
    
for (lr = ap_listeners; lr; lr = lr->next) {
        
if (lr->sd != NULL) {
                _beginthreadex(NULL, 
1000, winnt_accept,(void *) lr, 0&tid);
        }
    }
}

 

 

 线程的终止:

 

实战:

只创建一个监听:

场景设计:

将 httpd 配置为多端口监听的情况下, 我们希望能够分辨收到的数据来自哪个端口。httpd 为每个端口创建了一个线程。

ap_listeners()

 

子进程的主要代码:

 

ExpandedBlockStart.gifchild_main 的完整流程
// child.c
HANDLE exit_event;
static int shutdown_in_progress = 0;
static int workers_may_exit = 0;

static PCOMP_CONTEXT winnt_get_connection(PCOMP_CONTEXT context)
{
    
while (1) {
        
if (workers_may_exit) {
            
return NULL;
        }
    }
}
static unsigned int __stdcall winnt_accept(void *lr_)
{
    
while (!shutdown_in_progress) {
                Sleep(
100);

    }
    
if (!shutdown_in_progress) {
        
/* Yow, hit an irrecoverable error! Tell the child to die. */
        SetEvent(exit_event);
    }
    ap_log_error(APLOG_MARK, APLOG_INFO, APR_SUCCESS, ap_server_conf,
                 
"Child %d: Accept thread exiting.", my_pid);
    
return 0;
}


void child_main(apr_pool_t *pconf)
{
    HANDLE child_events[
2];
    child_events[
0= exit_event;
    
while (1) {
        
/* Check to see if the child has been told to exit */
        
if (WaitForSingleObject(exit_event, 0!= WAIT_TIMEOUT) {
            
break;
        }
        
/* wait for previous generation to clean up an entry in the scoreboard */
        apr_sleep(
1 * APR_USEC_PER_SEC);
    }
    
while (1) {
        rv 
= WaitForMultipleObjects(2, (HANDLE *) child_events, FALSE, 1000);
        cld 
= rv - WAIT_OBJECT_0;
        
if (rv != WAIT_TIMEOUT && rv != WAIT_FAILED && cld == 0 ) {
            ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf,
                         
"Child %d: Exit event signaled. Child process is ending.", my_pid);
            
break;
        }
    }
    shutdown_in_progress 
= 1;
    
/* Tell the worker threads to exit */
    workers_may_exit 
= 1;
}

 

 

<think>我们正在讨论操作系统进程管理、线程管理和内存管理相关的问题与解决方案。根据用户提供的引用和问题,我将结构化地分析这三个核心领域的关键问题及其解决方案。 ### 一、进程管理问题与解决方案 #### 问题1:进程崩溃影响其他进程 - **问题描述**:传统系统中,一个进程崩溃可能导致整个系统崩溃(如早期Windows 9x)。现代操作系统通过虚拟地址空间隔离进程[^1]。 - **解决方案**: - **虚拟内存机制**:每个进程拥有独立的虚拟地址空间,物理内存通过MMU映射隔离 - **进程控制块(PCB)**:保存进程状态(运行/就绪/阻塞),崩溃时仅回收自身资源[^2] - **信号处理**:Linux通过SIGSEGV等信号捕获崩溃,由init进程接管孤儿进程 #### 问题2进程创建/销毁开销大 - **问题描述**:进程创建需分配内存、加载程序、建立PCB等,典型耗时约100ms[^4] - **解决方案**: - **写时复制(Copy-On-Write)**:fork()时共享父进程内存,仅在实际修改时复制 - **进程池**:预创建进程(如Apache Worker Pool),避免运行时创建开销 - **轻量级进程(LWP)**:Solaris实现,共享部分内核资源 #### 问题3:僵尸进程与孤儿进程 - **僵尸进程**:子进程退出但父进程未调用wait(),PCB残留内核中 ```bash # 示例:Linux中查找僵尸进程 ps aux | grep 'Z' ``` - **解决方案**: - 父进程显式调用wait()/waitpid() - 父进程捕获SIGCHLD信号处理 - 孤儿进程由init进程(PID=1)自动接管回收[^2] --- ### 二、线程管理问题与解决方案 #### 问题1:线程同步问题 - **竞态条件**:多线程访问共享数据导致结果不确定性 - **解决方案**: | 机制 | 原理 | 适用场景 | |--------------|----------------------------------------------------------------------|------------------------| | **互斥锁** | pthread_mutex_lock()保证临界区独占访问 | 共享数据结构更新 | | **信号量** | 通过PV操作控制资源计数(如生产者-消费者模型) | 线程池任务调度 | | **条件变量** | pthread_cond_wait()实现事件驱动协作 | 异步任务通知 | | **原子操作** | CAS(Compare-And-Swap)指令实现无锁编程 | 高性能计数器 | #### 问题2:线程调度开销 - **问题描述**:线程上下文切换需保存寄存器、栈指针等,典型开销1-10μs - **解决方案**: - **线程本地存储(TLS)**:减少共享数据依赖 - **用户态线程**:Go语言的goroutine,切换由runtime管理不经过内核 - **绑核(Affinity)**:将线程固定到CPU核心,减少缓存失效 #### 问题3:死锁问题 - **必要条件**:互斥、持有并等待、非抢占、循环等待 - **解决方案**: 1. **预防策略**:破坏死锁条件,如全资源一次性申请(银行家算法) 2. **检测与恢复**:定期构建资源分配图检测环,强制终止进程 3. **避免策略**:Dijkstra银行家算法,安全状态判断公式: $$ \text{Max}[i][j] \leq \text{Available}[j] + \sum \text{Allocation}[k][j] $$ 其中$i$为进程,$j$为资源类型 --- ### 三、内存管理问题与解决方案 #### 问题1:内存碎片化 - **外部碎片**:空闲内存分散在多个不连续区域 - **解决方案**: - **伙伴系统**:Linux将内存按$2^n$大小分块,合并时要求相邻且同大小 - **Slab分配器**:内核对象(如task_struct)专用缓存,避免小块碎片 - **虚拟内存压缩**:移动已分配页面形成连续空间(ZRAM机制) #### 问题2:内存不足(OOM) - **问题描述**:进程申请内存超过物理内存+Swap空间 - **解决方案**: - **页面置换算法**: | 算法 | 特点 | 实现复杂度 | |-------------|----------------------------------------------------------------------|------------| | **OPT** | 理论最优(置换未来最晚使用的页) | 不可实现 | | **LRU** | 最近最少使用(时间局部性原理) | 中等 | | **Clock** | 近似LRU,使用访问位循环检测 | 简单 | - **OOM Killer**:Linux内核选择`badness`评分高的进程终止,评分公式: $$ \text{Score} = \frac{\text{占用内存} \times 10}{\sqrt{\text{运行时间}}} $$ #### 问题3:内存泄漏 - **问题描述**:进程持续申请内存未释放,如C/C++中malloc后未free - **检测工具**: - Valgrind:监测未释放堆内存 ```shell valgrind --leak-check=yes ./program ``` - GDB调试:观察进程RSS持续增长 - **解决方案**: - 智能指针(C++的shared_ptr/unique_ptr) - 垃圾回收机制(Java/Python GC) --- ### 关键机制关联分析 | 管理领域 | 核心机制 | 数学描述/公式 | 性能影响 | |--------------|---------------------------|-----------------------------------|------------------------| | **进程调度** | 多级反馈队列 | 响应时间$R = T_w + T_s$ | 平衡吞吐量与响应延迟 | | **内存映射** | 页表转换 | $物理地址 = \text{PTE} \times \text{PAGE\_SIZE} + \text{偏移量}$ | TLB命中率决定访问速度 | | **线程同步** | 自旋锁 | 忙等待循环次数$n \propto \frac{1}{\text{CPU频率}}$ | 临界区短时高效 | > **案例**:Chrome浏览器进程模型 > 采用多进程架构(渲染进程、插件进程分离),结合: > - 进程隔离:单个页面崩溃不影响整个浏览器[^1] > - 线程池:网络模块使用线程池处理并发请求(避免频繁创建线程)[^4] §§ 相关问题 §§ 1. 如何设计一个避免僵尸进程产生的进程监控系统? 2. 在多核CPU环境下,线程绑核策略如何优化缓存命中率? 3. 虚拟内存系统中LRU算法的实现代价如何平衡? 4. 用户态线程与内核态线程的混合调度机制有哪些? 5. 内存压缩技术(ZRAM)在移动设备中的应用原理是什么?</think>### 操作系统核心管理问题与解决方案 #### 一、进程管理 **问题1:进程崩溃影响范围** - **问题**:传统系统中进程崩溃可能影响整个系统 - **解决方案**: 1. **虚拟地址空间隔离**:每个进程拥有独立虚拟地址空间(如Linux的mm_struct)[^1] 2. **进程控制块(PCB)**:崩溃时内核仅回收自身资源(文件描述符、内存等)[^2] 3. **信号机制**:通过SIGKILL/SIGTERM终止进程而不影响其他进程 **问题2进程创建/销毁开销大** - **问题**:进程创建需分配资源(典型开销>100ms)[^4] - **解决方案**: ```c // 写时复制(Copy-On-Write)示例 pid_t pid = fork(); // 实际内存复制延迟到写入时发生 if (pid == 0) { // 子进程修改数据时才复制物理页 modify_data(); } ``` 1. **进程池技术**:预创建进程复用(如Apache Worker Pool) 2. **轻量级进程(LWP)**:Solaris/ Linux的clone()系统调用 **问题3:僵尸进程与孤儿进程** - **僵尸进程**:子进程退出但父进程未wait(),PCB残留内核 ```bash # 解决方案:父进程中处理SIGCHLD信号 signal(SIGCHLD, SIG_IGN); // 显式忽略信号,内核自动回收 ``` - **孤儿进程**:父进程先退出,由init进程(PID=1)接管[^2] --- #### 二、线程管理 **问题1:线程同步冲突** - **问题**:多线程访问共享数据导致竞态条件 - **解决方案**: | 同步机制 | 适用场景 | 示例 | |----------------|-------------------------|--------------------------| | 互斥锁 | 共享资源独占访问 | pthread_mutex_lock() | | 信号量 | 资源计数控制 | sem_wait()/sem_post() | | 条件变量 | 事件驱动协作 | pthread_cond_wait() | **问题2:线程调度开销** - **问题**:频繁线程切换消耗CPU(上下文切换开销1-10μs) - **解决方案**: 1. **线程池模式**:固定线程数处理任务(如Java Executor) 2. **用户态线程**:Go的goroutine,切换不经过内核 3. **绑核(Affinity)**:将线程固定到特定CPU核心 **问题3:死锁预防** - **死锁条件**:互斥、持有并等待、非抢占、循环等待 - **解决方案**: $$ \text{银行家算法:} \forall i, \text{ Available} \geq \text{Need}_i \rightarrow \text{安全状态} $$ 1. 有序资源分配法(打破循环等待) 2. 超时回滚机制 --- #### 三、内存管理 **问题1:内存碎片化** - **外部碎片**:空闲内存分散无法利用 - **解决方案**: 1. **分页机制**:物理内存按固定大小划分(通常4KB) $$ \text{物理地址} = \text{页框号} \times \text{PAGE\_SIZE} + \text{偏移量} $$ 2. **Slab分配器**:内核对象专用缓存(如Linux的kmem_cache) **问题2:内存不足(OOM)** - **问题**:物理内存+Swap空间耗尽 - **解决方案**: 1. **页面置换算法**: | 算法 | 特点 | 实现复杂度 | |--------|--------------------------|------------| | LRU | 最近最少使用 | 高 | | Clock | 近似LRU(访问位轮询) | 中 | 2. **OOM Killer**:Linux选择`badness`高的进程终止: $$ \text{badness} = \frac{\text{内存占用}^{10}}{\text{运行时间}} $$ **问题3:进程挂起与激活** - **场景**:内存不足时挂起阻塞态进程[^3] - **激活策略**: 1. 优先激活等待事件即将发生的进程 2. 使用二级存储(Swap分区)保存挂起进程镜像 --- ### 关键机制关联 | 管理领域 | 核心机制 | 性能影响 | |--------------|---------------------------|------------------------| | **进程-内存**| 写时复制(COW) | 减少90%进程创建开销 | | **线程-进程**| 线程共享进程资源 | 上下文切换快5-10倍 | | **内存-调度**| 页表缓存(TLB) | 地址转换提速100倍 | > **案例**:Chrome浏览器进程模型 > - 每个标签页独立进程:崩溃不影响整体(隔离性)[^1] > - 渲染线程与GPU线程分离:避免界面卡死(线程调度优化)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值