我了解Unix文件描述符在C中是如何工作的吗?
下面的简短程序旨在迭代从命令行传递的argv,并执行每个参数。这不是我的家庭作业,而是我在为做家庭作业做准备 第一个参数从STDIN和STDOUT获取输入,并写入管道。在每次迭代结束时(最后一次除外),将交换文件描述符,以便由最后一个exec写入的管道将由下一个exec读取。例如,以这种方式,我打算我了解Unix文件描述符在C中是如何工作的吗?,c,pipe,file-descriptor,io-redirection,C,Pipe,File Descriptor,Io Redirection,下面的简短程序旨在迭代从命令行传递的argv,并执行每个参数。这不是我的家庭作业,而是我在为做家庭作业做准备 第一个参数从STDIN和STDOUT获取输入,并写入管道。在每次迭代结束时(最后一次除外),将交换文件描述符,以便由最后一个exec写入的管道将由下一个exec读取。例如,以这种方式,我打算 ./a.out /bin/pwd /usr/bin/wc 仅打印工作目录的长度。代码如下 #include <stdio.h>
./a.out /bin/pwd /usr/bin/wc
仅打印工作目录的长度。代码如下
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
main(int argc, char * argv[]) {
int i;
int left[2], right[2], nbytes; /* arrays for file descriptors */
/* pointers for swapping */
int (* temp);
int (* leftPipe) = left;
int (* rightPipe) = right;
pid_t childpid;
char readbuffer[80];
/* for the first iteration, leftPipe is STDIN */
leftPipe[0] = STDIN_FILENO;
leftPipe[1] = STDOUT_FILENO;
for (i = 1; i < argc; i++) {
/* reopen the right pipe (is this necessary?) */
pipe(rightPipe);
fprintf(stderr, "%d: %s\n", i, argv[i]);
fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);
if ((childpid = fork()) == -1) {
perror("fork");
exit(1);
}
if (childpid == 0) {
/* read input from the left */
close(leftPipe[1]); /* close output */
dup2(leftPipe[0], STDIN_FILENO);
close(leftPipe[0]); /* is this necessary? A tutorial seemed to be doing this */
/* write output to the right */
close(rightPipe[0]); /* close input */
dup2(rightPipe[1], STDOUT_FILENO);
close(rightPipe[1]);
execl(argv[i], argv[i], NULL);
exit(0);
}
wait();
/* on all but the last iteration, swap the pipes */
if (i + 1 < argc) {
/* swap the pipes */
fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);
temp = leftPipe;
leftPipe = rightPipe;
rightPipe = temp;
fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);
}
}
/* read what was last written to the right pipe */
close(rightPipe[1]); /* the receiving process closes 1 */
nbytes = read(rightPipe[0], readbuffer, sizeof(readbuffer));
readbuffer[nbytes] = 0;
fprintf(stderr, "Received string: %s\n", readbuffer);
return 0;
}
使用第一个示例(./a.out/bin/pwd/usr/bin/wc)运行此程序的输出:
此时,终端挂起(可能等待输入)
如您所见,未收到字符串。我想象的是,我在上面做了一些错误的事情,或者是在交换指针时,或者是我不理解unix文件描述符。最后,我的任务是解释任意长的管道,这是我解决这个问题的想法之一。我很难判断自己是否走上了爬树的正确道路。我了解unix文件描述符吗
更新:
>> ./a.out /bin/ls /bin/cat /usr/bin/wc
1: /bin/ls
0 32767 3 4
0 32767 3 4
3 4 0 32767
2: /bin/cat
3 4 4 5
3 4 4 5
4 5 3 4
3: /usr/bin/wc
4 5 5 6
Received string: 266 294 4280
使用/bin/ls作为第二个参数运行它,我得到了以下结果(数字是各个点的文件描述符):
最后还有一些垃圾,但我现在更担心的是我不理解指针!尽管这两个命令彼此独立,但它们并没有真正利用管道
更新:垃圾字符来自未关闭字符串。现在我关上它,没有垃圾 要修复输出末尾的垃圾,请在最后的
printf
之前添加以下行
readbuffer[nbytes] = 0;
至于悬而未决的问题,我需要更多的思考来解决这个问题。我猜这与管道和缓冲有关。之所以出现悬挂,是因为分叉后,主流程中“右”管道的写入端没有正确关闭。因此,
wc
永远不会停止读取(毕竟,主进程仍然可以将内容写入管道!)。只有在写入端的文件描述符的所有副本都已关闭后,它才会停止读取
以下是一个固定版本:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char * argv[])
{
int i;
int left[2], right[2], nbytes; /* arrays for file descriptors */
/* pointers for swapping */
int (* temp);
int (* leftPipe) = left;
int (* rightPipe) = right;
pid_t childpid;
char readbuffer[80];
leftPipe[0] = STDIN_FILENO;
// no need to assign leftPipe[1] here, it will not be used
for (i = 1; i < argc; i++) {
pipe(rightPipe); // create new pipe
fprintf(stderr, "%d: %s\n", i, argv[i]);
fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);
if ((childpid = fork()) == -1) {
perror("fork");
exit(1);
}
if (childpid == 0) {
// use the reading end of the left pipe as STDIN
dup2(leftPipe[0], STDIN_FILENO);
// use the writing end of the right pipe as STDOUT
dup2(rightPipe[1], STDOUT_FILENO);
// close reading end of the right pipe
close(rightPipe[0]);
execl(argv[i], argv[i], NULL);
exit(0);
}
// IMPORTANT!! close writing end of the right pipe, otherwise
// the program will hang (this is the main bug in your original
// implementation)
close(rightPipe[1]);
// wait properly!
waitpid(childpid, NULL, 0);
/* on all but the last iteration, swap */
if (i + 1 < argc) {
fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);
temp = leftPipe;
leftPipe = rightPipe;
rightPipe = temp;
fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);
}
}
nbytes = read(rightPipe[0], readbuffer, sizeof(readbuffer));
readbuffer[nbytes] = 0;
fprintf(stderr, "Received string: %s\n", readbuffer);
return 0;
}
如果您有关于此解决方案的具体问题,请告诉我:)您的原始代码还有一些其他小问题:
- 使用指针是不必要的,我们可以在管道周围复制(性能肯定不会有问题;)
- 使用
代替int
size\t
- 您没有修复使用
标志编译时显示给您的所有警告-Wall
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
size_t i, nbytes;
int left[2], right[2], tmp[2];
pid_t childpid;
char readbuffer[80];
left[0] = STDIN_FILENO;
for (i = 1; i < argc; ++i) {
pipe(right);
switch ((childpid = fork())) {
case -1:
perror("fork");
exit(1);
case 0:
dup2(left[0], STDIN_FILENO);
dup2(right[1], STDOUT_FILENO);
close(right[0]);
execl(argv[i], argv[i], NULL);
default:
close(right[1]);
waitpid(childpid, NULL, 0);
}
if (i == argc - 1) break;
memcpy(tmp, left, sizeof tmp);
memcpy(left, right, sizeof left);
memcpy(right, tmp, sizeof right);
}
nbytes = read(right[0], readbuffer, sizeof readbuffer);
readbuffer[nbytes] = 0;
fprintf(stderr, "Received string: %s\n", readbuffer);
return 0;
}
#包括
#包括
#包括
#包括
#包括
int main(int argc,字符**argv){
尺寸(单位:N字节);
int左[2],右[2],tmp[2];
pid_t childpid;
字符读取缓冲区[80];
左[0]=STDIN_文件号;
对于(i=1;i
我建议将所有printf(…)
调用更改为fprintf(stderr…)
。将标准IO(printf(3)
)与较低级别的例程(pipe(2)
,dup2(2)
,close(2)
)混合使用会带来更多麻烦,值得注意!我想splint会同意的。你在打印之前不会终止字符串,这就是垃圾的原因。在read
之后尝试readbytes[nbytes]=0
。因此,在我看来,在交换之后,正在执行的进程无法从交换的管道中读取。如果没有交换,我们可以从管道中读取数据。如果第二个进程没有读取管道,那么它运行良好,并将输出放到管道中。很好!你说得对,我不是在用墙编译。通常我会,我也会修复夹板——微弱的警告,但这只是一个实验,所以我没有那么彻底。很高兴看到答案是相对较小的,而不是我所担心的绝对错误。所以答案是“是”,但我需要更多的细节练习。非常感谢您的解决方案非常好@齐吉:如果这对你有帮助,请你接受这个答案:)我当然会的!我倾向于:)
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char * argv[])
{
int i;
int left[2], right[2], nbytes; /* arrays for file descriptors */
/* pointers for swapping */
int (* temp);
int (* leftPipe) = left;
int (* rightPipe) = right;
pid_t childpid;
char readbuffer[80];
leftPipe[0] = STDIN_FILENO;
// no need to assign leftPipe[1] here, it will not be used
for (i = 1; i < argc; i++) {
pipe(rightPipe); // create new pipe
fprintf(stderr, "%d: %s\n", i, argv[i]);
fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);
if ((childpid = fork()) == -1) {
perror("fork");
exit(1);
}
if (childpid == 0) {
// use the reading end of the left pipe as STDIN
dup2(leftPipe[0], STDIN_FILENO);
// use the writing end of the right pipe as STDOUT
dup2(rightPipe[1], STDOUT_FILENO);
// close reading end of the right pipe
close(rightPipe[0]);
execl(argv[i], argv[i], NULL);
exit(0);
}
// IMPORTANT!! close writing end of the right pipe, otherwise
// the program will hang (this is the main bug in your original
// implementation)
close(rightPipe[1]);
// wait properly!
waitpid(childpid, NULL, 0);
/* on all but the last iteration, swap */
if (i + 1 < argc) {
fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);
temp = leftPipe;
leftPipe = rightPipe;
rightPipe = temp;
fprintf(stderr, "%d %d %d %d\n", leftPipe[0], leftPipe[1], rightPipe[0], rightPipe[1]);
}
}
nbytes = read(rightPipe[0], readbuffer, sizeof(readbuffer));
readbuffer[nbytes] = 0;
fprintf(stderr, "Received string: %s\n", readbuffer);
return 0;
}
>> ./a.out /bin/ls /bin/cat /usr/bin/wc
1: /bin/ls
0 32767 3 4
0 32767 3 4
3 4 0 32767
2: /bin/cat
3 4 4 5
3 4 4 5
4 5 3 4
3: /usr/bin/wc
4 5 5 6
Received string: 266 294 4280
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
size_t i, nbytes;
int left[2], right[2], tmp[2];
pid_t childpid;
char readbuffer[80];
left[0] = STDIN_FILENO;
for (i = 1; i < argc; ++i) {
pipe(right);
switch ((childpid = fork())) {
case -1:
perror("fork");
exit(1);
case 0:
dup2(left[0], STDIN_FILENO);
dup2(right[1], STDOUT_FILENO);
close(right[0]);
execl(argv[i], argv[i], NULL);
default:
close(right[1]);
waitpid(childpid, NULL, 0);
}
if (i == argc - 1) break;
memcpy(tmp, left, sizeof tmp);
memcpy(left, right, sizeof left);
memcpy(right, tmp, sizeof right);
}
nbytes = read(right[0], readbuffer, sizeof readbuffer);
readbuffer[nbytes] = 0;
fprintf(stderr, "Received string: %s\n", readbuffer);
return 0;
}