如何在unix环境下实现共享内存

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在unix环境下实现共享内存相关的知识,希望对你有一定的参考价值。

请问如何在unix下实现共享内存,关于书本上的那些基本的函数,就不用列举了。 当前,我一直搞不明白,在一般的书中所说的共享内存是申请了一个连续的已知大小的内存空间,倘若是想把已经申请了的空间分割成小的空间,不知道怎么实现 谢谢

mmap()范例
下面将给出使用mmap()的两个范例:范例1给出两个进程通过映射普通文件实现共享内存通信;范例2给出父子进程通过匿名映射实现共享内存。系统调用mmap()有许多有趣的地方,下面是通过mmap()映射普通文件实现进程间的通信的范例,我们通过该范例来说明mmap()实现共享内存的特点及注意事项。

范例1:两个进程通过映射普通文件实现共享内存通信

范例1包含两个子程序:map_normalfile1.c及map_normalfile2.c。编译两个程序,可执行文件分别为map_normalfile1及map_normalfile2。两个程序通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。map_normalfile2试图打开命令行参数指定的一个普通文件,把该文件映射到进程的地址空间,并对映射后的地址空间进行写操作。map_normalfile1把命令行参数指定的文件映射到进程地址空间,然后对映射后的地址空间执行读操作。这样,两个进程通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。

下面是两个程序代码:

/*-------------map_normalfile1.c-----------*/
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct
char name[4];
int age;
people;
main(int argc, char** argv) // map a normal file as shared mem:

int fd,i;
people *p_map;
char temp;

fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
lseek(fd,sizeof(people)*5-1,SEEK_SET);
write(fd,"",1);

p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );
close( fd );
temp = \'a\';
for(i=0; i<10; i++)

temp += 1;
memcpy( ( *(p_map+i) ).name, &temp,2 );
( *(p_map+i) ).age = 20+i;

printf(" initialize over \\n ");
sleep(10);
munmap( p_map, sizeof(people)*10 );
printf( "umap ok \\n" );

/*-------------map_normalfile2.c-----------*/
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct
char name[4];
int age;
people;
main(int argc, char** argv) // map a normal file as shared mem:

int fd,i;
people *p_map;
fd=open( argv[1],O_CREAT|O_RDWR,00777 );
p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
for(i = 0;i<10;i++)

printf( "name: %s age %d;\\n",(*(p_map+i)).name, (*(p_map+i)).age );

munmap( p_map,sizeof(people)*10 );


map_normalfile1.c首先定义了一个people数据结构,(在这里采用数据结构的方式是因为,共享内存区的数据往往是有固定格式的,这由通信的各个进程决定,采用结构的方式有普遍代表性)。map_normfile1首先打开或创建一个文件,并把文件的长度设置为5个people结构大小。然后从mmap()的返回地址开始,设置了10个people结构。然后,进程睡眠10秒钟,等待其他进程映射同一个文件,最后解除映射。

map_normfile2.c只是简单的映射一个文件,并以people数据结构的格式从mmap()返回的地址处读取10个people结构,并输出读取的值,然后解除映射。

分别把两个程序编译成可执行文件map_normalfile1和map_normalfile2后,在一个终端上先运行./map_normalfile2 /tmp/test_shm,程序输出结果如下:

initialize over
umap ok

在map_normalfile1输出initialize over 之后,输出umap ok之前,在另一个终端上运行map_normalfile2 /tmp/test_shm,将会产生如下输出(为了节省空间,输出结果为稍作整理后的结果):

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;

在map_normalfile1 输出umap ok后,运行map_normalfile2则输出如下结果:

name: b age 20; name: c age 21; name: d age 22; name: e age 23; name: f age 24;
name: age 0; name: age 0; name: age 0; name: age 0; name: age 0;

从程序的运行结果中可以得出的结论

1、 最终被映射文件的内容的长度不会超过文件本身的初始大小,即映射不能改变文件的大小;

2、 可以用于进程通信的有效地址空间大小大体上受限于被映射文件的大小,但不完全受限于文件大小。打开文件被截短为5个people结构大小,而在map_normalfile1中初始化了10个people数据结构,在恰当时候(map_normalfile1输出initialize over 之后,输出umap ok之前)调用map_normalfile2会发现map_normalfile2将输出全部10个people结构的值,后面将给出详细讨论。
注:在linux中,内存的保护是以页为基本单位的,即使被映射文件只有一个字节大小,内核也会为映射分配一个页面大小的内存。当被映射文件小于一个页面大小时,进程可以对从mmap()返回地址开始的一个页面大小进行访问,而不会出错;但是,如果对一个页面以外的地址空间进行访问,则导致错误发生,后面将进一步描述。因此,可用于进程间通信的有效地址空间大小不会超过文件大小及一个页面大小的和。

3、 文件一旦被映射后,调用mmap()的进程对返回地址的访问是对某一内存区域的访问,暂时脱离了磁盘上文件的影响。所有对mmap()返回地址空间的操作只在内存中有意义,只有在调用了munmap()后或者msync()时,才把内存中的相应内容写回磁盘文件,所写内容仍然不能超过文件的大小。

范例2:父子进程通过匿名映射实现共享内存

#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct
char name[4];
int age;
people;
main(int argc, char** argv)

int i;
people *p_map;
char temp;
p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
if(fork() == 0)

sleep(2);
for(i = 0;i<5;i++)
printf("child read: the %d people\'s age is %d\\n",i+1,(*(p_map+i)).age);
(*p_map).age = 100;
munmap(p_map,sizeof(people)*10); //实际上,进程终止时,会自动解除映射。
exit();

temp = \'a\';
for(i = 0;i<5;i++)

temp += 1;
memcpy((*(p_map+i)).name, &temp,2);
(*(p_map+i)).age=20+i;

sleep(5);
printf( "parent read: the first people,s age is %d\\n",(*p_map).age );
printf("umap\\n");
munmap( p_map,sizeof(people)*10 );
printf( "umap ok\\n" );


考察程序的输出结果,体会父子进程匿名共享内存:

child read: the 1 people\'s age is 20
child read: the 2 people\'s age is 21
child read: the 3 people\'s age is 22
child read: the 4 people\'s age is 23
child read: the 5 people\'s age is 24
parent read: the first people,s age is 100
umap
umap ok

回页首

四、对mmap()返回地址的访问

前面对范例运行结构的讨论中已经提到,linux采用的是页式管理机制。对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说,能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明:

注意:文件被映射部分而不是整个文件决定了进程能够访问的空间大小,另外,如果指定文件的偏移部分,一定要注意为页面大小的整数倍。下面是对进程映射地址空间的访问范例:

#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct
char name[4];
int age;
people;
main(int argc, char** argv)

int fd,i;
int pagesize,offset;
people *p_map;

pagesize = sysconf(_SC_PAGESIZE);
printf("pagesize is %d\\n",pagesize);
fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
lseek(fd,pagesize*2-100,SEEK_SET);
write(fd,"",1);
offset = 0; //此处offset = 0编译成版本1;offset = pagesize编译成版本2
p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);
close(fd);

for(i = 1; i<10; i++)

(*(p_map+pagesize/sizeof(people)*i-2)).age = 100;
printf("access page %d over\\n",i);
(*(p_map+pagesize/sizeof(people)*i-1)).age = 100;
printf("access page %d edge over, now begin to access page %d\\n",i, i+1);
(*(p_map+pagesize/sizeof(people)*i)).age = 100;
printf("access page %d over\\n",i+1);

munmap(p_map,sizeof(people)*10);


如程序中所注释的那样,把程序编译成两个版本,两个版本主要体现在文件被映射部分的大小不同。文件的大小介于一个页面与两个页面之间(大小为:pagesize*2-99),版本1的被映射部分是整个文件,版本2的文件被映射部分是文件大小减去一个页面后的剩余部分,不到一个页面大小(大小为:pagesize-99)。程序中试图访问每一个页面边界,两个版本都试图在进程空间中映射pagesize*3的字节数。

版本1的输出结果如下:

pagesize is 4096
access page 1 over
access page 1 edge over, now begin to access page 2
access page 2 over
access page 2 over
access page 2 edge over, now begin to access page 3
Bus error //被映射文件在进程空间中覆盖了两个页面,此时,进程试图访问第三个页面

版本2的输出结果如下:

pagesize is 4096
access page 1 over
access page 1 edge over, now begin to access page 2
Bus error //被映射文件在进程空间中覆盖了一个页面,此时,进程试图访问第二个页面

结论:采用系统调用mmap()实现进程间通信是很方便的,在应用层上接口非常简洁。内部实现机制区涉及到了linux存储管理以及文件系统等方面的内容,可以参考一下相关重要数据结构来加深理解。在本专题的后面部分,将介绍系统v共享内存的实现。

参考资料

[1] Understanding the Linux Kernel, 2nd Edition, By Daniel P. Bovet, Marco Cesati , 对各主题阐述得重点突出,脉络清晰。

[2] UNIX网络编程第二卷:进程间通信,作者:W.Richard Stevens,译者:杨继张,清华大学出版社。对mmap()有详细阐述。

[3] Linux内核源代码情景分析(上),毛德操、胡希明著,浙江大学出版社,给出了mmap()相关的源代码分析。

[4]mmap()手册
参考技术A shmget 函数用于创建(或者获取)一个由key键值指定的共享内存对象具体情况还要自己去定义 参考技术B man shmget 看到相关的函数都了解一下,兴许能找到

分布式集群环境下,如何实现session共享三(环境搭建)

  这是分布式集群环境下,如何实现session共享系列的第三篇。在上一篇:分布式集群环境下,如何实现session共享二(项目开发)中,准备好了一个通过原生态的servlet操作session的案例。本篇需要搭建相关的环境,包括:tomcat、nginx、redis。

  1.通过两个tomcat搭建集群:tomcat_1、tomcat_2

  2.通过nginx实现负载均衡

  3.通过redis存储session

1.安装tomcat

  1.1.tomcat_1

  上传tomcat_1到服务器192.168.80.22中。

#跳转到目录
cd /usr/local/develop

#创建目录
mkdir atomcat

cd atomcat

技术图片

 

   server.xml配置文件(端口):

进程监听端口:
    <Server port="8005" shutdown="SHUTDOWN">
http服务端口:
       <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
集群通信端口:
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

 

  1.2.tomcat_2

  上传tomcat_2到服务器192.168.80.22中。

#跳转到目录
cd /usr/local/develop

#创建目录
mkdir atomcat

cd atomcat

技术图片

 

   server.xml配置文件(端口):

进程监听端口:
    <Server port="8006" shutdown="SHUTDOWN">
http服务端口:
       <Connector port="8081" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
集群通信端口:
    <Connector port="8010" protocol="AJP/1.3" redirectPort="8443" />

 

2.安装nginx

  2.1.安装编译工具和库文件

yum -y install make zlib zlib-devel gcc-c++ libtool  openssl openssl-devel

 

  2.2.安装pcre

#进入目录
cd /usr/local/develop/anginx

#上传安装文件并解压
tar -zxvf pcre-8.38.tar.gz

#进入安装目录
cd pcre-8.38

#检查配置
./configure

#编译、安装
make && make install

#查看pcre版本
pcre-config --version

 

  2.3.安装nginx

#进入目录
cd /usr/local/develop/anginx

#上传安装文件,并解压
tar -zxvf nginx-1.8.1.tar.gz

#进入安装目录
cd nginx-1.8.1

#检查配置
./configure --prefix=/usr/local/develop/anginx/webserver/nginx --with-http_stub_status_module --with-http_ssl_module --with-pcre=/usr/local/develop/anginx/pcre-8.38

#编译安装
make && make install

#查看nginx版本
 /usr/local/develop/anginx/webserver/nginx/sbin/nginx -v
--------------------------------------------------------
[[email protected] webserver]# /usr/local/develop/anginx/webserver/nginx/sbin/nginx -v
nginx version: nginx/1.8.1

#配置nginx(检查)
/usr/local/develop/anginx/webserver/nginx/sbin/nginx -t

#nginx管理命令
/usr/local/develop/anginx/webserver/nginx/sbin/nginx              # 启动 Nginx
/usr/local/develop/anginx/webserver/nginx/sbin/nginx -s stop              # 停止 Nginx
/usr/local/develop/anginx/webserver/nginx/sbin/nginx -s reload            # 重新载入配置文件
/usr/local/develop/anginx/webserver/nginx/sbin/nginx -s reopen            # 重启 Nginx

 

  2.4.nginx基础配置

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  ‘$remote_addr - $remote_user [$time_local] "$request" ‘
                      ‘$status $body_bytes_sent "$http_referer" ‘
                      ‘"$http_user_agent" "$http_x_forwarded_for"‘;

    access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

       

}

 

  2.4.nginx负载均衡配置

#添加tomcat列表,真实应用服务器都放在这
upstream tomcat_pool{
        #server tomcat地址:端口号 weight表示权值,权值越大,被分配的几率越大;
        server 192.168.80.22:8080 weight=4 max_fails=2 fail_timeout=30s;
        server 192.168.80.22:8081 weight=4 max_fails=2 fail_timeout=30s;
        
        #均衡策略
        #ip_hash;
}
---------------------------------------------------------------------------
 server {
        listen       80;
        server_name  tomcat_pool;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            #root   html;
            #index  index.html index.htm;
            proxy_pass http://tomcat_pool;    #转向tomcat处理
            proxy_set_header   Host             $host;
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
        }

        #error_page  404              /404.html;
        
        .....................................................
   }

 

3.安装redis

  

#进入目录
cd /usr/local/develop/aredis

#上传并解压redis
tar -zxvf redis-3.2.11.tar.gz

#进入redis目录
cd redis-3.2.11

#编译
make

#进入src目录,并执行安装
cd src

make install PREFIX=/usr/local/develop/aredis/myredis

#启动redis服务端
[[email protected] bin]# ./redis-server 

#客户端连接操作
[[email protected] bin]# ./redis-cli

 

以上是关于如何在unix环境下实现共享内存的主要内容,如果未能解决你的问题,请参考以下文章

UNIX网络编程:共享内存区

Linux进程间通信--共享内存

Linux进程间通信--共享内存

删除类 UNIX 系统上的所有 SYSTEM V 共享内存和信号量

将一组结构放入unix中的共享内存中,以便客户端程序可以访问它

(转载)linux下的僵尸进程处理SIGCHLD信号Linux环境进程间通信: 共享内存(下)