进程间通信几种方式

管道

管道是由内核管理的一个缓冲区。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。

 

int main(void)
{
   int n;
   int fd[2]; //0读 1写
   pid_t pid;
   char line[MAXLINE];

   if(pipe(fd) == 0){                 /* 先建立管道得到一对文件描述符 */
       exit(0);
  }

   if((pid = fork()) == 0)            /* 父进程把文件描述符复制给子进程 */
       exit(1);
   else if(pid > 0){                /* 父进程写 */
       close(fd[0]);                /* 关闭读描述符 */
       write(fd[1], "\nhello world\n", 14);
  }
   else{                            /* 子进程读 */
       close(fd[1]);                /* 关闭写端 */
       n = read(fd[0], line, MAXLINE);
       write(STDOUT_FILENO, line, n);
  }

   exit(0);
}

命名管道

由于基于fork机制,所以管道只能用于父进程和子进程之间,或者拥有相同祖先的两个子进程之间 (有亲缘关系的进程之间)。为了解决这一问题,Linux提供了FIFO方式连接进程。FIFO又叫做命名管道(named PIPE)。

FIFO (First in, First out)为一种特殊的文件类型,它在文件系统中有对应的路径。当一个进程以读(r)的方式打开该文件,而另一个进程以写(w)的方式打开该文件,那么内核就会在这两个进程之间建立管道,所以FIFO实际上也由内核管理,不与硬盘打交道。

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *filename, mode_t mode);
int mknode(const char *filename, mode_t mode | S_IFIFO, (dev_t) 0 );

其中filename是被创建的文件名称,mode表示将在该文件上设置的权限位和将被创建的文件类型(在此情况下为S_IFIFO),dev是当创建设备特殊文件时使用的一个值。因此,对于先进先出文件它的值为0。

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

int main()  
{  
   int res = mkfifo("/tmp/my_fifo", 0777);  
   if (res == 0)  
  {  
       printf("FIFO created/n");  
  }  
    exit(EXIT_SUCCESS);  
}

信号量

信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。

  • P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行

  • V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

semget函数

创建一个新信号量或取得一个原有的信号量。

int semget(key_t key, int num_sems, int sem_flags); 
  • 第一个参数key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。

  • 第二个参数num_sems指定需要的信号量数目,它的值几乎总是1。

  • 第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

int semop(int semid, struct sembuf semoparray[], size_t numops);  
int semctl(int semid, int sem_num, int cmd, ...)

当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1; 如果是引用一个现有的集合,则将num_sems指定为 0 。

struct sembuf 
{
   short sem_num; // 信号量组中对应的序号,0~sem_nums-1
   short sem_op;  // 信号量值在一次操作中的改变量
   short sem_flg; // IPC_NOWAIT, SEM_UNDO
}

 

#include<stdio.h>
#include<stdlib.h>
#include<sys/sem.h>

// 联合体,用于semctl初始化
union semun
{
   int              val; /*for SETVAL*/
   struct semid_ds *buf;
   unsigned short  *array;
};

// 初始化信号量
int init_sem(int sem_id, int value)
{
   union semun tmp;
   tmp.val = value;
   if(semctl(sem_id, 0, SETVAL, tmp) == -1)
  {
       perror("Init Semaphore Error");
       return -1;
  }
   return 0;
}

// P操作:
//   若信号量值为1,获取资源并将信号量值-1
//   若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
   struct sembuf sbuf;
   sbuf.sem_num = 0; /*序号*/
   sbuf.sem_op = -1; /*P操作*/
   sbuf.sem_flg = SEM_UNDO;

   if(semop(sem_id, &sbuf, 1) == -1)
  {
       perror("P operation Error");
       return -1;
  }
   return 0;
}

// V操作:
//   释放资源并将信号量值+1
//   如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
   struct sembuf sbuf;
   sbuf.sem_num = 0; /*序号*/
   sbuf.sem_op = 1;  /*V操作*/
   sbuf.sem_flg = SEM_UNDO;

   if(semop(sem_id, &sbuf, 1) == -1)
  {
       perror("V operation Error");
       return -1;
  }
   return 0;
}

// 删除信号量集
int del_sem(int sem_id)
{
   union semun tmp;
   if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
  {
       perror("Delete Semaphore Error");
       return -1;
  }
   return 0;
}


int main()
{
   int sem_id;  // 信号量集ID
   key_t key;  
   pid_t pid;

   // 获取key值
   if((key = ftok(".", 'z')) < 0)
  {
       perror("ftok error");
       exit(1);
  }

   // 创建信号量集,其中只有一个信号量
   if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
  {
       perror("semget error");
       exit(1);
  }

   // 初始化:初值设为0资源被占用
   init_sem(sem_id, 0);

   if((pid = fork()) == -1)
       perror("Fork Error");
   else if(pid == 0) /*子进程*/
  {
       sleep(2);
       printf("Process child: pid=%d\n", getpid());
       sem_v(sem_id);  /*释放资源*/
  }
   else  /*父进程*/
  {
       sem_p(sem_id);   /*等待资源*/
       printf("Process father: pid=%d\n", getpid());
       sem_v(sem_id);   /*释放资源*/
       del_sem(sem_id); /*删除信号量集*/
  }
   return 0;
}

 

消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

  • 消息队列是面向记录的,消息具有特定的格式和优先级。

  • 消息队列独立于发送和接收线程。进程结束时,消息队列以及内容不会被删除。

  • 可以实现消息的随机查询,不一定是FIFO,也可以按照消息的类型读取

#include <sys/msg.h>
// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

在以下两种情况下,msgget将创建一个新的消息队列:

  • 没有与键值key对应的消息队列,并且flag中包含了IPC_CREAT标志位

  • key参数为IPC_PRIVATE

函数msgrcv在读取消息队列时,type参数有下面几种情况:

  • type == 0 返回队列的第一个消息

  • type>0 返回队列中消息类型为type的第一个消息

  • type<0 返回队列中消息类型值小于等于type绝对值的消息,如果有多个,则取类型值最小的消息。

type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。

#include <iostream>
#include <sys/msg.h>
#include <unistd.h>

#define MSG_FILE "/etc/passwd"

using namespace std;

struct msg {
long type;
char text[256];
};

int main()
{
int msgid;
key_t key;
msg m;

if ((key = ftok(MSG_FILE, 'z')) < 0) {
perror("ftok error");
goto  END;
}

cout << "Key : " << key << endl;
if ((msgid = msgget(key, IPC_CREAT | 0777)) == -1) {
perror("Msg get error");
goto END;
}

cout << "Msg id : " << msgid << endl;
cout << "Pid: " << getpid() << endl;

cout << "Waiting..." << endl;
while (true)
{
msgrcv(msgid, &m, sizeof m, 0, 888);
cout << "Msg type: " << m.type << " Msg Info:" << m.text << endl;
m.type = 999;
msgsnd(msgid, &m, sizeof m.text, 0);
}

END:
getc(stdin);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>

// 用于创建一个唯一的key
#define MSG_FILE "/etc/passwd"

// 消息结构
struct msg_form {
   long mtype;
   char mtext[256];
};

int main()
{
   int msqid;
   key_t key;
   struct msg_form msg;

   // 获取key值
   if ((key = ftok(MSG_FILE, 'z')) < 0)
  {
       perror("ftok error");
       exit(1);
  }

   // 打印key值
   printf("Message Queue - Client key is: %d.\n", key);

   // 打开消息队列
   if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
  {
       perror("msgget error");
       exit(1);
  }

   // 打印消息队列ID及进程ID
   printf("My msqid is: %d.\n", msqid);
   printf("My pid is: %d.\n", getpid());

   // 添加消息,类型为888
   msg.mtype = 888;
   sprintf(msg.mtext, "hello, I'm client %d", getpid());
   msgsnd(msqid, &msg, sizeof(msg.mtext), 0);

   // 读取类型为777的消息
   msgrcv(msqid, &msg, 256, 999, 0);
   printf("Client: receive msg.mtext is: %s.\n", msg.mtext);
   printf("Client: receive msg.mtype is: %d.\n", msg.mtype);
   return 0;
}

共享内存

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。

  • 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。

  • 多个线程可以同时操作,所以需要进行同步

  • 信号量+共享内存组合

#include <sys/shm.h>
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr);
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

 

posted @ 2022-03-17 01:33  wa小怪兽  阅读(142)  评论(0编辑  收藏  举报