【问题标题】:Determine programmatically if a program is running以编程方式确定程序是否正在运行
【发布时间】:2011-10-17 10:05:27
【问题描述】:

在 C 语言中,如何以编程方式确定某个进程是否已在 Linux/Ubuntu 上运行以避免它启动两次?我正在寻找类似于 pidof 的东西。

【问题讨论】:

  • (您也许可以在启动程序时在已知位置创建一个临时“锁定”文件,然后只需检查该文件是否存在(并在关机时将其删除 - 尽管如果您遇到问题程序意外终止))
  • @marnir - 这就是为什么您让程序将自己的 PID 写入文件,然后如果文件存在,您可以检查该 PID 是否仍然处于活动状态,如果是,如果进程名称与您自己的匹配。这不是 100% 万无一失的,但异常终止不应该经常发生那样。
  • 如果“锁定文件”中包含了程序的PID,几乎在所有情况下都可以检测到程序异常终止(是否存在PID与PID文件匹配的正在运行的进程)?这不是一个完美的解决方案(PID 数量有限,PID 回收)。
  • @marnir - 如果您确实锁定了锁定文件,则锁定将在进程退出时释放。程序只是尝试一个非阻塞的独占锁,如果失败,其他人正在运行。
  • 老实说,stackoverflow.com/questions/5460702/… 看起来是一个清晰、简单的解决方案

标签: c linux process pid


【解决方案1】:

您可以遍历/proc 中的pid 条目并在cmdline 文件中检查您的进程,或者在exe 链接上执行readlink(以下使用第一种方法)。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>

pid_t proc_find(const char* name) 
{
    DIR* dir;
    struct dirent* ent;
    char* endptr;
    char buf[512];

    if (!(dir = opendir("/proc"))) {
        perror("can't open /proc");
        return -1;
    }

    while((ent = readdir(dir)) != NULL) {
        /* if endptr is not a null character, the directory is not
         * entirely numeric, so ignore it */
        long lpid = strtol(ent->d_name, &endptr, 10);
        if (*endptr != '\0') {
            continue;
        }

        /* try to open the cmdline file */
        snprintf(buf, sizeof(buf), "/proc/%ld/cmdline", lpid);
        FILE* fp = fopen(buf, "r");

        if (fp) {
            if (fgets(buf, sizeof(buf), fp) != NULL) {
                /* check the first token in the file, the program name */
                char* first = strtok(buf, " ");
                if (!strcmp(first, name)) {
                    fclose(fp);
                    closedir(dir);
                    return (pid_t)lpid;
                }
            }
            fclose(fp);
        }

    }

    closedir(dir);
    return -1;
}


int main(int argc, char* argv[]) 
{
    if (argc == 1) {
        fprintf("usage: %s name1 name2 ...\n", argv[0]);
        return 1;
    }

    int i;
    for(int i = 1; i < argc; ++i) {
        pid_t pid = proc_find(argv[i]);
        if (pid == -1) {
            printf("%s: not found\n", argv[i]);
        } else {
            printf("%s: %d\n", argv[i], pid);
        }
    }

    return 0;
}

【讨论】:

  • 展示一下 - 显示每次迭代运行这段代码大约需要 500 毫秒,为什么这需要这么长时间?有更好的方法吗?
  • 这取决于您正在运行的进程数。
  • 删除程序名前面的文件路径怎么样?您可以使用 comm 代替 cmdline 吗?另外,你如何忽略被杀死的进程?
  • 对我来说效果很好。谢谢。
【解决方案2】:

这与 John Ledbetter 发布的代码相同。最好参考 /proc/pid/ 目录中名为 stat 的文件,而不是 cmdline,因为前者给出了进程状态和进程名称。 cmdline 文件提供了启动进程的完整参数。所以在某些情况下会失败。无论如何,约翰给出的想法都是好的。在这里,我发布了 John 的修改代码。我在 Linux 中寻找 c 中的代码来检查 dhcp 是否正在运行。使用此代码,我可以做到这一点。我希望它对像我这样的人有用。

#include <sys/types.h>
#include <dirent.h>
#include<unistd.h>

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

pid_t proc_find(const char* name) 
{
    DIR* dir;
    struct dirent* ent;
    char buf[512];

    long  pid;
    char pname[100] = {0,};
    char state;
    FILE *fp=NULL; 

    if (!(dir = opendir("/proc"))) {
        perror("can't open /proc");
        return -1;
    }

    while((ent = readdir(dir)) != NULL) {
        long lpid = atol(ent->d_name);
        if(lpid < 0)
            continue;
        snprintf(buf, sizeof(buf), "/proc/%ld/stat", lpid);
        fp = fopen(buf, "r");

        if (fp) {
            if ( (fscanf(fp, "%ld (%[^)]) %c", &pid, pname, &state)) != 3 ){
                printf("fscanf failed \n");
                fclose(fp);
                closedir(dir);
                return -1; 
            }
            if (!strcmp(pname, name)) {
                fclose(fp);
                closedir(dir);
                return (pid_t)lpid;
            }
            fclose(fp);
        }
    }


closedir(dir);
return -1;
}


int main(int argc, char* argv[]) 
{
    int i;
    if (argc == 1) {
        printf("usage: %s name1 name2 ...\n", argv[0]);
        return 1;
    }

    for( i = 1; i < argc; ++i) {
        pid_t pid = proc_find(argv[i]);
        if (pid == -1) {
            printf("%s: not found\n", argv[i]);
        } else {
            printf("%s: %d\n", argv[i], pid);
        }
    }

    return 0;
}

【讨论】:

  • 在 Android 5.1 上,/proc//stat 中的进程名称字段有一个非常小的字符限制。请注意,某些操作系统可能会遇到类似的问题。
【解决方案3】:

有一些方法可以避免 /proc 的使用(并且可能有充分的理由这样做,例如 /proc 可能根本没有安装,和/或它可能被符号链接到欺骗性的东西,或者 pid 有被隐藏在/proc)。当然,下面的方法看起来不太好,我希望有一个合适的 API!

无论如何,1997 年 Unix programming FAQ 的第 1.9 节说:

使用 kill() 和 0 作为信号编号。此调用有四种可能的结果:

  • kill() 返回 0

    这意味着存在具有给定 PID 的进程,并且 系统将允许您向其发送信号。它是 进程是否可能是僵尸进程取决于系统。

  • kill() 返回 -1,errno == ESRCH

    要么不存在具有给定 PID 的进程,要么不存在安全性 增强功能导致系统否认其存在。 (在 某些系统,该进程可能是僵尸。)

  • kill() 返回 -1,errno == EPERM

    系统不允许你杀死指定的进程。 这意味着任何一个进程都存在(同样,它可能是一个 僵尸)或存在严格的安全增强功能(例如,您的 不允许进程向任何人发送信号。

  • kill() 返回 -1,其他值为 errno

    你有麻烦了!

最常用的技术是用EPERM 假设成功或失败 意味着该过程存在,并且任何其他错误都意味着它 没有。

【讨论】:

  • 重新阅读您的问题后...如果您只想阻止进程运行两次,您可以创建一个具有唯一名称的命名内核对象(例如信号量或共享内存)并在一开始就检查它的存在。它可能比锁定文件更好,因为如果您的进程崩溃,它会自动消失。
  • 但是使用 proc 你将没有磁盘 I/O 访问权限。
  • 并非所有文件都需要访问磁盘。
【解决方案4】:

pidof 通过走过/proc filesystem 来工作。在 C 中,你可以通过枚举 /proc 来做类似的事情;为每个 X 打开 /proc/X/cmdline,其中 X 是一个或多个十进制数字的列表。我不知道您是否有任何可移植性要求,但如果您要依赖 /proc 的可用性,请记住这一点。

这个问题在类 UNIX 系统上更常见的解决方法是包装程序的启动并维护一个 PID 文件。有关此方法的经典示例,请参阅 /etc/init.d/*。您需要小心确保读取或写入 PID 文件的代码以安全的方式(原子地)执行此操作。如果您的目标操作系统具有更强大的 init(例如 systemd),您可以将这项工作外包给那个。

【讨论】:

  • Unix“足够好”但并不优雅的一个典型例子。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-21
  • 1970-01-01
  • 2010-10-02
相关资源
最近更新 更多