首先是创建共享内存
使用shmget函数创建共享内存,函数声明为
int shmget(key_t key, size_t size, int shmflg);
所在头文件
#include <sys/shm.h>
在Ubuntu使用man shmget
查看帮助,显示如下:
shmget() returns the identifier of the System V shared memory segment associated with the
value of the argument key. A new shared memory segment, with size equal to the value of
size rounded up to a multiple of PAGE_SIZE, is created if key has the value
IPC_PRIVATE or key isn't IPC_PRIVATE, no shared memory segment corresponding to key exists,
and IPC_CREAT is specified in shmflg.
大概翻译一下,shmget函数有三个参数,第一参数key的值有IPC_PRIVATE和ftok返回值两种,IPC_PRIVATE用于具有亲缘关系的进程间通信,ftok函数返回值用于没有亲缘关系的进程间通信,第二个参数是申请的共享内存的大小,以byte为单位,第三个参数是文件的权限,一般使用0777,表示具有读写权限
返回值
On success, a valid shared memory identifier is returned.
On error, -1 is returned, and errno is set to indicate the error.
申请成功,返回共享内存的id,申请失败,返回 -1
补充一个linux查看ipc对象的命令:
ipcs -m
删除ipc对象的命令:
ipcrm -m shmid #shmid是申请的共享内存对应的shmid
贴一个创建共享内存的代码
#include <iostream>
#include <sys/shm.h>
#include <stdlib.h>
using namespace std;
int main(){
int shmid = shmget(IPC_PRIVATE, 128, 0777);
switch (shmid){
case -1:
cout << "create share memory failed ..." << endl;
return -1;
break;
default:
cout << "create share memeory successfully and shmid = " << shmid << endl;
}
system("ipcs -m");
return 0;
}
运行以上代码,最后会显示出当前系统中所有的ipc对象,接着在命令行执行ipcrm -m shmid
将会释放对应的ipc对象和内存。
无亲缘关系的进程建通信需要用到ftok函数,所以首先来看一下ftok函数,函数声明在sys/ipc.h中,函数声明为:
key_t ftok(const char *pathname, int proj_id);
函数有两个参数,第一个是文件地址,例如"./a.c",第二个是一个最低8位有效的一个整型数字
ftok函数描述:
The ftok() function uses the identity of the file named by the given pathname (which must refer to an existing, accessible file)
and the least significant 8 bits of proj_id (which must be nonzero) to generate a key_t type System V IPC key, suitable for use
with msgget(2), semget(2), or shmget(2).
The resulting value is the same for all pathnames that name the same file, when the same value of proj_id is used. The value
returned should be different when the (simultaneously existing) files or the project IDs differ.
函数返回:
On success, the generated key_t value is returned. On failure -1 is returned, with errno indicating the error as for the stat(2)
system call.
如果成功,函数返回创建的key值,否则,返回 -1
下面贴一个使用ftok函数创建共享内存的代码:
#include <iostream>
#include <sys/shm.h>
#include <stdlib.h>
#include <sys/ipc.h>
using namespace std;
int main(){
int key = ftok("./test", 1);
if (key < 0) {
cout << "create key failed" << endl;
return -1;
}
else
cout << "create key successfully and key = "<< key << endl;
int shmid = shmget(key, 128, IPC_CREAT | 0777);
switch (shmid){
case -1:
cout << "create share memory failed ..." << endl;
return -1;
break;
default:
cout << "create share memeory successfully and shmid = " << shmid << endl;
}
system("ipcs -m");
return 0;
}
前面我们已经讲过ftok函数的两个参数,第一个是一个文件路径,我以为这个是瞎写的,可是在我运行的时候,老是创建失败,最后是创建了一个在当前文件夹下创建了一个同名文件才成功,至于为什么还有待讨论
两种创建共享内存的方法都已经说了,接下来说如何将共享内存映射到用户空间,已经用完后将用户空间和共享内存之间的映射关系删除,分别是shmat()和shmdt()函数
1、void *shmat(int shmid, const void *shmaddr, int shmflg)
描述:
shmat() attaches the System V shared memory segment identified by shmid to the address space of the calling process. The attach‐
ing address is specified by shmaddr with one of the following criteria:
* If shmaddr is NULL, the system chooses a suitable (unused) address at which to attach the segment.
* If shmaddr isn't NULL and SHM_RND is specified in shmflg, the attach occurs at the address equal to shmaddr rounded down to the
nearest multiple of SHMLBA.
* Otherwise, shmaddr must be a page-aligned address at which the attach occurs.
In addition to SHM_RND, the following flags may be specified in the shmflg bit-mask argument:
SHM_EXEC (Linux-specific; since Linux 2.6.9)
Allow the contents of the segment to be executed. The caller must have execute permission on the segment.
SHM_RDONLY
Attach the segment for read-only access. The process must have read permission for the segment. If this flag is not spec‐
ified, the segment is attached for read and write access, and the process must have read and write permission for the seg‐
ment. There is no notion of a write-only shared memory segment.
SHM_REMAP (Linux-specific)
This flag specifies that the mapping of the segment should replace any existing mapping in the range starting at shmaddr
and continuing for the size of the segment. (Normally, an EINVAL error would result if a mapping already exists in this
address range.) In this case, shmaddr must not be NULL.
The brk(2) value of the calling process is not altered by the attach. The segment will automatically be detached at process exit.
The same segment may be attached as a read and as a read-write one, and more than once, in the process's address space.
A successful shmat() call updates the members of the shmid_ds structure (see shmctl(2)) associated with the shared memory segment
as follows:
shm_atime is set to the current time.
shm_lpid is set to the process-ID of the calling process.
shm_nattch is incremented by one.
shmat函数用于将共享内存映射到用户空间,第一个参数是前面生成的shmid,第二个是共享内存的地址,一般用NULL 代替,表示系统自己去解决共共享内存的地址问题,第三个参数一般用0代替。
返回值:
On success, shmat() returns the address of the attached shared memory segment; on error, (void *) -1 is returned, and errno is set
to indicate the cause of the error.
如果成功则返回映射地址,否则返回-1
2、int shmdt(const void *shmaddr);
描述:
shmdt() detaches the shared memory segment located at the address specified by shmaddr from the address space of the calling
process. The to-be-detached segment must be currently attached with shmaddr equal to the value returned by the attaching shmat()
call.
On a successful shmdt() call, the system updates the members of the shmid_ds structure associated with the shared memory segment
as follows:
shm_dtime is set to the current time.
shm_lpid is set to the process-ID of the calling process.
shm_nattch is decremented by one. If it becomes 0 and the segment is marked for deletion, the segment is deleted.
返回值:
On success, shmdt() returns 0; on error -1 is returned, and errno is set to indicate the cause of the error.
当删除完地址映射后,我们还需要删除共享内存,也即释放共享内存,此时需要用到shmctl函数
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
描述:
shmctl() performs the control operation specified by cmd on the System V shared memory segment whose identifier is given in shmid.
shmctl一共有三个参数,第一个参数是共享内存id,第二个参数是对共享内存的操作命令,一共有好几个命令,这里我们要用到的是IPC_RMID,由于我们是要释放共享内存,所以第三个参数输入NULL就可以了
返回值:
operations return 0 on success.
On error, -1 is returned, and errno is set appropriately.
成功返回0.失败返回-1
贴一段代码:
#include <iostream>
#include <sys/shm.h>
#include <stdlib.h>
#include <sys/ipc.h>
using namespace std;
int main(){
int key = ftok("./test", 1);
if (key < 0) {
cout << "create key failed" << endl;
return -1;
}
else
cout << "create key successfully and key = "<< key << endl;
int shmid = shmget(key, 128, IPC_CREAT | 0777);
switch (shmid){
case -1:
cout << "create share memory failed ..." << endl;
return -1;
break;
default:
cout << "create share memeory successfully and shmid = " << shmid << endl;
}
system("ipcs -m");
char *p = (char *)shmat(shmid, NULL, 0);
if (*p == -1){
cout << "map share memory failed" << endl;
return -1;
}
else
cout << "map share memeory successfully" << endl;
cout << "please input a string: ";
cin >> p;
cout << p << endl;
cout << p << endl;
int ret = shmdt(p);
if (ret == -1){
cout << "detaches failed" << endl;
return -2;
}
else
cout << "detaches successfully" << endl;
ret = shmctl(shmid, IPC_RMID, NULL);
if (ret == 0)
cout << "destroy segment successfully" << endl;
else {
cout << "destroy segment failed" << endl;
return -3;
}
system("ipcs -m");
return 0;
}
以上程序主要实现了共享内存的创建,输入和销毁的操作,程序运行到一半的时候,会提示你输入一个字符串,随便输入一个就好
注意:
1、有以上代码可以看出,共享内存通信和管道通信不一样,管道通信的内容只能读一次,读完之后内存中的信息就被销毁,而共享内存不会,可以读多次
2、执行完shmdt函数后,就不能再对共享内存进行写操作了,否则会引发段错误
贴一个具有亲缘关系的进程间通信代码:
#include <iostream>
#include <sys/shm.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
using namespace std;
bool stopCmu = false;
void myfunc(int signum){
return;
}
void detachMapShareMemory(int signum){
cout << "changed stopCmu var" << endl;
stopCmu = true;
}
int main(){
int shmid = shmget(IPC_PRIVATE, 128, 0777);
switch (shmid){
case -1:
cout << "create share memory failed ..." << endl;
return -1;
break;
default:
cout << "create share memeory successfully and shmid = " << shmid << endl;
}
pid_t pid = fork();
if (pid > 0){
signal(SIGINT, detachMapShareMemory);
char *p = (char *)shmat(shmid, NULL, 0);
if (*p == -1){
cout << "parent process map share memory failed" << endl;
return -1;
}
else
cout << "parent process map share memeory successfully" << endl;
while (!stopCmu) {
signal(SIGUSR2, myfunc);
cout << "parent process: please input a string: ";
cin >> p;
kill(pid, SIGUSR1); // notice child process to read from
// share memory
pause(); // sleep process, wait signal from
// child process
}
int ret = shmdt(p);
if (ret == -1){
cout << "parent process detaches failed" << endl;
return -2;
}
else
cout << "parent process detaches successfully" << endl;
ret = shmctl(shmid, IPC_RMID, NULL);
if (ret == 0)
cout << "parent process destroy segment successfully" << endl;
else {
cout << "parent process destroy segment failed" << endl;
return -3;
}
if (waitpid(0, NULL, WNOHANG) == 0)
kill(9, pid);
wait(NULL);
}
else if (pid == 0){
char *p = (char *)shmat(shmid, NULL, 0);
if (*p == -1){
cout << "child process map share memory failed" << endl;
return -1;
}
else
cout << "child process map share memeory successfully" << endl;
while(!stopCmu) {
signal(SIGUSR1, myfunc);
pause();
cout << "child process print string: " << p << endl;
kill(getppid(), SIGUSR2);
}
int ret = shmdt(p);
if (ret == -1){
cout << "child process detaches failed" << endl;
return -2;
}
else
cout << "child process detaches successfully" << endl;
}
else {
cout << "fork process failed" << endl;
return -1;
}
return 0;
}
上面的代码实现的是具有亲缘关系的进程间通信,不过有一点小小的问题,我想在不想输入的时候,按ctrl+c结束程序,并删除共享内存映射关系和释放共享内存,但是没有实现,这个问题后边再解决,现在实现通信就可以了
上面代码的逻辑是:创建好共享内存后,父子进程开始通信,父进程在写入完之后给子进程发一个信号,并进入睡眠状态,子进程接收到父进程的信号被唤醒,读取共享内存中的内容,然后给父进程发一个信号,并进入睡眠状态,父进程接收到信号后,开始运行,并请求用户继续输入,输入完成之后给子进程发一个信号,又进入睡眠状态,如此循环往复。
接下来贴一个非亲缘关系进程之间的通信:
首先是server端,负责输入数据:
#include <iostream>
#include <sys/shm.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
using namespace std;
bool stopCmu = false;
struct mybuf{
int pid;
char buf[124];
};
void myfunc(int signum){
return;
}
void detachMapShareMemory(int signum){
cout << "changed stopCmu var" << endl;
stopCmu = true;
}
int main(){
int key = ftok("./a", 1);
if (key < 0){
cout << "crete key failed" << endl;
return -1;
}
else
cout << "create key successfully" << endl;
int shmid = shmget(key, 128, IPC_CREAT | 0777);
switch (shmid){
case -1:
cout << "create share memory failed ..." << endl;
return -1;
break;
default:
cout << "create share memeory successfully and shmid = " << shmid << endl;
}
signal(SIGINT, detachMapShareMemory);
signal(SIGUSR1, myfunc);
mybuf *p = (mybuf *)shmat(shmid, NULL, 0);
if (p == NULL){
cout << "server map share memory failed" << endl;
return -1;
}
else
cout << "server map share memeory successfully" << endl;
p->pid = getpid();
pause();
int pid = p->pid;
while (!stopCmu) {
cout << "server input: ";
cin >> p->buf;
kill(pid, SIGUSR2); // notice child process to read from
// share memory
pause(); // sleep process, wait signal from
// child process
}
int ret = shmdt(p);
if (ret == -1){
cout << "parent process detaches failed" << endl;
return -2;
}
else
cout << "parent process detaches successfully" << endl;
ret = shmctl(shmid, IPC_RMID, NULL);
if (ret == 0)
cout << "parent process destroy segment successfully" << endl;
else {
cout << "parent process destroy segment failed" << endl;
return -3;
}
return 0;
}
其次是client端,负责读取数据:
#include <iostream>
#include <sys/shm.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
using namespace std;
bool stopCmu = false;
struct mybuf {
int pid;
char buf[124];
};
void myfunc(int signum){
return;
}
void detachMapShareMemory(int signum){
cout << "changed stopCmu var" << endl;
stopCmu = true;
}
int main(){
int key = ftok("./a", 1);
if (key < 0){
cout << "crete key failed" << endl;
return -1;
}
else
cout << "create key successfully" << endl;
int shmid = shmget(key, 128,IPC_CREAT | 0777);
switch (shmid){
case -1:
cout << "create share memory failed ..." << endl;
return -1;
break;
default:
cout << "create share memeory successfully and shmid = " << shmid << endl;
}
signal(SIGINT, detachMapShareMemory);
signal(SIGUSR2, myfunc);
mybuf *p = (mybuf *)shmat(shmid, NULL, 0);
if (p == NULL){
cout << "server map share memory failed" << endl;
return -1;
}
else
cout << "server map share memeory successfully" << endl;
int pid = p->pid;
p->pid = getpid();
kill(pid, SIGUSR1);
while(!stopCmu){
cout << "client print: " << p->buf << endl;
kill(pid, SIGUSR1); // notice child process to read from
// share memory
pause(); // sleep process, wait signal from
// child process
}
int ret = shmdt(p);
if (ret == -1){
cout << "parent process detaches failed" << endl;
return -2;
}
else
cout << "parent process detaches successfully" << endl;
return 0;
}
这次的代码与前边的不一样的地方就是,创建了一个结构体用来保存通信内容,首先是一个整型变量用来保存进程的pid,因为互相通信的两个进程必须知道对方的pid,才可以通信,所以需要在共享内存的前四个字节,写入进程pid。逻辑如下:
我们现在设定先启动server端,那么需要server先在内存中写入自己的pid,然后进入睡眠状态,接着启动client端,client端先读取共享内存中server的pid,然后写入自己的pid,并且给刚才读到的server端pid发信号,server端接收到client端的信号之后,从睡眠状态变为运行状态,并且从共享内存中读取client端的pid,接下来两个没有亲缘关系的进程就可以开始通信了
同具有亲缘关系的代码一样,不具有亲缘关系的代码的按ctrl+C退出程序也有问题,不过双方通信是没有问题的,现就这样吧,以后再改。