在 Linux 中杀死僵尸进程
Killing zombie processes in Linux
我对 zombie-processes 有疑问。当我从客户端关闭连接时,僵尸 child 并没有死。如果我从服务器端关闭连接,一切正常。没有僵尸child。我正在使用下面的代码
任何帮助??
#define SERV_PORT 1051
#define LISTENQ 1024
void sig_chld(int signo)
{
pid_t pid;
int stat;
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
printf("child %d terminated\n", pid);
return;
}
void *pThread_TCP(void *ptr)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
void sig_chld(int);
unsigned char pData[255];
unsigned short n;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
struct ifreq ifr;
memset(&ifr, 0, sizeof(struct ifreq));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "usb0");
ioctl(listenfd, SIOCGIFINDEX, &ifr);
setsockopt(listenfd, SOL_SOCKET, SO_BINDTODEVICE, (void*)&ifr, sizeof(ifr));
bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
listen(listenfd, LISTENQ);
signal(SIGCHLD, sig_chld); /* must call waitpid() */
for ( ; ; ) {
clilen = sizeof(cliaddr);
if ( (connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)) < 0)
{
if (errno == EINTR)
continue; /* back to for() */
}
if ( (childpid = fork()) == 0)
{ /* child process */
while(1)
{
n = recvfrom(connfd,pData,100,0,(struct sockaddr *)&cliaddr,&clilen);
if(n>0)
{
if(pData[0] == '^') break;
sendto(connfd,"OK\r\n",4,0,(struct sockaddr *)&cliaddr,sizeof(cliaddr));
}
}
close(listenfd); /* close listening socket */
exit(0);
}
close(connfd); /* parent closes connected socket */
}
}
您的意思是 "zombie process"(Z
状态 ps
)?那些已经死了,但他们还没有被他们的 parent 收割。
recvfrom
/sendto
的地址参数在 SOCK_STREAM
套接字上没有用,实际上使用 NULL
/0 以外的值在某些实现上可能会失败。
- TCP 不是 message-based,因此检查
pData[0]
是错误的。发送 "^A\r\n^B\r\n"
的客户端可以合法地接收为 "^"
后跟 "A\r\n^B\r\n"
或 "^A\r\n^"
后跟 "B\r\n"
或任何其他拆分。
- 您在尝试发送
"OK\r\n"
时可能会收到短消息。
- 如果
recvfrom
returns <0
,因为它在尝试从关闭的套接字中读取等错误状态下所做的,您将在 while(1)
中永远循环。这不是僵尸 - 它是一个 运行 进程,虽然一个燃烧 CPU 没有用。
- 在parent进程中,你不处理
SIGCHLD
也不调用wait
/waitpid
/等。这意味着当 children 退出时,它们不会被回收,这将导致实际的僵尸进程。
僵尸进程只消耗进程table中的条目。内核维护它以允许 parent 进程的 wait(2)
系统调用(和系列)知道实际上有一个进程要等待并且不要失败调用 wait()
没有子流程四处走动。那些行尸走肉的进程是为了确保内核数据的一致性,因此,你不能杀死它们(即使是 root 用户)确保活着的 parent 周围没有这群僵尸的唯一方法是为它之前完成的每个 fork()
做一个 wait(2)
调用(你根本不做)。在您的代码中,线程将在关闭文件描述符后立即终止,您有机会在那里执行 waitpid(pid_of_child, ...);
,因此您将等待正确的 child。有关此系统调用的更多信息,请参阅 waitpid(2)
。这种方法将有一个 non-visible 缺点(您的线程将持续到 child 结束)。这与进程一起正常工作的原因(non-need 在 parent 进程中执行 wait()
)是因为你不会死于 parent(parent 活着线程死后)等等, fork()/wait()
关系保持不变。当 parent 死掉时,内核使 init (process with id == 1
) 成为你进程的 parent,而 init(8)
总是 为系统中的孤儿 children 制作 wait(2)
s。
只需在
之后添加以下代码
...
close(connfd); /* parent closes connected socket */
int retcode; /* return code of child process */
waitpid(childpid, &retcode, 0);
} /* for loop */
或者,因为您不打算检查 child 是如何终止的
...
close(connfd); /* parent closes connected socket */
waitpid(childpid, 0, 0);
} /* for loop */
这还有另一个缺点,就是你要等待 child 终止,并且不会在你的 child 终止之前进入 accept(2)
系统调用,这可以不是你想要的。如果你想避免创建 child 僵尸进程,还有另一种选择(但它有一些其他缺点)是在整个进程中忽略 SIGCHLD
信号,这使得内核不会创建那些僵尸(legacy方法,还有其他方法可以避免僵尸 children) 或者你可以有一个新线程来制作所需的 wait()
s 并将返回值从 children 发送到适当的地方,一旦他们死了。
我对 zombie-processes 有疑问。当我从客户端关闭连接时,僵尸 child 并没有死。如果我从服务器端关闭连接,一切正常。没有僵尸child。我正在使用下面的代码
任何帮助??
#define SERV_PORT 1051
#define LISTENQ 1024
void sig_chld(int signo)
{
pid_t pid;
int stat;
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
printf("child %d terminated\n", pid);
return;
}
void *pThread_TCP(void *ptr)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
void sig_chld(int);
unsigned char pData[255];
unsigned short n;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
struct ifreq ifr;
memset(&ifr, 0, sizeof(struct ifreq));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "usb0");
ioctl(listenfd, SIOCGIFINDEX, &ifr);
setsockopt(listenfd, SOL_SOCKET, SO_BINDTODEVICE, (void*)&ifr, sizeof(ifr));
bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
listen(listenfd, LISTENQ);
signal(SIGCHLD, sig_chld); /* must call waitpid() */
for ( ; ; ) {
clilen = sizeof(cliaddr);
if ( (connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)) < 0)
{
if (errno == EINTR)
continue; /* back to for() */
}
if ( (childpid = fork()) == 0)
{ /* child process */
while(1)
{
n = recvfrom(connfd,pData,100,0,(struct sockaddr *)&cliaddr,&clilen);
if(n>0)
{
if(pData[0] == '^') break;
sendto(connfd,"OK\r\n",4,0,(struct sockaddr *)&cliaddr,sizeof(cliaddr));
}
}
close(listenfd); /* close listening socket */
exit(0);
}
close(connfd); /* parent closes connected socket */
}
}
您的意思是 "zombie process"(Z
状态 ps
)?那些已经死了,但他们还没有被他们的 parent 收割。
recvfrom
/sendto
的地址参数在SOCK_STREAM
套接字上没有用,实际上使用NULL
/0 以外的值在某些实现上可能会失败。- TCP 不是 message-based,因此检查
pData[0]
是错误的。发送"^A\r\n^B\r\n"
的客户端可以合法地接收为"^"
后跟"A\r\n^B\r\n"
或"^A\r\n^"
后跟"B\r\n"
或任何其他拆分。 - 您在尝试发送
"OK\r\n"
时可能会收到短消息。 - 如果
recvfrom
returns<0
,因为它在尝试从关闭的套接字中读取等错误状态下所做的,您将在while(1)
中永远循环。这不是僵尸 - 它是一个 运行 进程,虽然一个燃烧 CPU 没有用。 - 在parent进程中,你不处理
SIGCHLD
也不调用wait
/waitpid
/等。这意味着当 children 退出时,它们不会被回收,这将导致实际的僵尸进程。
僵尸进程只消耗进程table中的条目。内核维护它以允许 parent 进程的 wait(2)
系统调用(和系列)知道实际上有一个进程要等待并且不要失败调用 wait()
没有子流程四处走动。那些行尸走肉的进程是为了确保内核数据的一致性,因此,你不能杀死它们(即使是 root 用户)确保活着的 parent 周围没有这群僵尸的唯一方法是为它之前完成的每个 fork()
做一个 wait(2)
调用(你根本不做)。在您的代码中,线程将在关闭文件描述符后立即终止,您有机会在那里执行 waitpid(pid_of_child, ...);
,因此您将等待正确的 child。有关此系统调用的更多信息,请参阅 waitpid(2)
。这种方法将有一个 non-visible 缺点(您的线程将持续到 child 结束)。这与进程一起正常工作的原因(non-need 在 parent 进程中执行 wait()
)是因为你不会死于 parent(parent 活着线程死后)等等, fork()/wait()
关系保持不变。当 parent 死掉时,内核使 init (process with id == 1
) 成为你进程的 parent,而 init(8)
总是 为系统中的孤儿 children 制作 wait(2)
s。
只需在
之后添加以下代码 ...
close(connfd); /* parent closes connected socket */
int retcode; /* return code of child process */
waitpid(childpid, &retcode, 0);
} /* for loop */
或者,因为您不打算检查 child 是如何终止的
...
close(connfd); /* parent closes connected socket */
waitpid(childpid, 0, 0);
} /* for loop */
这还有另一个缺点,就是你要等待 child 终止,并且不会在你的 child 终止之前进入 accept(2)
系统调用,这可以不是你想要的。如果你想避免创建 child 僵尸进程,还有另一种选择(但它有一些其他缺点)是在整个进程中忽略 SIGCHLD
信号,这使得内核不会创建那些僵尸(legacy方法,还有其他方法可以避免僵尸 children) 或者你可以有一个新线程来制作所需的 wait()
s 并将返回值从 children 发送到适当的地方,一旦他们死了。