深究更改进程名(一)

1.需求
如nginx这种,父进程可以fork出多个子进程,程序在同一台机器上运行多次产生多个进程,多个进程使用不同的conf从而监听不同的端口。
这样,在ps和top时,会有区分父子进程名的需求而不是都叫nginx;对于多个nginx的父进程希望知道进程的具体任务,这可以用conf区分。就像nginx实际处理一样:
nginx: master process /usr/local/nginx/sbin/nginx
nginx: worker process。


同理,对于多线程也有修改名称的必要,如下一个最简单的线程top示例:

#include <cstdio>
#include <unistd.h>
#include <pthread.h>

void *func(void *arg){
    sleep(60);
    return NULL;
}

pthread_t p1,p2,p3;

int main(){
    pthread_create(&p1,NULL,func,NULL);
    pthread_create(&p2,NULL,func,NULL);
    pthread_create(&p3,NULL,func,NULL);
    sleep(65);
    return 0;
}

24987 ubuntu    20   0   31088    388    300 S  0.0  0.0   0:00.00 threadbin                                                                                                                                                              
24988 ubuntu    20   0   31088    388    300 S  0.0  0.0   0:00.00 threadbin
24989 ubuntu    20   0   31088    388    300 S  0.0  0.0   0:00.00 threadbin
24990 ubuntu    20   0   31088    388    300 S  0.0  0.0   0:00.00 threadbin
看哪个进程在休眠或者等待事件、哪个进程自旋锁死锁一直cpu 100%、负载均衡哪些进程很闲哪些很忙、对于不同任务的线程哪一类任务占用cpu内存最多(涉及到调线程数)但看线程号很不直观。


2.方法
在查阅了网上,以及看了nginx源码后,方法:
prctl(PR_SET_NAME,"your_new_name",0,0,0)和设置argv[0]。


初步试一下:

#include <cstdio>
#include <errno.h>
#include <sys/prctl.h>
#include <unistd.h>
int main(int argc,char **argv){
    printf("before argv[0]:%s\n",argv[0]);
    sleep(20);
    if(prctl(PR_SET_NAME,"yesImChanged",0,0,0) != 0)    //用函数设置为yesImChanged
        perror(NULL);
    printf("changed done\n");
    argv[0][0] = 'y';   //修改argv[0]第一个字节.为y
    printf("after argv[0]:%s\n",argv[0]);
    sleep(20);
}

top结果:
26424 ubuntu    20   0    4204    356    276 S  0.0  0.0   0:00.00 notchanged
26424 ubuntu    20   0    4204    356    276 S  0.0  0.0   0:00.00 yesImChanged
ps aux结果:
ubuntu   26547  0.0  0.0   4204   356 pts/6    S+   10:57   0:00 ./notchanged
ubuntu   26547  0.0  0.0   4204   356 pts/6    S+   10:57   0:00 y/notchanged
可见top和ps aux修改名称都生效了,但是top时prctl生效后结果,ps aux是修改argv[0]的结果。此外,如果ps auxc多加一个c那么就是prctl的结果;而prctl最多支持16字节名称;修改argv[0]过长会破坏环境变量。这些下面着重深究。


3.argv和环境变量的位置
argv和环境变量位置的关系,argv和环境变量在内存中是紧紧相连的,验证下:

#include <cstdio>
#include <cstring>
extern char **environ;
int main(int argc,char **argv){
    char **ppc = environ;
    char *lastp = argv[0];
    int i = 0;
    printf("argvs:\n");
    while(i < argc){
        printf("%s,address=%p,strlen=%d,pdiff=%d\n",argv[i],argv[i],strlen(argv[i]),(int)(argv[i] - lastp));
        lastp = argv[i];
        i++;
    }
    printf("\n");
    while(*ppc != NULL){
        printf("%s,ppc address=%p,pc address=%p,strlen=%d,pdiff=%d\n",*ppc,ppc,*ppc,strlen(*ppc),(int)(*ppc - lastp));
        lastp = *ppc;
        ppc++;
    }
}

输出:
argvs:
./a.out,address=0x7ffc575bb832,strlen=7,pdiff=0         //environ开始
xixi,address=0x7ffc575bb83a,strlen=4,pdiff=8    //pdiff是前一段strlen加字符串尾1,8=7+1
haha,address=0x7ffc575bb83f,strlen=4,pdiff=5    //同上


XDG_SESSION_ID=300,ppc address=0x7ffc575bb478,pc address=0x7ffc575bb844,strlen=18,pdiff=5       //environ开始,同上
TERM=xterm,ppc address=0x7ffc575bb480,pc address=0x7ffc575bb857,strlen=10,pdiff=19      //同上
SHELL=/bin/bash,ppc address=0x7ffc575bb488,pc address=0x7ffc575bb862,strlen=15,pdiff=11 //同上
HISTSIZE=1000,ppc address=0x7ffc575bb490,pc address=0x7ffc575bb872,strlen=13,pdiff=16   //同上
可见的确如此。并且通过pmap可以看到argv起始位置在栈区之后,就像《unix高级环境编程》说的一样。


4.ps和top是怎么获得文件名的
在我的ubuntu上 dpkg -S /bin/ps之后,apt-get source procps下载源码。
有几行:
proc_t *restrict const t;
t->cmdline = file2strvec(path, "cmdline");      //path是/proc/pid/或者/proc/pid/task/tid
......
{"cmd",       "CMD",     pr_args,     sr_cmd,    27, ARG,    DEC, PO|UNLIMITED},
......
endp += escaped_copy(endp, *pp->cmdline, OUTBUF_SIZE, &rightward);
......
char **cmdline,      // (special)       command line string vector (/proc/#/cmdline),cmdline定义
大约(因为理清某种c源代码的调用关系比较麻烦,这里是根据看到的局部现象推测)ps是从proc中读取到cmdline字符串再打印的。测试下:
cat /proc/15440/cmdline
y/notchanged
cat /proc/15440/task/15440/cmdline
y/notchanged
proc中的cmdline的确跟随argv[0]一起改了,ok,证据链闭环。


再看下top的,这下学聪明点了,直接一路grep COMMAND到底:
case P_CMD:
        makeVAR(forest_display(q, p));  //调forest_display生成输出结果
......
const char *which = (CHKw(q, Show_CMDLIN)) ? *p->cmdline : p->cmd;      //默认返回cmd而非cmdline
......
#define Show_CMDLIN  0x000080     // 'c' - show cmdline vs. name
......
char cmd[16];    // stat,status     basename of executable file in call to exec(2),cmd定义
这里除了上面的cmdline(就是proc下cmdline,看现象是argv[0]),还有了个cmd,并且cmd是默认值。
函数stat2proc里有一行注释,Reads /proc/*/stat files, being careful not to trip over processes with,一行代码,memcpy(P->cmd, S, num);
测试下proc下的stat文件:
cat /proc/20528/stat
20528 (notchanged) S 15260 20528 15260 34831 20528 1077960704 189 0 0 0 0 0 0 0 20 0 1 0 672583548 4304896 89 18446744073709551615 4194304 4196588 140721140003936 140721140003208 139868578957600 0 0 0 0 1 0 0 17 0 0 0 0 0 0 6295056 6295648 13897728 140721140008994 140721140009017 140721140009017 140721140010987 0
cat /proc/20528/stat
20528 (yesImChanged) S 15260 20528 15260 34831 20528 1077960704 193 0 0 0 0 0 0 0 20 0 1 0 672583548 4304896 89 18446744073709551615 4194304 4196588 140721140003936 140721140003208 139868578957600 0 0 0 0 1 0 0 17 0 0 0 0 0 0 6295056 6295648 13897728 140721140008994 140721140009017 140721140009017 140721140010987 0
top的证据链也完毕了。


总之,默认情况下的ps和top都是从proc下获取命令名的,一个是从cmdline,一个从stat。而且cmdline似乎是被argv[0]影响,stat能给被prctl改变。


5.proc是如何更新cmdline和stat的
proc再linux内核代码fs/proc下。c文件名对应proc对应文件名,相当工整。
余下内容见下篇。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值