CheatSheet以下是我个人在使用服务器/Linux 发现的一些有用的小玩意。知道这些不会帮助你科研 什么的,答辩老师也不会因为你会这个而欣赏你,但是至少你会觉得很酷,进而产生 自己比较厉害的错觉。 目录使用 VIM使用 VIM 是一种信仰,用好了它真的会提高很大效率。 据说好的 VIM 用户绝不和 emacs 用户打架。 本人并不精通 VIM,还要好多东西要学习。 打开与关闭# 使用 vim 打开文件,如果文件不存在就新建一个 vim <filename> # 用 diff 模式打开两个文件,方便比较异同 vimdiff file1 file2 # 打开 vim 初学者教程(强烈推荐入门用户学习) vimtutor 获取帮助功能太多了,看得眼花缭乱。我怎么知道哪一个是我想要的?
普通模式VIM 编辑器有多个模式:普通模式,插入模式,VISUAL 模式。进入 VIM 编辑器后, 编辑器处于普通模式,在此模式下,VIM 编辑器会将按键解释成命令。
插入模式在插入模式下,即可插入文本到光标的位置。按下 Esc 键即可返回普通模式。
VISUAL 模式在普通模式按下 v 可以进入 VISUAL 模式,按下 Ctrl-v 可以进入 VISUAL BLOCK 模式。 两个模式下可对文本进行选择的操作。进而可将普通模式的操作只应用在选择的区域上。 VISUAL BLOCK 模式下批量注释 在没有安插件的情况下,VISUAL BLOCK 模式下可以完成一些简单的批量注释工作。 # 按下 Ctrl-v 进入 VISUAL BLOCK 模式 Ctrl+v # 选择需要注释的一些行的第一个字符 hjkl # 按下 I 进入插入模式,注意是大写的 I I # 输入注释用的字符,例如 # # 按下 Esc 键返回普通模式,等待几秒 查找与替换普通模式下,可进行查找和替换。
其他进入粘贴模式 在设置过自动缩进的 VIM 编辑器下,粘贴模式可以临时取消所有的自动缩进识别。 :set paste 退出这一模式使用 :set nopaste 缩进修复 VIM 可以对完全未缩进的代码进行缩进修复。 # 在普通模式下 gg=G
使用 MEX 文件MEX 文件在 MATLAB & C 混合编程的过程当中占据了重要角色。由于 MATLAB 不擅长处理循环,因此如果使用 MATLAB 做纯循环效率会很低下。 用户可以通过将 C 程序封装成 MATLAB 可执行的文件来为 MATLAB 计算加速。 以下举一个简单的例子来说明 MEX 是如何工作的。 设想我们要实现这样的一个效果:输入一个矩阵 A,而后计算每个元素的 2 倍,然后返回结果。 首先,我们准备一下所需的 C 语言代码。 simple_mult.c
#include "mex.h" void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]){ int i, m, n; double *a, *b; if (nrhs != 1){ mexErrMsgTxt("Usage: B = simple_mult(A)"); return; } m = mxGetM(prhs[0]); n = mxGetN(prhs[0]); a = mxGetPr(prhs[0]); plhs[0] = mxCreateDoubleMatrix(m, n, mxREAL); b = mxGetPr(plhs[0]); for (i = 0; i < m * n; ++i) b[i] = 2 * a[i]; } 以上代码中有以下几点是 MEX 文件的约定:
不同 MATLAB 版本支持的 C 编译器不同。例如 MATLAB R2015b 最高只能支持 gcc-4.7 的编译器。因此在执行编译之前,请确保正在使用的 gcc 版本被 MATLAB 支持。 高版本编译器依然可以用,只是会报 warning,我用过高版本的编译器编过几个,貌似 没什么问题。 进入 MATLAB 命令行模式(可使用命令 matn)。在 MATLAB 命令行下,输入 >> mex -setup C 接下来使用 >> mex simple_mult.c 即可编译 MEX 文件,其中 simple_mult.c 为源文件名,必须在当前工作目录之下。 编译成功后,MATLAB 会生成对应的 MEX 可执行文件。在我们的例子里, MATLAB 会生成一个名为 simple_mult.mexa64 的文件。使用此 MEX 函数的方法是 B = simple_mult(A) 小提示 Linux 用户可尝试将本地生成的 mexa64 文件上传至服务器, Windows/Mac用户则需要在服务器上编译 mex 文件。在Windows/Mac系统下生成的 mex 文件无法在 Linux 系统下使用。不过,仍然推荐 Linux 用户在服务器上重新编译 mex 文件, 以防止运行环境变化导致程序无法运行。 设置 Intel MKL 编译选项使用 Intel MKL 进行计算时,需要使用 gcc/g++ 编译工具,并将 MKL 库链接到你的目标文件上。 编译所需的命令行较为复杂,推荐使用Intel® Math Kernel Library Link Line Advisor来指导编译和链接。 MKL Link Line Advisor 工具需要用户指定选项才可正确完成链接命令的生成。各个选项的含义如下:
按照如上设置完毕后,即可在下方的输出中看到链接选项和编译选项。直接复制到 Makefile 中即可。 例:使用 MKL 的动态库编译程序,使用多线程的库并使用 GNU OpenMP。 Makefile
CC=gcc CFLAGS=-m64 -I${MKLROOT}/include LDFLAGS=-L${MKLROOT}/lib/intel64 -Wl,--no-as-needed -lmkl_intel_lp64 -lmkl_gnu_thread -lmkl_core -lgomp -lpthread -lm -ldl .PHONY: default clean default: main.c ${CC} -o main $< ${LDFLAGS} ${CFLAGS} clean: rm -rf *.o main 命令行操作很多初学者可能只会在命令行上用方向键/Home/End 来移动光标,这是 ok 的。不过 当你发现某个很长的命令中间有个词打错了的时候,是选择删掉重新打,还是将光标 移动到出错处更改,这就是个问题了。
禁用用户有时需要禁用服务器某个用户,不让他登录。最好不要用 userdel -r <name> 一删 了之,因为今后可能要重新启用它。 可以通过更改用户默认 shell 的方式让用户无法取得 shell 控制权,自然无法使用 服务器。更改用户 shell 的命令是 chsh。 sudo chsh -s /sbin/nologin <username> /sbin/nologin 是一个特殊的 shell,这个 shell 会显示 This account is currently not available 后直接退出,从而“礼貌拒绝登录”。此时,该用户无法从 tty 或者是使用 ssh 进行登录。 假如该用户可以访问服务器上的其它用户(例如另一个用户是他的朋友,他只需要 临时借用他朋友的账号,这个用户不需要是管理员),那么他是可以通过朋友的账号 拿回自己的 shell 权限的。 # user1 是被禁用账户,user2 是活动账户 [user2@server ~]$ su - user1 -s /bin/bash Password: # << 输入 user1 的密码 [user1@server ~]$ # << 居然可以登录 [user1@server ~]$ chsh -s /bin/bash # 用 chsh 拿回自己的默认 shell 这里看到,使用 su 命令可以对默认 shell 进行重写,这样我们做的工作全白费了。 使用 su 时需要密码,因此禁用 user1 的密码就可以避免这种方式的越权。 sudo passwd -l user1 上面的命令实际上是修改了 /etc/shadow 文件的密码字段,使其失效。 要恢复账号,只需要执行下面两条命令。 sudo passwd -u <username> # 解除密码的锁定 sudo chsh -s /bin/bash <username> # 还原正常的 shell 备注
GLIBC 版本错误在旧系统(例如 CentOS 6)上安装一些较新的预编译的库,运行时可能会发生如下 错误 >>> import torch Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/public/home/liuhy/.conda/envs/mytorch/lib/python3.6/site-packages/torch/__init__.py", line 78, in <module> from torch._C import * ImportError: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by /public/home/liuhy/.conda/envs/mytorch/lib/python3.6/site-packages/torch/_C.cpython-36m-x86_64-linux-gnu.so) 原因是系统使用的 GLIBC 版本过低,里面的函数版本未达到要求。 GLIBC 的地位比较特殊,它不像其他的库只需要改动运行时目录就好。这是因为许多系统 的关键命令,例如 ls, cat,都依赖这个库。GLIBC 有许多模块,如果配置错误 导致各个模块的版本不相同,使用 GLIBC 的系统命令就会崩溃,进而整个系统会无法 使用,因此以管理员身份操作 GLIBC 时会非常危险。 传统的解决办法是拿到软件的源码然后重新编译一个,然而有很多因素阻止人们这样做:
下面介绍一种直接修改二进制文件的方式,或许可以解决 GLIBC 的依赖问题。 处理一般的 GLIBC 版本错误是一个很难的问题,这里的方法不能确保一定成功。 笔者对 Linux 的 ELF 文件的理解也十分有限,不能确保下面所说的一定正确。 可执行文件依赖查找顺序在执行一个可执行二进制文件之前,程序会将控制权交给一个特定的 loader,让它 解析程序文件的 header 并决定要载入哪些库。在 64 位 CentOS 6 系统下, 这个 loader 通常是 /lib64/ld-linux-x86-64.so.2。解析完成并加载程序所需的库 后,控制权回到原来的程序,程序正式开始运行。ld-linux 按照如下顺序查找依赖
因为载入有顺序,同一个库即使被递归地依赖也不会载入两次。如果在一开始就强制载入 高版本的 GLIBC 库,那么之后遇到的 GLIBC 也自动会变成高版本的,这种办法就 可能解决问题。 查看 ELF 文件的依赖ldd 命令可以检查 ELF 文件的动态库依赖关系(ldd 并不是个二进制程序,它只是 一个脚本,不信你用文本编辑器打开看一下)。例如 $ ldd /bin/ls linux-vdso.so.1 => (0x00007ffcb7dd9000) libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fce3a13e000) libcap.so.2 => /lib64/libcap.so.2 (0x00007fce39f39000) libacl.so.1 => /lib64/libacl.so.1 (0x00007fce39d30000) libc.so.6 => /lib64/libc.so.6 (0x00007fce39963000) libpcre.so.1 => /lib64/libpcre.so.1 (0x00007fce39701000) libdl.so.2 => /lib64/libdl.so.2 (0x00007fce394fd000) /lib64/ld-linux-x86-64.so.2 (0x00007fce3a365000) libattr.so.1 => /lib64/libattr.so.1 (0x00007fce392f8000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fce390dc000) ldd 实际上调用的是 ld-linux-x86-64.so.2,这个程序根据上文中提到的顺序 对库进行查找。那么二进制文件如何找到 ld-linux-x86-64.so.2 这个 loader 呢? 这个 loader 的位置在编译的时候已经写死在二进制程序里了,不用找。 使用 readelf 命令可查看写死在 ELF 文件中的信息。 $ readelf -d -l /bin/ls # -d 的作用是查看 dynamic 部分 # -l 的作用是查看 program header 部分 Elf file type is EXEC (Executable file) Entry point 0x4043c4 There are 9 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x00000000000001f8 0x00000000000001f8 R E 8 INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238 0x000000000000001c 0x000000000000001c R 1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] # << 这就是 loader 的指定位置 (more information ...) Dynamic section at offset 0x1ada8 contains 27 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libselinux.so.1] 0x0000000000000001 (NEEDED) Shared library: [libcap.so.2] 0x0000000000000001 (NEEDED) Shared library: [libacl.so.1] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 0x000000000000000c (INIT) 0x402228 (more information ...) 有了 loader,程序的 header,依赖库的名字,程序运行前就能定出来它的依赖。 修改依赖有了上面的基础我们大致能猜出需要改些什么。在 ELF 文件加上高优先级的 RPATH (已过时),或更改 ELF 文件的 loader。修改库依赖的软件推荐使用 patchelf。 先到 patchelf 官网下载 patchelf,编译安装 即可(编译并不是很麻烦)。 几个需要用到的命令写在下面 # 修改 rpath patchelf --set-rpath RPATH FILENAME # 修改 interpreter(loader),仅限 program,一般动态库没有 interp patchelf --set-interpreter INTERP FILENAME # 查看 rpath/loader patchelf --print-rpath FILENAME patchelf --print-interpreter FILENAME 实际操作以下以 CentOS 6 下安装 pytorch-0.4-cpu 为例。 首先正常安装 pytorch。 conda create -n mytorch # 创建环境 source activate mytorch conda install python=3.6 # 安装包 conda install pytorch-cpu torchvision-cpu 直接 import 会出错 >>> import torch ImportError: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by /public/home/liuhy/.conda/envs/mytorch/lib/python3.6/site-packages/torch/_C.cpython-36m-x86_64-linux-gnu.so) 在这里虽然我们看见是 pytorch 的一个库缺少 GLIBC 的依赖,但是由于 GLIBC 非常 基础,在程序的最开始就已经载入这个库了。前面我们提到,同名的库只会 load 一次。 修改此动态库的字段是没用的。 缺少的库是 GLIBC_2.14,但这个错误只是其中的一个。其他的函数可能需要更高版本的 GLIBC。因此我们考虑安装 GLIBC_2.17。这是因为 CentOS 7 的预装库的版本就是 2.17。 到 GLIBC 官网下载 GLIBC 后,编译,安装。编译时建议使用系统自带的 gcc 和 binutils, 不推荐太新的软件(实际上太新了反而不识别)。 $ tar -xJf glibc-2.17.tar.xz $ cd glibc-2.17 $ mkdir build && cd build $ ../configure --prefix=$HOME/glibc # 注意填写 prefix $ make $ make install 编译过程不怎么顺利,遇到问题我也不懂怎么解决,自求多福吧。如果运气好成功安装 完了之后,下一步就是让出错的程序用这个版本的 GLIBC。 调用 pytorch 库的程序是 python,因此修改库的依赖最好直接从 python 本身开始 (这就是为什么需要在虚拟环境下再装一个 python 的原因)。 $ which python # 看看 python 在哪 ~/.conda/envs/mytorch/bin/python $ cd ~/.conda/envs/mytorch/bin # 进入这个路径 $ ldd python # 查看依赖 linux-vdso.so.1 => (0x00007ffcb10de000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00002af3e5739000) libc.so.6 => /lib64/libc.so.6 (0x00002af3e5957000) libdl.so.2 => /lib64/libdl.so.2 (0x00002af3e5ceb000) libutil.so.1 => /lib64/libutil.so.1 (0x00002af3e5eef000) librt.so.1 => /lib64/librt.so.1 (0x00002af3e60f3000) libm.so.6 => /lib64/libm.so.6 (0x00002af3e62fb000) /lib64/ld-linux-x86-64.so.2 (0x000000336d600000) $ patchelf --print-rpath python $ORIGIN/../lib # $ORIGIN 表示 python 所在当前目录 $ patchelf --set-interpreter /public/home/liuhy/glibc/ld-linux-x86-64.so.2 warning: working around a Linux kernel bug by creating a hole of 1835008 bytes in ‘python’ # 出了个 warning,害怕 $ readelf -l python # 看下 header,确实发生变化。 (information ignored ...) INTERP 0x000000000055f268 0x000000000055f268 0x000000000055f268 0x0000000000000032 0x0000000000000032 R 0x1 [Requesting program interpreter: /public/home/liuhy/glibc/lib/ld-linux-x86-64.so.2] (information ignored ...) 最后重新打开 python 测试 pytorch 是否可以 >>> import torch >>> torch.__version__ '0.4.0' # 奇迹般地可以!!! 备注
|