我了解Unix文件描述符如何在C中工作吗?
下面的短程序旨在遍历从命令行传递的argv并执行每个参数。这不是我的家庭作业,而是我准备做作业的一些事情。我了解Unix文件描述符如何在C中工作吗?
第一个参数从STDIN和STDOUT获取输入,并写入管道。在每次迭代结束时(除了最后一个),文件描述符都会被交换,以便最后一个exec写入的管道将被下一个读取。以这种方式,我打算,例如,对于
./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;
}
UPDATE:在所有我原本使用/斌/ WC但下面的测试案例WC reveiled座便器是不是在所有地方我想。我正在修改结果。
在一个微不足道的情况下的输出(./a.out/bin中/密码)是如预期:
1: /bin/pwd
Received string: /home/zeigfreid/Works/programmatical/Langara/spring_2012/OS/labs/lab02/play
从与第一实施例(./a.out/bin中运行该程序的输出/ pwd/usr/bin/wc):
1: /bin/pwd
0 1 3 4
3 4 0 1
2: /bin/wc
此时,终端挂起(可能等待输入)。
正如您所看到的,该字符串未被接收。我想象的是,我在上面做了一些错误的事情,无论是交换指针还是我不了解unix文件描述符。我的任务最终将解释任意长的管道,这是我解决问题的想法之一。我在判断我是否正在吠叫树的正确轨道上遇到困难。我了解unix文件描述符吗?
UPDATE:
与/斌/ LS作为第二个参数运行它,我得到了以下结果(号码在各个点的文件描述符):
1: /bin/pwd
0 1 3 4
0 1 3 4
3 4 0 1
2: /bin/ls
3 4 5 6
Received string: a.out
log
pipe2.c
play.c
@
有还有一些垃圾在那边,但我现在更关心的是我不懂指针!这两个命令虽然彼此独立,但并不真正使用管道。
UPDATE:垃圾字符是从不关闭字符串。现在我关闭它,并没有垃圾。
挂起是由于在分叉后主流程中“右”管的写入端没有正确关闭。正因为如此,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;
}
输出:
>> ./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
如果你有关于这个解决方案的具体问题,请让我知道:)还有一些其他的小问题您的原始代码:
- 使用指针是不必要的我们可以就在管道复制(性能肯定不会是一个问题;)
-
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;
}
要修复输出结尾的垃圾,请在最终的printf
之前添加以下行。
readbuffer[nbytes] = 0;
至于悬挂问题 - 我需要多一点想法来解决这个问题。我猜测这与管道和缓冲有关。
我想建议改变你的一切'的printf(...)调用''到fprintf中(错误,...)'。将标准IO('printf(3)')与低级别例程('pipe(2)'''dup2(2)'''close(2)')混合会比其值得的麻烦更麻烦。 – sarnold 2012-02-13 00:51:58
正式注意!我想夹板会同意。 – Ziggy 2012-02-13 00:55:47
打印之前,您不要终止字符串,这解释了垃圾。 'read'后尝试'readbytes [nbytes] = 0'。 – 2012-02-13 00:58:21