进程间通讯

函数摘要

函数 说明 成功 失败
管道和FIFO
pipe 单工通讯 0 -1
popen 执行一个shell命令,封装的PIPE和exec功能 文件指针 NULL
pclose 关闭标准I/O流,等待命令终止,而后返回shell的终止状态 返回cmdstring的终止状态 -1
mkfifo 打开一个fifo文件 0 -1
mkfifoat 打开一个fifo文件 0 -1
IPC的操做
ftok 路径和id建立一个IPC的key 返回key -1
消息队列 不推荐使用,FIFO代替
msgget 建立一个消息队列 返回消息队列ID -1
msgctl 修改消息队列的msqid_ds 0 -1
msgsnd 发送消息 0 -1
msgrcv 接受消息 消息数据部分的长度 -1
信号量 不推荐使用,用记录锁代替
semget 得到一个信号量 信号量队列ID -1
semctl 修改信号量的的semid_ds 0 -1
semop 执行信号集合上的操做数字 0 -1
共享存储
shmget 建立一个共享存储 共享存储的ID -1
shmctl 修改共享存储的shmid_ds 0 -1
shmat 链接一个共享存储的地址 指向共享存储段的指针 -1
shmdt 将共享存储的地址分离 0 -1
unix域套接字
socketpair 建立一个UNIX域套接字传输管道(相似于pipe) 0 -1

名称解释

  • 管道:shell

    • UNIX系统IPC的最古老形式,全部UNIX系统都提供次通讯机制
    • 返回2个文件描述符
    • 局限
      • 历史上是半双工。不该预约假定系统支持全双工
      • 只能在具备公共祖先的进程之间使用。一般,一个管道由一个进程建立,在进程调用fork以后,这个管道就能在父子进程之间使用。
  • FIFO:服务器

    • FIFO是一种文件类型
    • 未命名管道(pipe)只能在两个相关的进程之间使用,这两个相关的进程还要有一个共同的建立了他们的祖先进程。经过FIFO,不相关的进程也能交换数据。
  • XSI IPC:消息队列、信号量及共享存储器网络

  • IPC的标识符: 每一个内核中的IPC结构都用一个非负证书的标识符加以引用。dom

    • 与文件描述符不一样,IPC标识符不是小的整数。当一个IPC结构被建立,而后又被删除时,与这种结构相关的标识符连续加1,直到达到一个整形数的最大值,而后又回转到0.
    • 外部名:标识符是IPC对象的内部名。为了使多个合做进程可以在同一IPC对象上汇聚,须要提供一个外部名。为此每一个IPC对象都与一个键(key)相关联,将这个键做为该对象的外部名。
    • 获取方式
      • 服务器进程能够指定键IPC_PRIVATE建立一个新IPC结构,将返回的标识符存放在(如一个文件)以便客户进程取用。缺点:文件系统操做须要服务器将整型标识符写到文件中,此后客户进程又要取得次标识符。
      • 能够在一个公共头文件中定义一个客户进程和服务器进程都承认的键,而后服务器进程指定此键建立一个新的IPC结构。缺点:此键已被使用,则get函数出错。 服务器需删除已存在的IPC结构,而后试着建立它。
      • 客户进程和服务器进程认同一个路径名和项目ID,调用ftok将这两个值变换为一个键,而后使用上面的方法中使用此键
  • IPC的权限结构: XSI IPC为每一个IPC结构关联一个ipc_perm结构,规定了权限和全部者。socket

struct ipc_perm {
    uid_t uid; /*全部者(启动)userid*/
    gid_t gid; /*全部者(启动)groupid*/
    uid_t cuid; /*建立者userid*/
    gid_t cgid; /*建立者userid*/
    mode_t mode; /*访问模块*/
    ...
}

输入图片说明

  • IPC的结构限制: 3种XSI IPC都有内核限制,能够经过从新配置内核来改变。函数

  • 优势和缺点oop

    • IPC结构实在系统范围内起做用,没有引用计数器。
      • 例如:建立一个消息队列,而且在该队列中放入了几则消息。那么该消息队列及其内容不会被删除,直到主动删除。
    • IPC结构在文件系统中没有名字。
    • IPC不使用文件描述符,因此不能使用多路转接I/O函数(select/poll)。没有某种形式的忙等待循环(busy-wait loop),就不能使一个服务器进程等待要放在消息队列中任意一个中的消息。
  • 消息队列:消息队列是消息的连接表,存储在内核中,由消息队列标识符标识。测试

函数

管道

建立
#include <unistd.h>

int pipe(int fd[2]);

    -- '成功:0;出错:-1'
  • 特色:
    • 在fork以前,先建立一个管道。
    • 两个文件描述符:fd[0]为读而打开,fd[1]为写而打开
    • 父进程-->子进程:父进程关闭管道fd[0],子进程关闭fd[1]
    • 子进程-->父进程:父进程关闭管道fd[1],子进程关闭fd[0]
      • 当读(read)一个写端已被关闭的管道时,在全部数据都被读取后,read返回0,表示文件结束。
      • 若是(write)一个读端已被关闭的管道,则产生信号SIGPIPE。若是忽略该信号或者捕捉该信号从其处理程序返回,则write返回-1,errno=EPIPE。
      • 在写管道(或FIFO)时,常量PIPE_BUF规定了内核的管道缓冲区大小。多个进程同时写一个管道,字节超过PIPE_BUF,那么咱们所写的数据可能会与其它进程所写的数据相互交叉。

输入图片说明

popen和pclose
#include <stdio.h>

FILE *popen(const char *cmdstring, const char *type);

    -- '成功:文件指针;出错:NULL'

int pclose(FILE *fp);

    -- '成功:返回cmdstring的终止状态;出错:-1'
  • 特色:ui

    • popen的功能:建立一个管道,fork一个子进程,关闭未使用的管道端,执行一个shell运行命令,而后等待命令终止。设计

    • popen先执行fork,而后调用exec执行cmdstring,而且返回一个标准I/O文件指针。

      • type=r:文件指针链接到cmdstring的标准输出。
      • type=w:文件指针链接到cmdstring的标准输入。
    • pclose:关闭标准I/O流,等待命令终止,而后返回shell的终止状态。

输入图片说明

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'
  • 参数:

    • mode:与open函数中的mode相同。
    • mkdfifoat的path:
      • 绝对路径:fd被忽略
      • 相对路径:fd是一个打开目录的有效文件描述符,路径名和目录有关。
      • 相对路径且fd参数有一个特殊值AT_FDCWD:则路径名以当前目录开始,mkfifoat和mkfifo相似。
  • 特色:

    • 使用一个FIFO的时候,默认已知其路径
    • 当mkfifo与mkfifoat建立FIFO时,要用open来打开它。
    • 当open一个FIFO时,非阻塞标志(O_NONBLOCK) 会产生下列影响:
      • 通常状况下(没有指定O_NONBLOCK),只读open要阻塞到某个其它进程为写而打开的FIFO为止
      • 通常状况下(没有指定O_NONBLOCK),只写open要阻塞到某个其它进程为读而打开的FIFO为止
      • 指定O_NONBLOCK,只读当即返回。若是没有写方向,则返回-1,errno=SIGPIPE
      • 指定O_NONBLOCK,只写当即返回。若是没有读方向,则返回-1,errno=ENXIO
    • 某个FIFO的最后一个写进程关闭了该FIFO,则将为FIFO的读进程产生一个文件结束标志。
    • 一个给定的FIFO有多个写进程是常见的,若是不但愿多个进程所写的数据交叉,则必须考虑原子写操做
  • 用途:

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

IPC的操做

ftok:路径和id建立一个IPC的key
#include <sys/ipc.h>

key_t ftok(const char *path ,int id);

    -- '成功:返回键;出错:返回-1'

消息队列(不推荐使用,FIFO代替)

msgget:打开一个现有队列或建立一个新消息队列
#include <sys/msg.h>

int msgget(key_t key,int flag);

    -- '成功:返回消息队列ID;出错:-1'
    
// msqid_ds结构

struct msqid_ds {
    struct icp_perm msg_perm;   /*see Section 15.6.2*/
    msgqnum_t       msg_qnum;   /* # of message on queue */
    msglent_t       msg_qbytes; /* max # of bytes on queue */
    pid_t           msg_lspid;  /* pid of last msgsnd() */
    pid_t           msg_lrpid;  /* pid of last msgrcv() */
    time_t          msg_stime;  /* last-msgsnd() time */
    time_t          msg_rtime;  /* last-msgrcv() time */
    time_t          msg_ctime;  /* last-change time */
    ...
}
msgctl:修改msqid_ds
#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

    -- '成功:0;出错:-1'

*参数:

参数 说明
ICP_STAT 取次队列的msqid_ds结构,并将它存放在buf指向的结构中
ICP_SET 取次队列的msg_perm.uid,msg_perm.gid,msg_perm.mode和msg_qbytes从buf指向的结构复制到与这个队列相关的msqid_ds结构中。
ICP_RMID 从系统中删除该消息对i额以及人在该队列中的全部数据。
msgsnd:将数据放到消息队列
#include <sys/msg.h>

int msgsnd(int msqid, const void *ptr, size_t nbytes,int flag);

    --'成功:0;出错:-1'

//ptr设计成以下
struct mymesg {
    long mtype;
    char mtext[512];
};
  • 参数:

    • ptr:一个长整型数,包含了正的整型消息类型,其后紧接着的是消息数据。
    • flag:指定是否阻塞。设置成IPC_NOWAIT,使得msgsnd当即出错返回EAGAIN。
#include <sys/msg.h>

ssize_t msgrcv(int msqid, void *ptr, size_t bytes, long type, int flag );

    -- '成功:消息数据部分的长度;出错:-1'
  • 参数:

    • ptr:长整型数,消息的类型及存储实际消息数据的缓冲区。
    • nbytes:长整型数,消息的类型。
    • type: 指定想要哪种消息:
      • type0:队列中的第一个消息。
      • type>0:返回队列中消息类型为type的第一个消息。
      • type<0:返回队列中消息类型值小于等于type绝对值的消息,若是这种消息有若干个,则类型值最小的消息。

信号量(不推荐使用。记录锁代替)

  • 做用:用于共享资源的同步。

  • 信号量操做流程:

    • 测试控制资源的信号量。
    • 若此信号量的值为正,则进程可使用该资源。若次信号量的值为0,则进程进入休眠状态,直到信号量大于0,进程被唤醒,从头开始。
  • 经常使用的信号量呗称为二元信号量(binary semaphore)

  • 缺点:

    • 信号量并不是单个非负值,而必需定义为一个多个信号量值的集合。
    • 建立(semget)是独立于它的初始化(semctl)的。不能原子化的建立和初始化一个信号量。
    • 即便没有进程正则使用各类形式的XSI IPC,他们仍然是存在的。有得程序终止时没有释放已分配的信号量,必须考虑这种状况
建立
#include <sys/sem.h>

int semget(key_t key, int nsems, int flag );

    -- '成功:信号量ID;出错:-1'
修改
#include <sys/sem.h>

int semctl(int semid, int semnum,int cmd, ..../*uion semun arg */);
    
        -- '根据cmd返回'
执行信号集合上的操做数字
#include <sys/sem.h>

int semop(int semid, struct sembuf semoparray[], sieze_t nops);

    --'成功:0;出错:-1'

共享存储

  • 做用:容许两个或多个进程共享一个给定的存储区。
  • 最快的IPC:数据不须要在客户进程和服务器直接复制。
建立
#include <sys/shm.h>

int shmget(key_t key, size_t size, int flag);

    --'成功:共享存储的ID;出错:-1'
修改
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmids_ds *buf);

    -- '成功:0;出错:-1'

// 共享存储的结构
struct shmid_ds {
    struct      ipc_perm shm_perm;   /*权限结构*/
    size_t      shm_setgsz;          /*size of segment in bytes*/
    pid_t       shm_lpid;            /*pid of last shmop()*/
    pid_t       shm_cpid;            /*pid of creator*/
    shmatt_t    shm_nattch;          /*number of current attaches*/
    time_t      shm_atime;           /*last-attach time*/
    time_t      shm_dtime;           /*last-detach time*/
    time_t      shm_ctime;           /*last-change time*/
    ...
}
  • 参数:

    • cmd
      • IPC_STAT:取此段的shmid_ds结构,并将它存储在buf指向的结构中。
      • IPC_SET:按buf指向的结构中的值设置共享存储段相关的shmid_ds结构中的3个字段:shm_perm.uid,shm_perm.gid和shm_perm.mode
      • IPC_RMID:从系统中删除该共享存储段。
      • IPC_LOCK:在内存中对共享存储段加锁。
      • IPC_UNLOCK:解锁。
链接
#include <sys/shm.h>

void *shmat(int shmid, const void *addr, int flag);

    -- '成功:指向共享存储段的指针;出错:-1'
分离
#include <sys/shm.h>

int shmdt(const void *addr);

    -- '成功:0;出错:-1'
  • 特色:

    • 使得shmid_ds.shm_nattach计数器减1.

UNIX域套接字

  • 像套接字和管道的混合:全双工的做用
  • 和英特网域套接字区别:UNIX域套接字仅仅复制数据,他们并不执行协议处理,不须要添加或删除网络报头,无需计算验和,不要生成顺序号,无需发送确认报文。
  • UNIX域套接字提供流和数据报两种结构。
  • 命名UNIX域套接字:sockaddr_un的结构中 sun_path成员包涵一个路径名,当咱们将一个地址绑定到一个UNIX域套接字时,系统会用该路径建立一个S_IFSOCK类型的文件。
    • 例如:foo.socket.
  • 惟一链接 :服务器进程可使用标准bind、listen和accpet函数,为客户进程安排一个惟一UNIX域链接。
    • 客户端进程经过使用connect与服务器进程链接,服务器进程结构式connect请求后,服务器进程和客户端之间就存在惟一链接。

输入图片说明

#include <sys/socket.h>

int socketpari(int domain,int type, int protocol, int sockfd[2]);

    --'成功:0;出错:-1'

//用例
int fd_pipe(int fd[2])
{
    return (socketpair(AF_UNIX, SOCK_STREAM,0,fd));
}
相关文章
相关标签/搜索