在CentOS上安装MPICH以及MPI入门编程笔记
一、下载并安装mpich
1、下载安装包
(1)准备工作
1 2 3 4 5 | // 更新yum库 yum update // 下载相关依赖包 yum install wget -y yum install gcc gcc-c++ gcc-fortran kernel-devel -y |
(2)官网下载MPI安装包(http://www.mpich.org/downloads/)
2、解压编译安装
(1)一般的编译安装
1 2 3 4 5 6 7 | # 解压 tar -zxvf mpich-4.0.2.tar.gz # 进入目录并编译安装 cd mpich-4.0.2/ ./configure --prefix=/usr/local/mpich-4.0.2 --with-device=ch4:ofi 2>&1 | tee 1.log # ------------ps:下边两步要运行很久,可以泡杯咖啡休息等待哦----------- make && make install |
(2)使用脚本后台编译(vim cm.sh)
脚本文件./cm.sh > 1.log 2>&1 &(后台运行)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/bin/sh set -ex ./configure CC=/usr/bin/gcc \ CXX=/usr/bin/g++ \ FC=/usr/bin/gfortran \ F77=/usr/bin/gfortran \ --prefix=/usr/local/mpich-4.0.2 # --build=sw_64sw6a-sunway-linux-gnu \ # --host=sw_64sw6a-sunway-linux-gnu \ # --target=sw_64sw6a-sunway-linux-gnu #make -j 16 #make install |
3、配置环境变量
(1)编辑环境变量(vim ~/.bashrc)
1 2 3 4 | # 在最底层添加: MPICH=/usr/local/mpich-4.0.2 export PATH=$MPICH/bin:$PATH export LD_LIBRARY_PATH=$MPICH/lib:$LD_LIBRARY_PATH |
令环境变量生效
1 | source ~/.bashrc |
1 | mpirun -versions |

1 2 | mpiexec --version mpichversion |

4、软件包的卸载
很多source的makefile都有写uninstall规则,直接在source里make uninstall就行。安装错误解决办法:
#make uninstall&&make clean
之后重新configure、make、make install操作
5、编译程序出错
i、Fatal Error: File 'mpi.mod' opened at (1) is not a GNU Fortran module file
ii、/usr//bin/ld: warning: libgfortran.so.5, needed by /usr/local/lib/libmpifort.so, may conflict with libgfortran.so.3

【解决】:这样情况说明当前mpi的版本不是由当前gcc版本编译安装的。
例如我这里gcc当前版本是4.8.5,而我当前使用的mpi是由gcc-8.5.0编译的。
二、MPI入门编程笔记
1、第一个MPI小程序
1 | vim hw.c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <stdio.h> #include "mpi.h" int main( int argc, char **argv) { int myrank,nprocs,len; MPI_Status status; //状态 char Processorname[20]; MPI_Init(&argc,&argv); //初始化MPI并行环境 MPI_Comm_size(MPI_COMM_WORLD, &nprocs); //nprocs返回进程个数 MPI_Comm_rank(MPI_COMM_WORLD,&myrank); //myrank返回进程号(从0开始) MPI_Get_processor_name(Processorname, &len); //返回机器名字和名字长度 printf( "Hello world! Process %d of %d on %s.\n" ,myrank,nprocs,Processorname); <br> MPI_Finalize(); //终止MPI处理 } |
2、在同一台机器编译运行
方法一:
1 2 | mpicc hw.c // 编译(会生成一个a.out文件) ./a. out // 运行 |
方法二:
1 2 | mpicc -o hw hw.c // 编译(会生成一个hw可运行文件) mpirun ./hw // 运行 |
1 | mpirun -np 4 ./hw |

3、在同一台机器编译运行
首先要保证hw.c是在共享目录下("相同目录"不会的翻看我之前的NFS文件挂载)正常编译即可
1 | mpirun -np 8 -hosts david,client1,client2 ./hw |

三、解决统信UOS上安装的错误
1、./configure出错
这个问题是:不识别系统架构

(1)解决方法1:
添加--build编译选项
1 | ./configure --build=sw_64-unknown-linux-gnu //sw_64sw6a-unknown-linux-gnu |
将mpich源码包里所有的config.guess、config.sub替换掉
1 2 | find /usr -name "config.guess" /usr/bin/cp /usr/share/misc/config.* ./ |
i、config.guess添加:
1 2 3 4 5 6 7 8 9 10 11 12 | #xujb add SW sw_64:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in SW2) UNAME_MACHINE=sw_64sw2 ;; SW2f) UNAME_MACHINE=sw_64sw2f ;; SW2f*) UNAME_MACHINE=sw_64sw2f ;; SW*) UNAME_MACHINE=sw_64sw6a ;; esac objdump -- private -headers /bin/sh | grep -q ld.so.1 if test "$?" = 0 ; then LIBC= "gnulibc1" ; fi echo "${UNAME_MACHINE}" -sunway-linux- "${LIBC}" exit ;; |

ii、config.sub 中对应位置添加:
1 | | sw_64-* | sw_64sw2-* | sw_64sw6a-* \ |


2、make和使用时排错
(1)make时出错【注意】:UCX可能存在不匹配,报错如下:
此时,需要关闭UCX(见https://github.com/open-mpi/ompi/issues/6640):
1 | ./configure --build=sw_64-unknown-linux-gnu --enable-mca-no-build=btl-uct |
然后openmpi配置时,需加入--with-ucx=<UCX_INSTALL_PATH>
(2)检查或使用时出错(找不到libslurm.so.38或libmpi.so.12)
mpichversion: error while loading shared libraries: libmpi.so.12: cannot open shared object file: No such file or directorympiexec: error while loading shared libraries: libslurm.so.38: cannot open shared object file: No such file or directory

方法一:修改~/.bashrc或~/.bash_profile(~/.profile)或系统级别的/etc/profile
前两个只在root下生效,在普通用户下面依旧会提示错误,而添加在/etc/profile里是可以在普通用户下使用的,用source使其生效。
(source命令也称为“点命令”,也就是一个点符号(.)。source命令通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录)
方法二:这个没有修改LD_LIBRARY_PATH但是效果是一样的实现动态库的查找,
i、/etc/ld.so.conf下面加一行/usr/local/lib
ii、保存后执行ldconfig生效
(ldconfig命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式如前介绍,lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.缓存文件默认为/etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表.)
方法二设置稍微麻烦,好处是比较不受用户的限制。

四、国产众核mpich绑核
每个CPU上有6个主核,每个主核带有64个从核,国产众核服务器的CPU编号为0-5;对于带有从核DMA的程序,必须绑定CPU。
1、修改mpich源码进行cpu绑核
自己写代码,要把进程绑定到CPU,要用sched_setaffinity函数。
(1)修改launch.c文件1 2 3 4 5 6 7 8 9 10 11 12 13 14 | vim src/pm/hydra/lib/utils/launch.c // xujb #include <sched.h> #include <ctype.h> #include <string.h> #include <pthread.h> // xujb tpid = fork(); cpu_set_t cpumap; CPU_ZERO(&cpumap); CPU_SET(idx, &cpumap); sched_setaffinity(tpid, sizeof (cpumap), &cpumap); |
(2)验证
1 | mpirun -n 3 /mnt/sdb/source/system/yyj/bind/setcpu_nohy |

2、使用taskset命令进行绑定
(1)下载taskset工具
1 2 | yum install util-linux -y apt- get install schedutils |
(2)带有众核的单进程作业需要用swrun-107来提交。
1 | mpiexec -n 1 taskset -c 0 /bin/sw3run -b 1 -K 2048 -s 7000 -i ./setcpu_nohy |
其中taskset -c可以指定CPU编号。国产众核服务器的CPU编号为0-5。
(3)带有众核或众核DMA的多进程作业需要绑定CPU。
由于mpiexec可以同时提交多个进程,所以无法直接使用taskset命令进行作业绑定,需要使用taskset.sh进行绑定。
1 | srun -n 6 /bin/sh /root/taskset.sh /bin/swrun-107 -b 1 -K 2048 -s 7000 -E 64 -i ./setcpu_nohy |
在X86宿主机向小型机提交mpi作业时,用到的环境变量是X86宿主机上的,即是X86宿主机的环境变量传到小型机上,小型机按照此环境变量执行命令,而X86宿主机和小型机的默认环境变量会有所不同(比如X86宿主机PATH环境变量中可能没有“/bin”,小型机上很多命令在“/bin”目录,默认使用X86宿主机的环境变量就会使得在小型机上很多命令找不到)
所以,在X86宿主机向小型机提交mpi作业时,除了mpiexec命令本身不需要带绝对路径,其他命令需要带绝对路径,包括“sh”,“swrun-107”,“taskset.sh”,“可执行程序”等。
五、国产众核在x86上的交叉编译环境
1、配置swgcc、mpicc环境
(1)配置环境变量/usr/sw下共有lib、mpi、penv、swgcc四个文件夹
1 2 3 4 5 6 | SWGCC1=/usr/sw/swgcc/swgcc710-tools-SEA-1307/usr SWGCC2=/usr/sw/swgcc/swgcc710-tools-SEA-1307/usr_sw SWMPI=/usr/sw/mpi/mpi_20220608_SEA export PATH=${SWMPI}/bin:${SWGCC1}/bin:${SWGCC2}/bin:$PATH export LD_LIBRARY_PATH=${SWMPI}/lib/single_dynamic:${SWGCC1}/lib:${SWGCC1}/lib_for_gcc/lib:$LD_LIBRARY_PATH |
1 | scp /usr/sw/swgcc/swgcc710-tools-SEA-1307/swrun-107 mcn01:/usr/bin/ |
1 | mpirun -n 1 /usr/bin/swrun-107 -b 1 -c 0 -E 64 -s 12000 -K 1024 -i ./main_run |
2、自己利用swgcc编译mpich交叉编译环境
mpich-4.0.2安装包里面,若无法配置fortran(--disable-fortran或--enable-fortran=none)
--build在x86平台编译;--host=sw_64在众核平台上运行。
1 2 3 4 5 6 7 8 | ./configure CC=swgcc CXX=swg++ FC=swgfortran \ LDFLAGS= "-mhybrid -L/usr/sw/lib -lswverbs_ft_perf -lswutils -L/usr/sw/lib -lswtm -lm -Wl,-zmuldefs -ldl -lrt -lpthread" \ --prefix=/usr/sw/mpi/mpi_20220301_SEA \ --host=sw_64-unknown-linux-gnu \ --target=sw_64-unknown-linux-gnu \ --with-cross=/home/xujb/cross_file \ --enable-shared=no --enable- static =yes \ --with-ch3-rank-bits=32 --with-device=ch3 |
用mpi编译时需加上-mhybrid(mpicc -mhybrid -o aa cpi.c)
【注】:(1)为了能使用mpif90,加上了--with-cross;
(2)为了mpi编译器有-mhybrid,加上了--enable-shared=no(LDFLAGS里加上-mhybrid)
这样用mpi编译时需就不用加上-mhybrid,(mpicc -show会显示)(mpicc -o aa cpi.c)
vim /home/xujb/cross_file
1 2 3 4 5 6 7 8 9 10 11 12 13 | CROSS_F77_SIZEOF_INTEGER= "4" CROSS_F77_SIZEOF_REAL= "4" CROSS_F77_SIZEOF_DOUBLE_PRECISION= "8" CROSS_F77_TRUE_VALUE= "-1" CROSS_F77_FALSE_VALUE= "0" CROSS_F90_ADDRESS_KIND= "8" CROSS_F90_OFFSET_KIND= "8" CROSS_F90_INTEGER_KIND= "4" CROSS_F90_REAL_MODEL= " 6 , 37" CROSS_F90_DOUBLE_MODEL= " 15 , 307" CROSS_F90_INTEGER_MODEL= " 9" CROSS_F90_ALL_INTEGER_MODELS= " 2 , 1, 4 , 2, 9 , 4, 18 , 8," CROSS_F90_INTEGER_MODEL_MAP= " { 2 , 1 , 1 }, { 4 , 2 , 2 }, { 9 , 4 , 4 }, { 18 , 8 , 8 }," |
sw_64-sunway-linux-gnu
1 2 3 4 5 6 7 8 9 | ./configure CC=sw9gcc CXX=sw9g++ FC=sw9gfortran F77=sw9gfortran \ CFLAGS= "-mhybrid" CXXFLAGS= "-mhybrid" FCFLAGS= "-mhybrid" FFLAGS= "-mhybrid" \ LDFLAGS= "-L/usr/sw/lib -lswverbs_ft_perf -lswutils -L/usr/sw/lib -lswtm -lm -Wl,-zmuldefs -ldl -lrt -lpthread" \ --prefix=/usr/sw/mpi/mpi_20220301_SEA \ --build=x86_64-redhat-linux --host=sw_64sw6a-sunway-linux-gnu --target=sw_64sw6a-sunway-linux-gnu \ --with-cross=/home/xujb/cross_file \ --enable-shared=no --enable- static =yes \ --with-ch3-rank-bits=32 --with-device=ch3:sock \ --enable-threads=single --libdir=/usr/sw/mpi/mpi_20220301_SEA/lib/single_static |
--with-device=ch3:swch
1 | --enable-threads |
大多数系统都是默认执行这个的,如果希望线程支持最好还是加上。注意gcc认为threads在系统不可用的时候,这句话退化成等于--enable-threads=single.也就是和--disable-threads是一样的。
1 | --disable-threads |
单线程模式。--libdir=/usr/sw/mpi/mpi_20220301_SEA/lib/single_static
补充crossfile文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | CROSS_SIZEOF_CHAR= "1" CROSS_SIZEOF_SHORT= "2" CROSS_SIZEOF_INT= "4" CROSS_SIZEOF_LONG= "8" CROSS_SIZEOF_LONG_LONG= "8" CROSS_SIZEOF_FLOAT= "4" CROSS_SIZEOF_DOUBLE= "8" CROSS_SIZEOF_LONG_DOUBLE= "8" CROSS_SIZEOF_VOID_P= "8" CROSS_OFFSET_KIND= "8" CROSS_ADDRESS_KIND= "8" CROSS_BIGENDIAN= true FORT_INT8= "1" CROSS_HAVE_LONG_LONG= "1" CROSS_HAVE_LONG_DOUBLE= "1" CROSS_INTEGER_KIND= "4" |
六、脚本源码文件
(1)taskset.sh
1 2 3 4 5 6 7 8 | “$$”的意思是当前shell的pid,也就是脚本运行的当前进程号。 $0 当前脚本的执行名字 $n 当前脚本执行命令的第n个参数值,n = 1..9 $* 当前脚本执行命令的所有参数,此选项参数可超过9个 $# 当前脚本执行命令的输入参数个数,例如执行 ./test.sh aa bb cc ,则在 test.sh 里 $# 为 3 $! 上一个执行指令的PID(后台运行的最后一个进程的进程ID号) $- 显示shell使用的当前选项,与 set 命令功能相同 $@ 跟$*类似,但是可以当作数组用 |
Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。
下面对整个 /proc 目录作一个大略的介绍.
[number]
在 /proc 目录里, 每个正在运行的进程都有一个以该进程 ID 命名的子目录, 其下包括如下的目录和伪文件:
[number] /environ
该文件保存进程的环境变量, 各项之间以空字符分隔, 结尾也可能是一个空字符. 因此, 如果要输出进程 1 的环境变量, 你应该:
(cat /proc/1/environ; echo) | tr '\000' '\n'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/bin/bash mypid=$$ myrank_kv=`/bin/cat /proc/$mypid/environ|tr '\0' '\n' |/bin/grep PMI_RANK` myrank=`echo $myrank_kv|awk -F = '{print $2}' ` #mycpunum=`expr $myrank % 4` #mycpuid=`expr $mycpunum \* 4` mycpuid=`expr $myrank \% 6` #echo "$PMI_RANK $myrank_kv $myrank $mycpunum $mycpuid" /bin/taskset -c $mycpuid $@ # ---------- 或者是下面这样 ---------- # mycpuid=`expr $PMI_RANK \% 6` /bin/taskset -c $mycpuid $@ |
(2)setcpu.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | #include<stdlib.h> #include<stdio.h> #include<sys/types.h> #include<sys/sysinfo.h> #include<unistd.h> #define __USE_GNU #include<sched.h> #include<ctype.h> #include<string.h> #include<pthread.h> #include "crts.h" #define THREAD_MAX_NUM 200 //1个CPU内的最多进程数 int num=0; //cpu中核数 void * threadFun( void * arg) //arg 传递线程标号(自己定义) { cpu_set_t mask; //CPU核的集合 cpu_set_t get ; //获取在集合中的CPU int *a = ( int *)arg; int i; printf( "the thread is:%d\n" ,*a); //显示是第几个线程 CPU_ZERO(&mask); //置空 CPU_SET(*a,&mask); //设置亲和力值 if (sched_setaffinity(0, sizeof (mask), &mask) == -1) //设置线程CPU亲和力 { printf( "warning: could not set CPU affinity, continuing...\n" ); } CPU_ZERO(& get ); if (sched_getaffinity(0, sizeof ( get ), & get ) == -1) //获取线程CPU亲和力 { printf( "warning: cound not get thread affinity, continuing...\n" ); } for (i = 0; i < num; i++) { if (CPU_ISSET(i, & get )) //判断线程与哪个CPU有亲和力 { printf( "this thread %d is running processor : %d\n" , i,i); } } return NULL; } int main( int argc, char * argv[]) { int tid[THREAD_MAX_NUM]; int i; pthread_t thread[THREAD_MAX_NUM]; num = sysconf(_SC_NPROCESSORS_CONF); //获取核数 int num_onln = sysconf(_SC_NPROCESSORS_ONLN); // 真正获取当前可用核数 if (num > THREAD_MAX_NUM) { printf( "num of cores[%d] is bigger than THREAD_MAX_NUM[%d]!\n" , num, THREAD_MAX_NUM); return -1; } char hostbuffer[256]; gethostname(hostbuffer, sizeof (hostbuffer)); printf( "system has %i processor(s). actual processors number is : %d, cgn_id is :%d, hostnamne : %s\n" , num, num_onln, current_array_id(), hostbuffer); printf( "parent pid : %d, son pid: %d\n" , getppid(), getpid()); // int pid = getpid(); // pthread_create(&getpid(),NULL, ); for (i=0;i<1;i++) { tid[i] = i; //每个线程必须有个tid[i] pthread_create(&thread[i],NULL,threadFun,( void *)&tid[i]); } for (i=0; i< 1; i++) { pthread_join(thread[i],NULL); //等待所有的线程结束,线程为死循环所以CTRL+C结束 } return 0; } |
-
-
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· 地球OL攻略 —— 某应届生求职总结