前言

为了帮助同学们完成痛苦的实验课程设计,本作者将其作出的实验结果及代码贴至CSDN中,供同学们学习参考。如有不足或描述不完善之处,敬请各位指出,欢迎各位的斧正!

一、实验目的

  1. 理解Linux的进程间通信机制。
  2. 掌握和使用消息队列实现进程间通信。
  3. 掌握和使用共享主存实现进程间通信。
  4. 掌握何使用信号量实现进程同步。

二、实验工具与设备

装有Linux系统的计算机。

三、实验预备知识

Linux继承了Unix v的进程间通信机制,即消息队列、信号量和共享主存。通常把消息、信号量和共享主存统称为system v ipc对象或ipc资源。系统在进程请求ipc对象时在内核动态创建一个ipc数据结构并初始化,该结构定义了对该ipc对象访问的许可权, 每个对象都具有同样类型的接口-函数,这组函数为应用程序提供3种服务:
通过信号量实现与其他进程的同步。
通过消息队列以异步方式为通信频繁但数据量少的进程间通信提供服务。
通过共享主存,为大数据量的进程间通信提供服务。

  1. 共性描述
    1.1. 公有的数据结构
    每一类ipc资源都有一个ipc_ids结构的全局变量用来描述同一类资源的共用数据,三种ipc对象对应的三个全局变量分别是semid_ds,msgid_ds和shmid_ds。
    ipc_ids结构定义如下:
struct ipc_ids{
		int size;   /*entries数组的大小*/
		int in_use;	/*entries数组已使用的元素个数*/
		int max_id;
		unsigned short  seq;
		unsigned short seq_max;
		struct semaphore sem; /*控制对ipc_ids结构的访问*/
		spinlock_t ary; 		/*自旋锁控制对数组entries的访问*/
		struct ipc_id* entries;
	};

	struct ipc_id{ struct ipc_perm *p;};

每个ipc对象都有一个ipc_perm数据结构,用于描述其属性。

struct ipc_perm
{
    __key_t __key;	/* ipc键*/
    __uid_t uid;                /* Owner's user ID.  */
    __gid_t gid;                /* Owner's group ID.  */
    __uid_t cuid;        /* Creator's user ID.  */
    __gid_t cgid;        /* Creator's group ID.  */
    unsigned short int mode;/* Read/write permission.  */
    unsigned short int __seq;/* Sequence number.  */
};

每一个ipc对象都有一个32位的ipc键和一个ipc标识符,ipc标识符由内核分配给ipc对象,在系统内部惟一,IPC机制提供了相应的编程接口根据IPC键值获取标识符。对于一个IPC对象,在打开时返回一个标识符,而关闭后再次打开同一个IPC对象时,该标识符将顺序加1,而键是ipc对象的外部表示,可由程序员选择。不同的进程直接使用key去创建IPC对象容易引起混淆,并且不同的应用之间可能会因为使用同一个key而产生冲突。为此,Linux系统提供了如下机制产生惟一的关键字。
1)创建IPC对象时,指定关键字为IPC_PRIVATE。通过该参数创建的IPC对象的关键字值是0,所以无法在其他进程中通过关键字对该对象进行访问,只能通过返回的标识符进行访问。
2)调用函数ftok()产生一个惟一的关键字值。通过IPC进行通信的进程,只需要按照相同的参数调用ftok即可产生惟一的参数。通过该参数可有效解决关键字的产生及惟一性问题。

1.2. Linux提供的ipc函数
(1)ipc对象的创建函数
semget():获得信号量的ipc标识符
msgget():获得消息队列的ipc标识符
shmget():获得共享主存的ipc标识符
(2)ipc资源控制函数
创建ipc对象后,用户可以通过下面的库函数对ipc对象进行控制
semctl():对信号量资源进行控制
msgctl():对消息队列进行控制
shmctl():对共享主存进行控制
(3)资源操作函数
semop():用于对信号量资源进行操作,获得或释放一个ipc信号量
msgsnd()及msgrcv():发送和接收一个ipc消息
shmat()及shmdt():分别将一个ipc共享主存区附加到进程的虚拟地址空间,以及把共享主存区从进程的虚拟地址空间剥离出去

  1. 消息队列
    Linux中的消息可以被描述成在内核地址空间的一个内部链表,每一个消息队列由一个IPC的标识号唯一的标识。Linux 为系统中所有的消息队列维护一个 msg_queue 链表,该链表中的每个指针指向一个 msgid_ds 结构,该结构完整描述对应的一个消息队列。
    2.1、消息队列结构(msgid_ds)
struct msqid_ds { 
	struct ipc_perm msg_perm; 
	struct msg *msg_first; /* 队列上第一条消息,即链表头*/ 
	struct msg *msg_last; /* 队列中的最后一条消息,即链表尾 */ 
	time_t msg_stime; /* 发送给队列的最后一条消息的时间 */ 
	time_t msg_rtime; /* 从消息队列接收到的最后一条消息的时间 */ 
	time_t msg_ctime; /* 最后修改队列的时间*/ 
	ushort msg_cbytes; /*队列上所有消息总的字节数 */ 
	ushort msg_qnum; /*在当前队列上消息的个数 */ 
	ushort msg_qbytes; /* 队列最大的字节数 */ 
	ushort msg_lspid; /* 发送最后一条消息的进程的pid */ 
	ushort msg_lrpid; /* 接收最后一条消息的进程的pid */ 
	};

2.2、消息结构(msg)
内核把每一条消息存储在以msg结构为框架的队列中

struct msg { 
	struct msg *msg_next; /* 队列上的下一条消息 */ 
	long msg_type; /*消息类型*/ 
	char *msg_spot; /* 消息正文的地址 */ 
	short msg_ts; /* 消息正文的大小 */ 
	};

2.3、消息缓冲区(msgbuf)

struct msgbuf { 
	long mtype; /* 消息的类型,必须为正数 */ 
	char mtext[1]; /* 消息正文 */ 
	};

对发送消息,先预设一个msgbuf缓冲区并写入消息类型何内容,调用相应的发送函数。对读取消息,先分配一个msgbuf缓冲区。然后把消息读入该缓冲区
2.4、消息队列基本操作
(1)相应头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

(2)打开或创建消息队列
为了创建一个新的消息队列,或存取一个已经存在的队列,要使用msgget()系统调用

int msgget(key_t kye,int flag);
函数key是创建/打开队列的键值,直接用常量指定或由ftok()函数产生,flag参数指定创建/打开方式。
可以是ipc_create、ipc_excl、ipc_nowait或三者的或结果。
(3)发送消息

int msgsnd ( int msqid, struct msgbuf *msgp, int msgsz,int msgflg ); 

第一个参数是队列标识符,由msgget()调用返回。第二个参数msgp是一个指针,指向我们重新声明和装载的消息缓冲区。msgsz参数包含了消息以字节为单位的长度,其中包括了消息类型的4个字节。msgflg参数可以设置成0(忽略),或者:
IPC_NOWAIT :如果消息队列满,消息不写到队列中,并且控制权返回给调用进程(继续执行)。如果不指定
IPC_NOWAIT,调用进程将挂起(阻塞)直到消息被写到队列中。
(4)读取消息

int msgrcv ( int msqid, struct msgbuf *msgp, int msgsz,long mtype, int msgflg );

第一个参数用来指定要检索的队列(必须由msgget()调用返回),第二个参数(msgp)是存放检索到消息的缓冲区的地址,第三个参数(msgsz)是消息缓冲区的大小,包括消息类型的长度(4字节),第四个参数(mtype)指定了消息的类型,如果传递给mytype参数的值为0,就可以不管类型,只返回队列中最早的消息。
如果传递给参数msgflg的值为IPC_NOWAIT,并且没有可取的消息,那么给调用进程返回ENOMSG错误消息,否则,调用进程阻塞,直到一条消息到达队列并且满足msgrcv()的参数。
(5)消息队列属性操作

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

msgqid是打开的消息队列id,buf是用户缓冲区,供用户存放控制参数何查询结果,cmd是规定的命令:
IPC_STAT 读取消息队列的数据结构msqid_ds,并将其存储在b u f指定的地址中。
IPC_SET 设置消息队列的数据结构msqid_ds中的ipc_perm元素的值。这个值取自buf参数。
IPC_RMID 从系统内核中移走消息队列。
(6)消息队列的应用
见试验内容1

  1. 信号量
    system v支持的信号量其实是一个信号量集合,由多个单独的信号量组成,当创建一个信号量集合时,必须指定该集合中信号量的数量。Linux内核为每个信号量集合定义一个semid_ds的描述结构,系统中所有的信号量集组成一个sem_array的链表,该链表中的每个节点都指向一个semid_ds结构,每个sem_ds
    结构中有一个指向sem结构类型数组的指针,这个指针所指向的数组中的每一个元素代表一个信号量。
    3.1、信号量数据结构

struct sem{
int semval; /信号量的当前值/
int sempid; /在信号量上最后一次操作的进程识别号/
};

3.2、信号量集合数据结构

struct semid_ds {
	struct ipc_perm sem_perm;	/*IPC权限 */
	long	sem_otime; 		/* 最后一次对信号量操作(semop)的时间 */
	long	sem_ctime;		/* 对这个结构最后一次修改的时间 */
	struct sem		/*sem_base;		/* 在信号量数组中指向第一个信号量的指针 */
	struct sem_queue *sem_pending;		/* 待处理的挂起操作*/
	struct sem_queue **sem_pending_last;	 /	* 最后一个挂起操作 */
	struct sem_undo *undo; 		 /* 在这个数组上的undo 请求 */
	ushort	sem_nsems;	 	/* 在信号量数组上的信号量号 */
	};

3.3、信号量集中的队列结构

struct sem_queue {	
		struct sem_queue * next;   /* 队列中下一个节点 */
		struct sem_queue **    prev;   /* 队列中前一个节点, *(q->prev) == q */
		struct wait_queue *    sleeper;    /* 正在睡眠的进程 */
		struct sem_undo *  undo;   /* undo 结构*/
		int            pid;    /* 请求进程的进程识别号 */
		int            status; /* 操作的完成状态 */
		struct semid_ds *  sma;    /*有操作的信号量集合数组 */
		struct sembuf *    sops;   /* 挂起操作的数组 */
		 int    nsops;  /* 操作的个数 */
};

3.4、信号量基本操作
(1)头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

(2)打开或创建信号量
使用segget()系统调用创建一个新的信号量集合,或者存取一个已存在的集合。

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

参数key和semflg取值与msgget()函数对应参数相同,nsems指定要创建或打开的信号量集合中包含的信号量数目。
(3)信号量值操作

int semop ( int semid, struct sembuf *sops, unsigned nsops);

参数sops是一个指针,它指向在集合上执行操作的数组,数组类型为sembuf,而参数nsops是在那个数组上操作的个数。

struct sembuf {
            ushort  sem_num;        /* 在数组中信号量的索引值 */
            short   sem_op;         /* 信号量操作值(正数、负数或0) */
            short   sem_flg;        /* 操作标志,为IPC_NOWAIT或SEM_UNDO*/
    };

如果sem_op为负数,那么就从信号量的值中减去sem_op的绝对值,这意味着进程要获取资源,这些资源是由信号量控制或监控来存取的。如果没有指定IPC_NOWAIT,那么调用进程睡眠到请求的资源数得到满足(其它的进程可能释放一些资源),如果sem_op是正数,把它的值加到信号量,这意味着把资源归还给应用程序的集合。
Linux 按如下的规则判断是否所有的操作都可以成功:操作值和信号量的当前值相加大于 0,或操作值和当前值均为 0,则操作成功。
(4)信号量属性操作

int semctl ( int semid, int semnum, int cmd, union semun arg );

semnum是将要操作的信号量个数,cmd参数表示在集合上执行的命令,arg参数的类型为semun

union semun {
                int val;                /* value for SETVAL */
                struct semid_ds *buf;   /* buffer for IPC_STAT & IPC_SET */
                ushort *array;          /* array for GETALL & SETALL */
                struct seminfo *__buf;  /* buffer for IPC_INFO */
                void *__pad;
        };

cmd命令及解释

命令
解 释
IPC_STAT
从信号量集合上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中
IPC_SET
设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值
IPC_RMID
从内核中删除信号量集合
GETALL
从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中
GETNCNT
返回当前等待资源的进程个数
GETPID
返回最后一个执行系统调用semop()进程的PID
GETVAL
返回信号量集合内单个信号量的值
GETZCNT
返回当前等待100%资源利用的进程个数
SETALL
与GETALL正好相反
SETVAL
用联合体中val成员的值设置信号量集合中单个信号量的值

3.5、信号量应用
见试验内容2
4、共享主存
由于进程的虚拟地址可以映射到任意一处物理地址,因此两个进程的虚拟地址映射到相同的物理地址上,则这两个进程就可以利用该物理地址单元进行通信,system v共享主存将需要共享的主存放在ipc共享主存区,所有需要访问该共享区的进程都要把该共享区映射到本进程的虚拟地址空间。由于多个进程共享同一块主存区,对共享主存的访问必然要使用到某种同步机制。
共享主存的基本步骤:
(1)对线程或进程使用shmget()分配主存区;
(2)使用shmat()连接一个或多个进程或线程到共享主存区中;
(3)使用shmdt()从共享区中分离;
(4)使用shmctl()解除分配空间。

4.1、基本操作
(1)头文件

#include <sys/ipc,h>
#include <sys/shm.h>

(2)创建或获得一个共享主存区

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

size: 要建立共享内存的长度(打开已有共享内存时置 0) ,参数key与shmflg同msgget()函数
(3)连接共享主存区到进程虚拟空间

int *shmat(int shmid, const void *shmaddr, int shmflg) ;

shmaddr:共享内存的起始地址 ,shmflag:本进程对该内存的操作模式。
(4)从指定进程虚拟空间断开共享主存区

int shmdt(const v oid *shmaddr) ;

(5共享主存区属性控制

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
buf:保存内存模式状态和访问权限的数据结构,通常为 0。cmd:允许的操作,包括以下几种类型:
IPC_STAT,IPC_SET,IPC_RMID含义和消息队列属性控制函数对应一样

4.2、共享主存区应用
见试验内容3

三、实验内容和步骤

  1. 编译并运行下述程序,观察并记录程序运行的结果。
#include<sys/types.h>
#include<sys/msg.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<stdio.h>
void msg_stat(int,struct msqid_ds);

main()
{
 int gflags,sflags,rflags;
key_t key;
int msgid;
int reval;
struct msgsbuf{
 int mtype;
 char mtext[1];
}msg_sbuf;

struct msgmbuf{
 int mtype;
 char mtext[10];
}msg_rbuf;
struct msqid_ds msg_ginfo,msg_sinfo;
char *msgpath="/home/msgqueue";
key=ftok(msgpath,'a');
gflags=IPC_CREAT|IPC_EXCL;
msgid=msgget(key,gflags|00666);
if(msgid==-1){
 printf("msg create error\n");
 return;
}

msg_stat(msgid,msg_ginfo);
sflags=IPC_NOWAIT;
msg_sbuf.mtype=10;
msg_sbuf.mtext[0]='a';
reval=msgsnd(msgid,&msg_sbuf,sizeof(msg_sbuf.mtext),sflags);
if(reval==-1){
 printf("message send error\n");
}

msg_stat(msgid,msg_ginfo);
rflags=IPC_NOWAIT|MSG_NOERROR;
reval=msgrcv(msgid,&msg_rbuf,4,10,rflags);
if(reval==-1){
 printf("read msg error\n");
}
else
 printf("read from msg queue %d bytes\n",reval);

msg_stat(msgid,msg_ginfo);
msg_sinfo.msg_perm.uid=8;
msg_sinfo.msg_perm.gid=8;
msg_sinfo.msg_qbytes=16388;

reval=msgctl(msgid,IPC_SET,&msg_sinfo);
if(reval==-1){
 printf("msg set info error\n");
 return;
}
msg_stat(msgid,msg_ginfo);
reval=msgctl(msgid,IPC_RMID,NULL);
if(reval==-1){
 printf("unlink msg queue error\n");
 return;
}
}


void msg_stat(int msgid,struct msqid_ds msg_info){
 int reval;
 sleep(1);
 reval=msgctl(msgid,IPC_STAT,&msg_info);
 if(reval==-1){
  printf("get msg info error\n");
  return;
 }
 printf("\n");
 printf("current number of bytes on queue is %d\n",msg_info.msg_cbytes);
 printf("number of messages in queue is %d\n",msg_info.msg_qnum);
 printf("max number of bytes on queue is %d\n",msg_info.msg_qbytes);
 printf("pid of last msgsnd is %d\n",msg_info.msg_lspid);
 printf("pid of last msgrcv is %d\n",msg_info.msg_lrpid);
 printf("last msgsnd time is%s",ctime(&(msg_info.msg_stime)));
 printf("last msgrcv time is%s",ctime(&(msg_info.msg_rtime)));
 printf("last change time is%s",ctime(&(msg_info.msg_ctime)));
 printf("msg uid is%d\n",msg_info.msg_perm.uid);
 printf("msg gid is%d\n",msg_info.msg_perm.gid);
 
}

程序运行结果:
current number of bytes on queue is 0
number of messages in queue is 0
max number of bytes on queue is 65536
pid of last msgsnd is 0
pid of last msgrcv is 0
Segmentation fault (core dumped)

  1. 编译并运行下述程序,观察并记录程序运行的结果。
#include <Linux/sem.h>
#include <stdio.h>
#include <errno.h>
#define SEM_PATH "/home/hx/my_sem"
#define max_tries 3

int semid;
int main()
{
int flag1,flag2,key,i,init_ok,tmperrno;
struct semid_ds sem_info;
struct seminfo sem_info2;
union semun arg;    //union semun: 请参考附录2
struct sembuf askfor_res, free_res;
flag1=IPC_CREAT|IPC_EXCL|00666;
flag2=IPC_CREAT|00666;
key=ftok(SEM_PATH,'a');
//error handling for ftok here;
init_ok=0;
semid=semget(key,1,flag1);//create a semaphore set that only includes one semphore.
if(semid<0)
{
 tmperrno=errno;
 perror("semget");
if(tmperrno==EEXIST)
//errno is undefined after a successful library call( including perror call) so it is saved //in tmperrno.
  {
  semid=semget(key,1,flag2);
//flag2 只包含了IPC_CREAT标志, 参数nsems(这里为1)必须与原来的信号灯数目一致
  arg.buf=&sem_info;
  for(i=0; i<max_tries; i++)
  {
   if(semctl(semid, 0, IPC_STAT, arg)==-1)
   { perror("semctl error"); i=max_tries;}
   else
   {
    if(arg.buf->sem_otime!=0){ i=max_tries;  init_ok=1;}
    else  sleep(1); 
   }
  }
  if(!init_ok)
  // do some initializing, here we assume that the first process that creates the sem will
  // finish initialize the sem and run semop in max_tries*1 seconds. else it will not run
  // semop any more.
  {
   arg.val=1;
   if(semctl(semid,0,SETVAL,arg)==-1) perror("semctl setval error");
  }
 }
 else
 {perror("semget error, process exit"); 
 exit(1); 
 }
}
else //semid>=0; do some initializing  
{
 arg.val=1;
 if(semctl(semid,0,SETVAL,arg)==-1)
  perror("semctl setval error");
}
//get some information about the semaphore and the limit of semaphore in redhat8.0
 arg.buf=&sem_info;
 if(semctl(semid, 0, IPC_STAT, arg)==-1)
  perror("semctl IPC STAT");  
 printf("owner's uid is %d\n",  arg.buf->sem_perm.uid);
 printf("owner's gid is %d\n",  arg.buf->sem_perm.gid);
 printf("creater's uid is %d\n",  arg.buf->sem_perm.cuid);
 printf("creater's gid is %d\n",  arg.buf->sem_perm.cgid);

 arg.__buf=&sem_info2;
 if(semctl(semid,0,IPC_INFO,arg)==-1)
  perror("semctl IPC_INFO");
 printf("the number of entries in semaphore map is %d \n",    arg.__buf->semmap);
 printf("max number of semaphore identifiers is %d \n",     arg.__buf->semmni);
 printf("mas number of semaphores in system is %d \n",     arg.__buf->semmns);
 printf("the number of undo structures system wide is %d \n",   arg.__buf->semmnu);
 printf("max number of semaphores per semid is %d \n",     arg.__buf->semmsl);
 printf("max number of ops per semop call is %d \n",     arg.__buf->semopm);
 printf("max number of undo entries per process is %d \n",     arg.__buf->semume);
 printf("the sizeof of struct sem_undo is %d \n",         arg.__buf->semusz);
 printf("the maximum semaphore value is %d \n",      arg.__buf->semvmx);
 
//now ask for available resource: 
 askfor_res.sem_num=0;
 askfor_res.sem_op=-1;
 askfor_res.sem_flg=SEM_UNDO;  
  
  if(semop(semid,&askfor_res,1)==-1)//ask for resource
   perror("semop error");
 
 sleep(3); //do some handling on the sharing resource here, just sleep on it 3 seconds
 printf("now free the resource\n"); 
 
//now free resource 
 free_res.sem_num=0;
 free_res.sem_op=1;
 free_res.sem_flg=SEM_UNDO;

 if(semop(semid,&free_res,1)==-1)//free the resource.
  if(errno==EIDRM)
   printf("the semaphore set was removed\n");
//you can comment out the codes below to compile a different version:   
 if(semctl(semid, 0, IPC_RMID)==-1)
  perror("semctl IPC_RMID");
 else printf("remove sem ok\n");
} 

程序的运行结果(操作系统redhat8.0):
owner’s uid is 0
owner’s gid is 0
creater’s uid is 0
creater’s gid is 0
the number of entries in semaphore map is 32000
max number of semaphore identifiers is 128
mas number of semaphores in system is 32000
the number of undo structures system wide is 32000
max number of semaphores per semid is 250
max number of ops per semop call is 32
max number of undo entries per process is 32
the sizeof of struct sem_undo is 20
the maximum semaphore value is 32767
now free the resource
remove sem ok

Summary:信号灯与其它进程间通信方式有所不同,它主要用于进程间同步。通常所说的系统V信号灯实际上是一个信号灯的集合,可用于多种共享资源的进程间同步。每个信号灯都有一个值,可以用来表示当前该信号灯代表的共享资源可用(available)数量,如果一个进程要申请共享资源,那么就从信号灯值中减去要申请的数目,如果当前没有足够的可用资源,进程可以睡眠等待,也可以立即返回。当进程要申请多种共享资源时,Linux可以保证操作的原子性,即要么申请到所有的共享资源,要么放弃所有资源,这样能够保证多个进程不会造成互锁。Linux对信号灯有各种各样的限制,程序中给出了输出结果。另外,如果读者想对信号灯作进一步的理解,建议阅读sem.h源代码,该文件不长,但给出了信号灯相关的重要数据结构。

  1. 编译并运行下述程序,观察并记录程序运行的结果。
/***shmwritetest.c**/
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct{
    char name[4];
    int age;
} people;
main(int argc, char** argv)
{
    int shm_id,i;
    key_t key;
    char temp;
    people *p_map;
    char* name = "/home";
    key = ftok(name,0);
    if(key==-1)
        perror("ftok error");
    shm_id=shmget(key,4096,IPC_CREAT);    
    if(shm_id==-1)
    {
        perror("shmget error");
        return;
    }
    p_map=(people*)shmat(shm_id,NULL,0);
    temp='a';
    for(i = 0;i<10;i++)
    {
        temp+=1;
        memcpy((*(p_map+i)).name,&temp,1);
        (*(p_map+i)).age=20+i;
    }
    if(shmdt(p_map)==-1)
        perror(" detach error ");
} 

 /****shmreadtest.c*****/

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct{
    char name[4];
    int age;
} people;
main(int argc, char** argv)
{
    int shm_id,i;
    key_t key;
    people *p_map;
    char* name = "/home";
    key = ftok(name,0);
    if(key == -1)
        perror("ftok error");
    shm_id = shmget(key,4096,IPC_CREAT);   
    if(shm_id == -1)
    {
        perror("shmget error");
        return;
    }
    p_map = (people*)shmat(shm_id,NULL,0);
    for(i = 0;i<10;i++)
    {
    printf( "name:%s\n",(*(p_map+i)).name );
    printf( "age %d\n",(*(p_map+i)).age );
    }
    if(shmdt(p_map) == -1)
        perror(" detach error ");
}

shmwritetest.c创建一个系统V共享内存区,并在其中写入格式化数据;shmreadtest.c访问同一个系统V共享内存区,读出其中的格式化数据。分别把两个程序编译为shmwritetest及shmreadtest,先后执行./shmwritetest及./shmreadtest 则./shmreadtest输出结果如下:
name: b age 20; name: c age 21; name: d age 22; name: e age 23; name: f age 24;
name: g age 25; name: h age 26; name: I age 27; name: j age 28; name: k age 29;
通过对试验结果分析,对比系统V与mmap()映射普通文件实现共享内存通信,可以得出如下结论:

  1. 系统V共享内存中的数据,从来不写入到实际磁盘文件中去;而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。注:前面讲到,系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的,文件系统shm的安装点在交换分区上,系统重新引导后,所有的内容都丢失。
  2. 系统V共享内存是随内核持续的,即使所有访问共享内存的进程都已经正常终止,共享内存区仍然存在(除非显式删除共享内存),在内核重新引导之前,对该共享内存区域的任何改写操作都将一直保留。
  3. 通过调用mmap()映射普通文件进行进程间通信时,一定要注意考虑进程何时终止对通信的影响。而通过系统V共享内存实现通信的进程则不然。 注:这里没有给出shmctl的使用范例,原理与消息队列大同小异。

五、实验代码及步骤截图

#include<sys/types.h>
#include<sys/msg.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<stdio.h>
void msg_stat(int,struct msqid_ds);

main()
{
 int gflags,sflags,rflags;
key_t key;
int msgid;
int reval;
struct msgsbuf{
 int mtype;
 char mtext[1];
}msg_sbuf;

struct msgmbuf{
 int mtype;
 char mtext[10];
}msg_rbuf;
struct msqid_ds msg_ginfo,msg_sinfo;
char *msgpath="/home/msgqueue";
key=ftok(msgpath,'a');
gflags=IPC_CREAT|IPC_EXCL;
msgid=msgget(key,gflags|00666);
if(msgid==-1){
 printf("msg create error\n");
 return;
}

msg_stat(msgid,msg_ginfo);
sflags=IPC_NOWAIT;
msg_sbuf.mtype=10;
msg_sbuf.mtext[0]='a';
reval=msgsnd(msgid,&msg_sbuf,sizeof(msg_sbuf.mtext),sflags);
if(reval==-1){
 printf("message send error\n");
}

msg_stat(msgid,msg_ginfo);
rflags=IPC_NOWAIT|MSG_NOERROR;
reval=msgrcv(msgid,&msg_rbuf,4,10,rflags);
if(reval==-1){
 printf("read msg error\n");
}
else
 printf("read from msg queue %d bytes\n",reval);

msg_stat(msgid,msg_ginfo);
msg_sinfo.msg_perm.uid=8;
msg_sinfo.msg_perm.gid=8;
msg_sinfo.msg_qbytes=16388;

reval=msgctl(msgid,IPC_SET,&msg_sinfo);
if(reval==-1){
 printf("msg set info error\n");
 return;
}
msg_stat(msgid,msg_ginfo);
reval=msgctl(msgid,IPC_RMID,NULL);
if(reval==-1){
 printf("unlink msg queue error\n");
 return;
}
}


void msg_stat(int msgid,struct msqid_ds msg_info){
 int reval;
 sleep(1);
 reval=msgctl(msgid,IPC_STAT,&msg_info);
 if(reval==-1){
  printf("get msg info error\n");
  return;
 }
 printf("\n");
 printf("current number of bytes on queue is %d\n",msg_info.msg_cbytes);
 printf("number of messages in queue is %d\n",msg_info.msg_qnum);
 printf("max number of bytes on queue is %d\n",msg_info.msg_qbytes);
 printf("pid of last msgsnd is %d\n",msg_info.msg_lspid);
 printf("pid of last msgrcv is %d\n",msg_info.msg_lrpid);
 printf("last msgsnd time is%s",ctime(&(msg_info.msg_stime)));
 printf("last msgrcv time is%s",ctime(&(msg_info.msg_rtime)));
 printf("last change time is%s",ctime(&(msg_info.msg_ctime)));
 printf("msg uid is%d\n",msg_info.msg_perm.uid);
 printf("msg gid is%d\n",msg_info.msg_perm.gid);
 
}

程序运行结果:
在这里插入图片描述

#include <Linux/sem.h>
#include <stdio.h>
#include <errno.h>
#define SEM_PATH "/home/hx/my_sem"
#define max_tries 3

int semid;
int main()
{
int flag1,flag2,key,i,init_ok,tmperrno;
struct semid_ds sem_info;
struct seminfo sem_info2;
union semun arg;    //union semun: 请参考附录2
struct sembuf askfor_res, free_res;
flag1=IPC_CREAT|IPC_EXCL|00666;
flag2=IPC_CREAT|00666;
key=ftok(SEM_PATH,'a');
//error handling for ftok here;
init_ok=0;
semid=semget(key,1,flag1);//create a semaphore set that only includes one semphore.
if(semid<0)
{
 tmperrno=errno;
 perror("semget");
if(tmperrno==EEXIST)
//errno is undefined after a successful library call( including perror call) so it is saved //in tmperrno.
  {
  semid=semget(key,1,flag2);
//flag2 只包含了IPC_CREAT标志, 参数nsems(这里为1)必须与原来的信号灯数目一致
  arg.buf=&sem_info;
  for(i=0; i<max_tries; i++)
  {
   if(semctl(semid, 0, IPC_STAT, arg)==-1)
   { perror("semctl error"); i=max_tries;}
   else
   {
    if(arg.buf->sem_otime!=0){ i=max_tries;  init_ok=1;}
    else  sleep(1); 
   }
  }
  if(!init_ok)
  // do some initializing, here we assume that the first process that creates the sem will
  // finish initialize the sem and run semop in max_tries*1 seconds. else it will not run
  // semop any more.
  {
   arg.val=1;
   if(semctl(semid,0,SETVAL,arg)==-1) perror("semctl setval error");
  }
 }
 else
 {perror("semget error, process exit"); 
 exit(1); 
 }
}
else //semid>=0; do some initializing  
{
 arg.val=1;
 if(semctl(semid,0,SETVAL,arg)==-1)
  perror("semctl setval error");
}
//get some information about the semaphore and the limit of semaphore in redhat8.0
 arg.buf=&sem_info;
 if(semctl(semid, 0, IPC_STAT, arg)==-1)
  perror("semctl IPC STAT");  
 printf("owner's uid is %d\n",  arg.buf->sem_perm.uid);
 printf("owner's gid is %d\n",  arg.buf->sem_perm.gid);
 printf("creater's uid is %d\n",  arg.buf->sem_perm.cuid);
 printf("creater's gid is %d\n",  arg.buf->sem_perm.cgid);

 arg.__buf=&sem_info2;
 if(semctl(semid,0,IPC_INFO,arg)==-1)
  perror("semctl IPC_INFO");
 printf("the number of entries in semaphore map is %d \n",    arg.__buf->semmap);
 printf("max number of semaphore identifiers is %d \n",     arg.__buf->semmni);
 printf("mas number of semaphores in system is %d \n",     arg.__buf->semmns);
 printf("the number of undo structures system wide is %d \n",   arg.__buf->semmnu);
 printf("max number of semaphores per semid is %d \n",     arg.__buf->semmsl);
 printf("max number of ops per semop call is %d \n",     arg.__buf->semopm);
 printf("max number of undo entries per process is %d \n",     arg.__buf->semume);
 printf("the sizeof of struct sem_undo is %d \n",         arg.__buf->semusz);
 printf("the maximum semaphore value is %d \n",      arg.__buf->semvmx);
 
//now ask for available resource: 
 askfor_res.sem_num=0;
 askfor_res.sem_op=-1;
 askfor_res.sem_flg=SEM_UNDO;  
  
  if(semop(semid,&askfor_res,1)==-1)//ask for resource
   perror("semop error");
 
 sleep(3); //do some handling on the sharing resource here, just sleep on it 3 seconds
 printf("now free the resource\n"); 
 
//now free resource 
 free_res.sem_num=0;
 free_res.sem_op=1;
 free_res.sem_flg=SEM_UNDO;

 if(semop(semid,&free_res,1)==-1)//free the resource.
  if(errno==EIDRM)
   printf("the semaphore set was removed\n");
//you can comment out the codes below to compile a different version:   
 if(semctl(semid, 0, IPC_RMID)==-1)
  perror("semctl IPC_RMID");
 else printf("remove sem ok\n");
} 

程序运行结果:
以用户身份运行:(uid有别)
在这里插入图片描述

以管理员身份运行:(uid有别)
在这里插入图片描述

/***shmwritetest.c**/
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct{
    char name[4];
    int age;
} people;
main(int argc, char** argv)
{
    int shm_id,i;
    key_t key;
    char temp;
    people *p_map;
    char* name = "/home";
    key = ftok(name,0);
    if(key==-1)
        perror("ftok error");
    shm_id=shmget(key,4096,IPC_CREAT);    
    if(shm_id==-1)
    {
        perror("shmget error");
        return;
    }
    p_map=(people*)shmat(shm_id,NULL,0);
    temp='a';
    for(i = 0;i<10;i++)
    {
        temp+=1;
        memcpy((*(p_map+i)).name,&temp,1);
        (*(p_map+i)).age=20+i;
    }
    if(shmdt(p_map)==-1)
        perror(" detach error ");
} 
 /****shmreadtest.c*****/
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct{
    char name[4];
    int age;
} people;
main(int argc, char** argv)
{
    int shm_id,i;
    key_t key;
    people *p_map;
    char* name = "/home";
    key = ftok(name,0);
    if(key == -1)
        perror("ftok error");
    shm_id = shmget(key,4096,IPC_CREAT);   
    if(shm_id == -1)
    {
        perror("shmget error");
        return;
    }
    p_map = (people*)shmat(shm_id,NULL,0);
    for(i = 0;i<10;i++)
    {
    printf( "name:%s\n",(*(p_map+i)).name );
    printf( "age %d\n",(*(p_map+i)).age );
    }
    if(shmdt(p_map) == -1)
        perror(" detach error ");
}

实验执行结果:
在这里插入图片描述

六、实验总结

  1. 写出实验报告。
  2. 思考:不同进程间通信方式对比分析?
    答:系统V共享内存是以文件的形式组织在特殊文件系统shm中的。通过shmget可以创建或获得共享内存的标识符。取得共享内存标识符后,要通过shmat将这个内存区映射到本进程的虚拟地址空间(本进程内通过开辟一个内存进行映射)。
    其还包括:
    struct shmid_ds buf;
    shmctl( shmid , IPC_STAT , &buf ); // 取得共享内存的状态
    shmctl( shmid , IPC_RMID , &buf ); // 删除共享内存
    要使用共享内存,应该有如下步骤:
    1.开辟一块共享内存 shmget()
    2.允许本进程使用共某块共享内存 shmat()
    3.写入/读出
    4.禁止本进程使用这块共享内存 shmdt()
    5.删除这块共享内存 shmctl()或者命令行下ipcrm
    共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。
    uid=0(root) gid=0(root) 组=0(root)
    uid=1000(schumacher) gid=1000(schumacher) 组=1000(schumacher),10(wheel)
Logo

更多推荐