以编程方式确定程序是否正在运行

41

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


你可以在程序启动时在已知位置创建一个临时的“锁”文件,然后只需检查文件是否存在(并在关闭时删除它 - 尽管如果程序意外终止,则会出现问题)。 - marnir
@marnir - 这就是为什么你需要让程序将自己的PID写入文件,然后如果该文件存在,你可以检查该PID是否仍处于活动状态,如果是,则检查进程名称是否与你自己的匹配。这并不是100%可靠的,但是异常终止不应该发生得那么频繁。 - George
1
如果“锁文件”包含程序的PID,你几乎可以在所有情况下检测到异常程序终止(是否有一个与PID文件匹配的PID运行的进程)?这不是一个完美的解决方案(有限数量的PIDs,PID回收)。 - jmtd
2
@marnir - 如果你实际上锁定了锁文件,那么锁将在进程退出时释放。程序只是尝试进行非阻塞独占锁定,如果失败,则表示有其他人正在运行。 - unpythonic
1
老实说,https://dev59.com/j2035IYBdhLWcg3wYO11 看起来是一个清晰、简单的解决方案。 - mtk
显示剩余2条评论
4个回答

35

您可以在/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毫秒,为什么会花这么长时间?有没有更好的方法来做这个? - GregM
1
这取决于您正在运行的进程数量。 - To1ne
删除程序名称前面的文件路径怎么样?您能否使用comm而不是cmdline?另外,如何忽略已终止的进程? - gonzobrains
对我来说工作得很好。谢谢。 - Carlos Eduardo Olivieri

18
这与John Ledbetter发布的代码相同。与cmdline不同,最好参考/proc/pid/目录中名为stat的文件,因为前者提供了进程状态和进程名称。cmdline文件提供了启动进程的完整参数。因此,在某些情况下会失败。无论如何,John提出的想法很好。在这里,我发布了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/<PID>/stat中的进程名称字段具有相当小的字符限制。只是提醒一下,一些操作系统可能会遇到类似的问题。 - Allen

17

有方法可以避免使用/proc(这样做可能有很好的理由,例如/proc可能根本没有安装,或者它可能被链接到某个欺骗性的东西中,或者该pid已经隐藏在/proc中)。 值得注意的是,下面的方法看起来并不那么好,我希望有一个适当的API来解决这个问题!

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

使用带有0信号编号的kill()。 这个调用有四种可能的结果:

  • kill() 返回 0

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

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

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

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

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

  • kill() 返回 -1,并带有其他值的 errno

    您有麻烦了!

最常用的技术是假设使用EPERM成功或失败意味着进程存在,而任何其他错误意味着它不存在。

2
重新阅读您的问题后...如果您想要的仅是防止进程运行两次,您可以创建一个具有唯一名称的命名内核对象(例如,信号量或共享内存),并在开头检查其存在。 这可能比锁定文件更好,因为如果进程崩溃,它会自动消失。 - RCL
但是使用proc,您将无法访问磁盘I/O。 - kayle
并非所有文件都需要访问磁盘。 - RCL

3

pidof的工作原理是遍历/proc文件系统。在C语言中,您可以通过枚举/proc并打开每个X的/proc/X/cmdline(其中X是一个或多个十进制数字的列表)来实现类似的功能。如果您要依赖/proc的可用性,请注意是否有任何可移植性要求。

在类Unix系统上,这个问题通常通过包装程序的启动并维护PID文件来解决。查看/etc/init.d/*以获取此方法的经典示例。您需要小心确保读取或写入PID文件的代码以安全的方式(原子地)进行。如果您的目标操作系统具有更强大的init(例如systemd),则可能可以将此工作外包给它。


Unix是“足够好”但几乎不够优雅的典型例子。 - Prof. Falken

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