unix进程使用文件描述符0、1和2作为标准输入、输出和错误的通道。
当进程请求一个新的文件描述符的时候,系统内核将最低可用的文件描述符赋给它
1.将stdin定向到文件
(1)close-then-open
/* stdinredir1.c
* purpose: show how to redirect standard input by replacing file
* descriptor 0 with a connection to a file.
* action: reads three lines from standard input, then
* closes fd 0, opens a disk file, then reads in
* three more lines from standard input
*/
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
void main()
{
int fd ;
char line[100];
/* read and print three lines */
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
/* redirect input */
close(0);
fd = open("/etc/passwd", O_RDONLY);
if ( fd != 0 ){
fprintf(stderr,"Could not open data as fd 0\n");
exit(1);
}
/* read and print three lines */
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
}
(2)open-close-dup-close
(3)open-dup2-close
/* stdinredir2.c
* shows two more methods for redirecting standard input
* use #define to set one or the other
*/
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
#define CLOSE_DUP /* open, close, dup, close */
/* #define USE_DUP2 /* open, dup2, close */
void main()
{
int fd ;
int newfd;
char line[100];
/* read and print three lines */
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
/* redirect input */
fd = open("/etc/passwd", O_RDONLY); /* open the disk file */
#ifdef CLOSE_DUP
close(0);
newfd = dup(fd); /* copy open fd to 0 */
#else
newfd = dup2(fd,0); /* close 0, dup fd to 0 */
#endif
if ( newfd != 0 ){
fprintf(stderr,"Could not duplicate fd to 0\n");
exit(1);
}
close(fd); /* close original fd */
/* read and print three lines */
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
}
dup和dup2函数
2.为其他程序重定向I/O:who > userlist
/* whotofile.c
* purpose: show how to redirect output for another program
* idea: fork, then in the child, redirect output, then exec
*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
void main()
{
int pid ;
int fd;
printf("About to run who into a file\n");
/* create a new process or quit */
if( (pid = fork() ) == -1 ){
perror("fork");
exit(1);
}
/* child does the work */
if ( pid == 0 ){
close(1); /* close, */
fd = creat( "userlist", 0644 ); /* then open */
if(execlp( "who", "who", NULL ) == -1) /* and run */
perror("execlp");
exit(1);
}
/* parent waits then reports */
if ( pid != 0 ){
wait(NULL);
printf("Done running who. results in userlist\n");
}
}
文件描述符集合通过exec调用传递且不会被改变,shell使用进程通过fork产生子进程与子进程调用exec之间的时间间隔来重定向标准输入、输出到文件。
3.管道编程
(1)管道自己给自己发送数据
/* pipedemo.c * Demonstrates: how to create and use a pipe
* * Effect: creates a pipe, writes into writing
* end, then runs around and reads from reading
* end. A little weird, but demonstrates the idea.
*/
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
void main()
{
int len, i, apipe[2]; /* two file descriptors */
char buf[BUFSIZ]; /* for reading end */
/* get a pipe */
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]);
/* read from stdin, write into pipe, read from pipe, print */
while ( fgets(buf, BUFSIZ, stdin) ){
len = strlen( buf );
if ( write( apipe[1], buf, len) != len ){ /* send */
perror("writing to pipe"); /* down */
break; /* pipe */
}
for ( i = 0 ; i<len ; i++ ) /* wipe */
buf[i] = 'X' ;
len = read( apipe[0], buf, BUFSIZ ) ; /* read */
if ( len == -1 ){ /* from */
perror("reading from pipe"); /* pipe */
break;
}
if ( write( 1 , buf, len ) != len ){ /* send */
perror("writing to stdout"); /* to */
break; /* stdout */
}
}
}
(2)使用fork来共享信道
/* pipedemo2.c * Demonstrates how pipe is duplicated in fork()
* * Parent continues to write and read pipe,
* but child also writes to the pipe
*/
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#define CHILD_MESS "I want a cookie\n"
#define PAR_MESS "testing..\n"
#define oops(m,x) { perror(m); exit(x); }
void main()
{
int pipefd[2]; /* the pipe */
int len; /* for write */
char buf[BUFSIZ]; /* for read */
int read_len;
if ( pipe( pipefd ) == -1 )
oops("cannot get a pipe", 1);
switch( fork() ){
case -1:
oops("cannot fork", 2);
/* child writes to pipe every 5 seconds */
case 0:
len = strlen(CHILD_MESS);
while ( 1 ){
if (write( pipefd[1], CHILD_MESS, len) != len )
oops("write", 3);
sleep(5);
}
/* parent reads from pipe and also writes to pipe */
default:
len = strlen( PAR_MESS );
while ( 1 ){
if ( write( pipefd[1], PAR_MESS, len)!=len )
oops("write", 4);
sleep(1);
read_len = read( pipefd[0], buf, BUFSIZ );
if ( read_len <= 0 )
break;
write( 1 , buf, read_len );
}
}
}
(3)使用pipe、fork和exec
/* pipe.c
* * Demonstrates how to create a pipeline from one process to another
* * Takes two args, each a command, and connects
* av[1]'s output to input of av[2]
* * usage: pipe command1 command2
* effect: command1 | command2
* * Limitations: commands do not take arguments
* * uses execlp() since known number of args
* * Note: exchange child and parent and watch fun
*/
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#define oops(m,x) { perror(m); exit(x); }
void main(int ac, char **av)
{
int thepipe[2], /* two file descriptors */
newfd, /* useful for pipes */
pid; /* and the pid */
if ( ac != 3 ){
fprintf(stderr, "usage: pipe cmd1 cmd2\n");
exit(1);
}
if ( pipe( thepipe ) == -1 ) /* get a pipe */
oops("Cannot get a pipe", 1);
/* ------------------------------------------------------------ */
/* now we have a pipe, now let's get two processes */
if ( (pid = fork()) == -1 ) /* get a proc */
oops("Cannot fork", 2);
/* ------------------------------------------------------------ */
/* Right Here, there are two processes */
/* parent will read from pipe */
if ( pid > 0 ){ /* parent will exec av[2] */
close(thepipe[1]); /* parent doesn't write to pipe */
if ( dup2(thepipe[0], 0) == -1 )
oops("could not redirect stdin",3);
close(thepipe[0]); /* stdin is duped, close pipe */
execlp( av[2], av[2], NULL);
oops(av[2], 4);
}
/* child execs av[1] and writes into pipe */
close(thepipe[0]); /* child doesn't read from pipe */
if ( dup2(thepipe[1], 1) == -1 )
oops("could not redirect stdout", 4);
close(thepipe[1]); /* stdout is duped, close pipe */
execlp( av[1], av[1], NULL);
oops(av[1], 5);
}
4.管道并非文件
管道在unix进程只能单向数据流动,只有共同父进程的进程之间才可以使用管道连接。
(1)从管道中读数据
当进程试图从管道中读数据时,进程被挂起直到数据被写进管道;
当所有的写者关闭了管道,试图从管道中读取数据的调用返回0,这意味着文件的结束;
管道是一个队列,当进程从管道中读取完数据之后数据已经不存在了。
(2)向管道中写数据
写入数据阻塞直到管道有空间去容纳新的数据;
写入必须保证一个最小的块大小;
若无读者在读数据,则写操作失败。