进程间通信实例

本篇内容包括:共享内存,信号量,消息队列的实现
 
共享内存通信实现:
/*生成key
key_t ftok( char* fname, int id )
  fname就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:
  key_t key;
  key = ftok(".", 1); 这样就是将fname设为当前目录。
  id是子序号。
  在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。
  如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,
换算成16进制为0x26,则最后的key_t返回值为0x26010002。
  查询文件索引节点号的方法是: ls -i

    key标识共享内存的键值: 0/IPC_PRIVATE。 当key的取值为IPC_PRIVATE,
则函数shmget()将创建一块新的共享内存;如果key的取值为0,而参数shmflg中设置了IPC_PRIV
 ATE这个标志,则同样将创建一块新的共享内存。 在IPC的通信模式下,不管是使用消息队列还是共享内存,
甚至是信号量,每个IPC的对象(object)都有唯一的名字,称     为“键”(key)。通过“键”,进程能够识别
所用的对象。“键”与IPC对象的关系就如同文件名称之于文件,通过文件名,进程能够读写文件内的数据,
甚至多个进程能够共用     一个文件。而在IPC的通讯模式下,通过“键”的使用也使得一个IPC对象能为多
个进程所共用。 Linux系统中的所有表示SystemV中IPC对象的数据结构都包括一个ipc_perm     结构,
其中包含有IPC对象的键值,该键用于查找SystemV中IPC对象的引用标识符。如果不使用“键”,进程将无法
存取IPC对象,因为IPC对象并不存在于进程本身使用的内    存中。 通常,都希望自己的程序能和其他的
程序预先约定一个唯一的键值,但实际上并不是总可能的成行的,因为自己的程序无法为一块共享内存选择
一个键值。因此,    在此把key设为IPC_PRIVATE,这样,操作系统将忽略键,建立一个新的共享内存,
指定一个键值,然后返回这块共享内存IPC标识符ID。而将这个新的共享内存的标识符ID    告诉其他进程
可以在建立共享内存后通过派生子进程,或写入文件或管道来实现。

*/
/* 创建新的共享内存。返回共享内存的标志号,该标志号可以引用该共享内存的shmid_ds数据结构
int shmget(key_t ket, size_t size, int shmflag)
    shmflg主要和一些标志有关。其中有效的包括IPC_CREAT和IPC_EXCL,它们的功能与open()的O_CREAT
和O_EXCL相当。 IPC_CREAT 如果共享内存不存在,则创建一个共享内    存,否则打开操作。 IPC_EXCL
 只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。 如果单独使用IPC_CREAT,shmget()
函数要么返回一个已经存在     的共享内存的操作符,要么返回一个新建的共享内存的标识符。如果将IPC_CREAT
和IPC_EXCL标志一起使用,shmget()将返回一个新建的共享内存的标识符;如果该共享内    存已存在,或者返回-1。
IPC_EXEL标志本身并没有太大的意义,但是和IPC_CREAT标志一起使用可以用来保证所得的对象是新建的,而不是
打开已有的对象。对于用户的读    取和写入许可指定SHM_R和SHM_W,(SHM_R>3)和(SHM_W>3)是一组读取和写入许可
,而(SHM_R>6)和(SHM_W>6)是全局读取和写入许可。
  需要注意的是,使用参数要加上 | 0666 作为校验,在有些Linux系统中,如果不加此校验,则不能顺利获取共享
空间的值(如Ubuntu)。此外,有两个常用参数,一般要    同时出现,他们是:S_IRUSH | S_IWUSR 。
*/

/*
 共享内存链接到进程的整数段
void* shmat(int shmid, void *shmaddr, int shmflag)
    int shmid是那块共享内存的ID。
  char *shmaddr是共享内存的起始地址,参数shmaddr设置为0标示进程内数据段的地址由系统选择.
  int shmflag是本进程对该内存的操作模式。如果是SHM_RDONLY的话,就是只读模式。其它的是读写模式
  成功时,这个函数返回共享内存的起始地址。失败时返回-1

*/

/*
使共享内存快在本进程中不再被使用
int shmdt(void* shmaddr)
    addr:共享存储段的地址,以前调用shmat时的返回值
    shmdt将使相关shmid_ds结构中的shm_nattch计数器值减1
*/

/*
对共享内存快进行操作
int shmctl(int shmid, int cmd, shmid_ds &buf)
    int shmid:是共享内存的ID。
  int cmd: 是控制命令,可取值如下:
  IPC_STAT 得到共享内存的状态,
  IPC_SET 改变共享内存的状态
  IPC_RMID 删除共享内存
  struct shmid_ds *buf是一个结构体指针。IPC_STAT的时候,取得的状态放在这个结构体中。如果要改变共享内存的
状态,用这个结构体指定。
  返回值: 成功:0失败:-1
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>

#define PRAM IPC_CREAT

int main(){

  int shmid = 0;
  key_t shmkey;
  struct person{
    int age;
    char name[7];
  };
  shmkey = ftok("~/vhost/shmipc", 0);
  shmid = shmget(shmkey, 64, PRAM|0666);
  if (shmid < 0)perror("shmid");
  person *p = (struct person *)shmat(shmid, 0, 0);
  p->age = 1;
  strcpy(p->name ,"yvette");
  if(shmdt(p) < 0)perror("shmdt");
  //shmctl(shmid, IPC_RMID, NULL);
  return 0;
}
客户端:
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include <unistd.h>

int main(){

  struct person{
    int age;
    char name[7];
  };

  key_t shmkey;
  int shmid = 0;
  shmkey = ftok("~/vhost/myshm1", 3);
  shmid = shmget(shmkey, 64, IPC_CREAT|0666);
  person *p = (struct person *)shmat(shmid, 0, 0);
  printf("%d, %s", p->age, p->name);
  shmctl(shmid, IPC_RMID, NULL);
  return 0;
}
 
接下来的是信号量与共享内存结合:
 
/*
ftok
创建一个信号量集:
int semget(ket_t key, int nsems, int semflag)
    key:所创建或打开信号量集的键值。
  nsems:创建的信号量集中的信号量的个数,该参数只在创建信号量集时有效。
  flag:调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过or表示
  返回值说明:
  如果成功,则返回信号量集的IPC标识符。
  如果失败,则返回-1,errno被设定成以下的某个值
  EACCES:没有访问该信号量集的权限
  EEXIST:信号量集已经存在,无法创建
  EINVAL:参数nsems的值小于0或者大于该信号量集的限制;或者是该key关联的信号量集已存在,并且nsems
  大于该信号量集的信号量数
  ENOENT:信号量集不存在,同时没有使用IPC_CREAT
  ENOMEM :没有足够的内存创建新的信号量集
  ENOSPC:超出系统限制
*/
 /*创建信号操作结构
    struct sembuf{
        unsigned short sem_num;//semaphore number
        short sem_op;//seamphore opration
        short sem_flg;//operation flags
    sem_num:操作信号在信号集中的编号,第一个信号的编号是0。
  sem_op:如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;如果sem_op的值为
负数,而其绝对值又大于信号的现值,操作将会阻塞, 直到信号值大于或等于sem_op的绝对值。通常用于获取资源的
使用权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。
  sem_flg:信号操作标志,可能的选择有两种
  IPC_NOWAIT //对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
  SEM_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免
程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
  nsops:信号操作结构的数量,恒大于或等于1。
    }
    */
/*
 信号量操作
int semop(int semid, struct sembuf *sops, unsigned snops)
int semoptimeedop(int semid, struct sembuf *sops, unsigned snops, struct timespc *timeout)
timeout:当semtimedop()调用致使进程进入睡眠时,睡眠时间不能超过本参数指定的值。如果睡眠超时,semtimedop()
将失败返回,并设定错误值为EAGAIN。如果本参数的值为NULL,semtimedop()将永远睡眠等待
返回说明:
  成功执行时,两个系统调用都返回0。失败返回-1,errno被设为以下的某个值
  E2BIG:一次对信号的操作数超出系统的限制
  EACCES:调用进程没有权能执行请求的操作,并且不具有CAP_IPC_OWNER权能
  EAGAIN:信号操作暂时不能满足,需要重试
  EFAULT:sops或timeout指针指向的空间不可访问
  EFBIG:sem_num指定的值无效
  EIDRM:信号集已被移除
  EINTR:系统调用阻塞时,被信号中断
  EINVAL:参数无效
  ENOMEM:内存不足
  ERANGE:信号所允许的值越界
*/
/* 对信号集的操作
int semctl(int semid, int semnum, int cmd, union semunarg)
返回值:如果成功,则为一个正数。
  如果失败,则为-1:errno=EACCESS(权限不够)
  EFAULT(arg指向的地址无效)
  EIDRM(信号量集已经删除)
  EINVAL(信号量集不存在,或者semid无效)
  EPERM(EUID没有cmd的权利)
  ERANGE(信号量值超出范围)
  系统调用semctl用来执行在信号量集上的控制操作。这和在消息队列中的系统调用msgctl是十分相似的。但这两个
系统调用的参数略有不同。因为信号量一般是作为一个信号量集使用的,而不是一个单独的信号量。所以在信号量集的
操作中,不但要知道IPC关键字值,也要知道信号量集中的具体的信号量。这两个系统调用都使用了参数cmd,它用来指出
要操作的具体命令。两个系统调用中的最后一个参数也不一样。在系统调用msgctl中,最后一个参数是指向内核中使用的
数据结构的指针。我们使用此数据结构来取得有关消息队列的一些信息,以及设置或者改变队列的存取权限和使用者。但
在信号量中支持额外的可选的命令,这样就要求有一个更为复杂的数据结构。
  系统调用semctl()的第一个参数是关键字值。第二个参数是操作信号在信号集中的编号,第一个信号的编号是0。
  参数cmd中可以使用的命令如下:
  ·IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
  ·IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
  ·IPC_RMID将信号量集从内存中删除。
  ·GETALL用于读取信号量集中的所有信号量的值。
  ·GETNCNT返回正在等待资源的进程数目。
  ·GETPID返回最后一个执行semop操作的进程的PID。
  ·GETVAL返回信号量集中的一个单个的信号量的值。
  ·GETZCNT返回这在等待完全空闲的资源的进程数目。
  ·SETALL设置信号量集中的所有的信号量的值。
  ·SETVAL设置信号量集中的一个单独的信号量的值。
  参数arg代表一个semun的实例。semun是在linux/sem.h中定义的:
  arg for semctl systemcalls.
  union semun{
  int val;//value for SETVAL
  struct semid_ds*buf;//buffer for IPC_STAT&IPC_SET
  ushort*array;//array for GETALL&SETALL
  val当执行SETVAL命令时使用。buf在IPC_STAT/IPC_SET命令中使用。代表了内核中使用的信号量的数据结构。array
在使用GETALL/SETALL命令时使用的指针。
     如果semun用户没有定义,则编译器将报错
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <sys/wait.h>

#define PRAM IPC_CREAT

void init_sembuf(struct sembuf *sop, short num, short op, short flg){
  sop->sem_num = num;
  sop->sem_op = op;
  sop->sem_flg = flg;
}
 struct person{
    int age;
    char name[7];
  };

int main(){
  int semid = 0, shmid = 0, sem_ret = 0;
  key_t shmkey, semkey;
  struct sembuf semp, semv;
  init_sembuf(&semp, 0, -1, 0);
  init_sembuf(&semv, 0, 1, 0);

  shmkey = ftok("~/vhost/myshm1", 0);
  semkey = ftok("~/vhost/mysem1", 1);
  shmid = shmget(shmkey, 64, PRAM | 0666);
  semid = semget(semid, 1, PRAM | 0666);
  /* 先释放资源, 系统有一个标准错误输出*/
  if (semop(semid, &semv, 1) == -1)perror("semv!");
  if (shmid < 0 || semid < 0)perror("shmid or semid");
  person *p = (struct person *)shmat(shmid, 0, 0);
  while(((sem_ret = semop(semid, &semp, 1)) == -1) && (errno == EINTR));
  if(sem_ret == -1)perror("semopp!");
  p->age = 1;
  strcpy(p->name ,"yvette");
  while(((sem_ret = semop(semid, &semv, 1)) == -1) && (errno == EINTR));
  if(sem_ret == -1)perror("semopv!");
  if(shmdt(p) < 0)perror("shmdt");

  //shmctl(shmid, IPC_RMID, NULL);

  return 0;
}
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/sem.h>
#include <errno.h>
#include <sys/wait.h>

void init_sembuf(struct sembuf *sop, short num, short op, short flg){
  sop->sem_num = num;
  sop->sem_op = op;
  sop->sem_flg = flg;
}

int main(){

  struct person{
    int age;
    char name[7];
  };
  struct sembuf semp, semv;
  init_sembuf(&semp, 0, -1, 0);
  init_sembuf(&semv, 0, 1, 0);

  key_t shmkey, semkey;
  int shmid = 0, semid = 0, sem_ret = 0, status;
  shmkey = ftok("~/vhost/myshm1", 0);
  semkey = ftok("~/vhost/mysem1", 1);
  semid = semget(semid, 1, IPC_CREAT | 0666);
  shmid = shmget(shmkey, 64, IPC_CREAT|0666);
  if(semop(semid, &semv, 1) == -1)perror("semv!");
  while(((sem_ret = semop(semid, &semp, 1)) == -1) && (errno == EINTR));
  person *p = (struct person *)shmat(shmid, 0, 0);
  printf("%d, %s", p->age, p->name);
  while(((sem_ret = semop(semid, &semv, 1)) == -1) && (errno == EINTR));
  while((wait(&status) == -1) && (errno == EINTR));
  semctl(shmid, 0, IPC_RMID, NULL);
  shmctl(shmid, IPC_RMID, NULL);
  return 0;
}
消息队列实现
 
/*
 创建key,ftok
 创建消息队列
 int msgget(ket_t key, int msgflg)
    key:消息队列关联的键。
  msgflg:消息队列的建立标志和存取权限。
  返回说明:
  成功执行时,返回消息队列标识值。失败返回-1,
    errno被设为以下的某个值 ,有时也会返回0,这
    个时候也是可以正常使用的
  EACCES:指定的消息队列已存在,但调用进程没有
    权限访问它,而且不拥有CAP_IPC_OWNER权能
  EEXIST:key指定的消息队列已存在,而msgflg中同
    时指定IPC_CREAT和IPC_EXCL标志
  ENOENT:key指定的消息队列不存在同时msgflg中不
    指定IPC_CREAT标志
  ENOMEM:需要建立消息队列,但内存不足
  ENOSPC:需要建立消息队列,但已达到系统的限制

*/
/*
 在消息队列上进行接收消息,调用进程必须具有写权限
 才能发送消息,接收进程必须具有读权限。
 int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg);
 int msgrcv(int msgid, const void *msgp, size_t msgsz, long msgtyp, int msgflg);
    msqid:消息队列的识别码。
  msgp:指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,
   是一个用户可定义的通用结构,形态如下
  struct msgbuf {
  long mtype; // 消息类型,必须 > 0
  char mtext[1]; // 消息文本
  };
  msgsz:消息的大小。
  mtype:消息类型
  mtype等于0 则返回队列的最早的一个消息。
  mtype大于0,则返回其类型为mtype的第一个消息。
  mtype小于0,则返回其类型小于或等于mtype参数的绝对值的最小的一个消息。
  msgflg: 用来指明核心程序在队列没有数据的情况下所应采取的行动。如果msgflg
    和常数IPC_NOWAIT合用,则在msgsnd()执行时若是消息队列已 满,则msgsnd()将不
    会阻塞,而会立即返回-1,如果执行的是msgrcv(),则在消息队列呈空时,不做等待
马上返回-1,并设定错误码为 ENOMSG。当msgflg为0时,msgsnd()及msgrcv()在队列呈
满或呈空的情形时,采取阻塞等待的处理模式。
  返回说明:
  成功执行时,msgsnd()返回0,msgrcv()返回拷贝到mtext数组的实际字节数。失败两者
    都返回-1,errno被设为以下的某个值
  [对于msgsnd]
  EACCES:调用进程在消息队列上没有写权能,同时没有CAP_IPC_OWNER权能
  EAGAIN:由于消息队列的msg_qbytes的限制和msgflg中指定IPC_NOWAIT标志,消息不能被发送
  EFAULT:msgp指针指向的内存空间不可访问
  EIDRM:消息队列已被删除
  EINTR:等待消息队列空间可用时被信号中断
  EINVAL:参数无效
  ENOMEM:系统内存不足,无法将msgp指向的消息拷贝进来
  [对于msgrcv]
  E2BIG:消息文本长度大于msgsz,并且msgflg中没有指定MSG_NOERROR
  EACCES:调用进程没有读权能,同时没具有CAP_IPC_OWNER权能
  EAGAIN:消息队列为空,并且msgflg中没有指定IPC_NOWAIT
  EFAULT:msgp指向的空间不可访问
  EIDRM:当进程睡眠等待接收消息时,消息已被删除
  EINTR:当进程睡眠等待接收消息时,被信号中断
  EINVAL:参数无效
  ENOMSG:msgflg中指定了IPC_NOWAIT,同时所请求类型的消息不存在

*/
/*
系统调用msgctl() 下面我们继续讨论如何使用一个给定的消息队列的内部数据结构。我们可以使用
系统调用msgctl ( )来控制对消息队列的操作。 系统调用: msgctl( ) ;
  调用原型: int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
  返回值: 0 ,如果成功。
  - 1,如果失败:errno = EACCES (没有读的权限同时cmd 是IPC_STAT )
  EFAULT (buf 指向的地址无效)
  EIDRM (在读取中队列被删除)
  EINVAL (msgqid无效, 或者msgsz 小于0 )
  EPERM (IPC_SET或者IPC_RMID 命令被使用,但调用程序没有写的权限)
  下面我们看一下可以使用的几个命令:
  IPC_STAT
  读取消息队列的数据结构msqid_ds,并将其存储在b u f指定的地址中。
  IPC_SET
  设置消息队列的数据结构msqid_ds中的ipc_perm元素的值。这个值取自buf参数。
  IPC_RMID
  从系统内核中移走消息队列。
*/
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(){
  struct msgbuf{
    long mtype;
    char mtext[2];
  };
  key_t msgkey;
  int msgid = 0;
  msgkey = ftok("~/vhost/mymsg1", 0);
  msgid = msgget(msgkey, 0666|IPC_CREAT);
  msgbuf snd;
  snd.mtype = 1;
  strcpy(snd.mtext, "hi");
  msgsnd(msgid, &snd, sizeof(msgbuf), 0);
  return 0;
}
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(){
  struct msgbuf{
    long mtype;
    char mtext[2];
  };
  key_t msgkey;
  int msgid;
  msgkey = ftok("~/vhost/mymsg1", 0);
  msgid = msgget(msgkey, 0666|IPC_CREAT);
  msgbuf msg;
  msgrcv(msgid, &msg, sizeof(msg), 1, 0);
  printf("%s", msg.mtext);
  msgctl(msgid, IPC_RMID, NULL);
  return 0;
}

转载于:https://my.oschina.net/yvette/blog/107984

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值