先说说需求,原来的业务程序使用单进程多线程结构,守护使用的shell脚本,工作起来倒是没有什么大的问题。直到某处远程升级后出现小小的配置问题,进程运行就死掉,看门狗没人喂,守护脚本又判断不了出了什么问题,只是傻傻的重启。我只好想象着它一遍遍的重启,然后驱车300KM,到现场1分钟解决问题,白白花费了大半日功夫,一路风吹日晒。为了满足自己宅在办公室就能操控世界的伟大理想,我决定想个办法解决一下。最简单的就是自己fork来监控进程状态啦,为了保存旧的兼容性,脚本只允许一个同名进程,只好牺牲下自己,改个名欢快地跑起来。

网上传的最多的就是:

法一:

1
int prctl(PR_SET_NAME, name);

通过这个函数可以将当前进程的名称修改为 name 的内容。高高兴兴的改完,ps一下,名字还是一样的呢……经过一番搜索,/proc/$pid/stat等几个文件里面名字确实变了。某文章看到只有使用 ps -L 才能看到,达不到想要的效果。

法二:

父子进程管理,nginx一直做的很棒,它家的进程名字都自带小尾巴。代码中有名为set_proctitle函数用来修改进程名。一搜果然出来一些分析博文,linux的ps命令实际是以argv[0]处的值来作为进程的title的,因此只需要修改argv[0]处的值即可。原理如下图,一图胜千言。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdarg.h>
#include <sys/prctl.h>
#define MAXLINE 2048
extern char **environ;
static char **g_main_argv = NULL; /* pointer to argument vector */
static char *g_main_last_argv = NULL; /* end of argv */
//备份原始变量,以免修改argv[0]的时候覆盖了environ变量
void setproctitle_init(int argc, char **argv, char **envp) {
    int i;
    //计算环境变量链表长度,重新申请空间,然后把变量字符串也拷贝过去
    for(i = 0; envp[i] != NULL; i++)  // calc envp num
        continue;
    environ = (char **) malloc(sizeof(char *) * (i + 1));  // malloc envp pointer
    for(i = 0; envp[i] != NULL; i++) {
        environ[i] = (char*)malloc(sizeof(char) * strlen(envp[i]));
        strcpy(environ[i], envp[i]);
    }
    environ[i] = NULL;
    g_main_argv = argv;
    if(i > 0)
        g_main_last_argv = envp[i - 1] + strlen(envp[i - 1]);
    else
        g_main_last_argv = argv[argc - 1] + strlen(argv[argc - 1]);
}
void setproctitle(const char *fmt, ...) {
    char *p;
    int i;
    char buf[MAXLINE];
    va_list ap;
    p = buf;
    va_start(ap, fmt);
    vsprintf(p, fmt, ap);
    va_end(ap);
    i = strlen(buf);
    if(i > g_main_last_argv - g_main_argv[0] - 2) {
        i = g_main_last_argv - g_main_argv[0] - 2;
        buf[i] = '\0';
    }
    (void) strcpy(g_main_argv[0], buf);
    p = &g_main_argv[0][i];
    while(p < g_main_last_argv)
        *p++ = '\0';
    g_main_argv[1] = NULL;
    prctl(PR_SET_NAME, buf);
}

其实如果修改后的名字不比原来长,直接修改argv[0]也是可以的。在redis的源代码中还有一个实现,原理自然一样。翻阅一下,发现bsd库自带这个函数(“▔□▔) “)