MPI编程指南详解.pdf

上传人:文*** 文档编号:88138379 上传时间:2023-04-22 格式:PDF 页数:12 大小:1.62MB
返回 下载 相关 举报
MPI编程指南详解.pdf_第1页
第1页 / 共12页
MPI编程指南详解.pdf_第2页
第2页 / 共12页
点击查看更多>>
资源描述

《MPI编程指南详解.pdf》由会员分享,可在线阅读,更多相关《MPI编程指南详解.pdf(12页珍藏版)》请在得力文库 - 分享文档赚钱的网站上搜索。

1、MPI编程指南、MPI概述1.1 MPI的发展史MPI标准化涉及到大约60个国家的人们他们主要来自千美国和欧洲的40个组织,这包括并行计算机的多数主要生产商,还有来自大学、政府实验室和工厂的研究者们。1992年4月,并行计算研究中心在Williamsburg,Virginia,召开了一个关千消息传递的标准的工作会议,会议上讨论了标准消息传递的必要的、基本的特点,并建立了工作组继续进行标准化工作。1992年10月,MPI的初步草稿MPll形成,MPll主要包含的是在Williamsburg工作组会议上讨论的基本消息传递的接口,因为它的基本目的就是促进讨论并继续此项工作,所以它主要栠中在点对点的通

2、信。1993年1月,第一届MPI会议在Dallas举行,1993年2月MPII修定版本公布,之后定期召开了一系列关千MPI的核心的研讨会;1993年11月MPI的草稿和概述分别发表于Supercomputing93和theproceedings。直到1994年5月,MPI标准正式发布。1994年7月发布了MPI标准的勘误表。现在该工作组正在着手扩展MPI、完善MPI,从事千MPI2(MPI的扩展标准)的制定工作。1.2 MPI的优点可移植性和易于使用。以低级消息传递程序为基础的较高级和(或)抽象程序所构成的分布存储通信环境中,标准化的效益特别明显。MPI是被正式的详细说明的:它已经成为一个标准

3、。消息传递标准的定义能提供给生产商清晰定义的程序库,以便他们能有效地实现这些库或在某些情况下为库程序提供硬件支持,因此加强了可扩展性。MPI有完备的异步通信:使得send,recieve能与计算重叠。可以很有效的在MPP上或Cluster上用MPI编程:MPI的虚拟拓扑反映了应用程序所申请的一组结点的通信模式,在MPP上实现的MPI便可以利用这种信息来优化处理器间的通信路径。1.3主要内容点对点通信(point_to_pointcommunication)群体操作(collectiveoperations)进程组(processgroups)通信上下文(communicationcontext

4、s)进程拓扑结构(processtopologies)与Fortran77和C语言的邦定(bindingsfor Fortran 77 and C)环境的管理与查询(enviromnentalmanagement and inquiry)轮廓管理(profilinginterface)1.4 MPI的各种实现在国外有许多自从MPI标准制定以来在各种机器上的MPI的portable的实现,并且他们仍从车千自己的MPI实现的性能改进,其中最著名的一些见表1。表1国外MPI的各种实现名称单位网址CHIMP U.of Edinburgh ftg.egcc.ed.ac.uk/gub/chimg/.LAM

5、 Ohio state tbag.osc.edu/12ub/lam/MPICH Argonne Mississippi state info.mcs.anl.gov/12ub/m12i Unify Mississippi state ftQ.erc.msstate.edu/unify_、MPI入门介绍以下均以MPICH为例。2.1 MPI的编程方式对于基本的应用,MPI同其它消息传送系统一样易千使用。下面是一个简单的某千C语言的MPI样本代码。它是SPMD方式的,即每个进程都执行该程序,通过返回的进程编号来区分不同的进程。该程序完成进程0向进程1传递数据buf。/*first.c*/#incl

6、ude mpi.h/*MPI的头陌数,提供基本的MPI定义和类型I#include int main(argc,argv)int argc;char*argv;int rank,size,tag=333;int bufJ201 MPI_Status status MPI_Tnit(&argc,&argv);/*MPI的初始化函数MPI_Comm_rank(MPI_COMM_ WORLD.&rank);MPI_Comm_size(MPI_COMM_ WORLD,&size);if(rank=O)/该进程的编号总的进程数目MPI_Send(buf,20,MPI_lnt,I,tag,MPI_COMM

7、_ WORLD);发送buf到进程l*/if(rank=O)MPI_Recv(buf.20,MPI_lnt,0,tag,MPI_COMM_ WORLD,&status);/从进程0接枚bur/MPI_Finalizc();/*MPI的结束函数Ireturn O;2.2 MPI的基本语句介绍l、MPI世界进程由一个唯一的“标识数(整数)表示,进程的标识数为数0、l、2、N-1。MPI_COMM_ WORLD指“MPI应用中的所有进程”,它称为通信子,并且提供消息传递所需的所有信息。可移植库对通信子做更多的工作,以提供大多数其它系统所不能处理的同步保护。2、进入和退出MPI与其它系统一起,提供两个

8、团数来初始化和结束MPI进程:int MPI_Init(int*argc,char*argv)初始化int MPI_Finalize(void)结束3、我是谁?他们是谁?典型地,并行程序中的进程需要知道它是谁(它的标识数)以及其它进程是怎样存在的。一个进程通过调用MPI_Comm_rank()来发现它自己的标识数,用MPI_Comm_size()来返回进程总数:int MPI_Group_size(MPI_Group group,int*size)int MPI_Grouprank(MPI_Group group,int*rank)4、发送消息消息是一组给定数据类型的元素。消息被发给一个指定的

9、进程,而且用一个由用户说明的标识来标记。标识用来区分不同的由一个进程所能发送I接受的消息类型。int MPI_Send(void*buf,int count,MPI_Datatype datatype,int dest,int tag,MPI_Colllln comm)5、接受消息接受进程说明标识和发送进程的标识数。MPI_ANY_TAG和MPI_ANY_SOURCE可选地用千接受任意标识和从任意发送进程而来的消息。int MPI_Recv(void*buf,int count,MPI_Datatype datatype,int source,int tag,MPI_Corrun comm,M

10、PI_Status*status)有关所接受消息的信息在一个状态变垃status中返回。通过这些函数,你会准备编写任意的应用。在MPI中有许多其它、更特异的函数,但是迄今为止所有这些函数可以在这里提到的函数上构造。2.3 MPI的环境设置在并行机上的环境一般是设置好的,不需要变动。而在NOW上运行MPI程序需要设置一些配置文件:1、由千加载程序到结点上运行调用了Urux系统的rsh命令,所以需要在每个结点上设置rhosts文件,以使rsh能正确执行;2、由千NOW环境的异构性,需要在启动时指定运行结点的体系结构;若未指定,是指使用与启动并行程序的结点具有相同体系结构的结点。在启动并行程序的机器

11、里,具有相同体系结构的几台机器的名字存放在一个名为MPICH/util/machines/machines.的文件中,一台机器的名字占有文件的一行,其中MPICH是一个环境变噩,指明MPICH软件安装后所在的目录。并行程序加载运行时是按照文件中机器名字的先后顺序依次加载的。2.4 MPI的编译和运行对千简单的程序,可以使用专门的编译命令。对千大的项目,最好使用标准的Makefile。MPICH提供的编译命令有mpicc和mpif77,它们分别是C和Fortran的编译命令,mpicc-o first first.c mpif77-o first firstf.f 对千编译得到的目标程序,运行的

12、命令为:mpirun-arch xxx-np yyy first 其中XXX为machines.的,yyy为申请的进程数目。2.5例子l、环的C语言实现#include mpi.h#include#define T _SIZE 2000 mt main(argc,argv)int argc;char*argv;int ie订,prev,next,tag,rank,size;MPI_Status status;double send_bufIT _SLZEl,recv _buffT _SIZE);MPI_Init(&argc,&argv);MPI_Comrn_rank(MPI_COMM_ WOR

13、LD,&ran.k);MPI_Comm_size(MPI_COMM_ WORLD,&size);next=rank+l;if(next size)next=0;prev=rank-I;if(prev 0)prev=size-I;1f(rank=0)MPI_Send(send_buf,T_SIZE,MPI_DOUBLE,next,tag,MPI_COMM_ WORLD);MPI_Recv(recv_buf,T_SIZE,MPI_DOUBLE,prev,tag+I,MPI_COMM_ WORLD,status);)else MPI_Recv(recv_buf,T_SJZE,MPI_DOUBLE,p

14、rev,tag,MPI_COMM_WORLD,status);MPI_Send(recv _buf,T _SIZE,MPI_DOUBLE,next,tag+I,MPI_COMM_ WORLD);MPI_Finalize();2、环的Fortran语言实现program ring includempif.h integer ierr call MPI_INLT(ierr)call tescring call MPI_FINALIZE(ierr)end subroutine test_ring include mpif.h integer T_SIZE parameter(T _SIZE=2000

15、)integer ierr,prev,next,tag,rank,size,status(MPI_STATUS_SIZE)real send_buf(T _SIZE),recv _buf(T _SIZE)call MPI_COMM_RANK(MPI_COMM_ WORLD,rank,ierr)call MPI_COMM_SIZE(MPI_COMM_WORLD,size,比rr)next=rank+I if(next.ge.size)next=0 prev=rank-l if(prev.It.0)prev=size-I if(rank.eq.0)then call MPI_SEND(send_b

16、uf,T_SIZE,MPI_REAL,next,tag,MPI_COMM_ WORLD,ierr)call MPI_RECV(recv_buf,T_SIZE,MPI_REAL,prev,tag+l,MPI_COMM_WORLD,status,ierr)else call MPI_RECV(recv_buf,T_SIZE,MPI_REAL,prev,tag,MPI_COMM_ WORLD,status,ierr)call MPI_SEND(recv_buf,T_SIZE,MPI_REAL,next,tag+l,MPI_COMM_WORLD,如)end if 3、PI的C语言实现#include

17、mpi.h#include#include double f(a)double a;return(4.0/(1.0+a*a);int main(argc,argv)int argc;char*argvl;mt done=0,n=LOO,myid,numprocs,i;double PI25DT=3.l 4 l 592653589793238462643;double mypi,pi,h,sum,x,a,startwtime,endwtime;MPI_Init(&argc,&argv);MPI_Comm_size(MPJ_COMM_ WORLD,&numprocs);MPI_Comm_rank(

18、MPI_COMM_ WORLD,&myid);1f(myid=0)startwtime=MPI_ Wtime();h=1.0/(double)n;sum=0.0;for(i=myid+1;i=n;i+=numprocs)x=h*(double)i-0.5);sum+=f(x);l mypi=h*sum;MPI_Reduce(&mypi,&pi,I,MPI_DOUBLE,MPI_SUM,0,MPI_COMM_ WORLD);if(myid=0)printf(pi is%.16f,ErTor is%.16tn,pi,fabs(pi-Pl25DT);endwtime=MPI_ Wtirne();pr

19、intf(wall clock time=%tn,endwtime-startwtime);MPI_Finalize();4、计算PI的FortJ.an语言实现20 program main include叩f.hdouble precision PI25DT parameter(Pl25DT=3.l 4 I 592653589793238462643d0)double precision mypi,pi,h,sum,x,f,a integer n,myid,numprocs,i,re f(a)=4.dO I(I.dO+a*a)call MPI_INIT(im)call MPJ_COMM_RA

20、NK(MPI_COMM_ WORLD,myid,ierr)call MPI_COMM_SIZE(MPI_COMM_ WORLD,numprocs,ierr)n=IOO h=l.OdO/n sum=O.OdO do 20 i=myid+I,n,numprocs x=h*(double(i)-0.5d0)sum=sum+f(x)continue myp,=h*sum call MPI_REDUCE(mypi,pi,1,MPI_DOUBLE_pRECISlON,MPI_SUM,O,MPI_COMM_ WORLD,ierr)if(myid.eq.0)then write(6,97)pi,abs(pi-

21、Pl25DT)97 format(pi is:,Fl 8.16,Error is:,Fl8.16)endif 30 call MPI_FINALIZE(ierr)end、MPI中级介绍3.1基本概念l.消息包在消息传递过程中,发送和接收的消息除了数据部分外,消息还带有用于识别消息和选择接收消息的信息。这个信息是由确定的域组成的,我们称为信封,这些域有:source(源)、destination(目的)、tag(标识)、communicator(通信子)。消息的source隐含地由消息发送者的标识来决定。其他域是由发送操作的参数决定。整型值的消息tag能被程序用于识别不同的消息,有效标识值的范围

22、依赖千实现。2.组和通信子一个组(group)是进程的一个有序集。组内的每个进程与一个整数rank相联系。序列号是连续的并从0开始。组用模糊的组对象来描述,因此不能直接从一个进程到另一个进程传送。可在一个通信子中使用组来描述通信空间中的参与者并对这些参与者进行分级(rank)。上下文是通信子所具有的一个特性,它允许对通信空间进行划分。一个上下文所发送的消息不能被另一个上下文所接收。一个通信子Ccommunicator)指定一个通信操作的上下文。每个通信上下文提供一个单独的“通信全域;消息总是在一个的上下文内被发送和接收,不同的上下文发送的消息互不干涉。通信子也指定共享这个通信上下文的进程组。这

23、个进程组被编号,并且进程是由这个组中的进程号标识。一个预定义的通信子MPI_COMM_WORLD是由MPI提供。MPI初始化后,它允许和可存取的全部进程通信,进程是由他们在MPJ_COMM_WORLD组中的进程号所标识。3基本数据类型异构计算要求包含消息的数据是有类型的或者已描述的,使得它的机器的表达可以在计算机体系结构之间转换。MPI系统地描述了消息的数据类型,从简单的原始机器类型到复杂的结构、数组和下标。与C语言邦定的基本数据类型:MPl_CHAR Signed char MPI_SHORT Signed short int MPI_INT Signed int MPI LONG Sign

24、ed long int MPI_UNSIGNED_CHA Unsigned char R MPI_UNSIGNED_SHOR Unsigned short int T MPI UNSIGNED Unsigned int MPI_UNSIGNED_LON Unsigned long int G MPIFLOAT Float MP!DOUBLE Double MPI_LONG_DOUBLE Long double MPI_BYTE MPI PACKED 与Fortran语言邦定的基本数据类型:MPI_INTEGER INTEGER MPl_REAL REAL MPI_DOUBLE_pRECISJO

25、 DOUBLE N PRECISION MPI_COMPLEX COMPLEX MPI_DOUBLE_COMPLE DOUBLE COMPLEX X MPI_LOGICAL LOGICAL MPI_CHARACTER CHARCTER(l)MPI BYTE MPI_FACKED 3.2点对点通信点对点通信是MPI中比较复杂的一部分,它有两种消息传递的机制:阻塞的和非阻塞的。对于阻塞的方式,它必须等到消息从本地送出之后,才可以执行后续的语句,保证了消息缓冲区等资源的可再用性;而非阻塞的方式不须等到消息从本地送出,就可以执行后续的语句,从而允许通信和计算的重叠,利用合适的硬件可使得计算和数据通信同

26、时执行,但是非阻塞调用的返回并不保证资源的可再用性。点对点通讯的发送和接收语句必须是匹配的,为了区分不同进程或同一进程发送来的不同消息,可以采用通讯体和标志位(tag)来实现相应的语句的匹配,也可以接收不确定source和tag的消息,例如MP-ANY_SOURCE,MPI_ANYTAG。1、阻塞通信对千阻塞的标准通信,MPI_Send和MPI_Recv函数的格式为int MPI_Send(void*buf,int count,MPI_Datatype datatype,int dest,int tag,MPI_Comm comm)int MPI_Recv(void*buf,int count

27、,MPJ_Datatype datatype,int source,int tag,MPI_Comm cmrun,MPI_Status*status)发送的含义是将包含count个datatype类型的首地址为buf的消息发送到dest进程,该消息的是与标志tag和通信体comm韭装在一块的;接收的含义是将source进程接收标志为tag和通信体为comm的消息,将该消息写入首地址为buf的缓冲区,返回值status中包含大小、标志、源进程等信息。在C语言中,status是一个结构,其包含两个域,名字为MPI_SOURCE和MPI_TAG,该结构可以包含附加域。在Fortran中,status

28、是一个MPI_STATUS_SIZE大小的INTEGER型数组,status(MPJ_SOURCE)和status(MPl_TAG)分别包含被接收消息的源和标识。2、非阻塞通信对千非阻塞的标准通信,MPl_lsend和MPl_frecv函数的格式为int MPI_Isend(void*buf,int count,MPI_Datatype datatype,int dest,int tag,MPI_Comm comm,MP.I_Request*request)int MPI_Irecv(void*buf,int count,MPI_Datatype datatype,int source,int

29、 tag,MPI_Comm comm,MPI_Request*request)对千非阻塞通信,可以用MPI_WAIT,MPI_ TEST等来结束非阻塞通信,int MPI_ Wait(MPI_Request*request,MPI_Status*status)int MPI_ Test(MPI_Request*request,int*flag,MPI_Status*status)其中MPI_Test用来检测非阻塞操作提交的任务是否结束并立即返回,MPI_Wait则一直要等到非阻塞操作提交的任务结束才返回。我们可以认为:阻塞通信非阻塞通信MPl_Wait;MPI_ Wait=while(flag

30、=O)MPI_Test。3、消息的检渊MPI_Probe和MPl_lprobe操作允许在没有实际收到输入消息的情况下对其进行检查。用户于是可以根据这次返回的信息决定如何接收他们(该信息一般用status返回),或可以根据被检查消息的长度分配结束缓冲区大小,或进行条件分支等。他们的格式如下:int MPI_Probe(int source,int tag,MPI_Conun conun,MPI_Status*status)int MPI_Iprobe(int source,int tag,MPI_Comm comm,int*flag,MPI_Status*status)如果MPI_Iprobe返

31、回flag=true,则状态目标(status)才能够被获取source,tag及消息的长度。而MPI_Probe是一个阻塞的语句,它必须等到相匹配的消息Csource,tag)到达才完成。4、点对点通信的例子#include#include mpi.h int main(argc,argv)mt argc;char*argv;int locld,dataIOO,tag=8888;MPI_Status status;MPI_Init(&argc,&argv);MPI_Comm_rank(MPI_COMM_ WORLD,&locld);if(locld=0)MPI_Request events;

32、MPI_Isend(data,100,MPI_INT,l,tag,MPI_COMM_ WORLD,&events);MPL Wait(&events,&status);if(locld=I)MPI_Probe(MPI_ANY _SOURCE,tag,MPI_COMM_ WORLD,&status);if(status.M PI_SOURCE=O)MPI_Recv(data,100,MPI_INT,0,tag,MPI_COMM_ WORLD,&status);MPI_Finalize();3.3群体通信群体通信意味着一个通信子中的所有进程调用同一例程,所有的群体操作都是阻塞的,它包括如下一些:同

33、步(barrier)从一个进程到本组内的所有进程的播送(broadcast)(如图3.3的(a)从本组所有处理收集数据到一个进程(gather)(如图3.3的(b)从一个进程分散数据到本组内的所有进程(sactter)(如图3.3的(b)将gather的数据不是送到某一进程,而是要送到所有本组内的进程(allgather)(如图3.3的(c)组内的多对多的分散I收集(alltoall)(如图3.3的(d)求和,最大值,最小值及用户定义的函数等的reduce操作escan或prefix操作data Ao AO broadcast 趴0Ao Ao Ao(a)芭Ao Al A2 A3 A4 A5 A

34、o scatter Al A2 gather A3 A4(b)A5 图3.3群体操作的图例l、同步int MPI_Barrier(MPI_Comm co1nn1)它使得调用者阻塞,直到该通信子内所有进程都调用它。2、广播int MPI_Bcast(void*buffer,int count,MPI_Datatype datatype,int root,MPI_Comm comm);所有进程使用同一计数、数据类型、根和通信子。在操作前,根缓冲区包含一个消息。操作后,所有缓冲区包含来自根进程的消息。3、散播int MPI_Scatter(void*sndbuf,int sndcnt,MPI_Dat

35、atype sndtype,void*rcvbuf,int rcvcnt,MPI_Datatype rcvtype,int root,MPI_Cornm comm);所有进程使用同一计数、数据类型、根和通信子。在橾作前,根发送缓冲区包含长度为sndcnt*N的消息,这里N是进程数目。操作后,相等地划分消息,并且分散到随后标识数序的所有进程(包括根)。4、归约int MPI_Reduce(void*sndbuf,void*rcvbuf,int count,MPI_Datatype datatype,MPI_Op op,int root,MPI_Comm comm);所有进程使用同一计数、数据类型

36、、根和通信子。操作后,根进程在它的接受缓冲区中有所有进程的发送缓冲区的归约结果,包括:MPI_MAX,MPI_M叭,MPI_SUM,MPI_PROD,MPI_LAND,MPI_BAND,MPI_LOR,MPI_BOR,MPI_LXOR,MPI_BXOR,或者是用户定义的归约函数。5、收集int MPI_Gather(void*sndbuf,int sndcnt,MPI_Datatype sndtype,void*rcvbuf,int rcvcnt,MPI_Datatype rcvtype,int root,MPI_Comm comm);所有进程使用同一计数、数据类型、根和通信子。此例程是MPI

37、_Scatter()的相反:操作后,根进程在它的接受缓冲区中包含所有进程的发送缓冲区的连接(包括它自己),所有消息长度为rcvcnt*N,这里N是进程数目。按照随后的标识数序收集消息。6、群体通信的例子下面简单的代码段使用了四个基本的集合例程以操纵一个静态的已划分的规则区域(这里是一维)。全域的长度从根进程广播到所有其它进程。初始数据栠在进程间分配(分散)。在每一计算步骤之后,确定全局的最大数并由根所使用。根然后收集最终的数据集。#include int i,myrank,size,root,full_domain_Iength,sub_domain_length;double global_

38、max,local_max,*foll_domain,*sub_domain;MPJ_Comm_rank(MPI_COMM_ WORLD,&myrank);MPI_Comm_size(MPI_COMM_ WORLD,&size);root=O;if(myrank=root)get_full_domain(&full_domain,&full_domain_length);MPl_Bcast(&full_domain_length,I,MPI_JNT,root,MPI_COMM_ WORLD);sub_domain_length=full_domain_length/size;sub_domai

39、n=(double*)malloc(sub_domain_length*sizeof(double);MPI_Scatter(full_domain,sub_domain_length,MPI_DOUBLE,sub_domain,sub_domain_length,MPI_DOUBLE,root,MPI_COMM_ WORLD);compute(sub_domain,sub_domain_length,&local_max);MPJ_Reduce(&local_max,&global_max,I,MPI_DOUBLE,MPI_MAX,root,MPI_COMM_ WORLD);MPI_Gath

40、er(sub_domain,sub_domain_length,MPI_DOUBLE,full_domain,sub_domain_length,MPI_DOUBLE,root,MPI_COMM_ WORLD);3.3环境管理与查询MPI的环境管理与查询包括:设置正确的参数、执行环境(出错句柄),以及进入MPI和退出MPI等。这些函数对于写正确的、健壮的程序很里要,特别是对于建筑更高层的可移植的消息传递的程序尤其重要。l、时钟MPI定义了一个计时器,因为对并行程序计时在“性能调试”中是很重要的,而已存在的计时器要么是不方便的,要么是没能提供对高分辨率计时器的足够的访间。double MPI_

41、Wtime(void)它返回当前的系统的墙壁时钟的时间,是一个浮点的秒数。double MPI_ Wtick()MPI_Witck返回秒中MPI_WTIME的精度。即,它返回连续时钟滴答的秒数,是一双精度值。例如,如果由硬件实现的时钟作为每亳秒递增的计数器,那么由MPI_WTICK返回的值应为IOe-3。2、MPI的初始与结束int MPI_Init(int*argc,char*argv)int MPI_Finalize(void)它是MPI的初始化函数及MPI的结束函数,所有的MPI程序必须含有MPl_lnit,并且必须放在所有MPI的其它调用之前,MPI_Finalize消除所有的MPI的申明,用户必须确保所有的通信必须在调用MPI_Finalize之前完成。四、MPI高级介绍

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 教育专区 > 教案示例

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知得利文库网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号-8 |  经营许可证:黑B2-20190332号 |   黑公网安备:91230400333293403D

© 2020-2023 www.deliwenku.com 得利文库. All Rights Reserved 黑龙江转换宝科技有限公司 

黑龙江省互联网违法和不良信息举报
举报电话:0468-3380021 邮箱:hgswwxb@163.com