c - 生成了一个 bash shell。壳死了但管子没坏?
Posted
技术标签:
【中文标题】c - 生成了一个 bash shell。壳死了但管子没坏?【英文标题】:c - spawned a bash shell. Shell died but pipe not broken? 【发布时间】:2016-10-04 16:29:54 【问题描述】:问题
我正在尝试将内容从主例程通过管道传输到 execvp 的 bash shell。我遇到了一个问题,当我将“退出”写入子外壳时,它并没有告诉我管道真的坏了。应该是——对吧?进程终止,因此管道 fd 也应该返回 EOF 或 SIGPIPE。然而,它并没有,只是像往常一样继续读/写。
代码
这里附上代码:
/************************************************************
* Includes:
* ioctl - useless(?)
* termios, tcsetattr, tcgetattr - are for setting the
* noncanonical, character-at-a-time terminal.
* fork, exec - creating the child process for part 2.
* pthread, pipe - creating the pipe process to communicate
* with the child shell.
* kill - to exit the process
* atexit - does some cleanups. Used in termios, tcsetattr,
* tcgetattr.
************************************************************/
#include <sys/ioctl.h> // ioctl
#include <termios.h> // termios, tcsetattr, tcgetattr
#include <unistd.h> // fork, exec, pipe
#include <sys/wait.h> // waitpid
#include <pthread.h> // pthread
#include <signal.h> // kill
#include <stdlib.h> // atexit
#include <stdio.h> // fprintf and other utility functions
#include <getopt.h> // getopt
/**********************
* GLOBALS
**********************/
pid_t pid;
/**********************
* CONSTANTS
**********************/
static const int BUFFER_SIZE = 16;
static const int STDIN_FD = 0;
static const int STDOUT_FD = 1;
static const int STDERR_FD = 2;
// these attributes are reverted to later
struct termios saved_attributes;
// to revert the saved attributes
void
reset_input_mode (void)
tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
// to set the input mode to correct non-canonical mode.
void
set_input_mode (void)
struct termios tattr;
/* Make sure stdin is a terminal. */
if (!isatty (STDIN_FILENO))
fprintf (stderr, "Not a terminal.\n");
exit (EXIT_FAILURE);
/* Save the terminal attributes so we can restore them later. */
tcgetattr (STDIN_FILENO, &saved_attributes);
atexit (reset_input_mode);
/* Set the funny terminal modes. */
tcgetattr (STDIN_FILENO, &tattr);
tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
tattr.c_cc[VMIN] = 1;
tattr.c_cc[VTIME] = 0;
tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
// pthread 1 will read from pipe_fd[0], which
// is really the child's pipe_fd[1](stdout).
// It then prints out the contents.
void* thread_read(void* arg)
int* pipe_fd = ((int *) arg);
int read_fd = pipe_fd[0];
int write_fd = pipe_fd[1];
char c;
while(1)
int bytes_read = read(read_fd, &c, 1);
if(bytes_read > 0)
putchar(c);
else
close(read_fd);
close(write_fd);
fprintf(stdout, "The read broke.");
fflush(stdout);
break;
// pthread 2 will write to child_pipe_fd[1], which
// is really the child's stdin.
// but in addition to writing to child_pipe_fd[1],
// we must also print to stdout what our
// argument was into the terminal. (so pthread 2
// does extra).
void* thread_write(void* arg)
set_input_mode();
int* pipe_args = ((int *) arg);
int child_read_fd = pipe_args[0];
int child_write_fd = pipe_args[1];
int parent_read_fd = pipe_args[2];
int parent_write_fd = pipe_args[3];
char c;
while(1)
int bytes_read = read(STDIN_FD, &c, 1);
write(child_write_fd, &c, bytes_read);
putchar(c);
if(c == 0x04)
// If an EOF has been detected, then
// we need to close the pipes.
close(child_write_fd);
close(child_read_fd);
close(parent_write_fd);
close(parent_read_fd);
kill(pid, SIGHUP);
break;
int main(int argc, char* argv[])
/***************************
* Getopt process here for --shell
**************************/
int child_pipe_fd[2];
int parent_pipe_fd[2];
pipe(child_pipe_fd);
pipe(parent_pipe_fd);
// We need to spawn a subshell.
pid = fork();
if(pid < 0)
perror("Forking was unsuccessful. Exiting");
exit(EXIT_FAILURE);
else if(pid == 0) // is the child.
// We dup the fd and close the pipe.
close(0); // close stdin. child's pipe should read.
dup(child_pipe_fd[0]); // pipe_fd[0] is the read. Make read the stdin.
close(child_pipe_fd[0]);
close(1); // close stdout
dup(parent_pipe_fd[1]); // pipe_fd[1] is the write. Make write the stdout.
close(parent_pipe_fd[1]);
char* BASH[] = "/bin/bash", NULL;
execvp(BASH[0], BASH);
else // is the parent
// We dup the fd and close the pipe.
//
// create 2 pthreads.
// pthread 1 will read from pipe_fd[0], which
// is really the child's pipe_fd[1](stdout).
// It then prints out the contents.
//
// pthread 2 will write to pipe_fd[1], which
// is really the child's pipe_fd[0](stdin)
// but in addition to writing to pipe_fd[1],
// we must also print to stdout what our
// argument was into the terminal. (so pthread 2
// does extra).
//
// We also need to take care of signal handling:
signal(SIGINT, sigint_handler);
/*signal(SIGPIPE, sigpipe_handler);*/
int write_args[] = child_pipe_fd[0], child_pipe_fd[1],
parent_pipe_fd[0], parent_pipe_fd[1];
pthread_t t[2];
pthread_create(t, NULL, thread_read, parent_pipe_fd);
pthread_create(t+1, NULL, thread_write, write_args);
pthread_join(t[0], NULL);
pthread_join(t[1], NULL);
int status;
if (waitpid(pid, &status, 0) == -1)
perror("Waiting for child failed.");
exit(EXIT_FAILURE);
printf("Subshell exited with the error code %d", status);
exit(0);
return 0;
该程序基本上将来自终端的输入通过管道传输到子shell,并尝试执行它们并返回输出。要写入管道,我有一个 pthread 将标准输入输入写入子外壳。要读取管道,我有一个 pthread 将管道读取到父级。为了通过子shell消亡(调用exit)检测损坏的管道,我从读取线程中检测到EOF字符。
我的尝试
我添加了对 0x04 字符 (EOF) 的检查,我检查了 read_bytes == 0
或 read_bytes < 0
。除非我明确关闭写作端的管道,否则它似乎永远不会收到备忘录。如果我发送字符 ^D(在我的代码中,通过关闭子和父的所有管道来处理),它只会满足 EOF 字符。
任何 cmets 将不胜感激!谢谢。
【问题讨论】:
请在代码(最好是minimal reproducible example)中编辑问题本身。第三方链接是不可接受的,因为他们链接到的页面将来可能会被删除。 当然。经过一些编辑后,我会将其链接到此处。 (此外,到 pastebin.com 的第三方链接包含广告,通常是令人讨厌的动画广告;即使某些内容是补充而不是问题的重要部分,请考虑改用gist.github.com)。 没问题。日后注意。感谢您的提醒! @OneRaynyDay, ...顺便说一句,请考虑删除您的问题,直到您完成编辑并取消删除它——这样您就不会在此期间获得否决票或投票结束。 【参考方案1】:您的父进程持有子文件描述符的副本。因此,即使在子进程退出后,这些 FD 仍处于打开状态——因此这些管道的另一端也保持打开状态,从而阻止任何 SIGPIPE。
修改你的代码如下:
else
// pid >0; this is the parent
close(child_pipe_fd[0]); // ADD THIS LINE
close(parent_pipe_fd[1]); // ADD THIS LINE
【讨论】:
这修复了它!非常感谢你。你的回答给了我一个很好的教训 - 除非绝对必要,否则确保一次对每个 fd 有 1 个引用。以上是关于c - 生成了一个 bash shell。壳死了但管子没坏?的主要内容,如果未能解决你的问题,请参考以下文章
修改shell 将当前shell(默认是bash B SHELL )改为csh C SHELL