Zombies stacking & Shell 不会在 shell 实现中的管道命令后打印
Zombies stacking & Shell won't print after pipe command in a shell implementation
我正在 OS 上一门课程,对 linux 完全陌生。
我应该实现一个支持以下功能的 shell 程序:
运行 foreground/background 个进程,仅管道 2 个命令。
假设有一个主函数 运行 处于无限循环中并解析输入命令以正确生成 {char** arglist} - 我将实现一个函数 process_arglist
来执行命令。
我遇到了一些无法解决来源的问题。
首先,在执行单个管道命令后,例如ls -l | less
,任何进一步的命令都不会打印。
其次,当我在后台运行 运行 多个进程并开始使用 ps, kill
命令来查看僵尸进程行为时 - 我发现进程列表中堆放着僵尸进程。
希望早日杜绝丧尸
我做错了什么?
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#define PRINT_ERROR fprintf(stderr, "%s\n", strerror(errno)) /*prints error message to stderr according to current errno*/
int wait_confirm = 1; /* should parent wait for child or not */
int pipe_confirm = 0; /* does the command contain pipe symbol */
int pipe_index; /* which index in arglist is the pipe symbol */
void do_pipe(char** arglist, int pipe_index){
int fd[2];
int exit_code;
pid_t writer_pid, reader_pid;
arglist[pipe_index] = NULL;
if(pipe(fd)<0){
PRINT_ERROR;
exit(1);
}
if ((writer_pid = fork()) < 0){
PRINT_ERROR;
exit(1);
}
else if (writer_pid == 0){ /* Lefthand-Side of pipe enters here */
dup2(fd[1], STDOUT_FILENO);
close(fd[0]);
close(fd[1]);
execvp(arglist[0], arglist);
PRINT_ERROR; /* we get here if an error occured on execvp */
exit(1);
}
else { /* parent enters here */
if ((reader_pid = fork()) < 0){
PRINT_ERROR;
exit(1);
}
else if (reader_pid == 0){ /*Righthand-side of pipe enters here */
dup2(fd[0], STDIN_FILENO);
close(fd[1]);
close(fd[0]);
arglist += pipe_index+1; /* arglist points to the command on the right of '|' */
execvp(arglist[0], arglist);
PRINT_ERROR;
exit(1);
}
else{ /*parent enters here*/
close(fd[0]);
close(fd[1]);
waitpid(reader_pid, &exit_code, 0); /* wait for second command to complete */
}
}
}
void prevent_zombies(int signum){
wait(NULL);
}
// arglist - a list of char* arguments (words) provided by the user
// it contains count+1 items, where the last item (arglist[count]) and *only* the last is NULL
// RETURNS - 1 if should continue, 0 otherwise
int process_arglist(int count, char** arglist){
int i=0;
int exit_code;
/* Set arglist appropriately so we can pass it to execvp without '&', if exists. */
while(arglist[i]!=NULL){
if (strcmp(arglist[i], "&")==0){
wait_confirm = 0;
arglist[i] = NULL;
}
else if (strcmp(arglist[i], "|") == 0){
pipe_confirm = 1;
pipe_index = i;
}
i++;
}
if(pipe_confirm){ /* command is indeed a pipe command */
do_pipe(arglist, pipe_index);
}
else {
int pid = fork();
if (pid==0){ /* child enters here */
execvp(arglist[0], arglist);
PRINT_ERROR;
}
else{ /*parent enters here*/
if(wait_confirm){
wait(&exit_code);
}
else{
signal(SIGCHLD, prevent_zombies);
}
}
}
return 1;
}
鉴于您尚未提供 MCVE (Minimal, Complete, Verifiable Example)
(或 MRE 或 SO 现在使用的任何名称)
或一个
SSCCE (Short, Self-Contained, Correct Example), I've had to create a main()
function to run my adaptation of your code. It also makes minimal use of the error reporting code available in my SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c
and stderr.h
in the src/libsoq sub-directory.
#define _GNU_SOURCE
#include "stderr.h"
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define EXEC_ERROR(cmd) fprintf(stderr, "failed to execute %s (%d: %s)\n", cmd, errno, strerror(errno))
#define PRINT_ERROR(func) fprintf(stderr, "function %s() failed (%d: %s)\n", func, errno, strerror(errno))
static int wait_confirm = 1;
static int pipe_confirm = 0;
static int pipe_index;
static_assert(sizeof(pid_t) == sizeof(int), "sizeof(pid_t) != sizeof(int) - redo format strings");
static void print_command(int cmdnum, char **argv)
{
fprintf(stderr, "%d: command %d:", getpid(), cmdnum + 1);
while (*argv != NULL)
fprintf(stderr, " %s", *argv++);
putc('\n', stderr);
}
static void do_pipe(char **arglist, int pipe_index)
{
int fd[2];
pid_t writer_pid, reader_pid;
arglist[pipe_index] = NULL;
if (pipe(fd) < 0)
{
PRINT_ERROR("pipe");
exit(1);
}
if ((writer_pid = fork()) < 0)
{
PRINT_ERROR("fork - writer");
exit(1);
}
else if (writer_pid == 0)
{
dup2(fd[1], STDOUT_FILENO);
close(fd[0]);
close(fd[1]);
execvp(arglist[0], arglist);
EXEC_ERROR(arglist[0]);
exit(1);
}
else if ((reader_pid = fork()) < 0)
{
PRINT_ERROR("fork - reader");
exit(1);
}
else if (reader_pid == 0)
{
dup2(fd[0], STDIN_FILENO);
close(fd[1]);
close(fd[0]);
arglist += pipe_index + 1;
execvp(arglist[0], arglist);
EXEC_ERROR(arglist[0]);
exit(1);
}
else
{
close(fd[0]);
close(fd[1]);
fprintf(stderr, "%d: writer %d, reader %d\n", getpid(), writer_pid, reader_pid);
int status = 0xFFFF;
int corpse = waitpid(reader_pid, &status, 0);
fprintf(stderr, "%d: reader %d exited with status 0x%.4X\n", getpid(), corpse, status);
}
}
static void dec_str(char *target, int value, int width)
{
char *pos = target + width;
*--pos = value % 10 + '0';
value /= 10;
while (pos > target && value > 0)
{
*--pos = value % 10 + '0';
value /= 10;
}
while (pos > target)
*--pos = ' ';
}
static void hex_str(char *target, int value, int width)
{
char *pos = target + width;
while (pos > target)
{
*--pos = "0123456789ABCDEF"[value % 16];
value /= 16;
}
}
static void prevent_zombies(int signum)
{
int status = 0xFFFF;
int corpse = wait(&status);
char message[] = "XXXXX: PID XXXXX exited with status 0xXXXX (signal XX)\n";
dec_str(&message[ 0], getpid(), 5);
dec_str(&message[11], corpse, 5);
hex_str(&message[38], status, 4);
dec_str(&message[51], signum, 2);
write(2, message, strlen(message));
signal(signum, SIG_DFL);
}
static void process_arglist(char **arglist)
{
wait_confirm = 1;
pipe_confirm = 0;
for (int i = 0; arglist[i] != NULL; i++)
{
if (strcmp(arglist[i], "&") == 0)
{
wait_confirm = 0;
arglist[i] = NULL;
}
else if (strcmp(arglist[i], "|") == 0)
{
pipe_confirm = 1;
pipe_index = i;
}
}
int pid;
if (pipe_confirm)
{
do_pipe(arglist, pipe_index);
}
else if ((pid = fork()) < 0)
{
PRINT_ERROR("fork");
}
else if (pid == 0)
{
execvp(arglist[0], arglist);
EXEC_ERROR(arglist[0]);
exit(1);
}
else if (wait_confirm)
{
fprintf(stderr, "%d: child %d (%s) launched\n", getpid(), pid, arglist[0]);
int status = 0xFFFF;
int corpse = waitpid(pid, &status, 0);
fprintf(stderr, "%d: child %d (%s) exited with status 0x%.4X\n", getpid(), corpse, arglist[0], status);
}
else
{
fprintf(stderr, "%d: child %d running in background\n", getpid(), pid);
signal(SIGCHLD, prevent_zombies);
}
}
static void collect_zombies(void)
{
int status;
int corpse;
while ((corpse = waitpid(-1, &status, WNOHANG)) > 0)
fprintf(stderr, "%d: zombie %d exited with status 0x%.4X\n", getpid(), corpse, status);
}
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
int no_zombies = 0;
/* Can't reuse commands because process_arglist() modifies them */
char *tty = ttyname(0);
size_t srclen = strlen(err_getarg0()) + sizeof(".c");
char *source = malloc(srclen);
if (source == NULL)
err_syserr("failed to allocate %zu bytes of memory: ", srclen);
strcpy(source, err_getarg0());
strcat(source, ".c");
char *cmd1[] = { "ls", "-l", source, "|", "cat", NULL };
char *cmd2[] = { "ps", "-ft", tty, NULL };
char *cmd3[] = { "ls", "-l", source, "|", "cat", NULL };
char *cmd4[] = { "ps", "-ft", tty, "&", NULL };
char *cmd5[] = { "sleep", "5", NULL };
char *cmd6[] = { "ls", "-l", source, "|", "cat", NULL };
char *cmd7[] = { "ps", "-ft", tty, NULL };
char **cmds[] = { cmd1, cmd2, cmd3, cmd4, cmd5, cmd6, cmd7, NULL };
if (argc != 1)
no_zombies = 1;
fprintf(stderr, "%d: %s collecting zombies\n", getpid(), (no_zombies ? "is" : "is not"));
for (int i = 0; cmds[i] != NULL; i++)
{
print_command(i, cmds[i]);
process_arglist(cmds[i]);
if (no_zombies)
collect_zombies();
}
return 0;
}
我改进了打印信息量 — 在调试 shells 或 multi-process 进程时,大量使用打印并确保在大多数输出行中包含当前 PID。我拆分了 PRINT_ERROR
的用途,我更喜欢(现在发现它更方便使用)function-like 宏,而不是 object-like 宏,后者确实有效。
print_command
函数打印一系列参数 — 以确保我看到的是正确的命令。
一件好事是您确保关闭了足够多的文件描述符。
除了报告创建了哪些进程以及第二个进程的状态之外,do_pipe()
函数的细节大部分没有变化。
函数 dec_str()
和 hex_str()
是 signal-safe 并且从信号处理程序 (prevent_zombies()
) 中使用来报告在没有违反规则的情况下死亡的进程 POSIX 指定哪些函数可以在信号处理程序中调用。 dec_str()
函数右对齐并空白填充一个数字; hex_str()
函数右对齐但用零填充数字。 prevent_zombies()
函数报告捕获的信号和死亡的 child 及其状态。它还将 SIGCHLD 的信号处理重置为默认值。这在这里工作正常。你应该在没有那个的情况下进行试验;它改变了行为。
process_arglist()
中的一个关键点是它将全局变量 wait_confirm
和 pipe_confirm
重置为默认值。这些变量实际上不应该是全局的(我把它们设为静态的;这里只有一个源文件,所以除了 main()
之外什么都不需要在它之外访问)。分析代码更改为 for
循环以定位 i
.
可以调用collect_zombies()
函数来收集任何僵尸进程;它使用 WNOHANG
选项,这样它就不会等待进程结束——它 returns 如果没有进程已经结束。
main()
函数会做一些设置工作。 err_setarg0()
函数记录了进程名称——它将被检测到 malloc()
失败后调用的 err_syserr()
使用。该代码假定您 运行 的程序是从源文件名派生的。我的版本在 sh47.c
和 sh97.c
中,所以 err_getarg0()
报告 sh47
或 sh97
;这用于创建 sh47.c
或 sh97.c
.
因为process_arglist()
修改了传递给它的参数列表中的指针,命令不能被重用。如果参数是单独分配的,process_arglist()
也会泄漏内存。另外,因为很多 windows 中有很多进程,我确保 ps
命令只报告当前 window 中的进程,方法是使用 ttyname()
获取终端名称.同样,我只列出了程序的源代码 — 而不是目录中的 200 多个其他文件。
然后程序遍历命令列表,打印并执行每个命令。根据是否有任何命令行参数,它可能会收集任何僵尸。这是非常原始的参数处理; 'real' shell 将使用 getopt()
或类似的函数来控制参数处理。
示例运行 — 不收集僵尸
21013: is not collecting zombies
21013: command 1: ls -l sh97.c | cat
21013: writer 21014, reader 21015
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21013: reader 21015 exited with status 0x0000
21013: command 2: ps -ft /dev/ttys000
21013: child 21016 (ps) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21013 822 0 1:14PM ttys000 0:00.01 sh97
501 21014 21013 0 1:14PM ttys000 0:00.00 (ls)
0 21016 21013 0 1:14PM ttys000 0:00.00 ps -ft /dev/ttys000
21013: child 21016 (ps) exited with status 0x0000
21013: command 3: ls -l sh97.c | cat
21013: writer 21017, reader 21018
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21013: reader 21018 exited with status 0x0000
21013: command 4: ps -ft /dev/ttys000 &
21013: child 21019 running in background
21013: command 5: sleep 5
21013: child 21020 (sleep) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21013 822 0 1:14PM ttys000 0:00.01 sh97
501 21014 21013 0 1:14PM ttys000 0:00.00 (ls)
501 21017 21013 0 1:14PM ttys000 0:00.00 (ls)
0 21019 21013 0 1:14PM ttys000 0:00.00 ps -ft /dev/ttys000
501 21020 21013 0 1:14PM ttys000 0:00.00 sleep 5
21013: PID 21019 exited with status 0x0000 (signal 20)
21013: child 21020 (sleep) exited with status 0x0000
21013: command 6: ls -l sh97.c | cat
21013: writer 21021, reader 21022
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21013: reader 21022 exited with status 0x0000
21013: command 7: ps -ft /dev/ttys000
21013: child 21023 (ps) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21013 822 0 1:14PM ttys000 0:00.01 sh97
501 21014 21013 0 1:14PM ttys000 0:00.00 (ls)
501 21017 21013 0 1:14PM ttys000 0:00.00 (ls)
501 21021 21013 0 1:14PM ttys000 0:00.00 (ls)
0 21023 21013 0 1:14PM ttys000 0:00.00 ps -ft /dev/ttys000
21013: child 21023 (ps) exited with status 0x0000
示例运行 - 收集僵尸
21033: is collecting zombies
21033: command 1: ls -l sh97.c | cat
21033: writer 21034, reader 21035
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21033: reader 21035 exited with status 0x0000
21033: zombie 21034 exited with status 0x0000
21033: command 2: ps -ft /dev/ttys000
21033: child 21036 (ps) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21033 822 0 1:17PM ttys000 0:00.01 sh97 1
0 21036 21033 0 1:17PM ttys000 0:00.00 ps -ft /dev/ttys000
21033: child 21036 (ps) exited with status 0x0000
21033: command 3: ls -l sh97.c | cat
21033: writer 21037, reader 21038
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21033: reader 21038 exited with status 0x0000
21033: zombie 21037 exited with status 0x0000
21033: command 4: ps -ft /dev/ttys000 &
21033: child 21039 running in background
21033: command 5: sleep 5
21033: child 21040 (sleep) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21033 822 0 1:17PM ttys000 0:00.01 sh97 1
0 21039 21033 0 1:17PM ttys000 0:00.00 ps -ft /dev/ttys000
501 21040 21033 0 1:17PM ttys000 0:00.00 sleep 5
21033: PID 21039 exited with status 0x0000 (signal 20)
21033: child 21040 (sleep) exited with status 0x0000
21033: command 6: ls -l sh97.c | cat
21033: writer 21041, reader 21042
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21033: reader 21042 exited with status 0x0000
21033: zombie 21041 exited with status 0x0000
21033: command 7: ps -ft /dev/ttys000
21033: child 21043 (ps) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21033 822 0 1:17PM ttys000 0:00.01 sh97 1
0 21043 21033 0 1:17PM ttys000 0:00.00 ps -ft /dev/ttys000
21033: child 21043 (ps) exited with status 0x0000
比较
在第一个例子中,你可以看到僵尸(ls)
;第二,没有这样的过程。 sleep 5
命令在那里,因此背景 ps
的输出不会与后续的 ls
和 ps
混合——尝试删除 sleep
并查看会发生什么。
如您所见,运行执行一系列命令没有问题,也没有丢失输出的迹象。我不确定您的版本出了什么问题,但是您的大部分代码都没有提供,所以问题很可能不在您所展示的内容中。
有很多可能性。话虽如此,我认为您的问题的一个因素是未重置的全局变量。另一个可能是您没有在后台进程完成后重置处理。请注意,如果您的后台进程没有按照您启动它们的顺序整齐地完成(例如,一系列不同持续时间的 sleep
命令),那么后台处理将不会按预期工作。
确实值得如此彻底地监控您的代码,至少在您有理由相信它可以正常工作之前(并且值得保持监控代码可用,以便在您 运行 改成问题)。您可以查看 #define
macro for debug printing in C,了解如何在 compile-time 和 run-time 处配置调试代码。如果这样做,您可能需要一些适当的参数处理来控制调试(想想 sh -x
类固醇)。
我正在 OS 上一门课程,对 linux 完全陌生。 我应该实现一个支持以下功能的 shell 程序: 运行 foreground/background 个进程,仅管道 2 个命令。
假设有一个主函数 运行 处于无限循环中并解析输入命令以正确生成 {char** arglist} - 我将实现一个函数 process_arglist
来执行命令。
我遇到了一些无法解决来源的问题。
首先,在执行单个管道命令后,例如ls -l | less
,任何进一步的命令都不会打印。
其次,当我在后台运行 运行 多个进程并开始使用 ps, kill
命令来查看僵尸进程行为时 - 我发现进程列表中堆放着僵尸进程。
希望早日杜绝丧尸
我做错了什么?
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#define PRINT_ERROR fprintf(stderr, "%s\n", strerror(errno)) /*prints error message to stderr according to current errno*/
int wait_confirm = 1; /* should parent wait for child or not */
int pipe_confirm = 0; /* does the command contain pipe symbol */
int pipe_index; /* which index in arglist is the pipe symbol */
void do_pipe(char** arglist, int pipe_index){
int fd[2];
int exit_code;
pid_t writer_pid, reader_pid;
arglist[pipe_index] = NULL;
if(pipe(fd)<0){
PRINT_ERROR;
exit(1);
}
if ((writer_pid = fork()) < 0){
PRINT_ERROR;
exit(1);
}
else if (writer_pid == 0){ /* Lefthand-Side of pipe enters here */
dup2(fd[1], STDOUT_FILENO);
close(fd[0]);
close(fd[1]);
execvp(arglist[0], arglist);
PRINT_ERROR; /* we get here if an error occured on execvp */
exit(1);
}
else { /* parent enters here */
if ((reader_pid = fork()) < 0){
PRINT_ERROR;
exit(1);
}
else if (reader_pid == 0){ /*Righthand-side of pipe enters here */
dup2(fd[0], STDIN_FILENO);
close(fd[1]);
close(fd[0]);
arglist += pipe_index+1; /* arglist points to the command on the right of '|' */
execvp(arglist[0], arglist);
PRINT_ERROR;
exit(1);
}
else{ /*parent enters here*/
close(fd[0]);
close(fd[1]);
waitpid(reader_pid, &exit_code, 0); /* wait for second command to complete */
}
}
}
void prevent_zombies(int signum){
wait(NULL);
}
// arglist - a list of char* arguments (words) provided by the user
// it contains count+1 items, where the last item (arglist[count]) and *only* the last is NULL
// RETURNS - 1 if should continue, 0 otherwise
int process_arglist(int count, char** arglist){
int i=0;
int exit_code;
/* Set arglist appropriately so we can pass it to execvp without '&', if exists. */
while(arglist[i]!=NULL){
if (strcmp(arglist[i], "&")==0){
wait_confirm = 0;
arglist[i] = NULL;
}
else if (strcmp(arglist[i], "|") == 0){
pipe_confirm = 1;
pipe_index = i;
}
i++;
}
if(pipe_confirm){ /* command is indeed a pipe command */
do_pipe(arglist, pipe_index);
}
else {
int pid = fork();
if (pid==0){ /* child enters here */
execvp(arglist[0], arglist);
PRINT_ERROR;
}
else{ /*parent enters here*/
if(wait_confirm){
wait(&exit_code);
}
else{
signal(SIGCHLD, prevent_zombies);
}
}
}
return 1;
}
鉴于您尚未提供 MCVE (Minimal, Complete, Verifiable Example)
(或 MRE 或 SO 现在使用的任何名称)
或一个
SSCCE (Short, Self-Contained, Correct Example), I've had to create a main()
function to run my adaptation of your code. It also makes minimal use of the error reporting code available in my SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c
and stderr.h
in the src/libsoq sub-directory.
#define _GNU_SOURCE
#include "stderr.h"
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define EXEC_ERROR(cmd) fprintf(stderr, "failed to execute %s (%d: %s)\n", cmd, errno, strerror(errno))
#define PRINT_ERROR(func) fprintf(stderr, "function %s() failed (%d: %s)\n", func, errno, strerror(errno))
static int wait_confirm = 1;
static int pipe_confirm = 0;
static int pipe_index;
static_assert(sizeof(pid_t) == sizeof(int), "sizeof(pid_t) != sizeof(int) - redo format strings");
static void print_command(int cmdnum, char **argv)
{
fprintf(stderr, "%d: command %d:", getpid(), cmdnum + 1);
while (*argv != NULL)
fprintf(stderr, " %s", *argv++);
putc('\n', stderr);
}
static void do_pipe(char **arglist, int pipe_index)
{
int fd[2];
pid_t writer_pid, reader_pid;
arglist[pipe_index] = NULL;
if (pipe(fd) < 0)
{
PRINT_ERROR("pipe");
exit(1);
}
if ((writer_pid = fork()) < 0)
{
PRINT_ERROR("fork - writer");
exit(1);
}
else if (writer_pid == 0)
{
dup2(fd[1], STDOUT_FILENO);
close(fd[0]);
close(fd[1]);
execvp(arglist[0], arglist);
EXEC_ERROR(arglist[0]);
exit(1);
}
else if ((reader_pid = fork()) < 0)
{
PRINT_ERROR("fork - reader");
exit(1);
}
else if (reader_pid == 0)
{
dup2(fd[0], STDIN_FILENO);
close(fd[1]);
close(fd[0]);
arglist += pipe_index + 1;
execvp(arglist[0], arglist);
EXEC_ERROR(arglist[0]);
exit(1);
}
else
{
close(fd[0]);
close(fd[1]);
fprintf(stderr, "%d: writer %d, reader %d\n", getpid(), writer_pid, reader_pid);
int status = 0xFFFF;
int corpse = waitpid(reader_pid, &status, 0);
fprintf(stderr, "%d: reader %d exited with status 0x%.4X\n", getpid(), corpse, status);
}
}
static void dec_str(char *target, int value, int width)
{
char *pos = target + width;
*--pos = value % 10 + '0';
value /= 10;
while (pos > target && value > 0)
{
*--pos = value % 10 + '0';
value /= 10;
}
while (pos > target)
*--pos = ' ';
}
static void hex_str(char *target, int value, int width)
{
char *pos = target + width;
while (pos > target)
{
*--pos = "0123456789ABCDEF"[value % 16];
value /= 16;
}
}
static void prevent_zombies(int signum)
{
int status = 0xFFFF;
int corpse = wait(&status);
char message[] = "XXXXX: PID XXXXX exited with status 0xXXXX (signal XX)\n";
dec_str(&message[ 0], getpid(), 5);
dec_str(&message[11], corpse, 5);
hex_str(&message[38], status, 4);
dec_str(&message[51], signum, 2);
write(2, message, strlen(message));
signal(signum, SIG_DFL);
}
static void process_arglist(char **arglist)
{
wait_confirm = 1;
pipe_confirm = 0;
for (int i = 0; arglist[i] != NULL; i++)
{
if (strcmp(arglist[i], "&") == 0)
{
wait_confirm = 0;
arglist[i] = NULL;
}
else if (strcmp(arglist[i], "|") == 0)
{
pipe_confirm = 1;
pipe_index = i;
}
}
int pid;
if (pipe_confirm)
{
do_pipe(arglist, pipe_index);
}
else if ((pid = fork()) < 0)
{
PRINT_ERROR("fork");
}
else if (pid == 0)
{
execvp(arglist[0], arglist);
EXEC_ERROR(arglist[0]);
exit(1);
}
else if (wait_confirm)
{
fprintf(stderr, "%d: child %d (%s) launched\n", getpid(), pid, arglist[0]);
int status = 0xFFFF;
int corpse = waitpid(pid, &status, 0);
fprintf(stderr, "%d: child %d (%s) exited with status 0x%.4X\n", getpid(), corpse, arglist[0], status);
}
else
{
fprintf(stderr, "%d: child %d running in background\n", getpid(), pid);
signal(SIGCHLD, prevent_zombies);
}
}
static void collect_zombies(void)
{
int status;
int corpse;
while ((corpse = waitpid(-1, &status, WNOHANG)) > 0)
fprintf(stderr, "%d: zombie %d exited with status 0x%.4X\n", getpid(), corpse, status);
}
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
int no_zombies = 0;
/* Can't reuse commands because process_arglist() modifies them */
char *tty = ttyname(0);
size_t srclen = strlen(err_getarg0()) + sizeof(".c");
char *source = malloc(srclen);
if (source == NULL)
err_syserr("failed to allocate %zu bytes of memory: ", srclen);
strcpy(source, err_getarg0());
strcat(source, ".c");
char *cmd1[] = { "ls", "-l", source, "|", "cat", NULL };
char *cmd2[] = { "ps", "-ft", tty, NULL };
char *cmd3[] = { "ls", "-l", source, "|", "cat", NULL };
char *cmd4[] = { "ps", "-ft", tty, "&", NULL };
char *cmd5[] = { "sleep", "5", NULL };
char *cmd6[] = { "ls", "-l", source, "|", "cat", NULL };
char *cmd7[] = { "ps", "-ft", tty, NULL };
char **cmds[] = { cmd1, cmd2, cmd3, cmd4, cmd5, cmd6, cmd7, NULL };
if (argc != 1)
no_zombies = 1;
fprintf(stderr, "%d: %s collecting zombies\n", getpid(), (no_zombies ? "is" : "is not"));
for (int i = 0; cmds[i] != NULL; i++)
{
print_command(i, cmds[i]);
process_arglist(cmds[i]);
if (no_zombies)
collect_zombies();
}
return 0;
}
我改进了打印信息量 — 在调试 shells 或 multi-process 进程时,大量使用打印并确保在大多数输出行中包含当前 PID。我拆分了 PRINT_ERROR
的用途,我更喜欢(现在发现它更方便使用)function-like 宏,而不是 object-like 宏,后者确实有效。
print_command
函数打印一系列参数 — 以确保我看到的是正确的命令。
一件好事是您确保关闭了足够多的文件描述符。
除了报告创建了哪些进程以及第二个进程的状态之外,do_pipe()
函数的细节大部分没有变化。
函数 dec_str()
和 hex_str()
是 signal-safe 并且从信号处理程序 (prevent_zombies()
) 中使用来报告在没有违反规则的情况下死亡的进程 POSIX 指定哪些函数可以在信号处理程序中调用。 dec_str()
函数右对齐并空白填充一个数字; hex_str()
函数右对齐但用零填充数字。 prevent_zombies()
函数报告捕获的信号和死亡的 child 及其状态。它还将 SIGCHLD 的信号处理重置为默认值。这在这里工作正常。你应该在没有那个的情况下进行试验;它改变了行为。
process_arglist()
中的一个关键点是它将全局变量 wait_confirm
和 pipe_confirm
重置为默认值。这些变量实际上不应该是全局的(我把它们设为静态的;这里只有一个源文件,所以除了 main()
之外什么都不需要在它之外访问)。分析代码更改为 for
循环以定位 i
.
可以调用collect_zombies()
函数来收集任何僵尸进程;它使用 WNOHANG
选项,这样它就不会等待进程结束——它 returns 如果没有进程已经结束。
main()
函数会做一些设置工作。 err_setarg0()
函数记录了进程名称——它将被检测到 malloc()
失败后调用的 err_syserr()
使用。该代码假定您 运行 的程序是从源文件名派生的。我的版本在 sh47.c
和 sh97.c
中,所以 err_getarg0()
报告 sh47
或 sh97
;这用于创建 sh47.c
或 sh97.c
.
因为process_arglist()
修改了传递给它的参数列表中的指针,命令不能被重用。如果参数是单独分配的,process_arglist()
也会泄漏内存。另外,因为很多 windows 中有很多进程,我确保 ps
命令只报告当前 window 中的进程,方法是使用 ttyname()
获取终端名称.同样,我只列出了程序的源代码 — 而不是目录中的 200 多个其他文件。
然后程序遍历命令列表,打印并执行每个命令。根据是否有任何命令行参数,它可能会收集任何僵尸。这是非常原始的参数处理; 'real' shell 将使用 getopt()
或类似的函数来控制参数处理。
示例运行 — 不收集僵尸
21013: is not collecting zombies
21013: command 1: ls -l sh97.c | cat
21013: writer 21014, reader 21015
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21013: reader 21015 exited with status 0x0000
21013: command 2: ps -ft /dev/ttys000
21013: child 21016 (ps) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21013 822 0 1:14PM ttys000 0:00.01 sh97
501 21014 21013 0 1:14PM ttys000 0:00.00 (ls)
0 21016 21013 0 1:14PM ttys000 0:00.00 ps -ft /dev/ttys000
21013: child 21016 (ps) exited with status 0x0000
21013: command 3: ls -l sh97.c | cat
21013: writer 21017, reader 21018
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21013: reader 21018 exited with status 0x0000
21013: command 4: ps -ft /dev/ttys000 &
21013: child 21019 running in background
21013: command 5: sleep 5
21013: child 21020 (sleep) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21013 822 0 1:14PM ttys000 0:00.01 sh97
501 21014 21013 0 1:14PM ttys000 0:00.00 (ls)
501 21017 21013 0 1:14PM ttys000 0:00.00 (ls)
0 21019 21013 0 1:14PM ttys000 0:00.00 ps -ft /dev/ttys000
501 21020 21013 0 1:14PM ttys000 0:00.00 sleep 5
21013: PID 21019 exited with status 0x0000 (signal 20)
21013: child 21020 (sleep) exited with status 0x0000
21013: command 6: ls -l sh97.c | cat
21013: writer 21021, reader 21022
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21013: reader 21022 exited with status 0x0000
21013: command 7: ps -ft /dev/ttys000
21013: child 21023 (ps) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21013 822 0 1:14PM ttys000 0:00.01 sh97
501 21014 21013 0 1:14PM ttys000 0:00.00 (ls)
501 21017 21013 0 1:14PM ttys000 0:00.00 (ls)
501 21021 21013 0 1:14PM ttys000 0:00.00 (ls)
0 21023 21013 0 1:14PM ttys000 0:00.00 ps -ft /dev/ttys000
21013: child 21023 (ps) exited with status 0x0000
示例运行 - 收集僵尸
21033: is collecting zombies
21033: command 1: ls -l sh97.c | cat
21033: writer 21034, reader 21035
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21033: reader 21035 exited with status 0x0000
21033: zombie 21034 exited with status 0x0000
21033: command 2: ps -ft /dev/ttys000
21033: child 21036 (ps) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21033 822 0 1:17PM ttys000 0:00.01 sh97 1
0 21036 21033 0 1:17PM ttys000 0:00.00 ps -ft /dev/ttys000
21033: child 21036 (ps) exited with status 0x0000
21033: command 3: ls -l sh97.c | cat
21033: writer 21037, reader 21038
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21033: reader 21038 exited with status 0x0000
21033: zombie 21037 exited with status 0x0000
21033: command 4: ps -ft /dev/ttys000 &
21033: child 21039 running in background
21033: command 5: sleep 5
21033: child 21040 (sleep) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21033 822 0 1:17PM ttys000 0:00.01 sh97 1
0 21039 21033 0 1:17PM ttys000 0:00.00 ps -ft /dev/ttys000
501 21040 21033 0 1:17PM ttys000 0:00.00 sleep 5
21033: PID 21039 exited with status 0x0000 (signal 20)
21033: child 21040 (sleep) exited with status 0x0000
21033: command 6: ls -l sh97.c | cat
21033: writer 21041, reader 21042
-rw-r--r-- 1 jonathanleffler staff 5494 Apr 12 12:54 sh97.c
21033: reader 21042 exited with status 0x0000
21033: zombie 21041 exited with status 0x0000
21033: command 7: ps -ft /dev/ttys000
21033: child 21043 (ps) launched
UID PID PPID C STIME TTY TIME CMD
0 820 765 0 24Mar20 ttys000 0:00.03 login -pf jonathanleffler
501 822 820 0 24Mar20 ttys000 0:00.61 -bash
501 21033 822 0 1:17PM ttys000 0:00.01 sh97 1
0 21043 21033 0 1:17PM ttys000 0:00.00 ps -ft /dev/ttys000
21033: child 21043 (ps) exited with status 0x0000
比较
在第一个例子中,你可以看到僵尸(ls)
;第二,没有这样的过程。 sleep 5
命令在那里,因此背景 ps
的输出不会与后续的 ls
和 ps
混合——尝试删除 sleep
并查看会发生什么。
如您所见,运行执行一系列命令没有问题,也没有丢失输出的迹象。我不确定您的版本出了什么问题,但是您的大部分代码都没有提供,所以问题很可能不在您所展示的内容中。
有很多可能性。话虽如此,我认为您的问题的一个因素是未重置的全局变量。另一个可能是您没有在后台进程完成后重置处理。请注意,如果您的后台进程没有按照您启动它们的顺序整齐地完成(例如,一系列不同持续时间的 sleep
命令),那么后台处理将不会按预期工作。
确实值得如此彻底地监控您的代码,至少在您有理由相信它可以正常工作之前(并且值得保持监控代码可用,以便在您 运行 改成问题)。您可以查看 #define
macro for debug printing in C,了解如何在 compile-time 和 run-time 处配置调试代码。如果这样做,您可能需要一些适当的参数处理来控制调试(想想 sh -x
类固醇)。