概述Linux:system()+ SIGCHLD处理+multithreading
我有一个multithreading的应用程序,它为SIGCHLD安装一个处理程序,用于logging和收集subprocess。
我看到的问题开始时,我正在对system()进行调用。 system()需要等待subprocess结束并自己收割,因为它需要退出代码。 这就是为什么它调用sigprocmask()来阻止SIGCHLD。 但是在我的multithreading应用程序中,SIGCHLD仍然在另一个线程中调用,并且在system()有机会这样做之前收到subprocess。
这是POSIX中的一个已知问题吗?
解决这个问题的方法之一就是在所有其他线程中阻塞SIGCHLD,但在我的情况下,这并不真实,因为并不是所有线程都是由我的代码直接创build的。
我还有什么其他的select?
通过命令行恢复已停止的进程
在信号处理程序中使用长数据。
当subprocess运行时更好的方法来中断我的Python代码?
如何在不使用SIGWAIT的情况下阻塞线程中的所有信号?
linux编程接口中的信号处理程序示例
perf_event_open溢出信号
杀戮和孩子的过程
在信号处理程序中接收到信号时会发生什么情况?
从键盘发送SIGINT到bash中的pipe道命令
C使用信号同步进程
是的,这是一个已知(或至少强烈暗示)的问题。
在等待孩子终止的时候阻塞SIGCHLD可以防止应用程序在系统()获得状态之前从系统()的子进程中获取信号并获取状态。 …. 请注意,如果应用程序捕获SIGCHLD信号,它将在成功调用system()调用之前收到这样的信号。
(从system()的文档,强调增加。)
所以,POSIXly你是运气不好,除非你的实施发生队列SIGCHLD。 如果是这样,你当然可以记录你分叉的pID,然后只收获你期望的pID。
linuxly也是,你运气不好,因为signalfd似乎也崩溃了多个SIGCHLDs 。
然而,UNIXly却有许多聪明和太聪明的技巧来管理自己的孩子,而忽略了第三方例程。 继承管道的I / O复用是SIGCHLD捕获的一种替代方法,就像使用一个小型的专用“产卵助手”在独立进程中进行分叉和收割一样。
既然你有线程,你不能控制,我建议你编写一个预加载的库来介入system()调用(也可能是popen()等)与自己的实现。 我也将你的SIGCHLD处理程序包含在库中。
如果你不想通过env LD_PRELOAD=libwhatever.so yourprogram你的程序运行你的程序,你可以添加类似
const char *libs; libs = getenv(\”LD_PRELOAD\”); if (!libs || !*libs) { setenv(\”LD_PRELOAD\”,\”libwhatever.so\”,1); execv(argv[0],argv); _exit(127); }
在你的程序开始时,要让LD_PRELOAD适当的设置来重新执行它自己。 (请注意,如果你的程序是setuID或者setgID,可以考虑一下,详情请参阅man ld.so ,特别是如果libwhatever.so没有安装在系统库目录中,则必须指定一个完整的路径。
一种可能的方法是使用挂起的孩子的无锁数组(使用由C编译器提供的原子内置插件)。 而不是waitpID() ,你的system()实现分配一个条目,把子PID放在那里,然后等待一个信号量让子waitpID()退出,而不是调用waitpID() 。
这是一个示例实现:
#define _GNU_SOURCE #define _POSIX_C_SOURCE 200809L #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/wait.h> #include <fcntl.h> #include <signal.h> #include <semaphore.h> #include <dlfcn.h> #include <errno.h> /* Maximum number of concurrent children waited for. */ #define MAX_CHILDS 256 /* Lockless array of child processes waited for. */ static pID_t child_pID[MAX_CHILDS] = { 0 }; /* 0 is not a valID PID */ static sem_t child_sem[MAX_CHILDS]; static int child_status[MAX_CHILDS]; /* Helper function: allocate a child process. * Returns the index,or -1 if all in use. */ static inline int child_get(const pID_t pID) { int i = MAX_CHILDS; while (i–>0) if (__sync_bool_compare_and_swap(&child_pID[i],(pID_t)0,pID)) { sem_init(&child_sem[i],0); return i; } return -1; } /* Helper function: release a child descriptor. */ static inline voID child_put(const int i) { sem_destroy(&child_sem[i]); __sync_fetch_and_and(&child_pID[i],(pID_t)0); } /* SIGCHLD signal handler. * Note: Both waitpID() and sem_post() are async-signal safe. */ static voID sigchld_handler(int signum __attribute__((unused)),siginfo_t *info __attribute__((unused)),voID *context __attribute__((unused))) { pID_t p; int status,i; while (1) { p = waitpID((pID_t)-1,&status,WNOHANG); if (p == (pID_t)0 || p == (pID_t)-1) break; i = MAX_CHILDS; while (i–>0) if (p == __sync_fetch_and_or(&child_pID[i],(pID_t)0)) { child_status[i] = status; sem_post(&child_sem[i]); break; } /* Log p and status? */ } } /* Helper function: close descriptor,without affecting errno. */ static inline int closefd(const int fd) { int result,saved_errno; if (fd == -1) return EINVAL; saved_errno = errno; do { result = close(fd); } while (result == -1 && errno == EINTR); if (result == -1) result = errno; else result = 0; errno = saved_errno; return result; } /* Helper function: Create a close-on-exec socket pair. */ static int commsocket(int fd[2]) { int result; if (socketpair(AF_UNIX,SOCK_STREAM,fd)) { fd[0] = -1; fd[1] = -1; return errno; } do { result = fcntl(fd[0],F_SETFD,FD_CLOEXEC); } while (result == -1 && errno == EINTR); if (result == -1) { closefd(fd[0]); closefd(fd[1]); return errno; } do { result = fcntl(fd[1],FD_CLOEXEC); } while (result == -1 && errno == EINTR); if (result == -1) { closefd(fd[0]); closefd(fd[1]); return errno; } return 0; } /* New system() implementation. */ int system(const char *command) { pID_t child; int i,status,commfd[2]; ssize_t n; /* Allocate the child process. */ i = child_get((pID_t)-1); if (i < 0) { /* \”fork Failed\” */ errno = EAGAIN; return -1; } /* Create a close-on-exec socket pair. */ if (commsocket(commfd)) { child_put(i); /* \”fork Failed\” */ errno = EAGAIN; return -1; } /* Create the child process. */ child = fork(); if (child == (pID_t)-1) return -1; /* Child process? */ if (!child) { char *args[4] = { \”sh\”,\”-c\”,(char *)command,NulL }; /* If command is NulL,return 7 if sh is available. */ if (!command) args[2] = \”exit 7\”; /* Close parent end of comms socket. */ closefd(commfd[0]); /* Receive one char before continuing. */ do { n = read(commfd[1],1); } while (n == (ssize_t)-1 && errno == EINTR); if (n != 1) { closefd(commfd[1]); _exit(127); } /* We won\’t receive anything else. */ shutdown(commfd[1],SHUT_RD); /* Execute the command. If successful,this closes the comms socket. */ execv(\”/bin/sh\”,args); /* Failed. Return the errno to the parent. */ status = errno; { const char *p = (const char *)&status; const char *const q = (const char *)&status + sizeof status; while (p < q) { n = write(commfd[1],p,(size_t)(q – p)); if (n > (ssize_t)0) p += n; else if (n != (ssize_t)-1) break; else if (errno != EINTR) break; } } /* Explicitly close the socket pair. */ shutdown(commfd[1],SHUT_RDWR); closefd(commfd[1]); _exit(127); } /* Parent process. Close the child end of the comms socket. */ closefd(commfd[1]); /* Update the child PID in the array. */ __sync_bool_compare_and_swap(&child_pID[i],(pID_t)-1,child); /* Let the child proceed,by sending a char via the socket. */ status = 0; do { n = write(commfd[0],1); } while (n == (ssize_t)-1 && errno == EINTR); if (n != 1) { /* Release the child entry. */ child_put(i); closefd(commfd[0]); /* Kill the child. */ kill(child,SIGKILL); /* \”fork Failed\”. */ errno = EAGAIN; return -1; } /* Won\’t send anything else over the comms socket. */ shutdown(commfd[0],SHUT_WR); /* Try reading an int from the comms socket. */ { char *p = (char *)&status; char *const q = (char *)&status + sizeof status; while (p < q) { n = read(commfd[0],(size_t)(q – p)); if (n > (ssize_t)0) p += n; else if (n != (ssize_t)-1) break; else if (errno != EINTR) break; } /* Socket closed with nothing read? */ if (n == (ssize_t)0 && p == (char *)&status) status = 0; else if (p != q) status = EAGAIN; /* Incomplete error code,use EAGAIN. */ /* Close the comms socket. */ shutdown(commfd[0],SHUT_RDWR); closefd(commfd[0]); } /* Wait for the command to complete. */ sem_wait(&child_sem[i]); /* DID the command execution fail? */ if (status) { child_put(i); errno = status; return -1; } /* Command was executed. Return the exit status. */ status = child_status[i]; child_put(i); /* If command is NulL,then the return value is nonzero * iff the exit status was 7. */ if (!command) { if (WIFEXITED(status) && WEXITSTATUS(status) == 7) status = 1; else status = 0; } return status; } /* library initialization. * Sets the sigchld handler,* makes sure pthread library is loaded,and * unsets the LD_PRELOAD environment variable. */ static voID init(voID) __attribute__((constructor)); static voID init(voID) { struct sigaction act; int saved_errno; saved_errno = errno; sigemptyset(&act.sa_mask); act.sa_sigaction = sigchld_handler; act.sa_flags = SA_NOCLDStop | SA_RESTART | SA_SIGINFO; sigaction(SIGCHLD,&act,NulL); (voID)dlopen(\”libpthread.so.0\”,RTLD_Now | RTLD_GLOBAL); unsetenv(\”LD_PRELOAD\”); errno = saved_errno; }
如果你把上面的代码保存为child.c ,你可以使用它编译成libchild.so
gcc -W -Wall -O3 -fpic -fPIC -c child.c -lpthread gcc -W -Wall -O3 -shared -Wl,-soname,libchild.so child.o -ldl -lpthread -o libchild.so
如果你有一个测试程序,可以在不同的线程中调用system() ,你可以使用system()插入(和自动获得的子项)来运行它
env LD_PRELOAD=/path/to/libchild.so test-program
注意,根据那些不在你控制之下的线程,你可能需要插入更多的函数,包括signal() , sigaction() , sigprocmask() , pthread_sigmask()等,以确保那些线程不要改变你的SIGCHLD处理器的配置(在libchild.so库安装之后)。
如果那些失控的线程使用了popen() ,那么你可以用与上面的system()非常相似的代码来处理(和pclose() ),只是分成两部分。
(如果你想知道为什么我的system()代码不想把exec()失败报告给父进程,那是因为我通常使用这个代码的一个变体,把这个命令当作一个字符串数组;这样,该命令没有被找到,或者由于权限不足等原因而无法执行。在这种情况下,该命令始终是/bin/sh 。但是,由于无论如何都需要使用通信套接字来避免在子出口之间进行竞争,在* child_pID [] *数组中最新的PID,我决定离开“额外”的代码。)
对于那些仍在寻找答案的人来说,解决这个问题有一个更简单的方法:
重写SIGCHLD处理程序以使用带有标志WNOHANG | WNowAIT的waitID调用来在收集它们之前检查孩子的PID。 您可以选择检查/ proc / PID / stat(或类似的 *** 作系统界面)的命令名称。
用proc_system()替换system() proc_system() 。
总结
以上是内存溢出为你收集整理的Linux:system()+ SIGCHLD处理+multithreading全部内容,希望文章能够帮你解决Linux:system()+ SIGCHLD处理+multithreading所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
请登录后查看评论内容