这几天都在为新安装的系统Ubuntu18.04装东西,这些东西其实都很容易,但发现每次重新装了系统后都要重新整理,时间一长完全想不起怎么回事,好记性不如烂笔头,所以写到这里是给自己作个备注。
编译安装前的准备
首先是要安装一些包,如果没有这些包,一般会在最后make时报出来,大概是这个样子,
Python build finished successfully!
The necessary bits to build these optional modules were not found:
_bz2 _curses _curses_panel
_dbm _gdbm _hashlib
_lzma _sqlite3 _ssl
_tkinter _uuid readline
To find the necessary bits, look in setup.py in detect_modules() for the module's name.
The following modules found by detect_modules() in setup.py, have been
built by the Makefile instead, as configured by the Setup files:
_abc atexit pwd
time
Could not build the ssl module!
Python requires an OpenSSL 1.0.2 or 1.1 compatible libssl with X509_VERIFY_PARAM_set1_host().
LibreSSL 2.6.4 and earlier do not provide the necessary APIs,
https://github.com/libressl-portable/portable/issues/381
所以一般要安装这些包(Ubuntu18.04上验证OK,里面可能有重复的),
libbz2-dev
libc6-dev
libffi-dev
libgdbm-dev
libgdbm-compat-dev
liblzma-dev
libncurses5-dev
libncursesw5-dev (optional)
libreadline-gplv2-dev (optional?)
libreadline-dev
libsqlite3-dev
libssl-dev
openssl
sqlite3
tcl-dev (tcl8.6-dev)
tk-dev (tk8.6-dev)
uuid-dev
zlib1g-dev
软件包可以到这里去搜索,参考链接,
https://packages.ubuntu.com/search?keywords=lzma
参考下表
模块 | 依赖 | 说明 |
_bsddb | bsddb | Interface to Berkeley DB library。Berkeley数据库的接口 |
_curses | ncurses | Terminal handling for character-cell displays。 |
_curses_panel | ncurses | A panel stack extension for curses。 |
_sqlite3 | sqlite | DB-API 2.0 interface for SQLite databases。SqlLite,CentOS可以安装sqlite-devel |
_ssl | openssl-devel | TLS/SSL wrapper for socket objects。 |
_tkinter | N/A | a thin object-oriented layer on top of Tcl/Tk。如果不使用桌面程序可以忽略TKinter |
lzma | liblzma-dev | 老的bsddb模块,可忽略。 |
bz2 | bzip2-devel | Compression compatible with bzip2。bzip2-devel |
dbm | ||
dl | N/A | Call C functions in shared objects.Python2.6开始,已经弃用。 |
gdbm | gdbm-devel.i686 | GNU’s reinterpretation of dbm |
imageop | N/A | Manipulate raw image data。已经弃用。 |
readline | readline-devel | GNU readline interface |
sunaudiodev | N/A | Access to Sun audio hardware。这个是针对Sun平台的,CentOS下可以忽略 |
zlib | Zlib | Compression compatible with gzip |
安装
如果是自己下载编译安装,
首先去python官网下载python3的源码包,网址:https://www.python.org/
解压到某个文件夹(我这里假设是python372)后,进去就可以编译安装了,代码非常简单,如下
注:在编译之前,建议先看一下后面的问题2,取决于你的实际系统状态,有可能需要修改Setup
./configure --prefix=/usr/local --with-pydebug --enable-shared CFLAGS=-fPIC
make -j8 -s (-j means jobs for multiprocessing -s means silent)
sudo make install
说明:其中/usr/local是安装目录,当然你完全可以选其他的地方,如果你需要学习Python源码,那就要调试版,此时要加上--with-pydebug,更详细的过程可以参考官方说明:ref https://devguide.python.org/
补充:这里加上--enable-shared和-fPIC之后可以将python3的动态链接库编译出来,默认情况编译完lib下面只有python3.xm.a这样的文件,python本身可以正常使用,但是如果编译第三方库需要python接口的比如caffe等,则会报错;所以这里建议按照上面的方式配置,另外如果openssl不使用系统yum安装的,而是使用自己编译的比较新的版本可以使用--with-openssl=/usr/local/openssl这种方式指定,后面目录为openssl实际安装的目录,另外编译完还要将openssl的lib目录加入ld运行时目录中即可.
具体关于fPIC这个参数,请看后面的说明。
多个Python版本
下面讲一下如何让两个Python共存,实际上,我的系统里往往远远不止2个Python版本。
根据前面的命令,安装成功之后,安装目录就在/usr/local/
安装完成之后要简单做一下配置:
方法1:
即将python库路径添加到/etc/ld.so.conf配置中,然后执行ldconfig生效;
$ sudo gedit /etc/ld.so.conf
此命令会打开ld.so.conf文件,正常情况下,里面会包含一句,
include /etc/ld.so.conf.d/*.conf
你可以在这个文件中直接修改,也可以
$ sudo gedit /etc/ld.so.conf.d/python372.conf
文件内容写入
/usr/local/python372/lib
并保存,注意这个路径是你的libpython3.7dm.so文件所在的位置,对非调试版是libpython3.7m.so,如果是静态库,则是libpython3.7dm.a文件;我也不知道为什么,位置貌似不是固定的,也有可能是/usr/local/lib 或 /usr/local/lib/python3.7/config-3.7dm-x86_64-linux-gnu等文件夹下面。然后,
$ sudo ldconfig
方法2:
或者添加到$LD_LIBRARY_PATH中,这样在接下来运行python3是就不会报找不到库文件的错误了。具体做法请参考后面的资料。
系统中原来的python在/usr/bin/python,通过$ls -l /usr/bin/p*可以看到具体情况,通过$which python可以查找是当前python命令是哪个。
这里不要对原来默认的环境做任何修改,只新建一个python372的软链接即可,这里建立有关的软链接如下:
$sudo ln -s /usr/local/python372/bin/python3 /usr/bin/python372
$sudo ln -s /usr/local/python372/bin/pip3 /usr/bin/pip372
同样要注意,这个目录也是变动的,和系统配置有关,比如我两次不同的配置(具体有哪些不同我自己也不记得了),另一个结果为
$sudo ln -s /usr/local/bin/python3 /usr/bin/python372
$sudo ln -s /usr/local/bin/pip3 /usr/bin/pip372
如果不报错就表示建好了,以后直接执行python372命令就可以调用该版本的python3了,执行pip372可以安装需要的python372的模块;另外如果仔细看python安装目录下的bin目录,实际上python3也是个软链接,链接到python3.7.2,这样多次链接也是为了多个版本的管理更加方便,
总结
整个过程其实还是很简单的,这里做一个总结。为了给出多版本的特性,我这里用另一个版本,安装的地点也换成了/opt/cpython380,如果你根据我这里的做法,同时安装python372和python380,就会发现可以完全没有冲突了
git clone https://github.com/SpaceView/cpython.git
# you can go https://github.com/python/cpython.git if you don't need a fork
cd cpython
mkdir build
cd build
../configure --prefix=/opt/cpython380 --with-pydebug --enable-shared CFLAGS=-fPIC
# --prefix= the place where you need to install the cpython
make -j8 #, or make -j8 -s (-j means jobs for multiprocessing -s means silent)
# make test, if you wanna see something testing
sudo -H make install
sudo gedit /etc/ld.so.conf.d/python380.conf
# (write "/opt/cpython380/lib" in the file and save it)
sudo ldconfig
# --- now close the current terminal ----
# --- open a new terminal to let the above configure take effect ---
sudo ln -s /opt/cpython380/bin/python3 /usr/bin/python380
# --- install the latest pip version----
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
sudo -H python380 get-pip.py
sudo ln -s /opt/cpython380/bin/pip3 /usr/bin/pip380
--------------------------------------------华丽的分割线---------------------------------------------------
编译时的报错处理
-----------------------------------------------------------------------------
No.1
~/dev/python372/Modules/_cursesmodule.c: In function ‘PyCurses_setupterm’:
~/dev/python372/Modules/_cursesmodule.c:2564:35: error: implicit declaration of function ‘setupterm’; did you mean ‘set_term’? [-Werror=implicit-function-declaration]
if (!initialised_setupterm && setupterm(termstr,fd,&err) == ERR) {
^~~~~~~~~
set_term
cc1: some warnings being treated as errors
解决方式:
sed -i "s/Werror=implicit-function-declaration/Wno-error/g" configure(修改configure文件)
sudo make clean
sudo make
-----------------------------------------------------------------------------
No.2
pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.
这个问题比较顽固,先给出正确答案,
cd ~/python372/Module/
vi Setup
或者
gedit ~/dev/python372/Modules/Setup
找到下面这三行,取消前面的注释,让其起作用,然后保存退出
ssl _ssl.c \
-DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \
-L$(SSL)/lib -lssl -lcrypto
make
sudo make install
参考:https://stackoverflow.com/questions/49094768/ssl-module-in-python-is-not-available
Python3.6以前的版本貌似又不一样,具体可以参考(这里边所有的信息对我都没有用):
https://stackoverflow.com/questions/41328451/ssl-module-in-python-is-not-available-when-installing-package-with-pip3
-----------------------------------------------------------------------------
No.3
Failed to build these modules: _ctypes
请仔细检查,一般是缺少库文件引起的,比如缺少libffi-dev,libc6等库。
编译参数说明
gcc编译参数-fPIC的一些问题
-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),
则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
gcc -shared -fPIC -o 1.so 1.c
这里有一个-fPIC参数PIC就是position independent code,PIC使.so文件的代码段变为真正意义上的共享
如果不加-fPIC,则加载.so文件的代码段时,代码段引用的数据对象需要重定位, 重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的copy.每个copy都不一样,取决于 这个.so文件代码段和数据段内存映射的位置.
不加fPIC编译出来的so,是要再加载时根据加载到的位置再次重定位的.(因为它里面的代码并不是位置无关代码)
如果被多个应用程序共同使用,那么它们必须每个程序维护一份so的代码副本了.(因为so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)
我们总是用fPIC来生成so,也从来不用fPIC来生成a.
fPIC与动态链接可以说基本没有关系,libc.so一样可以不用fPIC编译,只是这样的so必须要在加载到用户程序的地址空间时重定向所有表目.
因此,不用fPIC编译so并不总是不好,如果你满足以下4个需求/条件:
1.该库可能需要经常更新
2.该库需要非常高的效率(尤其是有很多全局量的使用时)
3.该库并不很大.
4.该库基本不需要被多个应用程序共享
如果用没有加这个参数的编译后的共享库,也可以使用的话,可能是两个原因:
1:gcc默认开启-fPIC选项
2:loader使你的代码位置无关
从GCC来看,shared应该是包含fPIC选项的,但似乎不是所以系统都支持,所以最好显式加上fPIC选项。参见如下
`-shared'
Produce a shared object which can then be linked with other
objects to form an executable. Not all systems support this
option. For predictable results, you must also specify the same
set of options that were used to generate code (`-fpic', `-fPIC',
or model suboptions) when you specify this option.(1)
-fPIC 的使用,会生成 PIC 代码,.so 要求为 PIC,以达到动态链接的目的,否则,无法实现动态链接。
non-PIC 与 PIC 代码的区别主要在于 access global data, jump label 的不同。
比如一条 access global data 的指令,
non-PIC 的形势是:ld r3, var1
PIC 的形式则是:ld r3, var1-offset@GOT,意思是从 GOT 表的 index 为 var1-offset 的地方处
指示的地址处装载一个值,即var1-offset@GOT处的4个 byte 其实就是 var1 的地址。这个地址只有在运行的时候才知道,是由 dynamic-loader(ld-linux.so) 填进去的。
再比如 jump label 指令
non-PIC 的形势是:jump printf ,意思是调用 printf。
PIC 的形式则是:jump printf-offset@GOT,
意思是跳到 GOT 表的 index 为 printf-offset 的地方处指示的地址去执行,这个地址处的代码摆放在 .plt section,
每个外部函数对应一段这样的代码,其功能是呼叫dynamic-loader(ld-linux.so) 来查找函数的地址(本例中是 printf),然后将其地址写到 GOT 表的 index 为 printf-offset 的地方,
同时执行这个函数。这样,第2次呼叫 printf 的时候,就会直接跳到 printf 的地址,而不必再查找了。
GOT 是 data section, 是一个 table, 除专用的几个 entry,每个 entry 的内容可以再执行的时候修改;
PLT 是 text section, 是一段一段的 code,执行中不需要修改。
每个 target 实现 PIC 的机制不同,但大同小异。比如 MIPS 没有 .plt, 而是叫 .stub,功能和 .plt 一样。
可见,动态链接执行很复杂,比静态链接执行时间长;但是,极大的节省了 size,PIC 和动态链接技术是计算机发展史上非常重要的一个里程碑。
gcc manul上面有说
-fpic If the GOT size for the linked executable exceeds a machine-specific maximum size, you get an error message from the linker indicating that -fpic does not work; in that case, recompile with -fPIC instead. (These maximums are 8k on the SPARC and 32k on the m68k and RS/6000. The 386 has no such limit.)
-fPIC If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding any limit on the size of the global offset table. This option makes a difference on the m68k, PowerPC and SPARC. Position-independent code requires special support, and therefore works only on certain machines.
关键在于GOT全局偏移量表里面的跳转项大小。
intel处理器应该是统一4字节,没有问题。
powerpc上由于汇编码或者机器码的特殊要求,所以跳转项分为短、长两种。
-fpic为了节约内存,在GOT里面预留了“短”长度。
而-fPIC则采用了更大的跳转项。
动态库的配置
第一种,通过ldconfig命令
ldconfig命令的用途, 主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下, 搜索出可共享的动态链接库(格式如lib*.so*), 进而创建出动态装入程序(ld.so)所需的连接和缓存文件. 缓存文件默认为/etc/ld.so.cache, 此文件保存已排好序的动态链接库名字列表.
这里面涉及到的关键内容有命令:ldconfig,配置文件目录:/etc/ld.so.conf.d,配置文件在/etc/ld.so.conf内容由用户编辑,缓冲文件/etc/ld.so.cache。下面举个例子,比如你在部署软件时,有些动态库安装在exe目录下,可以通过如下方法实现,
1)配置exe.conf文件,里面加一行~/exe,然后将该文件放到/etc/ld.so.conf.d目录下;
2)在/etc/ld.so.conf文件中增加一行include ld.so.conf.d/exe.conf
3)执行 ldconfig命令
之后程序运行时,会自动增加在exe目录中搜索动态库。
第二种,通过LD_LIBRARY_PATH环境变量
可以通过在.bashrc或者.cshrc中配置该环境变量,LD_LIBRARY_PATH的意思是告诉loader在哪些目录中可以找到共享库. 可以设置多个搜索目录, 这些目录之间用冒号分隔开.
同样是上面的例子,可以通过以上的方法来实现
在.bashrc或.cshrc中增加一行,export LD_LIBRARY_PATH = ~/exe:$LD_LIBRARY_PATH即可。
第三种,通过编译选项-Wl, -rpath指定动态搜索的路径
-Wl选项告诉编译器将后面的参数传递给链接器。
通过上面的介绍,对/etc/ld.so.conf.d/的作用就比较清晰了。
现在我们知道了动态与静态函数库,也知道了当前的Linux大多是将函数库做成动态函数库,下面来讨论增加函数库读取性能的方法。我们知道,内存的访问速度是硬盘的好几倍,所以,如果将常用的动态函数库加载到内存中(高速缓存,cache),当软件套件要采用动态函数库时,就不需要重新从硬盘里读出,这样就可以提高动态函数库的读取速度。这个时候需要ldconfig与 /etc/ld.so.conf的帮助。
将动态函数库加载到高速缓存(cache)中的过程如下:
1. 首先,要在 /etc/ld.so.conf中写下“想要读入高速缓存中的动态函数库所在的目录”,注意,是目录而不是文件。
2. 利用ldconfig执行文件将 /etc/ld.so.conf的数据读入高速缓存中。
3. 同时在 /etc/ld.so.cache文件中记录数据。
范例一:假设MySQL数据库函数库在 /usr/lib/mysql中,如何读入高速缓存?
[root@linux ~]# vi /etc/ld.so.conf
include ld.so.conf.d/*.conf
/usr/lib/mysql <==这一行是新增的。
[root@linux ~]# ldconfig
# 画面上不会显示任何信息,正常。
[root@linux ~]# ldconfig -p
928 libs found in cache `/etc/ld.so.cache'
libz.so.1 (libc6) => /usr/lib/libz.so.1