Linux进程学习之:共享内存

首先是创建共享内存

使用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退出程序也有问题,不过双方通信是没有问题的,现就这样吧,以后再改。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值