8.ipc

作者: 大雄good | 来源:发表于2017-01-03 16:21 被阅读0次

    进程间通信

    Linux中的进程间通信主要有:管道FIFO消息队列信号量共享存储以及网络IPC中的套接字

    1.管道

    管道是最古老的IPC,有以下两种限制:

    • 半双工
    • 只能在具有公共祖先的两个进程间使用

    管道通过pipe函数创建:

    #include<unistd.h>
    int pipe(int fd[2]);
    /* 返回值:成功返回0,失败返回-1*/
    

    fd返回两个文件描述符:fd[0]为读打开,fd[1]为写打开。fd[1]的输出是fd[0]的输入。其关系可以用下图描述:

    pipe

    而对于进程间的pipe关系为:

    进程间pipe

    对于管道一端被关闭后,下列两条规则适用:

    • 一个写端被关闭的管道时,所有数据都被读取后,read返回0,表示文件结束。
    • 一个读端被关闭的管道,则产生信号SIGPIPE。如果忽略该信号或者捕捉该信号并从其处理程序返回,则write返回-1,errno设置为EPIPE

    下面通过实例程序演示,创建一个从父进程到子进程的管道,并且父进程经由该管道向子进程传送数据。

    #include "apue.h"
    
    int
    main(void)
    {
        int     n;
        int     fd[2];
        pid_t   pid;
        char    line[MAXLINE];
    
        if (pipe(fd) < 0)
            err_sys("pipe error");
        if ((pid = fork()) < 0) {
            err_sys("fork error");
        } else if (pid > 0) {       /* parent */
            close(fd[0]);
            write(fd[1], "hello world\n", 12);
        } else {                    /* child */
            close(fd[1]);
            n = read(fd[0], line, MAXLINE);
            write(STDOUT_FILENO, line, n);
        }
        exit(0);
    }
    

    2.函数popen和pclose

    标准I/O库提供了两个函数popenpclose用于实现:创建一个管道,fork一个子进程,关闭未使用的管道端,执行一个shell运行命令,然后等待命令终止。

    #include <stdio.h>
    FILE *popen(const char *cmdstring, const char *type);
    /* 返回值:成功返回文件指针,失败返回NULL */
    int pclose(FILE *fp);
    /* 返回值:成功返回cmdstring的终止状态,失败返回-1 */
    

    函数popen先执行fork,然后调用exec执行cmdstring,返回一个标准I/O文件指针。如果type是“r”,则文件指针连接到cmdstring的标准输出;如果type是“w”,则文件指针连接到cmdstring的标准输入。

    popenRead

    <center>read</center>

    popenWrite
    <center>write</center>
    shell会自动扩展cmdstring的语句,因此popen正常可以如下使用:
    fp = popen("ls *.c", "r");
    

    利用这两个函数可以减少代码量。

    3.FIFO

    FIFO又被称为命名管道,与PIPE不同,FIFO可以用于不相关的进程间通信。创建FIFO的方法与创建文件类似,因为FIFO也是一种文件类型,FIFO的路径名存在于文件系统中。

    #include <sys/stat.h>
    int mkfifo(const char *path, mode_t mode);
    int mkfifoat(int fd, const char *path, mode_t mode);
    /* 返回值:成功返回0,失败返回-1 */
    

    mkfifomodeopenmode相同。mkfifoat可以用来在fd文件描述符指定的位置创建FIFO

    FIFO可以由多个写进程,通常有以下两种用途:

    • shell命令使用FIFO将数据从一条管道传送到另一条时,无需创建中间临时文件
    • 客户进程-服务器进程应用程序中,FIFO用作汇聚点,在二者之间传递数据。

    4.消息队列

    消息队列是消息的连接表,存储在内核中,由消息队列标识符标识。msgget用于创建一个新队列或者打开一个现有队列。msgsnd将新消息添加到队列尾端。msgrcv用于从队列中取消息。

    每个队列都有一个msgid_ds结构与其关联:

    struct msqid_ds {
        struct ipc_perm msg_perm;   /* ipc限制 */
        msgqnum_t   msg_qnum;       /* 队列中消息数 */
        msglen_t    msg_qbytes;     /* 队列最大字节数 */
        pid_t       msg_lsPID;      /* 最后添加消息的pid */
        pid_t       msg_lrpid;      /* 最后取消息的pid */
        time_t      msg_stime;      /* 最后添加消息的时间 */
        time_t      msg_rtime;      /* 最后取消息的时间 */
        time_t      msg_ctime;      /* 最后change的时间 */
        ...
        };
    

    5.信号量

    信号量与前面的IPC不同,它是一个计数器,用于为多个进程提供对共享数据对象的访问。

    为了获得共享资源,进程需要执行下列操作:

    1. 测试控制该资源的信号量
    2. 若此信号量为正,则进程可以使用该资源,在该情况下,进程将信号量值减一,表示它占用了一个资源单元
    3. 否则,若此信号量为0,则进程进入休眠状态,直到信号量大于0,进程被唤醒,返回至步骤1.

    信号量的由一个无名数据结构表示,至少包含下列成员:

    struct{
        unsigned short  semval; /* 信号量的值 */
        pid_t           sempid; /* 最后一次操作的pid */
        unsigned short  semncnt;/* 等待信号量大于0的进程数量 */
        unsigned short  semzcnt;/* 等待信号量等于0的进程数量 */
        ...
        };
    

    6.共享存储

    共享存储允许两个或多个进程共享一个给定的存储区。因为数据不需要在进程间复制,因此这是最快的IPC。对于共享存储需要注意的就是对多个进程访问存储区的同步。

    内核为每个共享存储段维护这一个结构,该结构至少要为每个共享存储段包含以下成员:

    struct shmid_ds{
        struct ipc_perm shm_perm;   /* ipc限制 */
        size_t          shm_segsz;  /* 字节数表示的段大小 */
        pid_t           shm_lpid;   /* 最后shmop()的pid */
        pid_t           shm_cpid;   /* creator 的pid */
        shmatt_t        shm_nattch; /* 当前attached的进程数量 */
        time_t          shm_atime;  /* 最后attach的时间 */
        time_t          shm_dtime;  /* 最后dattach的时间 */
        time_t          shm_ctime;  /* 最后改变的时间 */
        ...
    };
    

    相关文章

      网友评论

          本文标题:8.ipc

          本文链接:https://www.haomeiwen.com/subject/grlnvttx.html