#include "sys/types.h"
#include "sys/file.h"
#include "stdio.h"
#include "unistd.h"
#include "string.h"
#include "stdlib.h"
char r_buf[4]; //读缓冲
char w_buf[4]; //写缓冲
int pipe_fd[2];
pid_t pid1, pid2, pid3, pid4;//pid_t的本质就是int
int producer(int id);
int consumer(int id);
int main(int argc,char **argv)
{
if(pipe(pipe_fd)<0){
printf("pipe create error \n");
exit(-1);
}else{
printf("pipe is created successfully!\n");
if((pid1=fork())==0) producer(1);
if((pid2=fork())==0) producer(2);
if((pid3=fork())==0) consumer(1);
if((pid4=fork())==0) consumer(2);
}
close(pipe_fd[0]); //需要加上这两句
close(pipe_fd[1]); //否这会有读者或者写者永远等待
int i,pid,status;
for(i=0;i<4;i++){
pid=wait(&status);
}
exit(0);
}
int producer(int id)
{
printf("producer %d is running!\n",id);
close(pipe_fd[0]);
for (int i = 0; i < 10; i++) {
sleep(3); //执行挂起3秒
if(id==1) //生产者1
strcpy(w_buf,"aaa\0");//每次重新清空数组并写入
else //生产者2
strcpy(w_buf,"bbb\0");
if(write(pipe_fd[1],w_buf,4)==-1){
printf("write to pipe error\n");
break;
}else{
printf("producer %d write %s to pipe\n",id, w_buf);
}
}
close(pipe_fd[1]);
printf("producer %d is over!\n",id);
exit(id);//退出进程导致其不再执行后面的fork()函数
}
int consumer(int id)
{
close(pipe_fd[1]);
printf("consumer %d is running!\n",id);
if (id==1) //消费者1
strcpy(w_buf,"ccc\0");
else //消费者2
strcpy(w_buf,"ddd\0");
while(1)//忙等待
{
sleep(1);//挂起1秒
strcpy(r_buf,"eee\0");
if(read(pipe_fd[0],r_buf,4)==0) break;
printf("consumer %d get %s, while the w_buf is %s\n",id,r_buf,w_buf);
}
close(pipe_fd[0]);
printf("consumer %d is over!\n", id);
exit(id);//退出进程导致其不再执行后面的fork()函数
}
pipe(pipe_fd)函数会建立管道,将文件描述词由参数pipe_fd数组返回。因为子进程复制了父进程的打开文件表,所以pipe()所建立的通信管道可被子进程继承,生产和消费进程可以通过对同一管道文件的读写进行通讯。
pipe_fd[0]为管道里的读取端。
pipe_fd[1]则为管道的写入端。
若成功则返回零,否则返回-1,错误原因存于errno中。
错误代码:
EMFILE 进程已用完文件描述词最大量。
ENFILE 系统已无文件描述词可用。
EFAULT 参数 filedes 数组地址不合法。
当管道中的数据被读取后,管道为空。一个随后的read()调用将默认的被阻塞,等待某些数据写入。若需要设置为非阻塞,则可做如下设置:
fcntl(pipe_fd[0], F_SETFL, O_NONBLOCK);
fcntl(pipe_fd[1], F_SETFL, O_NONBLOCK);
有关fork()函数的详细讲解,请看http://blog.youkuaiyun.com/stephan14/article/details/42872087这篇文章。
关于wait()函数,如果父进程创建的子进程退出后,父进程不调用wait()函数回收子进程的结束信息,子进程就会变成僵尸进程,如果子进程变成僵尸进程,那么会造成以下两点问题:
1.它的进程标示符占据着,意味着海量的子进程会占据满进程表项,会使后来的进程无法fork。
2.它的内核栈无法被释放掉,因为在栈的最低端,有着thread_info结构,它包含着struct_task结构,这里包含着一些退出信息。
有关僵尸进程的介绍:
一个进程终止的方法很多,进程终止后有些信息对于父进程和内核还是很有用的,例如进程的ID号、进程的退出状态、进程运行的CPU时间等。因此进程在终止时,回收所有内核分配给它的内存、关闭它打开的所有文件等等,但是还会保留以上极少的信息,以供父进程使用。父进程可以使用 wait/waitpid 等系统调用来为子进程收拾,做一些收尾工作。
因此,一个僵尸进程产生的过程是:父进程调用fork创建子进程后,子进程运行直至其终止,它立即从内存中移除,但进程描述符仍然保留在内存中(进程描述符占有极少的内存空间)。子进程的状态变成EXIT_ZOMBIE,并且向父进程发送SIGCHLD 信号,父进程此时应该调用 wait() 系统调用来获取子进程的退出状态以及其它的信息。在 wait 调用之后,僵尸进程就完全从内存中移除。因此一个僵尸存在于其终止到父进程调用 wait 等函数这个时间的间隙,一般很快就消失,但如果编程不合理,父进程从不调用 wait 等系统调用来收集僵尸进程,那么这些进程会一直存在内存中。
至于write与read函数:
ssize_t write (int fd, const void * buf, size_t count);
write()会把参数buf 所指的内存写入count 个字节到参数fd 所指的文件内. 当然, 文件读写位置也会随之移动。
如果顺利write()会返回实际写入的字节数. 当有错误发生时则返回-1, 错误代码存入errno 中.
错误代码:
EINTR 此调用被信号所中断.
EAGAIN 当使用不可阻断I/O 时 (O_NONBLOCK), 若无数据可读取则返回此值.
EADF 参数fd 非有效的文件描述词, 或该文件已关闭.
ssize_t read(int fd, void * buf, size_t count);
read()会把参数fd 所指的文件传送count 个字节到buf 指针所指的内存中. 若参数count 为0, 则read()不会有作用并返回0. 返回值为实际读取到的字节数, 如果返回0, 表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动.如果顺利 read()会返回实际读到的字节数, 最好能将返回值与参数count 作比较, 若返回的字节数比要求读取的字节数少, 则有可能读到了文件尾等。当有错误发生时则返回-1, 错误代码存入errno 中, 而文件读写位置则无法预期.
错误代码:
EINTR 此调用被信号所中断.
EAGAIN 当使用不可阻断I/O 时(O_NONBLOCK), 若无数据可读取则返回此值.
EBADF 参数fd 非有效的文件描述词, 或该文件已关闭.