本节开始进入到进程的话题
在正式进行多进程代码编写前,还需要修改一下本工程运行之后在终端显示的进程名称,根据之前几节的内容,在main函数中添加一个死循环,保证程序运行后不退出,以便观察进程名称。
for(;;)
{
sleep(1);
}
重新编译运行后,在终端查看进程名称
ps aux | grep -E 'bash|PID|nginx'
可以看到,在未作任何修改前,进程名称显示为 ./nginx_sim。
接下来,根据nginx官方源码,我们给自己的程序修改显示的进程名
在 ngx_global.h 文件中新增几个全局变量保存参数和环境变量用以备份
//用于修改进程名称
extern char **g_os_argv;
extern char *gp_envmem;
extern int g_environlen;
extern int g_argvlen;
extern int g_os_argc; //参数个数
在 main.cxx 中定义这几个变量
char **g_os_argv;
char *gp_envmem = nullptr;
int g_environlen = 0;
int g_argvlen = 0;
int g_os_argc; //参数个数
然后再main 函数中用这几个全局变量保存环境变量和main函数的输入参数
for(int i = 0; i<argc; ++i)
{
g_argvlen += strlen(argv[i]) + 1;
}
//统计环境变量所占的内存。注意判断方法是environ[i]是否为空作为环境变量结束标记
for(int i = 0; environ[i]; ++i)
{
g_environlen += strlen(environ[i]) + 1;
}
g_os_argc = argc;
g_os_argv = (char **) argv;
到这里就可以开始动手了
Note: 本工程我们不会用到命令行输入的参数,所以直接在内存中将其覆盖掉了。
在 ngx_func.h 文件声明修改进程名相关的函数
//设置可执行程序标题相关函数
void ngx_init_setproctitle();
void ngx_setproctitle(const char *title);
在 src 目录新建 ngx_setproctitle.cxx 文件用于对这两个函数的实现
- 初始化函数用于参数和环境变量的备份
//设置可执行程序标题相关函数:分配内存,并且把环境变量拷贝到新内存中来
void ngx_init_setproctitle()
{
//new 失败时就让程序崩溃吧【这里分配了内存,一定要记得释放,本程序放在了 main.cxx 中的 freereSource() 函数中】
gp_envmem = new char[g_environlen];
memset(gp_envmem,0,g_environlen); //内存要清空防止出现问题
char *ptmp = gp_envmem;
//把原来的内存内容搬到新地方来
for (int i = 0; environ[i]; i++)
{
//这个加1还是很重要的,这个位置存放 '\0'
size_t size = strlen(environ[i])+1 ;
strcpy(ptmp,environ[i]); //把原环境变量内容拷贝到新地方【新内存】
environ[i] = ptmp; //然后还要让新环境变量指向这段新内存
ptmp += size;
}
return;
}
- 修改进程名字
//设置可执行程序标题
void ngx_setproctitle(const char *title)
{
//我们假设,所有的命令 行参数我们都不需要用到了,可以被随意覆盖了;
//注意:我们的标题长度,绝对不能长到原始标题和原始环境变量都装不下
//计算新标题长度
size_t ititlelen = strlen(title);
size_t esy = g_argvlen + g_environlen; //argv和environ内存总和
if( esy <= ititlelen)
{
//标题太长的拒绝
return;
}
//设置后续的命令行参数为空,表示只有argv[]中只有一个元素
g_os_argv[1] = NULL;
//把标题弄进来,注意原来的命令行参数都会被覆盖掉,不要再使用这些命令行参数,而且g_os_argv[1]已经被设置为NULL了
char *ptmp = g_os_argv[0]; //让ptmp指向g_os_argv所指向的内存
strcpy(ptmp,title);
ptmp += ititlelen; //跳过标题
//把剩余的原argv以及environ所占的内存全部清0,否则可能会残余一些没有被覆盖的内容;
//内存总和减去标题字符串长度(不含字符串末尾的\0),剩余的大小,就是要memset的;
size_t cha = esy - ititlelen;
memset(ptmp,0,cha);
return;
}
可以在 mian 函数中添加测试代码了
int main(int argc, char* const* argv)
{
ngx_pid = getpid();
for(int i = 0; i<argc; ++i)
{
g_argvlen += strlen(argv[i]) + 1;
}
//统计环境变量所占的内存。注意判断方法是environ[i]是否为空作为环境变量结束标记
for(int i = 0; environ[i]; ++i)
{
g_environlen += strlen(environ[i]) + 1;
}
g_os_argc = argc;
g_os_argv = (char **) argv;
//加载配置文件内容
if(false == CConfig::GetInstance()->LoadConfig("nginx_sim.conf"))
{
//日志初始化(创建/打开日志文件)
ngx_log_init();
ngx_log_stderr(0,"配置文件[%s]载入失败,退出!","nginx_sim.conf");
//找不到文件
goto lblexit;
}
//日志初始化(创建/打开日志文件)
ngx_log_init();
//把环境变量搬家
ngx_init_setproctitle();
//这里设置进程名测试
ngx_setproctitle("nginx-sim: master process");
//这个死循环主要方便观察进程名称
for(;;)
{
sleep(1);
}
ngx_log_stderr(0, "从配置文件中读到的 log 目录:%s",(CConfig::GetInstance()->GetString("Log")).c_str());
ngx_log_stderr(0, "从配置文件中读到的 log 等级:%d",CConfig::GetInstance()->GetIntDefault("LogLevel",NGX_LOG_NOTICE));
lblexit:
ngx_log_stderr(0,"程序退出!");
//该释放的资源要释放掉
freereSource(); //一系列的main返回前的释放动作函数
return 0;
}
可以看到,我们启动程序以后终端显示的进程名字已经编程我们自己设置的了,目前只有一个master进程,后续我们将在代码中添加多个 worker 进程。
接下来将我们的程序做成一个守护进程。