在CentOS上安装MPICH以及MPI入门编程笔记

一、下载并安装mpich

1、下载安装包

(1)准备工作

// 更新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)一般的编译安装

# 解压
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 &(后台运行)

#!/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)

# 在最底层添加:
MPICH=/usr/local/mpich-4.0.2
export PATH=$MPICH/bin:$PATH
export LD_LIBRARY_PATH=$MPICH/lib:$LD_LIBRARY_PATH

令环境变量生效

source ~/.bashrc
(2)检查安装情况
mpirun -versions
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小程序

vim hw.c
#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);
    
MPI_Finalize(); //终止MPI处理 }

2、在同一台机器编译运行

方法一:

mpicc hw.c  // 编译(会生成一个a.out文件)
./a.out  // 运行

方法二:

mpicc -o hw hw.c  // 编译(会生成一个hw可运行文件)
mpirun ./hw  // 运行
 也可以指定进程数运行
mpirun -np 4 ./hw

3、在同一台机器编译运行

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

三、解决统信UOS上安装的错误

1、./configure出错

这个问题是:不识别系统架构


(1)解决方法1:
添加--build编译选项
./configure --build=sw_64-unknown-linux-gnu //sw_64sw6a-unknown-linux-gnu
(2)解决方法2:
将mpich源码包里所有的config.guess、config.sub替换掉
find /usr -name "config.guess"
/usr/bin/cp /usr/share/misc/config.* ./
(3)解决方法3:修改config.guess、config.sub,使--build能识别sw_64sw6a-sunway-linux-gnu
i、config.guess添加:
    #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 中对应位置添加:
| sw_64-* | sw_64sw2-* | sw_64sw6a-* \


2、make和使用时排错

(1)make时出错
【注意】:UCX可能存在不匹配,报错如下:
此时,需要关闭UCX(见https://github.com/open-mpi/ompi/issues/6640):
./configure --build=sw_64-unknown-linux-gnu --enable-mca-no-build=btl-uct
或者先安装UCX。安装方法见:https://github.com/openucx/ucx 
然后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 directory
mpiexec: 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文件
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)验证

mpirun -n 3 /mnt/sdb/source/system/yyj/bind/setcpu_nohy

2、使用taskset命令进行绑定

(1)下载taskset工具

yum install util-linux -y
apt-get install schedutils

(2)带有众核的单进程作业需要用swrun-107来提交。

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进行绑定。

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四个文件夹
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
(2)将swrun-107拷贝到众核服务器上
scp /usr/sw/swgcc/swgcc710-tools-SEA-1307/swrun-107 mcn01:/usr/bin/ 
(3)将可执行文件main_run拷贝到众核服务器上运行
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在众核平台上运行。

./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

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 

./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

--enable-threads

大多数系统都是默认执行这个的,如果希望线程支持最好还是加上。注意gcc认为threads在系统不可用的时候,这句话退化成等于--enable-threads=single.也就是和--disable-threads是一样的。

--disable-threads
等于--enable-threads=single
单线程模式。--libdir=/usr/sw/mpi/mpi_20220301_SEA/lib/single_static
补充crossfile文件
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

“$$”的意思是当前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'

#!/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

#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;
}

 

 

 

  

  

 

 

 

 

-

 

 -

 

posted @ 2022-05-17 16:31  惊小呆  阅读(2500)  评论(0编辑  收藏  举报