nginx进程模型

(1)nginx进程模型?是怎么样设计的,好处和作用是什么?
nginx进程模型:1个master进程,多个worker进程
master进程的责任:作为监工,专门用来管理和监控worker进程
worker进程的责任:用来干主要的活的,(和用户交互)
master 和 worker 之间的通讯可以用信号也可以用共享内存
采用这种机制,使得服务器框架能够稳定灵活的运行的体现之一是:worker进程 一旦挂掉,那么master进程会立即fork()一个新的worker进程投入工作中去;

nginx还支持配置文件的重载、支持热更新、热回滚机制
直接服务器启停关闭
进程控制?
直接给 Master 进程发送信号,这是比较老的操作方式,Nginx 在 0.8 版本之后,引入了一系列命令行参数,来方便我们管理。比如 ./nginx -s reload,就是来重启 Nginx ,./nginx -s stop,就是来停止 Nginx 的运行。

我们启动nginx时候可以看到进程标题标记master和worker来区分?怎么实现得呢?
先学习一下怎么给master进程改标题名字?

原理和实现思路分析
argc:命令行参数的个数
argv:是个数组,每个数组元素都是指向一个字符串的char *,里边存储的内容是所有命令行参数;
./nginx -v -s 5
argc = 4
argv[0] = ./nginx ----指向的就是可执行程序名: ./nginx
argv[1] = -v
argv[2] = -s
argv[3] = 5
比如你输入 ./nginx -12 -v 568 -q gess
argv内存之后,接着连续的就是环境变量参数信息内存【是咱们这个可执行程序执行时有关的所有环境变量参数信息】
可以通过一个全局的environ[char **]就可以访问
environ内存和argv内存紧紧的挨着
修改可执行程序的实现思路:
(1)重新分配一块内存,用来保存environ中的内容;
(2)修改argv[0]所指向的内存;

在这里插入图片描述

然后来学习一下怎么创建多个worker进程,并且分别给worker进程改名?

循环fork流程图
在这里插入图片描述

fork返回的是2个值和2个进程
在这里插入图片描述

  • fork失败可能受制于系统进程数的创建上限

  • 写时复制,读时共享:全局数据,父子数据修改独立

  • 竞争条件:同步信号以规避竞争条件(比sleep好用点) 《 linux系统编程 24.6》

建议将此类同步方法(阻塞信号,发送信号,捕获信号)封装
为一组标准的进程同步函数。这一做法的优点在于,如果有意,后续可以其他进程间通信
(IPC)机制替换信号的使用。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <time.h>
#include <string.h>

// 同步状态(可重入性)
volatile sig_atomic_t sync_status = 0;
// 信号计数,用于处理可能的多次信号(可重入性)
volatile sig_atomic_t signal_count = 0;
// 最大等待时间(秒)
#define MAX_WAIT_TIME 5
// 最大重试次数
#define MAX_RETRIES 3

// 错误处理宏
#define HANDLE_ERROR(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

// 信号处理函数,一定要是可重入函数且简短
void signal_handler(int signo) {
    if (signo == SIGUSR1) {
        sync_status = 1;
        signal_count++;
    }
}

// 设置超时定时器
void set_timer(int seconds) {
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    
    if (sigaction(SIGALRM, &sa, NULL) == -1) {
        HANDLE_ERROR("sigaction for SIGALRM failed");
    }
    
    alarm(seconds);
}

// 等待信号带超时机制
int wait_for_signal(int timeout_seconds) {
    time_t start_time = time(NULL);
    
    while (!sync_status) {
        // 检查是否超时
        if (time(NULL) - start_time >= timeout_seconds) {
            return -1;  // 超时
        }
        
        // 使用较短的休眠时间,避免过长阻塞
        struct timespec ts = {0, 100000000}; // 100ms
        nanosleep(&ts, NULL);
    }
    
    return 0;  // 成功收到信号
}

// 发送信号带重试机制
int send_signal_with_retry(pid_t target_pid) {
    for (int i = 0; i < MAX_RETRIES; i++) {
        if (kill(target_pid, SIGUSR1) == 0) {
            return 0;  // 成功发送信号
        }
        
        if (errno != EINTR) {  // 如果不是因为被中断,则报错退出
            return -1;
        }
        
        // 短暂延迟后重试
        usleep(100000);  // 100ms
    }
    
    return -1;  // 达到最大重试次数
}

// 子进程任务
int child_task() {
    printf("[子进程 %d] 开始执行任务...\n", getpid());
    
    // 模拟一些实际工作
    for (int i = 1; i <= 3; i++) {
        printf("[子进程 %d] 执行步骤 %d/3\n", getpid(), i);
        sleep(1);
    }
    
    printf("[子进程 %d] 任务完成\n", getpid());
    return 0;
}

// 父进程任务
int parent_task() {
    printf("[父进程 %d] 开始初始化...\n", getpid());
    
    // 模拟初始化工作
    for (int i = 1; i <= 2; i++) {
        printf("[父进程 %d] 初始化步骤 %d/2\n", getpid(), i);
        sleep(1);
    }
    
    printf("[父进程 %d] 初始化完成\n", getpid());
    return 0;
}

int main() {
    // 设置信号处理
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    
    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        HANDLE_ERROR("sigaction failed");
    }

    pid_t pid = fork();
    
    if (pid < 0) {
        HANDLE_ERROR("fork failed");
    }
    
    if (pid == 0) {  // 子进程
        printf("[子进程 %d] 等待父进程就绪信号...\n", getpid());
        
        // 等待父进程信号,带超时机制
        if (wait_for_signal(MAX_WAIT_TIME) < 0) {
            fprintf(stderr, "[子进程 %d] 等待父进程超时\n", getpid());
            exit(EXIT_FAILURE);
        }
        
        if (signal_count > 1) {
            printf("[子进程 %d] 警告:收到多次信号 (%d次)\n", 
                   getpid(), signal_count);
        }
        
        // 执行子进程任务
        if (child_task() < 0) {
            fprintf(stderr, "[子进程 %d] 任务执行失败\n", getpid());
            exit(EXIT_FAILURE);
        }
        
        exit(EXIT_SUCCESS);
        
    } else {  // 父进程
        // 执行父进程初始化任务
        if (parent_task() < 0) {
            fprintf(stderr, "[父进程 %d] 初始化失败\n", getpid());
            // 终止子进程
            kill(pid, SIGTERM);
            exit(EXIT_FAILURE);
        }
        
        // 发送就绪信号给子进程
        printf("[父进程 %d] 发送就绪信号给子进程...\n", getpid());
        if (send_signal_with_retry(pid) < 0) {
            fprintf(stderr, "[父进程 %d] 无法发送信号给子进程\n", getpid());
            kill(pid, SIGTERM);
            exit(EXIT_FAILURE);
        }
        
        // 等待子进程结束
        int status;
        pid_t waited_pid = waitpid(pid, &status, 0);
        
        if (waited_pid < 0) {
            HANDLE_ERROR("waitpid failed");
        }
        
        if (WIFEXITED(status)) {
            int exit_status = WEXITSTATUS(status);
            if (exit_status == 0) {
                printf("[父进程 %d] 子进程正常结束,退出码: %d\n", 
                       getpid(), exit_status);
            } else {
                fprintf(stderr, "[父进程 %d] 子进程异常结束,退出码: %d\n", 
                        getpid(), exit_status);
                exit(EXIT_FAILURE);
            }
        } else if (WIFSIGNALED(status)) {
            fprintf(stderr, "[父进程 %d] 子进程被信号 %d 终止\n", 
                    getpid(), WTERMSIG(status));
            exit(EXIT_FAILURE);
        }
    }
    
    return 0;
}

/*
使用示例输出可能如下:

[父进程 1234] 开始初始化...
[父进程 1234] 初始化步骤 1/2
[父进程 1234] 初始化步骤 2/2
[父进程 1234] 初始化完成
[父进程 1234] 发送就绪信号给子进程...
[子进程 1235] 等待父进程就绪信号...
[子进程 1235] 开始执行任务...
[子进程 1235] 执行步骤 1/3
[子进程 1235] 执行步骤 2/3
[子进程 1235] 执行步骤 3/3
[子进程 1235] 任务完成
[父进程 1234] 子进程正常结束,退出码: 0
*/
  • 父子进程调试指令
    使用gdb调试的时候,gdb只能跟踪一个进程。可以在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或者是跟踪子进程。默认跟踪父进程。

set follow-fork-mode child 命令设置gdb在fork之后跟踪子进程。

set follow-fork-mode parent 设置跟踪父进程。

注意,一定要在fork函数调用之前设置才有效。

参考:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值