2011-05-20 57 views
22

我寫了一個簡單的守護進程。 當我運行任何程序時,這個守護進程應該會響應。 如何做到這一點? 在一個大的守護循環:檢測Linux平臺上程序的啓動

while(1) 
{ 
    /* function which catches new programm running */ 
} 

在linux下調用什麼功能,當我運行一個新的程序(創建新的進程)?

+4

我認爲我們將需要更多的信息 – 2011-05-20 16:40:18

+5

@Adam Batkin - 我不同意,這個問題已經足夠陳述了,海報想要在流程創建時被告知 – 2011-05-20 16:42:32

+0

這個守護進程啓動firefox還是由外部啓動的firefox用戶和你想通知?你的問題可以使用一點澄清。 – 2011-05-20 16:44:29

回答

1

我不知道是否存在更好的方法,但可以定期掃描/proc文件系統。例如,/proc/<pid>/exe是進程可執行文件的符號鏈接。

在我的系統(Ubuntu/RedHat)上,/proc/loadavg包含正在運行的進程數(正斜槓後面的數字)以及最近啓動的進程的pid。如果您的守護程序輪詢文件,對這兩個數字中任何一個的任何更改都會告知它何時需要重新掃描/proc以尋找新進程。

這絕不是防彈的,但它是我能想到的最適合的機制。

+0

Thx的信息,那將是值得發佈的答案!/proc/loadavg確實不錯。但是,這些信息如何添加到/ proc/loadavg中? – nub 2011-05-20 17:00:59

+0

@nub:我建議'/ proc/loadavg'作爲通知機制(必須被輪詢)。除此之外,它不會告訴你有什麼改變。要弄清楚發生了什麼變化,你需要掃描'/ proc/[0-9] *'。 – NPE 2011-05-20 17:03:36

+0

是的,但新的PID的信息如何得到?因爲首先獲取這些信息(如何?),然後推入/ proc/loadavg。 – nub 2011-05-20 17:10:08

0

您既可以掃描操作系統以查找符合條件的程序,也可以等待程序向守護程序報告自己。您選擇哪種技術在很大程度上取決於您對非守護程序的控制程度。

掃描可以通過內核系統調用,或通過讀取用戶空間廣告的內核細節(與/ proc文件系統一樣)來完成。請注意,掃描並不能保證您能找到任何特定的程序,就好像程序設法在掃描週期之間啓動和終止一樣,它也不會被檢測到。

更復雜的過程檢測技術是可能的,但它們也需要更復雜的實現。因爲你所做的每件事情都不獨立於你正在監控的系統,所以在你開始尋找異乎尋常的解決方案(插入內核驅動程序等)之前,知道真正需要什麼是非常重要的。你實際上是通過觀察環境來改變環境的,而一些觀察環境的方法可能會不適當地改變環境。

18

我有興趣試圖弄清楚如何在沒有輪詢的情況下做到這一點。 inotify()似乎不適用於/ proc,所以這個想法就沒有了。

但是,任何動態鏈接的程序都將在啓動時訪問某些文件,例如動態鏈接程序。這將是無用的,安全的目的,因爲它不會在靜態鏈接程序觸發,但仍可能會感興趣:

#include <stdio.h> 
#include <sys/inotify.h> 
#include <assert.h> 
int main(int argc, char **argv) { 
    char buf[256]; 
    struct inotify_event *event; 
    int fd, wd; 
    fd=inotify_init(); 
    assert(fd > -1); 
    assert((wd=inotify_add_watch(fd, "/lib/ld-linux.so.2", IN_OPEN)) > 0); 
    printf("Watching for events, wd is %x\n", wd); 
    while (read(fd, buf, sizeof(buf))) { 
     event = (void *) buf; 
     printf("watch %d mask %x name(len %d)=\"%s\"\n", 
     event->wd, event->mask, event->len, event->name); 
    } 
    inotify_rm_watch(fd, wd); 
    return 0; 
} 

這種打印出不包含任何有趣的信息的事件 - 的的PID觸發過程似乎不是由inotify提供的。但它可以用來喚醒並觸發/ proc重新掃描也可以意識到,在這個事情喚醒並完成掃描/ proc之前,短命的程序可能會再次消失 - 大概你會知道它們已經存在,但不能夠了解他們是什麼。當然,任何人都可以繼續打開和關閉一個fd到dyanmic鏈接器,以淹沒你的聲音。

+1

可愛的解決方案。 +1 – 0xC0000022L 2011-05-20 17:54:00

56

對於Linux,內核中似乎有一個接口。在研究這個問題的同時,我遇到了使用CONFIG_CONNECTOR和CONFIG_PROC_EVENTS內核配置來處理進程死亡事件的人。

一些更多的谷歌,我發現這一點:

http://netsplit.com/2011/02/09/the-proc-connector-and-socket-filters/

的PROC連接器和插座過濾 斯科特

的PROC連接器是那些有趣的一個發佈於2011年2月9日,內核功能大多數人很少遇到,甚至更少發現文檔。同樣的插座過濾器。這是一個恥辱,因爲它們都是非常有用的接口,如果它們有更好的文檔記錄,它們可以用於各種目的。

proc連接器允許您接收進程事件的通知,例如fork和exec調用,以及對進程的uid,gid或sid(會話標識)的更改。這些通過基於套接字的接口提供了通過讀取在內核標題中定義的結構proc_event實例....

感興趣的標題是:

#include <linux/cn_proc.h> 

我發現示例代碼在這裏:

http://bewareofgeek.livejournal.com/2945.html

/* This file is licensed under the GPL v2 (http://www.gnu.org/licenses/gpl2.txt) (some parts was originally borrowed from proc events example) 

pmon.c 

code highlighted with GNU source-highlight 3.1 
*/ 

#include <sys/socket.h> 
#include <linux/netlink.h> 
#include <linux/connector.h> 
#include <linux/cn_proc.h> 
#include <signal.h> 
#include <errno.h> 
#include <stdbool.h> 
#include <unistd.h> 
#include <string.h> 
#include <stdlib.h> 
#include <stdio.h> 

/* 
* connect to netlink 
* returns netlink socket, or -1 on error 
*/ 
static int nl_connect() 
{ 
int rc; 
int nl_sock; 
struct sockaddr_nl sa_nl; 

nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR); 
if (nl_sock == -1) { 
    perror("socket"); 
    return -1; 
} 

sa_nl.nl_family = AF_NETLINK; 
sa_nl.nl_groups = CN_IDX_PROC; 
sa_nl.nl_pid = getpid(); 

rc = bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl)); 
if (rc == -1) { 
    perror("bind"); 
    close(nl_sock); 
    return -1; 
} 

return nl_sock; 
} 

/* 
* subscribe on proc events (process notifications) 
*/ 
static int set_proc_ev_listen(int nl_sock, bool enable) 
{ 
int rc; 
struct __attribute__ ((aligned(NLMSG_ALIGNTO))) { 
    struct nlmsghdr nl_hdr; 
    struct __attribute__ ((__packed__)) { 
    struct cn_msg cn_msg; 
    enum proc_cn_mcast_op cn_mcast; 
    }; 
} nlcn_msg; 

memset(&nlcn_msg, 0, sizeof(nlcn_msg)); 
nlcn_msg.nl_hdr.nlmsg_len = sizeof(nlcn_msg); 
nlcn_msg.nl_hdr.nlmsg_pid = getpid(); 
nlcn_msg.nl_hdr.nlmsg_type = NLMSG_DONE; 

nlcn_msg.cn_msg.id.idx = CN_IDX_PROC; 
nlcn_msg.cn_msg.id.val = CN_VAL_PROC; 
nlcn_msg.cn_msg.len = sizeof(enum proc_cn_mcast_op); 

nlcn_msg.cn_mcast = enable ? PROC_CN_MCAST_LISTEN : PROC_CN_MCAST_IGNORE; 

rc = send(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0); 
if (rc == -1) { 
    perror("netlink send"); 
    return -1; 
} 

return 0; 
} 

/* 
* handle a single process event 
*/ 
static volatile bool need_exit = false; 
static int handle_proc_ev(int nl_sock) 
{ 
int rc; 
struct __attribute__ ((aligned(NLMSG_ALIGNTO))) { 
    struct nlmsghdr nl_hdr; 
    struct __attribute__ ((__packed__)) { 
    struct cn_msg cn_msg; 
    struct proc_event proc_ev; 
    }; 
} nlcn_msg; 

while (!need_exit) { 
    rc = recv(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0); 
    if (rc == 0) { 
    /* shutdown? */ 
    return 0; 
    } else if (rc == -1) { 
    if (errno == EINTR) continue; 
    perror("netlink recv"); 
    return -1; 
    } 
    switch (nlcn_msg.proc_ev.what) { 
    case PROC_EVENT_NONE: 
     printf("set mcast listen ok\n"); 
     break; 
    case PROC_EVENT_FORK: 
     printf("fork: parent tid=%d pid=%d -> child tid=%d pid=%d\n", 
      nlcn_msg.proc_ev.event_data.fork.parent_pid, 
      nlcn_msg.proc_ev.event_data.fork.parent_tgid, 
      nlcn_msg.proc_ev.event_data.fork.child_pid, 
      nlcn_msg.proc_ev.event_data.fork.child_tgid); 
     break; 
    case PROC_EVENT_EXEC: 
     printf("exec: tid=%d pid=%d\n", 
      nlcn_msg.proc_ev.event_data.exec.process_pid, 
      nlcn_msg.proc_ev.event_data.exec.process_tgid); 
     break; 
    case PROC_EVENT_UID: 
     printf("uid change: tid=%d pid=%d from %d to %d\n", 
      nlcn_msg.proc_ev.event_data.id.process_pid, 
      nlcn_msg.proc_ev.event_data.id.process_tgid, 
      nlcn_msg.proc_ev.event_data.id.r.ruid, 
      nlcn_msg.proc_ev.event_data.id.e.euid); 
     break; 
    case PROC_EVENT_GID: 
     printf("gid change: tid=%d pid=%d from %d to %d\n", 
      nlcn_msg.proc_ev.event_data.id.process_pid, 
      nlcn_msg.proc_ev.event_data.id.process_tgid, 
      nlcn_msg.proc_ev.event_data.id.r.rgid, 
      nlcn_msg.proc_ev.event_data.id.e.egid); 
     break; 
    case PROC_EVENT_EXIT: 
     printf("exit: tid=%d pid=%d exit_code=%d\n", 
      nlcn_msg.proc_ev.event_data.exit.process_pid, 
      nlcn_msg.proc_ev.event_data.exit.process_tgid, 
      nlcn_msg.proc_ev.event_data.exit.exit_code); 
     break; 
    default: 
     printf("unhandled proc event\n"); 
     break; 
    } 
} 

return 0; 
} 

static void on_sigint(int unused) 
{ 
need_exit = true; 
} 

int main(int argc, const char *argv[]) 
{ 
int nl_sock; 
int rc = EXIT_SUCCESS; 

signal(SIGINT, &on_sigint); 
siginterrupt(SIGINT, true); 

nl_sock = nl_connect(); 
if (nl_sock == -1) 
    exit(EXIT_FAILURE); 

rc = set_proc_ev_listen(nl_sock, true); 
if (rc == -1) { 
    rc = EXIT_FAILURE; 
    goto out; 
} 

rc = handle_proc_ev(nl_sock); 
if (rc == -1) { 
    rc = EXIT_FAILURE; 
    goto out; 
} 

    set_proc_ev_listen(nl_sock, false); 

out: 
close(nl_sock); 
exit(rc); 
} 

我發現這個代碼,需要以root身份得到通知符運行ications。

+0

Splendid one - 幫助爲http://superuser.com/questions/354150/linux-service-to-set-proc-pid-oom-score-adj-of-new-processes ;-)創建解決方案 – 2013-10-17 15:22:43

+0

這很漂亮,開始認爲我將不得不深入內核代碼...... – 2016-01-19 17:20:11

10

看看Sebastian Krahmer的this little program它確實以資源高效的方式和非常簡單的代碼提出了問題。

它確實需要您的內核啓用CONFIG_PROC_EVENTS,例如最新的Amazon Linux Image(2012.09)就不是這種情況。

更新:繼request to Amazon亞馬遜Linux映像內核現在支持PROC_EVENTS

9

使用forkstat,它是PROC事件最完整的客戶端:

sudo forkstat -e exec,comm,core 

封裝在Ubuntu,Debian和AUR中。


在此之前,有cn_proc

bzr branch lp:~kees/+junk/cn_proc 

的Makefile文件需要一個小的變化(LDLIBS而不是LDFLAGS)。

cn_proc和exec-notify.c(其中Arnaud發佈)共享一個共同的祖先; cn_proc可處理更多事件並具有更清晰的輸出,但當進程快速退出時不會恢復。


噢,找到另一個exec-notify的分支,extrace。 這一個將子進程縮回到其父項下方(使用pid_depth啓發式)。

3

您選擇的搜索機器的關鍵字是「process event connector」。

我發現了兩個利用它們的工具,exec-notifycn_proc

我更喜歡後者,但都做得很好。