列出正在运行的进程的Linux API?

60

我需要一个C/C++ API,可以列出Linux系统上正在运行的进程,并列出每个进程打开的文件。

不想直接读取/proc/文件系统。

有人能想到一种方法吗?


6
lsof 工具可以实现这个功能。它是开源的,阅读代码并查看其工作原理(尽管它必须使用 /proc)。 - 0x6adb015
9个回答

48

http://procps.sourceforge.net/

http://procps.cvs.sourceforge.net/viewvc/procps/procps/proc/readproc.c?view=markup

这是 ps 和其他进程工具的源代码,它们确实使用了 proc(这表明这可能是传统和最佳的方法)。它们的源代码相当易读。其中一个文件是

/procps-3.2.8/proc/readproc.c

可能会有用。ephemient发布了一个有用的建议,即链接到由libproc提供的API,这应该在您的存储库中可用(或已安装,我想说),但您需要使用"-dev"变体来获取标题和其他内容。

祝你好运


2
procps还可以构建一个libproc.a,例如Debian在http://packages.debian.org/libproc-dev中打包。 - ephemient
@ephemient - 链接方面的建议很好 :) - Aiden Bell
1
提供此源文件的链接将非常有用,我知道我可以使用谷歌,但如果我们都应该使用谷歌来查找所有答案,那么这个网站就不需要存在了。 - Petr
2
http://procps.cvs.sourceforge.net/viewvc/procps/procps/proc/readproc.c?view=markup - Petr
两个链接都失效了。 - BЈовић

25
如果您不想从“/proc”中读取。那么您可以考虑编写一个内核模块来实现自己的系统调用。您的系统调用应该被编写成能够获取当前进程列表的形式,如下所示:
/* ProcessList.c 
    Robert Love Chapter 3
    */
    #include < linux/kernel.h >
    #include < linux/sched.h >
    #include < linux/module.h >

    int init_module(void) {
        struct task_struct *task;
        for_each_process(task) {
              printk("%s [%d]\n",task->comm , task->pid);
        }
        return 0;
    }
   
    void cleanup_module(void) {
        printk(KERN_INFO "Cleaning Up.\n");
    }

以上代码摘自我的这篇文章,链接在此 http://linuxgazette.net/133/saha.html。当你拥有自己的系统调用后,便可从用户空间程序中调用它。

14
让你的程序依赖于自定义内核模块比读取/proc/更加麻烦。我不会这样做,除非有非常充分的理由... - aksommerville
应该不需要加锁吗? - Roman Byshko
2
编写新的系统调用通常是一个不好的想法。最糟糕的情况是你可以在没有它的情况下获取正在运行的进程列表。编写新的系统调用应该有非常充分的理由。《Linux内核开发》一书对此进行了简要描述。 - gumik
21
来吧,伙计们,这是很有趣的东西!让我们享受一下生活吧 :-) - Freedom_Ben
@aksommerville 当然。不过,如果你只是为自己编写程序,我认为这是一个非常好的理由。 - flarn2006

9

这是相关的 C/C++ 代码:

你可以在这里找到它: http://ubuntuforums.org/showthread.php?t=657097

基本上,它会循环遍历 /proc/<pid> 中的所有数字文件夹,然后对 /proc/<pid>/exe 进行 readlink 操作,或者如果你想要命令行参数,则使用 cat /proc/<pid>/cmdline 命令。

进程打开的文件描述符在 /proc/<pid>/fd/<descriptor> 中,你可以通过对每个符号链接进行 readlink 操作来获取文件名,例如:readlink /proc/<pid>/fd/<descriptor>。fd 可以是设备(如 /dev/null),套接字或文件,还可能有其他类型。

#include <unistd.h>

ssize_t readlink(const char *path, char *buf, size_t bufsiz);
如果成功,readlink() 返回放入 buf 中的字节数。
如果出错,返回 -1 并设置 errno 来指示错误。

顺便说一下,这与 readproc.c 所做的相同(或者至少曾经相同)。 当然,希望他们在没有缓冲区溢出的情况下完成了这个操作。

#ifndef __cplusplus
    #define _GNU_SOURCE
#endif

#include <unistd.h>
#include <dirent.h>
#include <sys/types.h> // for opendir(), readdir(), closedir()
#include <sys/stat.h> // for stat()

#ifdef __cplusplus
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdarg>
#else
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdarg.h>
#endif


#define PROC_DIRECTORY "/proc/"
#define CASE_SENSITIVE    1
#define CASE_INSENSITIVE  0
#define EXACT_MATCH       1
#define INEXACT_MATCH     0


int IsNumeric(const char* ccharptr_CharacterList)
{
    for ( ; *ccharptr_CharacterList; ccharptr_CharacterList++)
        if (*ccharptr_CharacterList < '0' || *ccharptr_CharacterList > '9')
            return 0; // false
    return 1; // true
}


int strcmp_Wrapper(const char *s1, const char *s2, int intCaseSensitive)
{
    if (intCaseSensitive)
        return !strcmp(s1, s2);
    else
        return !strcasecmp(s1, s2);
}

int strstr_Wrapper(const char* haystack, const char* needle, int intCaseSensitive)
{
    if (intCaseSensitive)
        return (int) strstr(haystack, needle);
    else
        return (int) strcasestr(haystack, needle);
}


#ifdef __cplusplus
pid_t GetPIDbyName(const char* cchrptr_ProcessName, int intCaseSensitiveness, int intExactMatch)
#else
pid_t GetPIDbyName_implements(const char* cchrptr_ProcessName, int intCaseSensitiveness, int intExactMatch)
#endif
{
    char chrarry_CommandLinePath[100]  ;
    char chrarry_NameOfProcess[300]  ;
    char* chrptr_StringToCompare = NULL ;
    pid_t pid_ProcessIdentifier = (pid_t) -1 ;
    struct dirent* de_DirEntity = NULL ;
    DIR* dir_proc = NULL ;

    int (*CompareFunction) (const char*, const char*, int) ;

    if (intExactMatch)
        CompareFunction = &strcmp_Wrapper;
    else
        CompareFunction = &strstr_Wrapper;


    dir_proc = opendir(PROC_DIRECTORY) ;
    if (dir_proc == NULL)
    {
        perror("Couldn't open the " PROC_DIRECTORY " directory") ;
        return (pid_t) -2 ;
    }

    // Loop while not NULL
    while ( (de_DirEntity = readdir(dir_proc)) )
    {
        if (de_DirEntity->d_type == DT_DIR)
        {
            if (IsNumeric(de_DirEntity->d_name))
            {
                strcpy(chrarry_CommandLinePath, PROC_DIRECTORY) ;
                strcat(chrarry_CommandLinePath, de_DirEntity->d_name) ;
                strcat(chrarry_CommandLinePath, "/cmdline") ;
                FILE* fd_CmdLineFile = fopen (chrarry_CommandLinePath, "rt") ;  // open the file for reading text
                if (fd_CmdLineFile)
                {
                    fscanf(fd_CmdLineFile, "%s", chrarry_NameOfProcess) ; // read from /proc/<NR>/cmdline
                    fclose(fd_CmdLineFile);  // close the file prior to exiting the routine

                    if (strrchr(chrarry_NameOfProcess, '/'))
                        chrptr_StringToCompare = strrchr(chrarry_NameOfProcess, '/') +1 ;
                    else
                        chrptr_StringToCompare = chrarry_NameOfProcess ;

                    //printf("Process name: %s\n", chrarry_NameOfProcess);
                    //printf("Pure Process name: %s\n", chrptr_StringToCompare );

                    if ( CompareFunction(chrptr_StringToCompare, cchrptr_ProcessName, intCaseSensitiveness) )
                    {
                        pid_ProcessIdentifier = (pid_t) atoi(de_DirEntity->d_name) ;
                        closedir(dir_proc) ;
                        return pid_ProcessIdentifier ;
                    }
                }
            }
        }
    }
    closedir(dir_proc) ;
    return pid_ProcessIdentifier ;
}

#ifdef __cplusplus
    pid_t GetPIDbyName(const char* cchrptr_ProcessName)
    {
        return GetPIDbyName(cchrptr_ProcessName, CASE_INSENSITIVE, EXACT_MATCH) ;
    }
#else
    // C cannot overload functions - fixed
    pid_t GetPIDbyName_Wrapper(const char* cchrptr_ProcessName, ... )
    {
        int intTempArgument ;
        int intInputArguments[2] ;
        // intInputArguments[0] = 0 ;
        // intInputArguments[1] = 0 ;
        memset(intInputArguments, 0, sizeof(intInputArguments) ) ;
        int intInputIndex ;
        va_list argptr;

        va_start( argptr, cchrptr_ProcessName );
            for (intInputIndex = 0;  (intTempArgument = va_arg( argptr, int )) != 15; ++intInputIndex)
            {
                intInputArguments[intInputIndex] = intTempArgument ;
            }
        va_end( argptr );
        return GetPIDbyName_implements(cchrptr_ProcessName, intInputArguments[0], intInputArguments[1]);
    }

    #define GetPIDbyName(ProcessName,...) GetPIDbyName_Wrapper(ProcessName, ##__VA_ARGS__, (int) 15)

#endif

int main()
{
    pid_t pid = GetPIDbyName("bash") ; // If -1 = not found, if -2 = proc fs access error
    printf("PID %d\n", pid);
    return EXIT_SUCCESS ;
}

1
strstr_Wrapper()中的类型转换看起来很奇怪。使用return (strstr(haystack, needle) != NULL)可能会更好。 - Craig McQueen
基本上,循环遍历/proc/<pid>中的所有数字文件夹,然后执行readlink /proc/<pid>/exe或cat /proc/<pid>/cmdline。 - Stefan Steiger
可能会出现chrarry_NameOfProcess[300]的堆栈溢出。这段代码不太好。 - Craig McQueen
@Thomas:大部分的糟糕体验来自于C语言不支持函数重载。如果你愿意牺牲这一点,那么它就会少很多糟糕之处。另一个糟糕之处是安全性。但是这段代码实际上只是为了在我的机器上编写一个aimbot而已 ;) 如果你不喜欢变量命名,请加入我们的俱乐部,并在你的副本中更改它们。 - Stefan Steiger
@Craig McQueen:是的,但是您需要C++ - 而C++是一种可怕的语言,引用Linus Torvalds ;) http://harmful.cat-v.org/software/c++/linus - Stefan Steiger
显示剩余2条评论

8
如果您不这样做,那么我猜无论您使用什么API都将最终读取/proc文件系统。以下是一些执行此操作的程序示例: 但不幸的是,这并不构成一个API。

7
没错。读取/proc文件系统是Linux内核向用户空间导出进程信息的官方机制。要求不使用它的解决方案基本上就是显示对系统工作原理的无知。这不会带来更好的软件。 - Andy Ross

6

PS和其他工具(除内核模块外)都从/proc读取数据。 /proc是由内核动态创建的特殊文件系统,使得用户空间进程可以读取原本仅可由内核访问的数据。

因此,建议从/proc读取数据。

您可以快速直观地查看/proc文件系统的结构。 对于每个进程,都有一个/proc/pid文件夹,其中pid是进程ID号码。在这个文件夹中,有几个文件包含有关当前进程的不同数据。 如果您运行

strace ps -aux

您将会看到程序ps如何从/proc中读取这些数据。


4

如果不读取/proc,唯一的方法是调用“ps aux”,遍历每行,读取第二列(PID),并使用它调用lsof -p [PID]。

...我建议阅读/proc ;)


2
在运行中的进程上执行操作本质上是不可移植的,因此我认为这在实践中不太可能成为问题。另请注意,GNU ps具有控制输出格式的选项,允许您仅获取所需的数据,而无需解析整个行。 - Andy Ross
ps 读取 /proc,因此与其他答案一样,这并不违反问题的 无正当限制 - Chris Stratton

3

有一个来自procps-ng项目的libprocps库。在Ubuntu 13.04上,如果你执行 strace ps,你可以看到ps使用了libprocps


现在看来这是一个好的方案。一种相当标准化的方法,可以读取/proc/###/*数据,而不需要自己找出如何读取可用的字段。 - Alexis Wilke

1

读取proc并不太难。我无法用C++展示给你,但以下的D代码应该能指引你朝正确的方向:

import std.stdio;
import std.string;
import std.file;
import std.regexp;
import std.c.linux.linux;
alias std.string.split explode;
string srex = "^/proc/[0-9]+$"; string trex = "State:[ \t][SR]"; RegExp rex; RegExp rext;
string[] scanPidDirs(string target) { string[] result;
bool callback(DirEntry* de) { if (de.isdir) { if (rex.find(de.name) >= 0) { string[] a = explode(de.name, "/"); string pid = a[a.length-1]; string x = cast(string) std.file.read(de.name ~ "/status"); int n = rext.find(x); if (n >= 0) { x = cast(string) std.file.read(de.name ~ "/cmdline"); // This is null terminated if (x.length) x.length = x.length-1; a = explode(x, "/"); if (a.length) x = a[a.length-1]; else x = ""; if (x == target) { result ~= pid ~ "/" ~x; } } } } return true; }
listdir("/proc", &callback); return result.dup; }
void main(string[] args) { rex= new RegExp(srex); rext= new RegExp(trex); string[] a = scanPidDirs(args[1]); if (!a.length) { writefln("未找到"); return; } writefln("%d 个匹配进程", a.length); foreach (s; a) { string[] p = explode(s, "/"); int pid = atoi(p[0]); writef("停止 %s (%d)? ", s, pid); string r = readln(); if (r == "Y\n" || r == "y\n") kill(pid, SIGUSR1); } }

-1

通过名称轻松找到任何进程的PID的方法

pid_t GetPIDbyName(char* ps_name)
{

    FILE *fp;
    char *cmd=(char*)calloc(1,200);
    sprintf(cmd,"pidof %s",ps_name);
    fp=popen(cmd,"r");
    fread(cmd,1,200,fp);
    fclose(fp);
    return atoi(cmd);
}

2
这不是Linux API。 - Victor Polevoy
在我的 CentOS7 Linux 系统上,“pidof” 可用,是 rpm “sysvinit-tools”的成员。 - karsten
在Debian Linux 6.0上也找到了它。 - karsten
调用 pidof 并不构成 API。OP 特别要求不使用 pidof 所使用的 procfs。 - Ezequiel Garcia

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