Linux C程序修改进程名称
Posted 彼方丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux C程序修改进程名称相关的知识,希望对你有一定的参考价值。
Linux C程序修改进程名称
1、前言
Linux C程序运行时,进程的名称通常就是argv[0],而通过修改内存中argv[0]存储的内容就可以修改进程名了。下面对此作详细介绍。
2、命令行参数(argc, argv)以及环境变量(environ)介绍
2.1、C程序典型的存储空间布局
如图所示,是C程序典型的存储空间布局,可以很明显看到命令行参数和环境变量处在最顶端,而且是挨在一起的,命令行参数后面紧接着就是环境变量,具体的内容大家可以自行看一下《UNIX环境高级编程》第七章,下图也是从里面摘录出来的
2.2、argc, argv介绍
每个C语言程序都必须有一个称为
main
的函数,一般形式为int main(int argc, char* argc[])
,改函数作为程序启动的起点。执行程序时,命令行参数通过argc
和argv
这两个两个入参传递给main
函数。第一个表示命令行参数的个数,而第二个参数则是一个指向命令行参数的指针数组,每一参数都是C类型字符串,以空字符\\0
结尾的,而最后一个参数argc[argv]
则是一个空指针NULL
。其中第一个字符串(argv[0]
),则是该程序的名称,也就是运行时的进程名。
内存示例如图所示
2.3、environ介绍
每一个进程都有与其相关的称为环境列表(environment list)的字符串数组。其中每个字符串都以名称=值(name=value)形式定义。新进程在创建时,会继承其父进程的环境副本,子进程创建后,父、子进程均可更改各自的环境变量,且这些变更对对方而言不再可见。
在C程序中,可以使用全局变量extern char** environ
获取环境变量表(C运行时启动代码定义了该变量并以当前环境列表位置为其赋值)environ
与argv
参数比较相似,是一个指针数组,数组每个成员指向C类型字符串,最后一个指针也是一个NULL
。
内存示例如图所示
2.4、编写程序验证修改进程名是否可以成功
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char* argv[])
{
sleep(8);
strcpy(argv[0], "112233445566");
while (1);
return 0;
}
代码如上,可以看到,程序先休眠8秒钟,然后修改argv[0]
的值,编译运行一下,运行结果如下,可以看到,一开始进程名是./all
,8秒后就变成了112233445566
,修改成功,而且我们也能看出一点细节,那就是修改后的进程名超过了原先的进程名也没有报错,有可能的原因是argc[0]
后面的内存也是可用的(其实从内存布局就可以看出来可用了)
2.5、查看进程名变长之后影响了那部分内存的数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
extern char** environ;
int main(int argc, char* argv[])
{
printf("begin environ:\\n");
for (int i = 0; environ[i] != NULL && i < 5; i++)
printf("%s\\n", environ[i]);
sleep(5);
strcpy(argv[0], "112233445566");
printf("\\n\\n");
printf("end environ:\\n");
for (int i = 0; environ[i] != NULL && i < 5; i++)
printf("%s\\n", environ[i]);
return 0;
}
代码如上,为了方便显示,环境变量只打印前五个,看看效果就行。运行结果如下图所示,可以看到,一开始打印的第一个环境变量为XDG_SESSION_ID=3585
,当修改完进程名之后,该变量变为445566
,原先进程名为./all
,加上结束符是6个字节,剩余四个字节的内容也就是445566
刚好把第一个环境变量的内存给覆盖了(这里没有传入参数,所以覆盖的是环境变量的内存)。
2.6、结论
Linux C程序中传入参数和环境变量是存放在一段连续的内存空间的,而argv
和environ
这两个指针数组里面存放的指针就是指向这些内存的
3、Redis修改进程名的做法
Redis作为一个优秀的企业级存储框架,里面有许多值得学习的地方,而里面也有一段代码修改进程名的,接下来看看Redis是如何实现的
extern char **environ;
static struct {
/* original value */
const char *arg0;
/* title space available */
char *base, *end;
/* pointer to original nul character within base */
char *nul;
_Bool reset;
int error;
} SPT;
void spt_init(int argc, char *argv[]) {
char **envp = environ;
char *base, *end, *nul, *tmp;
int i, error, envc;
if (!(base = argv[0]))
return;
nul = &base[strlen(base)];
end = nul + 1;
for (i = 0; i < argc || (i >= argc && argv[i]); i++) {
if (!argv[i] || argv[i] < end)
continue;
if (end >= argv[i] && end <= argv[i] + strlen(argv[i]))
end = argv[i] + strlen(argv[i]) + 1;
}
for (i = 0; envp[i]; i++) {
if (envp[i] < end)
continue;
if (end >= envp[i] && end <= envp[i] + strlen(envp[i]))
end = envp[i] + strlen(envp[i]) + 1;
}
envc = i;
if (!(SPT.arg0 = strdup(argv[0])))
goto syerr;
if (!(tmp = strdup(program_invocation_name)))
goto syerr;
program_invocation_name = tmp;
if (!(tmp = strdup(program_invocation_short_name)))
goto syerr;
program_invocation_short_name = tmp;
/* Now make a full deep copy of the environment and argv[] */
if ((error = spt_copyenv(envc, envp)))
goto error;
if ((error = spt_copyargs(argc, argv)))
goto error;
SPT.nul = nul;
SPT.base = base;
SPT.end = end;
return;
syerr:
error = errno;
error:
SPT.error = error;
}
从代码中可以看出以下几点:
base
指向的是argv[0]
,nul
指向argv[1]
,而end
经过两个for
循环遍历之后指向的是传入参数+环境变量内存空间的最末尾- 使用
strdup
函数将原本的进程名复制给SPT.arg0
spt_copyenv
和spt_copyargs
函数的作用是将原先的传入参数(从argv[1]
开始)和环境变量复制到一个新的内存区域,然后原先的指针数组里元素指向新的内存地址,完成了数据的迁移- 经过上面的步骤之后,
argv[0]
还是指向原来的地址,而其他的传入参数以及环境变量均指向新的内存空间了,现在就可以任意修改进程名而不影响其他数据了
4、总结
本文介绍了如何修改Linux C程序的进程名,以及Redis源代码中是如何在保持原有传入参数以及环境变量的情况下,对程序的进程名进行修改。测试代码比较简单,大家可以自行编写程序去进行测试验证
以上是关于Linux C程序修改进程名称的主要内容,如果未能解决你的问题,请参考以下文章