我们知道在C语言程序中,可以通过修改argv[0]
的值,来实现改变一个进程在ps
命令中显示的标题,先给一个C语言的demo如下:
#include <stdio.h>
#include <string.h>
extern char **environ;
int main(int argc , char *argv[]) {
int i;
printf("argc:%d\n" , argc);
for (i = 0; i < argc; i++) {
printf("argv[%d]:%s\t0x%x\n" , i , argv[i], argv[i]);
}
for (i = 0; i < argc && environ[i]; i++) {
printf("evriron[%d]:%s\t0x%x\n" , i , environ[i], environ[i]);
}
strcpy(argv[0], "nginx: process is shuting down");
sleep(1000);
return 0;
}
进程原本的名称是demo
,但是我们通过strcpy
修改了argv[0]
之后,ps
命令显示进程的名称变为我们指定的nginx: process is shuting down
,如下
➜ build git:(master) ✗ ps -ef | grep nginx
root 1263942 1252322 0 15:29 pts/2 00:00:00 nginx: process is shuting down
root 1263973 1253542 0 15:29 pts/3 00:00:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox nginx
这也是nginx程序实现在修改进程标题为nginx: master
, nginx: worker
以及 nginx: process is shuting down
的原理,从而实现在ps的标题中标识不同类别的进程。
我们在rust语言中如何达到相同的效果呢,查阅资料了解到,通过下面方法可以修改进程名
use nix::libc::{PR_SET_NAME, prctl};
use std::ffi::CString;
fn main() {
let new_name = CString::new("NewProcessName").expect("CString::new failed");
unsafe {
prctl(PR_SET_NAME, new_name.as_ptr() as usize, 0, 0, 0);
}
}
但是实际使用后,发现,这种方法修改的进程名并不是ps
命令显示的进程标题,ps
命令显示的进程标题还是不变,而是修改了pstree
命令显示的进程树种的进程名称,所以,这种方法并不能达到我们想要的效果。
我们尝试修改argv[0]
,在rust中是通过env::args()
来获取程序的传参,也即argv,追踪到env::args()
调用的是env::args_os()
,于是我们有这么一段代码尝试修改argv[0]:
use std::env;
use std::ffi::{CStr, CString, OsString};
use std::os::unix::ffi::OsStringExt;
fn set_process_title(title: &str) {
let args: Vec<OsString> = env::args_os().collect();
let mut argv: Vec<*mut i8> = args.iter()
.map(|arg| {
let arg_cstring = CString::new(arg.as_bytes()).expect("Failed to create CString");
arg_cstring.into_raw()
})
.collect();
argv.push(std::ptr::null_mut());
let title_cstring = CString::new(title).expect("Failed to create CString");
unsafe {
strcpy(argv[0] as *mut c_char, title_cstring.as_ptr());
}
}
fn main() {
set_process_title("MyWorker");
// 继续执行其他操作...
}
但是很遗憾,并没有修改argv[0]的效果,继续追踪args_os()
发现,其实在多个地方存在clone()
操作,我们获取到的argv
早就不是原始的argv
,args_os()
的实现如下
#[stable(feature = "env", since = "1.0.0")]
pub fn args_os() -> ArgsOs {
ArgsOs { inner: sys::args::args() }
}
sys::args::args()
的实现如下
/// Returns the command line arguments
pub fn args() -> Args {
imp::args()
}
imp::args()
的实现如下
pub fn args() -> Args {
Args { iter: clone().into_iter() }
}
这里clone()
实现如下
fn clone() -> Vec<OsString> {
unsafe {
// Load ARGC and ARGV, which hold the unmodified system-provided
// argc/argv, so we can read the pointed-to memory without atomics
// or synchronization.
//
// If either ARGC or ARGV is still zero or null, then either there
// really are no arguments, or someone is asking for `args()`
// before initialization has completed, and we return an empty
// list.
let argv = ARGV.load(Ordering::Relaxed);
let argc = if argv.is_null() { 0 } else { ARGC.load(Ordering::Relaxed) };
let mut args = Vec::with_capacity(argc as usize);
for i in 0..argc {
let ptr = *argv.offset(i) as *const libc::c_char;
// Some C commandline parsers (e.g. GLib and Qt) are replacing already
// handled arguments in `argv` with `NULL` and move them to the end. That
// means that `argc` might be bigger than the actual number of non-`NULL`
// pointers in `argv` at this point.
//
// To handle this we simply stop iterating at the first `NULL` argument.
//
// `argv` is also guaranteed to be `NULL`-terminated so any non-`NULL` arguments
// after the first `NULL` can safely be ignored.
if ptr.is_null() {
break;
}
let cstr = CStr::from_ptr(ptr);
args.push(OsStringExt::from_vec(cstr.to_bytes().to_vec()));
}
args
}
}
可以看到argv
是从ARGV.load(Ordering::Relaxed);
中加载出来的。可以看到argv经历了多次拷贝,最终才到args,然后通过args_os()
再呈现在我们面前,实际早就不再是最初的那个argv。