【Linux 编程】进程间通信

  进程间通信方式有多种,包括管道、FIFO、消息队列、共享内存、信号量等。

  1. 半双工管道

  该方式只能在具有公共祖先的进程之间使用。通常,一个管道由一个进程创建,然后进程通过fork函数创建一个子进程,因此父、子进程之间就可以应用该管道作为父子之间的通信方式。

  函数pipe()创建管道:

#include <unistd.h>
int pipe(int fileds[2]);

  其中,参数fileds返回两个文件描述符:filefds[0]为读而打开,fileds[1]为写而打开,且fileds[1]的输出作为fileds[0]的输入。如图所示:

                        图1 单进程中半双工管道        

  当管道的一端被关闭后,下列两条规则其作用(man 7 pipe):

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

  在写管道(或FIFO)时,常量PIPE_BUF规定了内核中管道缓冲区的大小。如果对管道的数据量小于等于PIPE_BUF,则不会与其他进程对同一管道(或FIFO)的write操作穿插进行。若多个进程同时写一个管道(或FIFO)且数据大于PIPE_BUF时,则可能出现穿插现象。

  2. FIFO

  FIFO是一种文件类型。stat结构成员st_mode的编码指明文件是否为FIFO类型,可以使用S_ISFIFO宏对其进行测试。

  函数mkfifo来创建FIFO:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

  其中,pathname作为FIFO文件的文件名,mode指定FIFO文件的权限。

  3. IPC基本知识

  IPC包括消息队列、信号量及共享内存存储区。在内核中,每个IPC结构都用一个非负整数的标识符加以标识或引用。标识符作为内部名存在,而键值作为外部名使得多个进程能够在一个ipc对象上操作。因此,无论何时创建IPC结构(调用msgget、semget或shmget),都应指定一个数据类型为key_t的键。

  多种方法使客户进程与服务器进程在同一结构上会和:

  (1)服务器进程可以指定键IPC_PRIVATE来创建一个新的IPC结构,将返回的标识符存在某处(如文件)以便客户进程使用。键IPC_PRIVATE保证服务器进程创建一个新的IPC结构。

  这种方法的缺点:服务器进程要将整数标识符写到文件中,此后客户进程又要读取文件取得此标识符。

  (2)在一个公用的头文件中定义一个客户进程和服务器进程都认可的键,然后服务器进程指定此键创建一个新的IPC结构。

  这种方法的问题:若此键已与一个IPC结构相结合,则get函数(msgget、semget或shmget)出错返回。因此服务器需要删除已存在的IPC结构,然后试着再创建该键。

  (3)客户进程和服务器进程认同一个路径名和项目ID(项目ID为0~255之间的字符值),接着调用ftok函数将这两个值变换为一个键,然后在方法(2)中使用此键。

  ftok函数提供的唯一服务:由一个路径名和项目ID产生一个键

#include <sys/ipc.h>
key_t ftok(const char *path, int id);

  其中,path参数必须引用一个现存文件。当产生键时,只使用id参数的低8位。

  若满足下列两个条件之一,则创建一个新的IPC结构:

  • key是IPC_PRIVATE;
  • key当前未与特定类型的IPC结构相结合,并且flag中指定了IPC_CREAT位;

  每一个IPC结构都将设置一个ipc_perm结构,该结构规定了权限和所有者信息。

  在使用IPC时,需要注意的问题:

  • IPC结构是在系统范围内起作用的,没有访问计数;
  • IPC结构在文件系统中没有名字。

 4. 消息队列 

  消息队列实际上就是消息的连接表,存放在内核中并由消息队列标识符来标识。

  函数msgget():创建一个消息队列或者打开一个现存的队列。

#include <sys/msg.h>
int msgget(key_t key, int flag);
            // 成功返回消息队列ID;若出错则返回-1

  函数msgsnd():将新消息插入到队列尾部。每个消息包含一个正长整型的类型字段,一个非负长度以及实际数据字节。成功添加后,将更新msqid_ds结构中信息。

#include <sys/msg.h>
int msgsnd(int msqid, void *ptr, size_t nbytes, int flag);
            // 若成功返回0;若出错返回-1

  其中,ptr指向一个包含类型字段和数据字段的结构体,例如:

struct mymsg {
     long mtype;        // positive message type
     char mtext[512];  // message data. of length nbytes
};

  函数msgrcv():从指定队列中提取消息。

#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag);
            // 若成功则返回消息数据部分的长度;若出错则返回-1.

  其中,在提取消息时,根据type值的不同提取不同的消息:

  • type == 0:返回消息队列中第一个消息;
  • type > 0:返回队列中消息类型为type的第一个消息;
  • type < 0:返回队列中消息类型小于等于type绝对值的消息。若这种消息存在多个,则去类型值最小的消息。

  函数msgctl():用于对消息队列执行多种操作。

#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
            // 若成功则返回0;若出错则返回-1

  其中,该函数中参数cmd指定了要对msqid队列执行的命令:

  • IPC_STAT:取msqid标识的消息队列的msqid_ds结构,并将其放入到buf所指的内存中;
  • IPC_SET:将msqid标识的消息队列的msqid_ds结构设置为由buf指向的新数据;
  • IPC_RMID:从系统中移除该消息队列以及存在该队列中的所有数据。

  5. 信号量

  信号量(内核中使用结构体semid_ds)是一个计数器,用于多进程对共享数据对象的访问控制。为了获取共享资源,进程需要执行以下操作:

  • 测试控制资源的信号量;
  • 若此信号量的值为正,则进程可以使用该资源。进程将信号量减1,表示它使用了一个资源单位;
  • 若此信号量的值为0,则进程进入休眠状态,直至信号量大于0。进程被唤醒后,返回到开始阶段。

  函数semget():获得一个信号量ID。

#include <sys/sem.h>
int semget(key_t key, int nsems, int flag);
            // 若成功则返回信号量ID;若出错则返回-1。

  其中,参数nsems是该集合中的信号量数量。若创建新信号量,则nsems > 0;若引用一个现存的信号量,则将nsems的值设为0。

  函数semop():在信号量集合上自动执行参数semoparray数组的命令。

#include <sys/sem.h>
int semop(int semid, struct sembuf semoparray[], size_t nops);
        // 若成功则返回0;若出错则返回-1。

  其中,结构体sembuf:

struct sembuf {
    unsigned short    sem_num;    // member # in set
    short             sem_op;     // operation (negative, 0, positve)
    short             sem_flg;    // IPC_NOWAIT, SEM_UNDO
};

  semop函数执行的操作主要根据sem_op的值来操作:

  • sem_op > 0:进程释放占用的资源数,则将sem_op的值加到信号量的值上。如果指定了undo标志,则也从该进程的此信号调整量中减去sem_op;
  • sem_op < 0:进程要获取由该信号量控制的资源。
    (1)若该信号的值大于等于sem_op的绝对值(资源量满足需求),则从该信号量中减去sem_op的绝对值。如果指定undo标志,则sem_op的绝对值加到该进程的此信号量的调整值上;
    (2)若信号量小于sem_op的绝对值(资源量不能满足需求),则:
      (a)若指定IPC_NOWAIT,则semop出错并返回EAGAIN;
      (b)若未指定IPC_NOWAIT,则信号量semid_ds结构中的semncnt加1(因为调用进程将进入休眠状态),然后调用进程被挂起直到下列事件之一发生:
        (i)此信号量大于等于sem_op的绝对值,唤醒调用进程;
        (ii)从系统中删除了此信号。在这种情况下,函数出错并返回EIDRM;
        (iii)进程捕捉到一个信号,并从信号处理程序返回。在此情况下,信号量的semncnt减1,并且函数出错返回EINTR。
  • sem_op == 0:表示调用进程希望等待该信号量变为0。在该情况的处理方式,与sem_op > 0的操作方式一样,只是条件变为sem_op == 0。

  函数semctl():包含了多种对信号量的操作。

#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);
            // 返回值参考手册(man 2 semctl)。

  其中,该函数常用的指令与消息队列中一样:IPC_STAT、IPC_SET和IPC_RMID。最后一个参数是可选参数,其类型为semun,它是多个特定指令参数的联合。

union semun {
    int             val;        // for SETVAL
    struct semid_ds   *buf;      // for IPC_STAT and IPC_SET
    unsigned short    *array;    // for GETALL and SETALL
};

  6. 共享存储区

  共享存储区允许两个或多个进程共享一给定的存储区。使用共享存储区时,利用信号量或锁机制来控制多进程之间对给定存储区的同步访问。内核为每个共享存储区设置了一个结构体shmid_ds。

  函数shmget():创建一个共享存储区标识符。

#include <sys/shm.h>
int shmget(key_t key, size_t size, int flag);
        // 若成功则返回共享存储区的ID;若出错则返回-1.

  其中,size是该共性存储区的长度,通常将其向上取为系统页长的整数倍。如果正在创建一个新存储区,则必须制定size;如果正在引用一个现存的储存区,则将size值为0。

  函数shmctl():对共享存储区执行多种操作。

#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
            // 若成功则返回0;若出错则返回-1。

  其中,参数cmd常用的有:IPC_STAT、IPC_SET、IPC_RMID。其余的指令为各系统特有的,请参考书册(linux下,man 2 shmctl)。

  函数shmat():将已创建的共享存储区连接到调用进程的地址空间上。

#include <sys/shm.h>
int *shmat(int shmid, const void *addr, int flag);
            // 若成功则返回指向共享存储区的指针,若出错则返回-1。

  函数shmdt():将共享存储区与连接地址脱离。注意,这个操作并不删除共享存储区的标识符以及其数据结构。该标识符仍然存在,直到调用shmctl(shmid, IPC_RMID, NULL)来删除它。

#include <sys/shm.h>
int shmdt(void *addr);
            // 若成功返回0;若出错则返回-1。

  7. Unix域套接字

  Unix域套接字用于在同一台机器上运行的进程之间的通信。Unix域套接字仅仅复制数据;它们并不执行协议处理,不需要添加或删除网络报头,无须计算校验和、不需要产生顺序号、不需发送确认报文。

  Unix域套接字提供流和数据报两种接口,Unix域数据报服务是可靠的,既不会丢失消息也不会传递出错。Unix域套接字是套接字和管道之间的混合物。

  当创建一个命名的、相互连接的Unix套接字:

  • 使用面向网络的Unix域套接字接口;
  • 使用socketpair函数,该函数仅支持AF_UNIX域;
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sockfd[2]);
            // 若成功则返回0;若出错则返回-1
原文地址:https://www.cnblogs.com/life91/p/3124215.html