基于管道的进程间通信代码分析

来源:互联网 发布:办公软件应用2003 编辑:程序博客网 时间:2024/05/16 12:20
实验设计
用pipe()创建一个管道文件,然后用fork()创建两个生产进程和两个消费进程,它们之间通过pipe()传递信息。
实验代码:
#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的本质就是intint 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.csdn.net/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 非有效的文件描述词, 或该文件已关闭.

0 0