进程是程序一次执行过程,用户态进程之间是隔离的,就像不同城市的人们,他们之间的通信方式有很多种

Linux通信方式主要从unix继承而来,而对unix发展做出巨大贡献的是AT&T和BSD,前者主要是对早期的unix进程间通信进行系统的改进和升级,形成了system V IPC,这种通信方式主要是单个计算机内,而后者则跳出了这个限制,形成了socket。而linux把这两者都继承下来了具体如下:

UNIX进程间通信(IPC)方式包括:管道,FIFO,信号

system v进程间通信(IPC):system v消息队列,system v信号量,system v共享内存区

posix进程间通信(IPC):posix消息队列,posix信号量,posix共享内存区

现在linux中使用较多的进程间通信有以下几种:

  • 管道和有名管道:管道用于有亲缘关系的进程之间,有名管道可以用在没有亲缘关系的进程之间
  • 信号:是在软件层次上对中断机制的一种模拟,比较复杂,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上是一样的
  • 消息队列:消息队列是消息的链接表,包括posix消息队列,system v 消息队列,克服了前两种信息量有限的限制,具有写权限可以按照一定的规则向消息队列中添加消息,对消息队列有读权限的进程可以从消息队列中读取消息
  • 共享内存:可以说这是最有用的进程间通信的方式,他使得多个进程可以访问同一个内存空间,不同进程可以看到对方进程中对共享内存中数据的更新,这种通信方式需要某种同步机制如互斥锁,信号量
  • 信号量:主要作为进程之间,以及同一进程的不同线程之间的同步或者互斥手段
  • 套接字:用于网络中进程的通信,应用非常广泛

管道是linux中很重要的通信方式,它是把一个程序的输出直接连接到另一个程序的输入,例如ps -ef| grep ntp如下图:

管道是linux中重要的通信方式,这里所说的是无名管道,无名管道具有以下特点:

  • 他只能用于具有亲缘关系的进程之间(也就是父子进程或者是兄弟进程之间)
  • 是一种半双工的通信模式,具有固定的读端和写端
  • 可以把管道看成是一种普通的文件,对他的读写可以使用普通的read()和write()函数,但是又不是普通的文件,不属于其他任何文件系统,并且只存在于内核的内存空间中

管道是基于文件描述符的通信方式,当一个管道建立时,他会创建两个文件描述符fds[0], fds[1],其中fds[0]用于读管道,fds[1]用于写管道,这样就构成了半双工的通道:

关闭管道只需调用close()逐个关闭各个文件描述符即可

ps:当一个管道共享多对文件描述符时,若将其中一对读写文件描述符删除,则此管道就失效了

管道创建函数pipe()函数语法如下:

管道读写说明:

通常是先创建一个管道,再通过fork()函数创建一个子进程,该子进程会继承父进程所创建的管道,此时父子进程的文件描述符如下所示:

此时看上出非常复杂,只需将相关的文件描述符关闭即可,将父进程的fd[1]和子进程的fd[0]关闭,就变成了子进程写,父进程读的管道:

ps:父进程可以创建多个子进程,子进程会继承相应的fd[0]和fd[1]此时只需要关闭相应端口就可以建立起各子进程之间的通道

这里的例子首先创建一个管道,然后使用fork()创建一个子进程,然后通过关闭父子进程描述符建立起他们之间的管道通信

#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_DATA_LEN 256
#define DELAY_TIME 1
int main()
{
    pid_t pid ;
    int pipe_fd[2] ;
    char buf[MAX_DATA_LEN] ;
    const char data[] = "pipe test program" ;
    int real_read, real_write ;
    memset((void *)buf, 0, sizeof(buf)) ;
		//创建管道
    if (pipe(pipe_fd) < 0)
    {
        printf("pipe create error\n") ;
        exit(1) ;
    }
    if ((pid = fork()) == 0)//子进程

    {
        close(pipe_fd[1]) ;//关闭写端
        sleep(DELAY_TIME * 3) ;//等待父进程关闭文件描述符
        if ((real_read = read(pipe_fd[0], buf, MAX_DATA_LEN))>0)
        {
            printf("%d bytes read from the pipe is '%s'\n", real_read,
                                                            buf) ;
        }
        close(pipe_fd[0]) ;
        exit(0) ;
    }
    else if (pid > 0)//父进程
    {
        close(pipe_fd[0]) ;//关闭读端
        sleep(DELAY_TIME) ;//等待子进程关闭写端
        if ((real_write = write(pipe_fd[1], data, strlen(data))) != -1)
        {
            printf("parent wrote %d bytes:'%s'\n", real_write, data) ;
        }
        close(pipe_fd[1]) ;
        waitpid(pid, NULL, 0) ;//收集子进程退出消息
        exit(0) ;
    }
}

运行结果如下:

管道读写注意一下几点:

  1. 只有存在读端,向管道中写才有意义,否则会传来SIGPIPE信号
  2. 向管道写入数据时不能保证其原子性,管道缓冲区一有空闲区域写端就是想其中写入,如果读进程不读取管道缓冲区中的数据,则写操作就会被阻塞
  3. 父子进程在运行时不能保证先后次序,这里为了保证父子进程已经关闭了相应的文件描述符,可以在两个进程中调用sleep函数,这个不是好的解决办法,学习了进程的同步与互斥之后可以修改程序
Logo

更多推荐