在运行时设置 Mac OS X 的进程名称

23

我正在尝试在运行时更改进程名称,以便在 psActivity Monitor 中显示。我发现有几个注意事项指出没有可移植的方法来实现这一点(但我不在意)。

下面是我尝试过的方法,但都没有成功:

  • 更改 argv [0](在某些 Unix 系统中似乎是正确的方法)
  • 调用 [[NSProcessInfo processInfo] setProcessName:@“someName”]
  • 调用 setprogname(调用getprogname返回我设置的名称,但这与问题无关)

我还读到一个名为setproctitle 的函数,如果可用,则应在stdlib.h中定义,但它不存在。

一定有办法实现这一点,因为 QTKitServer - QuickTime Player X 的无面解码器 - 其对应的 QuickTime Player PID 在其进程名称中。

有人知道如何实现吗? 我非常希望使用 Core Foundation 或 POSIXy 方法而不是 Objective-C 方法来实现此操作。

谢谢,

Marco

编辑: 如果相关的话,我正在使用 Mac OS X 10.6.5 和 Xcode 3.2.5


1
@ Alex Brown(编辑标签的人):我故意将此问题的标签设置为“Mac”而不是“posix”,因为我发现的POSIX方式对我无效(argv [0]setproctitle())。 - Marco Masser
2个回答

13

改变进程名称有很好的理由。Java软件应该更改进程名称,因为在运行不同的Java工具时,我想知道哪个Java进程是哪个工具。

Chromium做到了:http://src.chromium.org/viewvc/chrome/trunk/src/base/mac/mac_util.mm

Node.js使用相同的代码来实现Process.title = 'newtitle'https://github.com/joyent/node/blob/master/src/platform_darwin_proctitle.cc

注意:如果有人使用su切换到其他未登录用户,则此方法会失败:https://github.com/joyent/node/issues/1727

这里是源代码的全部内容。顺便说一句,有人告诉我它也适用于Mac OS X Lion,并且在使用su时也会失败。

// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
void SetProcessName(CFStringRef process_name) {
  if (!process_name || CFStringGetLength(process_name) == 0) {
    NOTREACHED() << "SetProcessName given bad name.";
    return;
  }

  if (![NSThread isMainThread]) {
    NOTREACHED() << "Should only set process name from main thread.";
    return;
  }

  // Warning: here be dragons! This is SPI reverse-engineered from WebKit's
  // plugin host, and could break at any time (although realistically it's only
  // likely to break in a new major release).
  // When 10.7 is available, check that this still works, and update this
  // comment for 10.8.

  // Private CFType used in these LaunchServices calls.
  typedef CFTypeRef PrivateLSASN;
  typedef PrivateLSASN (*LSGetCurrentApplicationASNType)();
  typedef OSStatus (*LSSetApplicationInformationItemType)(int, PrivateLSASN,
                                                          CFStringRef,
                                                          CFStringRef,
                                                          CFDictionaryRef*);

  static LSGetCurrentApplicationASNType ls_get_current_application_asn_func =
      NULL;
  static LSSetApplicationInformationItemType
      ls_set_application_information_item_func = NULL;
  static CFStringRef ls_display_name_key = NULL;

  static bool did_symbol_lookup = false;
  if (!did_symbol_lookup) {
    did_symbol_lookup = true;
    CFBundleRef launch_services_bundle =
        CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices"));
    if (!launch_services_bundle) {
      LOG(ERROR) << "Failed to look up LaunchServices bundle";
      return;
    }

    ls_get_current_application_asn_func =
        reinterpret_cast<LSGetCurrentApplicationASNType>(
            CFBundleGetFunctionPointerForName(
                launch_services_bundle, CFSTR("_LSGetCurrentApplicationASN")));
    if (!ls_get_current_application_asn_func)
      LOG(ERROR) << "Could not find _LSGetCurrentApplicationASN";

    ls_set_application_information_item_func =
        reinterpret_cast<LSSetApplicationInformationItemType>(
            CFBundleGetFunctionPointerForName(
                launch_services_bundle,
                CFSTR("_LSSetApplicationInformationItem")));
    if (!ls_set_application_information_item_func)
      LOG(ERROR) << "Could not find _LSSetApplicationInformationItem";

    CFStringRef* key_pointer = reinterpret_cast<CFStringRef*>(
        CFBundleGetDataPointerForName(launch_services_bundle,
                                      CFSTR("_kLSDisplayNameKey")));
    ls_display_name_key = key_pointer ? *key_pointer : NULL;
    if (!ls_display_name_key)
      LOG(ERROR) << "Could not find _kLSDisplayNameKey";

    // Internally, this call relies on the Mach ports that are started up by the
    // Carbon Process Manager.  In debug builds this usually happens due to how
    // the logging layers are started up; but in release, it isn't started in as
    // much of a defined order.  So if the symbols had to be loaded, go ahead
    // and force a call to make sure the manager has been initialized and hence
    // the ports are opened.
    ProcessSerialNumber psn;
    GetCurrentProcess(&psn);
  }
  if (!ls_get_current_application_asn_func ||
      !ls_set_application_information_item_func ||
      !ls_display_name_key) {
    return;
  }

  PrivateLSASN asn = ls_get_current_application_asn_func();
  // Constant used by WebKit; what exactly it means is unknown.
  const int magic_session_constant = -2;
  OSErr err =
      ls_set_application_information_item_func(magic_session_constant, asn,
                                               ls_display_name_key,
                                               process_name,
                                               NULL /* optional out param */);
  LOG_IF(ERROR, err) << "Call to set process name failed, err " << err;
}

编辑: 这是一个复杂而混乱的问题。

在OS X上没有setproctitle(3)。一个人必须写入argv数组(丑陋并且有点危险,因为可能会用虚假的东西覆盖一些环境变量)。如果正确地完成,它可以很好地工作。

此外,苹果公司有ActivityMonitor应用程序,类似于Windows下的任务管理器。上面的代码操作ActivityMonitor,但这种操作似乎并不被苹果公司鼓励(因此使用了未经文件化的函数)。

重要提示:ps和ActivityMonitor不显示相同的信息。

还要注意:如果您没有GUI,则无法使用ActivityMonitor。如果您通过ssh登录到远程Apple盒子,并且没有任何人通过GUI登录,则可能会发生这种情况。可悲的是,苹果公司存在一个错误,在我看来。只是查询是否有GUI会向stderr发送一个烦人的警告消息。

总结: 如果您需要更改ActivityMonitor,请使用上面的代码。如果您遇到无GUI的情况并且不喜欢stderr上的警告,请在调用SetProcessName时将stderr暂时重定向到/dev/null。如果您需要更改ps信息,请写入argv。


1
顺便问一下,你的项目是开源的吗?如果不是,考虑版权问题,因为代码属于Chromium项目。 - nalply
不,我需要的项目不是开源的。但是不用担心,我不会直接复制代码。我所需要的是调用_LSSetApplicationInformationItem()函数所提供的信息。我将围绕这个函数编写代码,并在我们自己的库的上下文中进行修改,因此不会违反GPL。 - Marco Masser
嗯...我又尝试了一下,似乎有些机制完全没有注意到这个变化。例如[[NSProcessInfo processInfo] processName]仍然具有旧名称。我不知道它从哪里获取信息,但如果出现这种不一致,我就不太敢使用那段代码了。不管怎样,还是非常感谢! - Marco Masser
这是直接来自苹果的部分未记录代码。难怪Chromium开发人员会说:警告:这里有龙!此外,以下划线开头的名称(如“_LSSetApplicationInformationItem”)通常被视为私有。遗憾的是,苹果不得不在Activity Monitor中使用专有内容。 - nalply
这个已经从Chromium中移除了。根据修订日志:'这涉及到进程管理器函数,在10.9中会导致WindowServer检查,从而导致任何不泵送事件(也就是除了渲染器之外的所有子进程)的"未响应"通知。' - whoKnows
啊,又是一根钉子钉进了棺材。 - nalply

7
您可以使用自10.6版本起一直搭载在macOS上的lsappinfo工具进行操作,该工具已经适用于最新的10.13.2版本。

Shell命令:

lsappinfo setinfo <PID> --name <NAME>

C++:

#include <sstream>
#include <string>
#include <stdlib.h>

void setProcessName (pid_t pid, std::string name)
{
    std::ostringstream cmd;
    cmd << "/usr/bin/lsappinfo setinfo " << pid;
    cmd << " --name \"" << name << "\"";
    system (cmd.str().c_str());
}

1
哇,我不知道这个命令,看起来很有趣。像这样设置名称非常有效!“lsappinfo”似乎是@nalply代码描述的功能的前端。七年多过去了,现在已经没有这样做的需要了。 - Marco Masser
3
我试图使用它重命名一个Python进程,但没有成功。lsappinfo processList的输出仅列出图形化应用程序,而不是在shell上运行的进程,因此我认为它只适用于那种类型的应用程序。 - hdiogenes
4
我在Catalina系统上看到了 err=-600 的提示。 - ldeck
1
只有在使用sudo运行lsappinfo时,它才会更改名称。 - Alexander Artemenko
7
我认为这只适用于GUI应用程序,而不是从终端运行的脚本,对于这些脚本,你会得到一个err=-600,意思是无效的PID。 - Jan M
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接