0.摘要
概念与技巧
-I/O重定向:概念与原因
-标准输入,输出和标准错误的定义
-重定向标准I/O到文件
-使用fork来为其他程序重定向
-管道(Pipe)
-创建管道后调用fork
相关的系统调用与函数
-dup,dup2
-pipe
1.shell编程
首先将介绍编写shell脚本时的I/O重定向和管道起的作用.然后,本章将介绍操作系统中对I/O重定向的支持.最后,写一个程序来改变进程的输入和输出流..
utmp函数列出用户列表
2.标准I/O与重定向的若干概念
shell中的三种流分别位如下三种:
1.标准输入–需要处理的数据流
2.标准输出–结果数据流
3.标准错误输出–错误消息流
2.1 3个标准文件描述符
stdin:0
stdout:1
stderr:2
2.2默认的连接:tty
在unix和linux中,shell默认链接着stdin,stdout,stderr三个文件。很多程序没有输入,只是把正确的输出写到文件描述符位1,并且把错误消息写到文件描述符2中.
2.3重定向I/O的是shell来操作而不是程序
当使用输出重定向标志,命令cmd>filename是由shell将文件描述符1定位到文件.把原有的链接打断.
本章的三个主要的工作:
1.who>userlist 将stdout连接到一个文件
2.sort
3.如何将stdin定向到文件
进程是从文件描述符读取数据的.并且由将标准输入重定向到可以从文件读入数据.
3.1.方法1:close then open
开始的时候,0连接输入,1连接输出,2连接出错.三种标准流是被连接到终端设备上的.通过文件close可以指定标准的输入输出关闭。如果调用close来关闭程序的stdin(0),之后打开open一个指定文件,则它会按照最低可用文件描述符来匹配,得到打开新文件对应到stdin位置
close(0);
fd = open(<file path>,O_RDONLY);
3.2.方法2:open..close..dup..close
1.open打开一个文件,fd不为0,
2.close(0)关闭文件描述符为0。
3.dup(fd)来将文件描述符fd做一个复制。把复制得到的文件fd根据最低可用文件描述符原则与0相连。
4.close(fd)关闭最先打开的文件。
其中在这个方法中,close(0)和dup(fd)可以结合在一起作为一个单独的系统调用dup2
fd=open(<file path>)
close(0)
dup(fd)
close(fd)
3.3.方法3:open..dup2..close
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
//#define DUP2
#define CLOSE_DUP
void main()
{
int fd;
int newfd; //the new file descriptor
char line[100];
fgets(line,100,stdin);
printf("%s",line);
fd = open("./data",O_RDONLY); //read data from file which creates by myself
#ifdef CLOSE_DUP
close(0);
dup(fd);
#else
newfd = dup2(fd,0);
#endif
if(newfd !=0)
{
perror("gouge cuole");
exit(1);
}
close(fd); //every time you wouldn't use the file descriptor, you can close it
fgets(line,100,stdin);
printf("%s",line);
}
4.为其他程序重定向I/O:who>userlist
伪代码:
pid=fork()
if(pif ==0 )
{
close(1);
fd=creat(<file>,0644);//open a newho file
execlp("who","who",NULL);
perror("execlp");
exit(1);
}
if(pid!=0)
{
wait(NULL);
}
重定向到文件的小结:
1.标准输入、输出以及错误输出分别对应于文件描述符0、1、 2
2.内核总是使用最低可用文件描述符
3.文件描述符集合通过exec调用传递,且不会被改变。
5.管道编程
管道是内核中的一个单向的数据通道。管道有一个读取端和一个写入端。实现who|sort的两个技巧:如何创建管道,如何使标准输入和输出通过管道联系起来。
5.1创建管道
result = pipe(int array[2]);
其中管道的输出端是array[0]
中的文件描述符表示,输入端是array[1]
中的文件描述符表示.
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
void main()
{
int len,i,apipe[2]; /*two file descriptor*/
char buf[BUFSIZ];
if(pipe(apipe)==-1)
{
perror("could not make pipe");
exit(1);
}
printf("Got a pipe! It is file descriptors: {%d %d \n",apipe[0],apipe[1]);
while(fgets(buf,BUFSIZ,stdin))
{
len = strlen(buf);
if(write(apipe[1],buf,len)!=len)
{
perror("writing to pipe");
break;
}
for(i=0;i<len;i++) /*wipe*/
buf[i]='X'; //nothing will be changed
if((len = read(apipe[0],buf,BUFSIZ))==-1)
{
perror("reading from price");
break;
}
if(write(1,buf,len)!=len){ /*send*/
perror("writing to stdout"); /*to*/
break; /*pipe*/
}
}
}
可以看到上述代码创建一个管道,默认使用3和4文件描述符.执行过程如图
当然很少有自己拿个管道和自己玩的,一般用在进程间通信的比较多,要把pipe和fork结合起来玩.
5.2使用fork来共享管道
父进程创建子进程后,他们之间共享管道,两个进程都访问管道的两端。而进程创建时复制了文件描述符表等其他内容。因此管道可以用作进程间的数据交流
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#define CHILD_MESS "I want a cookie\n"
#define PARENTS_MESS "I am the parents\n"
#define opps(m,x) {perror(m);exit(x);}
void main()
{
int apipe[2];
int len;
int read_len;
char buf[BUFSIZ];
if(pipe(apipe)==-1)
{
opps("pipe",1);
}
switch(fork())
{
case -1:
opps("fork ",1);
case 0:
len = strlen(CHILD_MESS);
while(1){
if(write(apipe[1],CHILD_MESS,len)!=len) //write the CHILD_MESS to parents
opps("child wirte",3);
sleep(5);
}
/*parent reads from pipe and write to pipe*/
default:
len = strlen(PARENTS_MESS);
while(1){
if(write(apipe[1],PARENTS_MESS,len)!=len) //write the PARENTS_MESS to apipe[0]
opps("parents write",4);
sleep(1);
read_len = read(apipe[0],buf,BUFSIZ);
if(read_len <=0)
break;
write(1,buf,read_len);
}
}
}
当然这个管道存在大家都可以往里面写,可以在里面读,所以就容易造成混乱.所以要让子进程写,让父进程读,在不同的进程中关闭无用的文件描述符.
5.3技术细节:管道并非文件
管道像文件一样,是一种不带有任何结构的字节序列,另一方面,管道又与文件不同.
1.从管道中读数据
(1)管道读取 阻塞
当进程试图从管道中读取数据时,进程被挂起直到读取完成,随之而来的如何避免这个问题?(flush写完成之后flush一下?)
(2)管道的读取结束标志
当所有写者关闭了管道的写数据端时,试图从管道读取数据的调用返回0,意味着文件的结束
(3)多个读者可能引起麻烦
管道是一个队列。当进程从管道中读取数据之后,数据已经不存在了。所以多个读者可能会出现读取数据不完整的情况.
2.向管道中写数据
(1)写入数据阻塞直到管道有空间去接纳新的数据
如果一次写入量超过管道大小,则等管道传输完前一部分,再导入一部分。
(2)写入必须保证一个最小的块大小:POSIX保证内核不会被拆分成小于512字节的块,而linux保证的是4096字节连续.
(3 )若无读者在读取数据,则写操作执行失败
如果所有的读者关闭了管道的端口,那么首先,内核发送SIGPIPE消息给进程。若进程被终止,则无任何事情发横。否则,write返回-1报错,errno设置位EPIPE
以上已经介绍了了如何在一个进程中输入,输出数据.那么数据是怎么在两个进程直接传递,切是双向传递呢?还有上述说的管道只是在父子进程之间能够起到传递数据的作用,且是单项传递的,如何做到双向传递?如何在两个距离很远的计算机之间传递信息?如何做到可靠传输?