当前位置: 首页 > article >正文

通过nginx学习linux进程名的修改

目录

  • 1. 缘起
  • 2. 背景知识
  • 3. 源码分析
    • 3.1 准备工作
    • 3.2 设置进程名字

1. 缘起

  在运行nginx的时候,用ps查看nginx的进程信息,可能的输出如下:

root      42169    3105  0 16:51 ?        00:00:00 nginx: master process ./objs/nginx
root      42170   42169  0 16:51 ?        00:00:00 nginx: worker process
root      42171   42169  0 16:51 ?        00:00:00 nginx: worker process

  显示的最后一列进程名字非常清晰,有master process, worker process,一目了然。可是如果我们自己写一个程序,运行起来的时候则完全不是那样,显示的就是运行这个程序的命令行,如果是一个多进程的程序,则不太好区分不同的进程到底是干什么用的。那么就来研究一下nginx到底是怎么做的吧。

2. 背景知识

  通过ps或者top看到的linux进程实际上就是操作系统给当前进程分配的命令行缓冲区中的内容,在c程序中就是 main(int argc, char *const *argv)函数的argv指向的地址。这是一个连续的地址,存放了命令行参数列表,而命令行参数后面则是环境变换缓冲区。如下图:
在这里插入图片描述

  可以看到,命令行和环境变量两个缓冲区是前后相邻的。我们需要通过修改命令行参数对应的缓冲区内容来修改进程名字信息,但是需要考虑的一个问题是,因为命令行参数和环境变量的缓存是前后相邻的,如果新修改的进程名字的信息比原先的长,就可能出现覆盖后面的环境变量的问题,导致程序故障。因此,我们需要在修改进程名的时候对环境变量部分另外再分配一段内存来存储,来避开这个问题。

  接下来看看nginx的实现代码,学会了nginx的处理逻辑,我们完全可以今后拿过来在自己的其他程序中来使用了。

3. 源码分析

  nginx的这部分源码只存在于os/unix/ngx_setproctitle.c中,由于windows系统是不能支持修改进程名的,所以在os/win32目录中没有类似的实现。它的实现很简单,分为两个函数,第一个函数用来做准备工作,给环境变量挪到一个新分配的位置;第二个函数才真正设置进程名。

3.1 准备工作

ngx_int_t
ngx_init_setproctitle(ngx_log_t *log)
{
    u_char      *p;
    size_t       size;
    ngx_uint_t   i;

    size = 0;
    
	/* 计算环境变量总共需要多少缓存空间 */
    for (i = 0; environ[i]; i++) {
        size += ngx_strlen(environ[i]) + 1;
    }

	/* 分配一个新的缓存空间来存放环境变量 */
    p = ngx_alloc(size, log);
    if (p == NULL) {
        return NGX_ERROR;
    }

	/* ngx_os_argv_last 表示命令行参数可以存储的内存上界 */
    ngx_os_argv_last = ngx_os_argv[0];

	/* 遍历整个命令行参数列表 */
    for (i = 0; ngx_os_argv[i]; i++) {
        if (ngx_os_argv_last == ngx_os_argv[i]) {
            ngx_os_argv_last = ngx_os_argv[i] + ngx_strlen(ngx_os_argv[i]) + 1;
        }
    }

	/* 以下将环境变量复制到新的缓冲区 */
    for (i = 0; environ[i]; i++) {
        if (ngx_os_argv_last == environ[i]) {

            size = ngx_strlen(environ[i]) + 1;
            ngx_os_argv_last = environ[i] + size;
			
            ngx_cpystrn(p, (u_char *) environ[i], size);
            environ[i] = (char *) p;
            p += size;
        }
    }
	
    ngx_os_argv_last--;

    return NGX_OK;
}

以上代码逻辑非常简单明了,就是给环境变量缓冲区挪了个位置。

3.2 设置进程名字

void
ngx_setproctitle(char *title)
{
    u_char     *p;

#if (NGX_SOLARIS)

    ngx_int_t   i;
    size_t      size;

#endif
	/* 将命令行参数的第1个元素设置为空,表示将进程名后面的命令行参数全部忽略掉 */
    ngx_os_argv[1] = NULL;
    
	/* 将命令行参数的第0个元素指向的地址设置为想要的进程名title,
	   以下分为两次拷贝,第一次写入前缀nginx: 第二次才写入传入的参数title
	*/
    p = ngx_cpystrn((u_char *) ngx_os_argv[0], (u_char *) "nginx: ",
                        ngx_os_argv_last - ngx_os_argv[0]);

    p = ngx_cpystrn(p, (u_char *) title, ngx_os_argv_last - (char *) p);

	/* solaris 部分的解析这里略过不看了 */
#if (NGX_SOLARIS)

    size = 0;

    for (i = 0; i < ngx_argc; i++) {
        size += ngx_strlen(ngx_argv[i]) + 1;
    }

    if (size > (size_t) ((char *) p - ngx_os_argv[0])) {

        /*
         * ngx_setproctitle() is too rare operation so we use
         * the non-optimized copies
         */

        p = ngx_cpystrn(p, (u_char *) " (", ngx_os_argv_last - (char *) p);

        for (i = 0; i < ngx_argc; i++) {
            p = ngx_cpystrn(p, (u_char *) ngx_argv[i],
                            ngx_os_argv_last - (char *) p);
            p = ngx_cpystrn(p, (u_char *) " ", ngx_os_argv_last - (char *) p);
        }

        if (*(p - 1) == ' ') {
            *(p - 1) = ')';
        }
    }

#endif
	/* 将命令行缓冲区后面多出来的空间填充空格,
	   我感觉这行判断语句代码有点小毛病,如果p > ngx_os_argv_last呢,
	   当然实际应该不太会发生这种情况
	 */
    if (ngx_os_argv_last - (char *) p) {
        ngx_memset(p, NGX_SETPROCTITLE_PAD, ngx_os_argv_last - (char *) p);
    }

    ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
                   "setproctitle: \"%s\"", ngx_os_argv[0]);
}

   好了,以上就是对如何设置进程名字的一个分析和学习的过程。


http://www.kler.cn/a/231696.html

相关文章:

  • Tailscale 自建 Derp 中转服务器
  • 父组件提交时让各自的子组件验证表格是否填写完整
  • 解决微信小程序自定义tabbar点击两次才能跳转
  • Ubuntu 22.04.4 LTS + certbot 做自动续签SSL证书(2024-11-14亲测)
  • 关于 Oracle Database Express Edition 的功能和安装
  • 怎样在软件设计中选择使用GOF设计模式
  • 每日一题!如约而至!(图片整理,寻找数组的中心下标)
  • 寒假作业2月5号
  • 配置dns服务的正反向解析
  • JUnit5单元测试框架提供的注解
  • Django学习记录01
  • 如何使用websocket
  • LLVM实战之opt工具的使用
  • 每日一练 | 华为认证真题练习Day182
  • 大厂聚合支付系统架构演进(下)
  • 【开源】基于JAVA+Vue+SpringBoot的新能源电池回收系统
  • 【C#】Xasset加载资源模块
  • 中科大计网学习记录笔记(四):Internet 和 ISP | 分组延时、丢失和吞吐量
  • nodeJS 的 npm 设置国内高速镜像之淘宝镜像的方法
  • 小白水平理解面试经典题目LeetCode 71. Simplify Path【Stack类】
  • Java-加解密-roadmap
  • 16:定时器和计数器
  • 【Ubuntu】安装filebeat
  • SpringCache缓存快速实现注解
  • 在angular12中proxy.conf.json中配置详解
  • 【Git版本控制 03】远程操作