菜鸟学习历程【19】进程通信(1)

进程通信(1)

定义:一些复杂的应用程序可能会需要多个进程分工协作来满足所需的功能需求,这就必然涉及到数据在进程之间的共享或交换,称为IPC(Inter-process communication,进程间通信)。

进程的用户空间是互相独立的,一般不能互相访问,唯一的例外是共享内存区;

目的:

  1. 数据传输:一个进程需要将它的数据发送到另一个进程,发送的数据量在一个字节到几兆字节之间。
  2. 共享数据:多个进程需要共享数据,一个进程对共享数据的修改,别的进程应该能看到。
  3. 通知事件:一个进程需要向另一个或一组进程发送信息,通知它(它们)发生了某件事情(如进程终止要通知父进程)。
  4. 资源共享:多个进程之间要共享同样的资源。为了做到这一点,需要内核提供锁和同步机制。
  5. 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有信息和异常,并能够知道它的状态。

方式:

  1. 管道(Pipe)和有名管道
  2. 信号(Signal)
  3. 消息队列
  4. 共享内存(Shared Memory)
  5. 信号量(Semaphore)
  6. 套接字(Socket)

管道通信

管道的通信方式分为:无名管道、有名管道,无名管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制;

特点

  • 半双工,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
  • 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
  • 单独构成一个独立的文件系统,管道对于管道两端的进程而言,就是一个文件,但不是一个普通的文件,不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在于内存中;
  • 数据的读出和写入,一个进程向管道中写的内容被管道另一端的进程读出,写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

无名管道

一、建立无名管道

pipe函数用于建立管道

int pipe(int filedes[2]);
说明:pipe()会建立管道,并将文件描述符由参数fildes数组返回,fildes[0]为管道里的读取端 fildes[1]为管道的写入端
返回值:成功返回0,否则返回-1

二、读写无名管道

管道两端可分别用描述符fd[0]和fd[1]来描述,需要注意的是,管道两端固定了任务,一端只能用于读,由fd[0]来表示,称为管道读端;另一端只能用于写fd[1],称为管道写端。如果试图在写端读取数据,或者在读端试图写入数据,则会发生错误。一般文件的I/O函数都可以用于管道,如close,read,write;

从管道中读取数据:
(1)如果管道写端不存在,则认为已经读到数据的末尾,读函数返回后的读出字节数为0;
(2)当管道的写端存在,但请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数;如果请求的数目不大于PIPE_BUF,则返回现有的数据字节数(此时管道里的数据字节数可能大于请求量或者小于请求量)

向管道中写入数据:
向管道中写入数据时,Linux将不保证写入的原子性(???),管道缓存区一有空闲区域,写进程就会试图向管道数据。。如果读进程不读走缓冲区的数据,那么写操作将一直阻塞。

注意:只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIFPIPE信号,应用程序可以处理该信号,也可以忽略。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main()
{
    int fd[2] = {0};
    int ret, count = 0;
    pid_t pid;

    ret = pipe(fd);  //创建无名管道
    if(-1 == ret)
    {
        perror("pipe");
        exit(1);
    }

    pid = fork();  //创建一个进程,实现子进程写,父进程读
    if(-1 == pid)
    {
        perror("fork");
        exit(1);
    }
    else if(0 == pid)  //子进程
    {
    //  sleep(1);
        count++;
        printf("Child Process:count = %d\n", count);
        ret = write(fd[1], &count, sizeof(count));
        if(-1 == ret)
        {
            perror("write");
            exit(1);
        }
    }
    else  //父进程
    {
        ret = read(fd[0], &count, sizeof(count));
        if(-1 == ret)
        {
            perror("read");
            exit(1);
        }
        count++;
        printf("Parent Process:count = %d\n", count);
    }
    return 0;
}

说明:对于管道而言,fd[0]为读管道,fd[1]为写管道,这两个不能搞反,对于一般的父子进程而言,如果子进程睡眠一秒后再执行,那么其父进程已执行完毕,且父进程中没有wait,则子进程变为孤儿进程,但对于管道而言,这种情况不会发生。因为,读会阻塞(读不到数据,一直等待,直到读到数据,),当子进程睡眠一秒后才写数据进入管道时,父进程会一直等待,所以这种情况下,子进程不会变为孤儿。

有名管道

管道应用有一个重大的限制,就是它没有名字,因此,只能应用于亲缘关系的进程间通信,在有名管道(FIFO或者Named Pipe)提出后,该限制得到了解决。FIFO不同于管道之处在于它提供了一个与路径名与之关联,以FIFO的形式存在于文件系统中。

一、创建有名管道

mkfifo用来创建有名管道

int mkfifo(const char *pathname, mode_t mode);

参数说明:mkfifo会依据pathname建立特殊的FIFO文件,该文件必须不存在,而参数mode为该文件的权限

二、读写有名管道

从管道中读取数据

(1)如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志的读操作来说则返回-1,当前errno值为EAGAIN,提醒稍后再试;
(2)读打开的阻塞标志只对本进程的第一个读操作施加作用,如果本进程内有多个读操作,则在第一个读操作被唤醒并完成读操作后,其他要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样;
(3)如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞;

向管道中写入数据

(1)对于设置了阻塞标志的写操作
1.当要写入的数据量不大于PIPE_BUF时,Linux会保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作
2.当要写入的数据量大于PIPE_BUF时,Linux不再保证写入的原子性。PIPI缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写得数据后返回。

(2)对于没有设置阻塞标志的写操作
1.当要写入的数据量大于PIPE_BUF时,Linux不再保证写入数据的原子性。在写满所有FIFO空闲缓冲区后,写操作返回
2.当要写入的数据量不大于PIPE_BUF时,Linux保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完成后返回;如果当期FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒稍后再写。

fifo_read.c

#include <stdio.h>                                           
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    int ret, fd;
    char buf[100] = {0};

    ret = mkfifo("fifo.tmp", O_CREAT | O_EXCL);
    if(-1 == ret)
    {
        perror("mkfifo");
        exit(1);
    }

    fd = open("fifo.tmp", O_RDONLY);
    if(-1 == fd)
    {
        perror("open");
        exit(1);
    }

    while(1)
    {
        ret = read(fd, buf, sizeof(buf));
        if(-1 == ret)
        {
            perror("read");
            exit(1);
        }

        if(!strncmp(buf, "bye", 3))
        {
            break;
        }

        printf("Read is: %s\n", buf);

        memset(buf, 0, sizeof(buf));

        unlink("fifo.tmp");
    }

    close(fd);
    return 0;
}
fifo_write.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int main()
{
    int ret, fd;
    char buf[100] = {0};

    fd = open("fifo.tmp", O_WRONLY);
    if(-1 == fd)
    {
        perror("open");
        exit(1);
    }

    while(1)
    {
        scanf("%s", buf);

        ret = write(fd, buf, strlen(buf));
        if(-1 == ret)
        {
            perror("write");
            exit(1);
        }

        if(!strncmp(buf, "bye", 3))
        {
            break;
        }

        memset(buf, 0, sizeof(buf));
    }

    close(fd);
    return 0;
}

上述的代码实现的就是一种简单的管道的读写操作,通过临时文件fifo.tmp,将两个毫无关系的进程联系起来,这既是有名管道的优势之处。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值