(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函数调用之前设置才有效。
参考:

3482

被折叠的 条评论
为什么被折叠?



