编写自己的 shell - 处理某些管道时代码挂起 - 用 C
我目前正在编写自己的 shell 作为一个类的项目,并且一切实际上都在工作。我的问题是我的管道,有时它们可以工作,有时,它们只是挂起直到我中断它们。我对此进行了研究,似乎将其标准输入写入的函数没有从第一个进程接收到 EOF;通常,据我所知,问题是管道没有关闭,但我的代码并非如此(据我所知)。
所有重定向工作及其任何变体:
ls -l >文件1
wc <文件1> file2
以下管道命令有效:
w |头 -n 4
w |头-n 4 > file1
这不起作用:ls | grep file1
它显示正确的输出,并且永远不会结束,除非用户向它发送中断信号。 ls | grep 文件1 > file2
也不起作用。它挂起而不显示输出,创建 file2,但从不写入它。
不管怎样,我希望我遗漏了一些其他人能够注意到的东西;我已经从事这个工作有一段时间了。让我知道是否还有我可以提供的代码。我在下面发布的代码是主文件,没有删除任何内容。
/*
* This code implemenFts a simple shell program
* At this time it supports just simple commands with
* any number of args.
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include "input.h"
#include "myShell.h"
#include "BackgroundStack.h"
/*
* The main shell function
*/
main() {
char *buff[20];
char *inputString;
BackgroundStack *bgStack = malloc(sizeof(BackgroundStack));
initBgStack(bgStack);
struct sigaction new_act;
new_act.sa_handler = sigIntHandler;
sigemptyset ( &new_act.sa_mask );
new_act.sa_flags = SA_RESTART;
sigaction(SIGINT, &new_act, NULL);
// Loop forever
while(1) {
const char *chPath;
doneBgProcesses(bgStack);
// Print out the prompt and get the input
printPrompt();
inputString = get_my_args(buff);
if (buff[0] == NULL) continue;
if (buff[0][0] == '#') continue;
switch (getBuiltInCommand(buff[0])) {
case EXIT:
exit(0);
break;
case CD:
chPath = (buff[1]==NULL) ? getenv("HOME") : buff[1];
if (chdir(chPath) < 0) {
perror(": cd");
}
break;
default:
do_command(buff, bgStack);
}
//free up the malloced memory
free(inputString);
}// end of while(1)
}
static void sigIntHandler (int signum) {}
/*
* Do the command
*/
int do_command(char **args, BackgroundStack *bgStack) {
int status, statusb;
pid_t child_id, childb_id;
char **argsb;
int pipes[2];
int isBgd = isBackgrounded(args);
int hasPipe = hasAPipe(args);
if (isBgd) removeBackgroundCommand(args);
if (hasPipe) {
int cmdBi = getSecondCommandIndex(args);
args[cmdBi-1] = NULL;
argsb = &args[cmdBi];
pipe(pipes);
}
// Fork the child and check for errors in fork()
if((child_id = fork()) == -1) {
switch(errno) {
case EAGAIN:
perror("Error EAGAIN: ");
return;
case ENOMEM:
perror("Error ENOMEM: ");
return;
}
}
if (hasPipe && child_id != 0) {
childb_id = fork();
if(childb_id == -1) {
switch(errno) {
case EAGAIN:
perror("Error EAGAIN: ");
return;
case ENOMEM:
perror("Error ENOMEM: ");
return;
}
}
}
if(child_id == 0 || (childb_id == 0 && hasPipe)) {
if (child_id != 0 && hasPipe) args = argsb;
if (child_id == 0 && isBgd) {
struct sigaction new_act;
new_act.sa_handler = SIG_IGN;
sigaction(SIGINT, &new_act, 0);
}
if (child_id == 0 && hasPipe) {
if (dup2(pipes[1], 1) != 1) fatalPerror(": Pipe Redirection Output Error");
close(pipes[0]);
close(pipes[1]);
}
if (child_id != 0 && hasPipe) {
if (dup2(pipes[0], 0) != 0) fatalPerror(": Pipe Redirection Input Error");
close(pipes[0]);
close(pipes[1]);
waitpid(child_id, NULL, 0);
}
if ((child_id != 0 && hasPipe) || !hasPipe) {
if (hasAReOut(args)) {
char outFile[100];
getOutFile(args, outFile);
int reOutFile = open(outFile, O_RDWR|O_CREAT|O_TRUNC, S_IREAD|S_IWRITE);
if (reOutFile<0) fatalPerror(": Redirection Output Error");
if (dup2(reOutFile,1) != 1) fatalPerror(": Redirection Output Error");
close(reOutFile);
}
}
if ( (child_id == 0 && hasPipe) || !hasPipe) {
if (hasAReIn(args)) {
char inFle[100];
getInFile(args, inFle);
int reInFile = open(inFle, O_RDWR);
if (reInFile<0) fatalPerror(": Redirection Input Error");
if (dup2(reInFile,0) != 0) fatalPerror(": Redirection Input Error");
close(reInFile);
} else if (isBgd && !hasPipe) {
int bgReInFile = open("/dev/null", O_RDONLY);
if (bgReInFile<0) fatalPerror(": /dev/null Redirection Input Error");
if (dup2(bgReInFile,0) != 0) fatalPerror(": /dev/null Redirection Input Error");
close(bgReInFile);
}
}
// Execute the command
execvp(args[0], args);
perror(args[0]);
exit(-1);
}
// Wait for the child process to complete, if necessary
if (!isBgd) waitpid(child_id, &status, 0);
else if (!hasPipe) {
printf("Child %ld started\n", (long)child_id);
BackgroundProcess *bgPrs = malloc(sizeof(BackgroundProcess));
bgPrs->pid = child_id;
bgPrs->exitStatus = -1;
addProcessToBgStack(bgStack, bgPrs);
}
if (hasPipe) waitpid(childb_id, &statusb, 0);
if ( WIFSIGNALED(status) && !isBgd ) printf("Child %ld terminated due to signal %d\n", (long)child_id, WTERMSIG(status) );
if ( hasPipe && WIFSIGNALED(statusb) ) printf("Child %ld terminated due to signal %d\n", (long)childb_id, WTERMSIG(status) );
} // end of do_command
I'm currently writing my own shell as a project for a class, and have everything virtually working. My problem is with my pipes, sometimes they work, and sometimes, they just hang until I interrupt them. I've done research on this, and it seems that the function that is getting it's stdin written to isn't receiving an EOF from the first process; usually as I've learned the problem is that the pipe isn't being closed, but this isn't the case (to my knowledge) with my code.
All redirection works and any variation thereof:
ls -l > file1
wc < file1 > file2
The following piped commands work:
w | head -n 4
w | head -n 4 > file1
This doesn't work: ls | grep file1
it shows the correct output and never ends unless an interrupt signal is sent to it by the user. ls | grep file1 > file2
also does not work. It hangs without showing output, creates the file2, but never writes to it.
Anyway, I hope there's something I'm missing that someone else can notice; I've been at this for a while. Let me know if there's anymore code I can provide. The code I've posted below is the main file, nothing removed.
/*
* This code implemenFts a simple shell program
* At this time it supports just simple commands with
* any number of args.
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include "input.h"
#include "myShell.h"
#include "BackgroundStack.h"
/*
* The main shell function
*/
main() {
char *buff[20];
char *inputString;
BackgroundStack *bgStack = malloc(sizeof(BackgroundStack));
initBgStack(bgStack);
struct sigaction new_act;
new_act.sa_handler = sigIntHandler;
sigemptyset ( &new_act.sa_mask );
new_act.sa_flags = SA_RESTART;
sigaction(SIGINT, &new_act, NULL);
// Loop forever
while(1) {
const char *chPath;
doneBgProcesses(bgStack);
// Print out the prompt and get the input
printPrompt();
inputString = get_my_args(buff);
if (buff[0] == NULL) continue;
if (buff[0][0] == '#') continue;
switch (getBuiltInCommand(buff[0])) {
case EXIT:
exit(0);
break;
case CD:
chPath = (buff[1]==NULL) ? getenv("HOME") : buff[1];
if (chdir(chPath) < 0) {
perror(": cd");
}
break;
default:
do_command(buff, bgStack);
}
//free up the malloced memory
free(inputString);
}// end of while(1)
}
static void sigIntHandler (int signum) {}
/*
* Do the command
*/
int do_command(char **args, BackgroundStack *bgStack) {
int status, statusb;
pid_t child_id, childb_id;
char **argsb;
int pipes[2];
int isBgd = isBackgrounded(args);
int hasPipe = hasAPipe(args);
if (isBgd) removeBackgroundCommand(args);
if (hasPipe) {
int cmdBi = getSecondCommandIndex(args);
args[cmdBi-1] = NULL;
argsb = &args[cmdBi];
pipe(pipes);
}
// Fork the child and check for errors in fork()
if((child_id = fork()) == -1) {
switch(errno) {
case EAGAIN:
perror("Error EAGAIN: ");
return;
case ENOMEM:
perror("Error ENOMEM: ");
return;
}
}
if (hasPipe && child_id != 0) {
childb_id = fork();
if(childb_id == -1) {
switch(errno) {
case EAGAIN:
perror("Error EAGAIN: ");
return;
case ENOMEM:
perror("Error ENOMEM: ");
return;
}
}
}
if(child_id == 0 || (childb_id == 0 && hasPipe)) {
if (child_id != 0 && hasPipe) args = argsb;
if (child_id == 0 && isBgd) {
struct sigaction new_act;
new_act.sa_handler = SIG_IGN;
sigaction(SIGINT, &new_act, 0);
}
if (child_id == 0 && hasPipe) {
if (dup2(pipes[1], 1) != 1) fatalPerror(": Pipe Redirection Output Error");
close(pipes[0]);
close(pipes[1]);
}
if (child_id != 0 && hasPipe) {
if (dup2(pipes[0], 0) != 0) fatalPerror(": Pipe Redirection Input Error");
close(pipes[0]);
close(pipes[1]);
waitpid(child_id, NULL, 0);
}
if ((child_id != 0 && hasPipe) || !hasPipe) {
if (hasAReOut(args)) {
char outFile[100];
getOutFile(args, outFile);
int reOutFile = open(outFile, O_RDWR|O_CREAT|O_TRUNC, S_IREAD|S_IWRITE);
if (reOutFile<0) fatalPerror(": Redirection Output Error");
if (dup2(reOutFile,1) != 1) fatalPerror(": Redirection Output Error");
close(reOutFile);
}
}
if ( (child_id == 0 && hasPipe) || !hasPipe) {
if (hasAReIn(args)) {
char inFle[100];
getInFile(args, inFle);
int reInFile = open(inFle, O_RDWR);
if (reInFile<0) fatalPerror(": Redirection Input Error");
if (dup2(reInFile,0) != 0) fatalPerror(": Redirection Input Error");
close(reInFile);
} else if (isBgd && !hasPipe) {
int bgReInFile = open("/dev/null", O_RDONLY);
if (bgReInFile<0) fatalPerror(": /dev/null Redirection Input Error");
if (dup2(bgReInFile,0) != 0) fatalPerror(": /dev/null Redirection Input Error");
close(bgReInFile);
}
}
// Execute the command
execvp(args[0], args);
perror(args[0]);
exit(-1);
}
// Wait for the child process to complete, if necessary
if (!isBgd) waitpid(child_id, &status, 0);
else if (!hasPipe) {
printf("Child %ld started\n", (long)child_id);
BackgroundProcess *bgPrs = malloc(sizeof(BackgroundProcess));
bgPrs->pid = child_id;
bgPrs->exitStatus = -1;
addProcessToBgStack(bgStack, bgPrs);
}
if (hasPipe) waitpid(childb_id, &statusb, 0);
if ( WIFSIGNALED(status) && !isBgd ) printf("Child %ld terminated due to signal %d\n", (long)child_id, WTERMSIG(status) );
if ( hasPipe && WIFSIGNALED(statusb) ) printf("Child %ld terminated due to signal %d\n", (long)childb_id, WTERMSIG(status) );
} // end of do_command
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
第二个子进程不应该等待第一个子进程退出 - 它应该立即开始运行(它将阻塞,直到第一个子进程在管道上产生一些输出),因此删除该
waitpid()
由childb
执行。相反,父进程应该等待两个子进程(或者可能只是第二个子进程)。 (事实上,正如 JeremyP 所指出的,这个waitpid()
调用无论如何都会失败,因为childb
不是child
的父级)。不过,您的问题是父进程正在维护管道的打开文件描述符。就在注释
// Wait for the child process to finish, if need
之前,父进程应该关闭其管道文件描述符:父进程中打开的文件描述符意味着子进程
grep< /code> 进程永远不会看到 EOF,因此它不会退出。
The second child should not wait for the first child to exit - it should just start running straight away (it will block until some output is produced on the pipe by the first child), so remove that
waitpid()
executed bychildb
. Instead, the parent process should wait for both child processes (or perhaps just the second one). (Indeed, as noted by JeremyP, thiswaitpid()
call is failing anyway, sincechildb
is not the parent ofchild
).Your problem, though, is that the parent process is mainintaining open file descriptors to the pipe. Right before the comment
// Wait for the child process to complete, if necessary
, the parent process should close its pipe file descriptors:The open file descriptor in the parent means that the child
grep
process never sees EOF, so it doesn't exit.我不知道答案,但我发现了一个问题。
您会同意 for 的条件
仅对于两个子进程为 true,但在 if 语句块内您有以下内容:
waitpid()
调用不正确,因为它是从第二个子进程调用的等待第一个孩子。它可能因ECHILD
失败,因为第一个孩子不是第二个孩子的孩子。至于您真正的问题,我怀疑这与 grep 命令在其输入关闭之前不会终止这一事实有关。可能存在某种死锁情况,阻止这种情况发生。您需要在调试器中运行它或进行一些日志记录以查看父进程挂起的位置。
编辑
咖啡馆的答案告诉了我们一切。
我假设 grep 的输入被关闭,因为 ls 将在终止时关闭其输出,但当然,父进程也打开了 grep 的输入文件描述符。使用
head
的版本可以正常工作,因为head -n 4
在四行后终止,无论其输入文件描述符是否关闭。I don't know the answer but I have spotted one issue.
You'll agree that the condition for
is true only for the two child processes, but inside the if statement block you have this:
The
waitpid()
call is incorrect because it is called from the second child to wait for the first child. It's probably failing withECHILD
because the first child is not a child of the second child.As for your real problem, I suspect it has to do with the fact that the
grep
command will not terminate until its input is closed. There might be some deadlock condition going on that stops that from happening. You need to run this in a debugger or put some logging in to see where the parent process is hanging.Edit
caf's answer tells us everything.
I was assuming that the input to grep was being closed because ls will close its output when it terminates, but of course, the parent process also has grep's input file descriptor open. The version using
head
works properly becausehead -n 4
terminates after four lines regardless of whether its input file descriptor is closed or not.