共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,比如前面说到的信号量。
与信号量一样,在Linux中也提供了一组函数接口用于使用共享内存,而且使用共享共存的接口还与信号量的非常相似,而且比使用信号量的接口来得简单。它们声明在头文件 sys/shm.h中。
使用共享内存的方法是:
首先要用的函数是shmget,它获得一个共享存储标识符。
#include <sys/types.h>
#include
<sys/ipc.h>
#include
<sys/shm.h>
int shmget(key_t key, int size, int
flag);
这个函数有点类似大家熟悉的malloc函数,系统按照请求分配size大小的内存用作共享内存。Linux系统内核中每个IPC结构都有的一个非负整数的标识符,这样对一个消息队列发送消息时只要引用标识符就可以了。这个标识符是内核由IPC结构的关键字得到的,这个关键字,就是上面第一个函数的key。数据类型key_t是在头文件sys/types.h中定义的,它是一个长整形的数据。
当共享内存创建后,其余进程可以调用shmat()将其连接到自身的地址空间中。
void *shmat(int shmid, void *addr, int flag);
shmid为shmget函数返回的共享存储标识符,addr和flag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址,进程可以对此进程进行读写操作。
使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程去读取数据时,它所想要的数据已经写好了。通常,信号量被要来实现对共享存储数据存取的同步,另外,可以通过使用shmctl函数设置共享存储内存的某些标志位如SHM_LOCK、SHM_UNLOCK等来实现。
共享内存机制使得两个不相关进程可以读取和修改同一段内存从而实现数据共享。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <string.h>
int main(int argc,char **argv)
{
int shm_id;
pid_t pid;
char *paddr,*caddr;
if(argc != 2)
{
printf("please enter string\n");
exit(1);
}
if((shm_id = shmget(IPC_PRIVATE,1024,0666)) < 0)
{
perror("shmget");
exit(1);
}
if((pid = fork()) < 0)
{
perror("fork");
exit(1);
}
if(pid > 0) //父进程
{
paddr = shmat(shm_id,0,0); //映射
memset(paddr,'\0',1024); //清空
strncpy(paddr,argv[1],1024); //放数据到共享内存
wait();
exit(0);
}
else //子进程
{
sleep(1);
caddr = shmat(shm_id,0,0);
printf("child printf %s\n",caddr);
exit(0);
}
}
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/shm.h> #include <unistd.h> #include <sys/ipc.h> #include <string.h> int main(int argc,char **argv) { int shm_id; pid_t pid; char *paddr,*caddr; if(argc != 2) { printf("please enter string\n"); exit(1); } if((shm_id = shmget(IPC_PRIVATE,1024,0666)) < 0) { perror("shmget"); exit(1); } system("ipcs -m"); //查看共享内存状态 if((pid = fork()) < 0) { perror("fork"); exit(1); } if(pid > 0) //父进程 { paddr = shmat(shm_id,0,0); //映射 memset(paddr,'\0',1024); //清空 strncpy(paddr,argv[1],1024); //放数据到共享内存 system("ipcs -m"); //查看共享内存状态 if(shmdt(paddr) < 0) { perror("paddr shmdt"); exit(1); } else { printf("parent deattach share-memory\n"); } system("ipcs -m"); //查看共享内存状态 wait(); exit(0); } else //子进程 { sleep(1); caddr = shmat(shm_id,0,0); printf("child printf %s\n",caddr); system("ipcs -m"); //查看共享内存状态 if(shmdt(caddr) < 0) { perror("caddr shmdt"); exit(1); } else { printf("child deattach share-memory\n"); } system("ipcs -m"); //查看共享内存状态 if(shmctl(shm_id,IPC_RMID,NULL) < 0) { perror("shmctl"); exit(1); } else { printf("delete share-memory\n"); } exit(0); } }
在之前的代码基础上添加了部分操作,然后运行结果如下:(nattch在不停变哟)
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。Linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。Linux的消息队列(queue)实质上是一个链表, 它有消息队列标识符(queue ID). msgget创建一个新队列或打开一个存在的队列; msgsnd向队列末端添加一条新消息; msgrcv从队列中取消息, 取消息是不一定遵循先进先出的, 也可以按消息的类型字段取消息.
Linux提供了一系列消息队列的函数接口来让我们方便地使用它来实现进程间的通信。它的用法与信号量和共享内存相似。消息队列, 信号量和共享内存, 都属于内核中的IPC结构, 它们都用标识符来描述. 这个标识符是一个非负整数, 与文件描述符不同的是, 创建时并不会重复利用通过删除回收的整数, 而是每次+1, 直到整数最大值回转到0。标识符是IPC对象的内部名, 而它的外部名则是key(键), 它的基本类型是key_t, 在头文件<sys/types.h>中定义为长整型。键由内核变换成标识符。
创建/打开消息队列:
msgget函数
该函数用来创建和访问一个消息队列。它的原型为:
int msgget(key_t, key, int msgflg);
返回值: 成功则返回消息队列ID, 出错则返回-1.
与其他的IPC机制一样,程序必须提供一个键来命名某个特定的消息队列。msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。
它返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1。
key是IPC_PRIVATE。
key当前未与特定类型的IPC结构相结合, 并且msgflg中指定了IPC_CREAT位。
数据放到消息队列:
调用msgsnd将数据放到消息队列中。
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);返回值: 成功则返回0, 出错则返回-1。
说 明: 可以定义一个消息结构, 结构中带类型, 这样就可用非先进先出顺序取消息了。当msgsnd成功返回, 与消息队列相关的msqid_ds结构得到更新, 以标明发出该调用的进程ID(msg_lsqid), 进行该调用的时间(msg_stime), 并指示队列中增加了一条消息(msg_qnum).
消息队列中取消息:
调用msgrcv将从消息队列中取消息.
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
返回值: 成功则返回消息的数据部分的长度, 出错则返回-1.
参数:
msg_ptr: 指向一个长整型数(返回的消息类型存放在其中), 跟随其后的是存放实际消息数据的缓冲区.
msg_st: 数据缓冲区的长度. 若返回的消息大于msg_st, 且在msgflg中设置了MSG_NOERROR, 则该消息被截短.
msgtype:
msgtype == 0: 返回队列中的第一个消息.
msgtype > 0: 返回队列中消息类型为type的第一个消息.
msgtype < 0: 返回队列中消息类型值小于或等于type绝对值的消息, 如果这种消息有若干个, 则取类型值最小的消息.
说明: 当msgrcv成功返回时, 与消息队列相关的msqid_ds结构被更新, 以指示调用者的进程ID, 调用时间和队列中的消息数减1.
msgctl函数
该函数用来控制消息队列,它与共享内存的shmctl函数相似,它的原型为:
int msgctl(int msgid, int command, struct msgid_ds *buf);
返回值: 成功则返回0, 出错则返回-1.
参数: command参数说明对msqid指定的队列要执行的命令:
IPC_STAT: 取此队列的msqid_ds结构, 并将它存放在buf指向的结构中.
IPC_SET: 按由buf指向结构中的值, 设置与此队列相关结构中的msg_perm.uid, msg_perm.gid, msg_perm.mode和msg_qbytes. 该命令只有下列两种进程可以执行:
有效用户ID等于msg_perm.cuid或msg_per.uid.
具有超级用户特权的进程.
IPC_RMID: 从系统中删除该消息队列以及仍在该队列中的所有数据. 执行权限同上。
发送部分代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <string.h> #include <sys/msg.h> //int msgget(key_t key, int msgflg); //int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg); #define MAXSIZE 512 struct msgBuf { long msgType; char msg_text[MAXSIZE]; }; int main() { key_t key; int qid; struct msgBuf msg; if((key = ftok(".",'a')) < 0) { perror("ftok"); exit(1); } if((qid = msgget(key,IPC_CREAT | 0666)) < 0) //获取或打开一个消息队列 { perror("msgget"); exit(1); } printf("open queue %d\n",qid); while(1) { printf("please enter message to the queue:\n"); if(fgets(msg.msg_text,MAXSIZE,stdin) == NULL) { printf("no message\n"); exit(1); } msg.msgType = getpid(); if(msgsnd(qid,&msg,strlen(msg.msg_text),0) < 0) { perror("message snd"); exit(1); } if(strncmp(msg.msg_text,"quit",4) == 0) { break; } } exit(0); }
接收部分代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <string.h> #include <sys/msg.h> //int msgget(key_t key, int msgflg); //int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg); #define MAXSIZE 512 struct msgBuf { long msgType; char msg_text[MAXSIZE]; }; int main() { key_t key; int qid; struct msgBuf msg; if((key = ftok(".",'a')) < 0) { perror("ftok"); exit(1); } if((qid = msgget(key,IPC_CREAT | 0666)) < 0) //获取或打开一个消息队列 { perror("msgget"); exit(1); } printf("open queue %d\n",qid); while(1) { memset(msg.msg_text,0,MAXSIZE); if(msgrcv(qid,(void*)&msg,MAXSIZE,0,0) < 0) { perror("message rcv"); exit(1); } printf("the message from %d:%s\n",msg.msgType,msg.msg_text); if(strncmp(msg.msg_text,"quit",4) == 0) { break; } } if((msgctl(qid,IPC_RMID,NULL)) < 0) { perror("msgctl"); exit(1); } exit(0); }
运行的结果是:
在最初运行接收部分时,碰到过一个错误,报错:Argument list too long,上网找到了问题出在哪里了。报错Argument list too long错在使用msgsnd和msgrcv函数的时候,size_t msgsz 这个msgsz的大小双方不一致,msgsnd的长度大于了msgrcv的长度,所以就导致报错:Argument list too long
回复
有奖活动 | |
---|---|
【有奖活动】分享技术经验,兑换京东卡 | |
话不多说,快进群! | |
请大声喊出:我要开发板! | |
【有奖活动】EEPW网站征稿正在进行时,欢迎踊跃投稿啦 | |
奖!发布技术笔记,技术评测贴换取您心仪的礼品 | |
打赏了!打赏了!打赏了! |