Linux-进程通信-消息队列/信号灯/共享内存

消息队列

    消息队列提供了进程间发送数据块的方法,每个数据块都可以被认为是有一个类型,接受者接受的数据块可以有不同的类型;我们可以通过发送消息来避免命名管道的同步和阻塞问题;消息队列与命名管道一样,每个数据块都有一个最大长度的限制;我们可以将每个数据块当作是一中消息类型(频道),发送和接收的内容就是这个类型(频道)对应的消息(节目),每个类型(频道)相当于一个独立的管道,相互之间互不影响。

    例子:子进程发送10次消息,父进程接收7次。

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

#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>

#define MAX 1024
typedef struct ___msgbuf{
	long mtype;     //消息类型/通道号
	pid_t pid;		//进程ID
	char mtext[MAX];//消息正文
}MSGBUF;

int main()
{
	key_t key = ftok(".", 123);
	if( 0 > key)
	{
		perror("ftok");
		exit(-1);
	}
	int msgid = msgget(key, IPC_CREAT|IPC_EXCL|0666);
	if(0 > msgid)
	{
		if(EEXIST != errno)
		{
			perror("msgget");
			exit(-1);
		}
		msgid = msgget(key, IPC_EXCL|0666);
	}

	pid_t pid = fork();
	if(0 > pid)
	{
		perror("fork");
		exit(-1);
	}
	else if(0 == pid)
	{
		sleep(3);
		printf("-------1-------\n");
		int k = 10;
		while(k--)
		{
			MSGBUF msgbuf;
			memset(&msgbuf, 0, sizeof(msgbuf));
			msgbuf.mtype = rand()%10+1;
			msgbuf.pid   = getpid();
			strcpy(msgbuf.mtext, "hello world");
			printf("------NO.%d-%ld, %d------\n", k, msgbuf.mtype, msgbuf.pid);
			int ret = msgsnd(msgid, &msgbuf, sizeof(msgbuf.pid)+strlen(msgbuf.mtext), 0);
			if(0 > ret)
			{
				perror("msgsnd");
				break;
			}
			sleep(1);
		}
		printf("-------2-------\n");
	}
	else
	{
		int k = 7;
		while(k--)
		{
			MSGBUF msgbuf;
			memset(&msgbuf, 0, sizeof(msgbuf));
			int ret = msgrcv(msgid, &msgbuf, sizeof(msgbuf.pid)+sizeof(msgbuf.mtext), 0, 0);
			if(0 > ret)
			{
				perror("msgrcv");
				break;
			}
			printf("RECV: %d, %s\n", msgbuf.pid, msgbuf.mtext);
		}

		wait(NULL);
		if(0 > msgctl(msgid, IPC_RMID, NULL))
		{
			perror("msgctl");
		}
	}
	exit(0);
}

信号灯(信号量)

     信号灯提供这样的一种访问机制,让一个临界区同一时间只有一个进程访问它,也就是说信号灯是用来调协进程对共享资源的访问的。信号灯是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号灯是只能取0和1的变量,这也是信号灯量最常见的一种形式,叫做二进制信号灯。

     工作原理:

          P操作,如果值大于0就减1,并执行后面程序;如果它的值为零,就挂起该进程的执行;

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

    使用流程:

     key_t key = ftok(".", 1);

     int semget(key_t key, int nsems, int semflg);   nsems:信号灯数量为1;semflg标志:IPC_CREAT|IPC_EXCL

扫描二维码关注公众号,回复: 3655384 查看本文章

     int semctl(int semid, int semnum, int cmd, ...);    控制信号灯,SETVAL:用来把信号量初始化为一个已知的值,一般初始化为1;IPC_RMID:用于删除一个已经无需继续使用的信号量标识符;

     int semop(int semid, struct sembuf *sops, unsigned nsops);   改变信号量的值,sembuf 定义如下:

     struct sembuf{

           unsigned short sem_num;  /* semaphore number */一般为0
           short          sem_op;   /* semaphore operation */信号灯操作,P为-1,V为+1
           short          sem_flg;  /* operation flags */一般为SEM_UNDO

     };

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

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <wait.h>

int P(int sid)
{
	struct sembuf lock={0, -1, SEM_UNDO};
	return semop(sid, &lock, 1);
}

int V(int sid)
{
	struct sembuf lock={0, 1, SEM_UNDO};
	return semop(sid, &lock, 1);
}

int Wait(int sid)
{
	struct sembuf lock={0, 0, SEM_UNDO};
	return semop(sid, &lock, 1);
}

int setsem(int sid, int val)
{
	return semctl(sid, 0, SETVAL, val);
}

int rmsem(int sid)
{
	return semctl(sid, 0, IPC_RMID, 0);
}

int main()
{
	key_t key = ftok(".", 1);
	if(0 > key)
	{
		perror("ftok");
		exit(-1);
	}
	
	int sid = semget(key, 1, IPC_CREAT|IPC_EXCL);
	if(0 > sid)
	{
		if(EEXIST != errno)
		{
			perror("semget");
			exit(-1);
		}
		sid = semget(key, 1, IPC_EXCL);
	}

	setsem(sid, 1);

	pid_t pid = fork();
	if(0 > pid)
	{
		perror("fork");
		exit(-1);
	}
	else if(0 == pid)
	{
		P(sid);
		printf("------3-------\n");
		sleep(1);
		printf("------4-------\n");
		V(sid);
	}
	else
	{
		P(sid);
		printf("------1-------\n");
		sleep(1);
		printf("------2-------\n");
		V(sid);
		wait(NULL);
		rmsem(sid);
	}
	exit(0);
}

共享内存

    System V进程间的通信方式共享内存,就是允许两个不相关的进程访问同一个物理内存,在系统建立IPC通信时,通过ftok()函数得到一个key值,可用于非血缘关系进程间通信;

    共享内存的操作步骤分为四步:

        <1>创建共享内存

        int id = shmget(key, MAXSZ, IPC_CREAT|IPC_EXCL);    key值为IPC_PRIVATE=0时为血缘关系进程间通信;MAXSZ为申请物理内存空间大小,以PAGE_SIZE=4KB为基本单位;IPC_CREAT表示如果共享内存不存在则创建一个共享内存,否则直接打开已存在的,返回其ID;IPC_EXCL只有在共享内存不存在的时候,新的共享内存才建立,否则若是存在,shmget调用失败,并设置EEXITST错误码;

        <2>映射共享内存,即把指定的共享内存映射到多个进程的地址空间,方便进程的访问;

        char *shmp=shmat(id, NULL, 0);    后续就可以直接操作shmp这个指针,可用其他数据类型设置指针空间数据类型;

        <3>撤销共享内存的映射

        shmdt(shmp)

        <4>删除共享内存

        shmctl(id, IPC_RMID, NULL)

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

#define MAXSZ 4096

int main()
{
	key_t key = ftok("./test.c", 10);
	if(key<0)
	{
		perror("ftok");
		exit(-1);
	}

	int id = shmget(key, MAXSZ, IPC_CREAT|IPC_EXCL);
	if(id<0)
	{
		if(EEXIST != errno)
		{
			perror("shmget");
			exit(-1);
		}
	}

	char *shmp=shmat(id, NULL, 0);
	if((void*)-1 == shmp)
	{
		perror("shmat");
		exit(-1);		
	}

	memset(shmp, 0, MAXSZ);
	strcpy(shmp,"hello world");
	printf("shmp=%s\n", shmp);

	pid_t pid = fork();
	if(0 > pid)
	{
		perror("fork");
		exit(-1);
	}
	else if(0 == pid)
	{
		int k = 10;
		while(k--)
		{
			sprintf(shmp,"hello world %d",k);
			sleep(1);
		}
	}
	else
	{
		sleep(1);
		int k = 10;
		while(k--)
		{
			printf("shmp %d=%s\n",k, shmp);
			sleep(1);
		}
		wait(NULL);

		if(-1 == shmdt(shmp))
		{
			perror("shmdt");
		}
		if(0 > shmctl(id, IPC_RMID, NULL))
		{
			perror("shmctl");
		}
	}
	exit(0);
}

    查看共享内存命令:ipcs

猜你喜欢

转载自blog.csdn.net/TSZ0000/article/details/83143296