使用 SIGTERM 杀死 child
Killing child with SIGTERM
我有 2 个程序:1) 父亲 2) Child。
当 Father 收到 SIGINT (CTRL-C) 信号时,他的处理程序会向他的 child 发送一个 SIGTERM。问题是它经常(不总是,不知道为什么)在 SIGINT:
之后循环显示这个错误
Invalid Argument
父亲的目标是创建一个 child 然后活着准备好处理 SIGINT。
父亲
#include "library.h"
static void handler();
int main(int argc, char* argv[]){
int value, que_id;
char str_que_id[10], **child_arg;
pid_t child_pid;
sigaction int_sa;
//Create message queue
do{
que_id = msgget(IPC_PRIVATE, ALL_PERM | IPC_CREAT);
}while(que_id == -1);
snprintf(str_que_id, sizeof(str_que_id), "%d", que_id);
//Set arguments for child
child_arg = malloc(sizeof(char*) * 3);
child[0] = "child";
child[1] = str_que_id;
child[2] = NULL;
//Set handler for SIGINT
int_sa.sa_handler = &handler;
int_sa.sa_flags = SA_RESTART;
sigemptyset(&int_sa.sa_mask);
sigaddset(&int_sa.sa_mask, SIGALRM);
sigaction(SIGINT, &int_sa, NULL);
//Fork new child
if(value = fork() == 0){
child_pid = getpid();
do{
errno = 0;
execve("./child", child_arg, NULL);
}while(errno);
}
//Keep alive father
while(1);
return 0;
}
static void handler(){
if(kill(child_pid, SIGTERM) != -1)
waitpid(child_pid, NULL, WNOHANG);
while(msgctl(que_id, IPC_RMID, NULL) == -1);
free(child_arg);
exit(getpid());
}
child 的目标(仅在我的项目中)只是等待来自消息队列的新消息。由于不会有任何消息,所以它会一直被阻止。
Child
#include "library.h"
typedef struct _Msgbuf {
long mtype;
char[10] message;
} Msgbuf;
int main(int argc, char * argv[]){
int que_id;
//Recovery of message queue id
que_id = atoi(argv[1]);
//Set handler for SIGTERM
signal(SIGTERM, handler);
//Dynamic allocation of message
received = calloc(1, sizeof(Msgbuf));
while(1){
do{
errno = 0;
//This will block child because there won't be any message incoming
msgrcv(que_id, received, sizeof(Msgbuf) - sizeof(long), getpid(), 0);
if(errno)
perror(NULL);
}while(errno && errno != EINTR);
}
}
static void handler(){
free(received);
exit(getpid());
}
The calling process catches a signal. In this case the system call fails with errno set to EINTR. (msgrcv() is never automatically restarted after being interrupted by a signal handler, regardless of the setting of the SA_RESTART flag when establishing a signal handler.)
那么为什么它会循环打印那个错误呢?它应该在处理程序中退出,而不是在处理程序返回之后(自 free(received)
起)它没有找到将 errno 设置为 EINVAL 的消息缓冲区。
(几乎)总是 errno
只有当且仅当函数调用失败时, 才具有合理的值。
msgrcv()
就是这种情况。
RETURN VALUE
Upon successful completion, msgrcv()
shall return a value equal to the number of bytes actually placed into the buffer mtext
. Otherwise, no message shall be received, msgrcv()
shall return -1, and errno
shall be set to indicate the error.
所以只使用 errno
如果 msgrcv()
returned -1
, 否则 errno
的值是未定义的并且它很可能包含垃圾或者不...
下面的代码没有意义...
msgrcv(que_id, received, sizeof(Msgbuf) - sizeof(long), getpid(), 0);
if(errno)
perror(NULL);
} while(errno && errno != EINTR);
...应该看起来像:
if (-1 == msgrcv(que_id, received, sizeof(Msgbuf) - sizeof(long), getpid(), 0))
{
/* Only here errno had a well defined value. */
perror("msgrcv() failed"); /* perror() translates errno into a human readable text prefixed by its argument and logs it to the stderr. */
}
else
{
errno = 0;
}
} while (errno && errno != EINTR);
顺便说一句
do{
errno = 0;
execve("./child", child_arg, NULL);
}while(errno);
仅作为 exec*()
函数族的成员工作 仅 return 出错。所以当 while
的条件被测试时 execve()
had 失败了,虽然 errno
had放。这里也是初始的 errnr = 0;
设置是没用的。
你的程序有很多问题。它通过从信号处理程序中调用 exit
、free
和 msgctl
来调用未定义的行为。 The Open Group Base Specifications 的 Signal Actions 部分中的 table 列出了可以从信号处理程序中安全调用的函数。在大多数情况下,您只想从处理程序中切换 "running" 标志并让主循环 运行 直到它被告知退出。类似于以下简单示例:
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/* this will be set when the signal is received */
static sig_atomic_t running = 1;
void
sig_handler(int signo, siginfo_t *si, void *context)
{
running = 0;
}
int
main(int argc, char *argv[])
{
int rc;
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = &sig_handler;
rc = sigaction(SIGINT, &sa, NULL);
if (rc < 0) {
perror("sigaction");
exit(EXIT_FAILURE);
}
printf("Waiting for SIGINT\n");
while (running) {
printf("... sleeping for 10 seconds\n");
sleep(10);
}
printf("Signal received\n");
return 0;
}
我也在 repl.it 上组合了一个更复杂的 session。
另一个问题是您假设 errno
在函数调用中保留零值。这很可能是这种情况,但是关于 errno
您唯一应该假设的是,当库函数 return 是一个失败代码时,它将被分配一个值——例如,read
returns -1
并将 errno
设置为指示错误的内容。调用 C 运行time 库函数的常规方法是检查 return 值并在适当时参考 errno
:
int bytes_read;
unsigned char buf[128];
bytes_read = read(some_fd, &buf[0], sizeof(buf));
if (bytes_read < 0) {
printf("read failed: %s (%d)\n", strerror(errno), errno);
}
您的应用程序可能正在循环,因为 parent 行为不正常并且没有等待 child 或类似的东西(参见上面关于 未定义行为 )。如果消息队列在 child 退出之前被删除,那么 msgrcv
调用将失败并将 errno
设置为 EINVAL
。在 检查errno
之前,您应该检查msgrcv
是否失败。 child 在遇到 msgrcv
失败且 errno
等于 EINVAL
时也应该终止循环,因为这是一个终止条件——匿名消息队列永远不会在它不复存在后重新创建。
我有 2 个程序:1) 父亲 2) Child。 当 Father 收到 SIGINT (CTRL-C) 信号时,他的处理程序会向他的 child 发送一个 SIGTERM。问题是它经常(不总是,不知道为什么)在 SIGINT:
之后循环显示这个错误Invalid Argument
父亲的目标是创建一个 child 然后活着准备好处理 SIGINT。
父亲
#include "library.h"
static void handler();
int main(int argc, char* argv[]){
int value, que_id;
char str_que_id[10], **child_arg;
pid_t child_pid;
sigaction int_sa;
//Create message queue
do{
que_id = msgget(IPC_PRIVATE, ALL_PERM | IPC_CREAT);
}while(que_id == -1);
snprintf(str_que_id, sizeof(str_que_id), "%d", que_id);
//Set arguments for child
child_arg = malloc(sizeof(char*) * 3);
child[0] = "child";
child[1] = str_que_id;
child[2] = NULL;
//Set handler for SIGINT
int_sa.sa_handler = &handler;
int_sa.sa_flags = SA_RESTART;
sigemptyset(&int_sa.sa_mask);
sigaddset(&int_sa.sa_mask, SIGALRM);
sigaction(SIGINT, &int_sa, NULL);
//Fork new child
if(value = fork() == 0){
child_pid = getpid();
do{
errno = 0;
execve("./child", child_arg, NULL);
}while(errno);
}
//Keep alive father
while(1);
return 0;
}
static void handler(){
if(kill(child_pid, SIGTERM) != -1)
waitpid(child_pid, NULL, WNOHANG);
while(msgctl(que_id, IPC_RMID, NULL) == -1);
free(child_arg);
exit(getpid());
}
child 的目标(仅在我的项目中)只是等待来自消息队列的新消息。由于不会有任何消息,所以它会一直被阻止。
Child
#include "library.h"
typedef struct _Msgbuf {
long mtype;
char[10] message;
} Msgbuf;
int main(int argc, char * argv[]){
int que_id;
//Recovery of message queue id
que_id = atoi(argv[1]);
//Set handler for SIGTERM
signal(SIGTERM, handler);
//Dynamic allocation of message
received = calloc(1, sizeof(Msgbuf));
while(1){
do{
errno = 0;
//This will block child because there won't be any message incoming
msgrcv(que_id, received, sizeof(Msgbuf) - sizeof(long), getpid(), 0);
if(errno)
perror(NULL);
}while(errno && errno != EINTR);
}
}
static void handler(){
free(received);
exit(getpid());
}
The calling process catches a signal. In this case the system call fails with errno set to EINTR. (msgrcv() is never automatically restarted after being interrupted by a signal handler, regardless of the setting of the SA_RESTART flag when establishing a signal handler.)
那么为什么它会循环打印那个错误呢?它应该在处理程序中退出,而不是在处理程序返回之后(自 free(received)
起)它没有找到将 errno 设置为 EINVAL 的消息缓冲区。
(几乎)总是 errno
只有当且仅当函数调用失败时, 才具有合理的值。
msgrcv()
就是这种情况。
RETURN VALUE
Upon successful completion,
msgrcv()
shall return a value equal to the number of bytes actually placed into the buffermtext
. Otherwise, no message shall be received,msgrcv()
shall return -1, anderrno
shall be set to indicate the error.
所以只使用 errno
如果 msgrcv()
returned -1
, 否则 errno
的值是未定义的并且它很可能包含垃圾或者不...
下面的代码没有意义...
msgrcv(que_id, received, sizeof(Msgbuf) - sizeof(long), getpid(), 0);
if(errno)
perror(NULL);
} while(errno && errno != EINTR);
...应该看起来像:
if (-1 == msgrcv(que_id, received, sizeof(Msgbuf) - sizeof(long), getpid(), 0))
{
/* Only here errno had a well defined value. */
perror("msgrcv() failed"); /* perror() translates errno into a human readable text prefixed by its argument and logs it to the stderr. */
}
else
{
errno = 0;
}
} while (errno && errno != EINTR);
顺便说一句
do{
errno = 0;
execve("./child", child_arg, NULL);
}while(errno);
仅作为 exec*()
函数族的成员工作 仅 return 出错。所以当 while
的条件被测试时 execve()
had 失败了,虽然 errno
had放。这里也是初始的 errnr = 0;
设置是没用的。
你的程序有很多问题。它通过从信号处理程序中调用 exit
、free
和 msgctl
来调用未定义的行为。 The Open Group Base Specifications 的 Signal Actions 部分中的 table 列出了可以从信号处理程序中安全调用的函数。在大多数情况下,您只想从处理程序中切换 "running" 标志并让主循环 运行 直到它被告知退出。类似于以下简单示例:
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/* this will be set when the signal is received */
static sig_atomic_t running = 1;
void
sig_handler(int signo, siginfo_t *si, void *context)
{
running = 0;
}
int
main(int argc, char *argv[])
{
int rc;
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = &sig_handler;
rc = sigaction(SIGINT, &sa, NULL);
if (rc < 0) {
perror("sigaction");
exit(EXIT_FAILURE);
}
printf("Waiting for SIGINT\n");
while (running) {
printf("... sleeping for 10 seconds\n");
sleep(10);
}
printf("Signal received\n");
return 0;
}
我也在 repl.it 上组合了一个更复杂的 session。
另一个问题是您假设 errno
在函数调用中保留零值。这很可能是这种情况,但是关于 errno
您唯一应该假设的是,当库函数 return 是一个失败代码时,它将被分配一个值——例如,read
returns -1
并将 errno
设置为指示错误的内容。调用 C 运行time 库函数的常规方法是检查 return 值并在适当时参考 errno
:
int bytes_read;
unsigned char buf[128];
bytes_read = read(some_fd, &buf[0], sizeof(buf));
if (bytes_read < 0) {
printf("read failed: %s (%d)\n", strerror(errno), errno);
}
您的应用程序可能正在循环,因为 parent 行为不正常并且没有等待 child 或类似的东西(参见上面关于 未定义行为 )。如果消息队列在 child 退出之前被删除,那么 msgrcv
调用将失败并将 errno
设置为 EINVAL
。在 检查errno
之前,您应该检查msgrcv
是否失败。 child 在遇到 msgrcv
失败且 errno
等于 EINVAL
时也应该终止循环,因为这是一个终止条件——匿名消息队列永远不会在它不复存在后重新创建。