进程的数据流
标准输出是三大默认数据流之一。顾名思义,数据流就是流动的数据,数据从一个进程流出,然后流入另一个进程。除了标准输入、标准输出和标准错误,还有其他形式的数据流,例如文件连接和网络连接也属于数据流。重定向进程的输出,相当于改变进程发送数据的方向。原来标准输出会把数据发送到屏幕,现在可以让它把数据发送到文件。
进程含有它正在运行的程序,还有栈和堆数据空间。除此之外,进程还需要记录数据流的连向,比如标准输出连到了哪里。进程用文件描述符表示数据流,所谓的描述符其实就是一个数字。进程会把文件描述符和对应的数据流保存在描述符表中。下表中,前两列是描述符表,后一列是我自己加进去的。
# | 数据流 | 名称 |
---|---|---|
0 | 键盘 | 标准输入 |
1 | 屏幕 | 标准输出 |
2 | 屏幕 | 标准错误 |
3 | 数据库连接 | 进程也可能打开其他形式的数据流 |
描述符表的前三项万年不变: 0号标准输入, 1号标准输出, 2号标准错误。其他项要么为空,要么连接进程打开的数据流。比如程序在打开文件进行读写时,就会占用其中一项。创建进程以后,标准输入连到键盘,标准输出和标准错误连到屏幕。它们会保持这样的连接,直到有人把它们重定向到了其他地方。
重定向即替换数据流
标准输入/输出/错误在描述符表中的位置是固定的,但它们指向的数据流可以改变。也就是说,如果想重定向标准输出,只需要修改表中1号描述符对应的数据流就行了。
# | 数据流 |
---|---|
0 | 键盘 |
1 | |
2 | 屏幕 |
3 | 数据库连接 |
所有向标准输出发送数据的函数会先查看描述符表,看1号描述符指向哪条数据流,然后再把数据写到这条数据流中, printf()便是如此。在 Head First C (1) 数据流 一文中提到过,标准错误要用“2 >” 来重定向,因为2是标准错误在描述符表中的编号。在很多操作系统中,也可以 用 “1 > ” 来重定向标准输出。
fileno()返回描述符号
每打开一个文件,操作系统都会在描述符表中新注册一项。假设你打开了某个文件:
FILE *my_file = fopen("guitar.mp3", "r");
操作系统会打开guitar.mp3文件,然后返回一个指向它的指针,操作系统还会遍历描述符表寻找空项,把新文件注册在其中。那么如何根据文件指针知道它是几号描述符呢?答案是调用fileno()函数。
int descriptor = fileno(my_file); //它会返回4
# | 数据流 |
---|---|
0 | 键盘 |
1 | 屏幕 |
2 | 屏幕 |
3 | 数据库连接 |
4 | guitar.mp3文件 |
dup2()复制数据流
如果你想修改某个已经注册过的数据流,比如想让3号描述符重新指向其他数据流,该怎么做?可以用dup2()函数, dup2()可以复制数据流。
dup2(4, 3);
# | 数据流 |
---|---|
0 | 键盘 |
1 | 屏幕 |
2 | 屏幕 |
3 | |
4 | guitar.mp3文件 |
错误代码独家秘方
麻烦代码:
pid_t pid = fork();
if (pid == -1)
{
fprintf(stderr, "无法克隆进程: %s\n", strerror(errno));
return 1;
}
if (execle(...) == -1)
{
fprintf(stderr, "无法运行脚本: %s\n", strerror(errno)); //重复错误代码
return 1;
}
简单代码:
#include<stdlib.h> //为了使用exit系统调用,必须包含stdlib.h头文件
void error(char *msg)
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(1);
}
pid_t pid = fork();
if (pid == -1)
error("无法克隆进程");
if (execle(...) == -1)
error("无法运行脚本");
练习1
程序把rssgossip.py脚本的输出保存到stories.txt文件中。 程序只搜索一个RSS源, 其他都和newshound一样。 具体解释见上一篇。
int main(int argc, char *argv[])
{
char *phrase = argv[1];
char *vars[] = {"RSS_FEED=http://www.cnn.com/rss/celebs.xml", NULL};
FILE *f = fopen("stories.txt", "w"); //以“写”模式打开stories.txt
if (!f) //如果f是0,说明无法打开文件
error("Can't open stories.txt");
pid_t pid = fork();
if (pid == -1)
error("Can't fork process");
if (!pid) //这段代码会修改子进程,因为pid是0
{
if ( dup2(fileno(f),1)==-1 ) //令1号描述符指向stories.txt文件
error("Can't redirect Standard Output");
if (execle("/usr/bin/python", "/usr/bin/python", "./rssgossip.py",phrase, NULL, vars) == -1)
error("Can't run script");
}
return 0;
}
在这段代码执行结束后,新闻并没有被保存在stories.txt文件中,是因为子进程一旦创立就和父进程没有关系了,当子进程还没完成的任务的时候,父进程已经结束。所以,操作系统必须提供一种方式,让父进程等待子进程完成任务。修改程序:
#include <sys/wait.h>
...
//这一段代码加到newshound2程序底部
int pid_status;
// pid:进程号
// pid_status:这个变量用来保存进程信息,是一个int指针
if (waitpid(pid, &pid_status, 0) == -1)
error("等待子进程时发生了错误");
return 0;
}
重定向输入、输出,然后让进程相互等待, 进程间通信就这么简单。