Linux下源码编译安装详解
前⾔
源码要运⾏,必须先转成⼆进制的机器码。这是编译器的任务。
⽐如,下⾯这段源码(假定⽂件名叫做test.c)。
#include <stdio.h>
int main(void)
{
fputs("Hello, world!\n", stdout);
return 0;
}
要先⽤编译器处理⼀下,才能运⾏。
$ gcc test.c
$ ./a.out
Hello, world!
对于复杂的项⽬,编译过程还必须分成三步。
$ ./configure
$ make
$ make install
编译过程流程图:
⼀.编译的具体过程
1. 配置(configure)
编译器在开始⼯作之前,需要知道当前的系统环境,⽐如标准库在哪⾥、软件的安装位置在哪⾥、需要安装哪些组件等等。这是因为不同计算机的系统环境不⼀样,通过指定编译参数,编译器就可以灵活适应环境,编译出各种环境都能运⾏的机器码。这个确定编译参数的步骤,就叫做"配置"(configure)。
这些配置信息保存在⼀个配置⽂件之中,约定俗成是⼀个叫做configure的脚本⽂件。通常它是由autoconf⼯具⽣成的。编译器通过运⾏这个脚本,获知编译参数。
configure脚本已经尽量考虑到不同系统的差异,并且对各种编译参数给出了默认值。如果⽤户的系统环境⽐较特别,或者有⼀些特定的需求,就需要⼿动向configure脚本提供编译参数。
$ ./configure --prefix=/www --with-mysql
上⾯代码是php源码的⼀种编译配置,⽤户指定安装后的⽂件保存在www⽬录,并且编译时加⼊mysql模块的⽀持。
2. 确定标准库和头⽂件的位置
源码肯定会⽤到标准库函数(standard library)和头⽂件(header)。它们可以存放在系统的任意⽬录中,编译器实际上没办法⾃动检测它们的位置,只有通过配置⽂件才能知道。
编译的第⼆步,就是从配置⽂件中知道标准库和头⽂件的位置。⼀般来说,配置⽂件会给出⼀个清单,列出⼏个具体的⽬录。等到编译时,编译器就按顺序到这⼏个⽬录中,寻⽬标。
3. 确定依赖关系
对于⼤型项⽬来说,源码⽂件之间往往存在依赖关系,编译器需要确定编译的先后顺序。假定A⽂件依赖于B⽂件,编译器应该保证做到下⾯两点。
(1)只有在B⽂件编译完成后,才开始编译A⽂件。
(2)当B⽂件发⽣变化时,A⽂件会被重新编译。
编译顺序保存在⼀个叫做makefile的⽂件中,⾥⾯列出哪个⽂件先编译,哪个⽂件后编译。⽽makefile⽂件由configure脚本运⾏⽣成,这就是为什么编译时configure必须⾸先运⾏的原因。
在确定依赖关系的同时,编译器也确定了,编译时会⽤到哪些头⽂件。
4. 头⽂件的预编译(precompilation)
不同的源码⽂件,可能引⽤同⼀个头⽂件(⽐如stdio.h)。编译的时候,头⽂件也必须⼀起编译。为
了节省时间,编译器会在编译源码之前,先编译头⽂件。这保证了头⽂件只需编译⼀次,不必每次⽤到的时候,都重新编译了。
不过,并不是头⽂件的所有内容,都会被预编译。⽤来声明宏的#define命令,就不会被预编译。
5. 预处理(Preprocessing)
预编译完成后,编译器就开始替换掉源码中bash的头⽂件和宏。以本⽂开头的那段源码为例,它包含头⽂件stdio.h,替换后的样⼦如下。extern int fputs(const char *, FILE *);
extern FILE *stdout;
int main(void)
{
fputs("Hello, world!\n", stdout);
return 0;
}
为了便于阅读,上⾯代码只截取了头⽂件中与源码相关的那部分,即fputs和FILE的声明,省略了stdio.h的其他部分(因为它们⾮常长)。另外,上⾯代码的头⽂件没有经过预编译,⽽实际上,插⼊源码的是预编译后的结果。编译器在这⼀步还会移除注释。
这⼀步称为"预处理"(Preprocessing),因为完成之后,就要开始真正的处理了。
6. 编译(Compilation)
预处理之后,编译器就开始⽣成机器码。对于某些编译器来说,还存在⼀个中间步骤,会先把源码转为汇编码(assembly),然后再把汇编码转为机器码。
下⾯是本⽂开头的那段源码转成的汇编码。
.file  "test.c"
.section    .rodata
.LC0:
.string "Hello, world!\n"
.
text
.globl  main
.type  main, @function
main:
.LFB0:
.cfi_startproc
pushq  %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq    %rsp, %rbp
.cfi_def_cfa_register 6
movq    stdout(%rip), %rax
movq    %rax, %rcx
movl    $14, %edx
movl    $1, %esi
movl    $.LC0, %edi
call    fwrite
movl    $0, %eax
popq    %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.
LFE0:
.size  main, .-main
.ident  "GCC: (Debian 4.9.1-19) 4.9.1"
.section    .note.GNU-stack,"",@progbits
这种转码后的⽂件称为对象⽂件(object file)。
注:make (gcc), 其调⽤ gcc 执⾏编译的过程依赖于配置⽂件makefile
7. 连接(Linking)
对象⽂件还不能运⾏,必须进⼀步转成��执⾏⽂件。如果你仔细看上⼀步的转码结果,会发现其中引⽤了stdout函数和fwrite函数。也就是说,程序要正常运⾏,除了上⾯的代码以外,还必须有stdout和fwrite这两个函数的代码,它们是由C语⾔的标准库提供的。
编译器的下⼀步⼯作,就是把外部函数的代码(通常是后缀名为.lib和.a的⽂件),添加到可执⾏⽂件中。这就叫做连接(linking)。这种通过拷贝,将外部函数库添加到可执⾏⽂件的⽅式,叫做静态连接(static linking),后⽂会提到还有动态连接(dynamic linking)。
make命令的作⽤,就是从第四步头⽂件预编译开始,⼀直到做完这⼀步。
8. 安装(Installation)
上⼀步的连接是在内存中进⾏的,即编译器在内存中⽣成了可执⾏⽂件。下⼀步,必须将可执⾏⽂件保存到⽤户事先指定的安装⽬录。
表⾯上,这⼀步很简单,就是将可执⾏⽂件(连带相关的数据⽂件)拷贝过去就⾏了。但是实际上,这⼀步还必须完成创建⽬录、保存⽂件、设置权限等步骤。这整个的保存过程就称为"安装"(Installation)。
9. 操作系统连接
可执⾏⽂件安装后,必须以某种⽅式通知操作系统,让其知道可以使⽤这个程序了。⽐如,我们安装了⼀个⽂本阅读程序,往往希望双击txt⽂件,该程序就会⾃动运⾏。
这就要求在操作系统中,登记这个程序的元数据:⽂件名、⽂件描述、关联后缀名等等。Linux系统中,这些信息通常保存
在/usr/share/applications⽬录下的.desktop⽂件中。另外,在Windows操作系统中,还需要在Start启动菜单中,建⽴⼀个快捷⽅式。
这些事情就叫做"操作系统连接"。make install命令,就⽤来完成"安装"和"操作系统连接"这两步。
10. ⽣成安装包
写到这⾥,源码编译的整个过程就基本完成了。但是只有很少⼀部分⽤户,愿意耐着性⼦,从头到尾做⼀遍这个过程。事实上,如果你只有源码可以交给⽤户,他们会认定你是⼀个不友好的家伙。⼤部分⽤户要的是⼀个⼆进制的可执⾏程序,⽴刻就能运⾏。这就要求开发者,将上⼀步⽣成的可执⾏⽂件,做成可以分发的安装包。
所以,编译器还必须有⽣成安装包的功能。通常是将可执⾏⽂件(连带相关的数据⽂件),以某种⽬录结构,保存成压缩⽂件包,交给⽤户。
11. 动态连接(Dynamic linking)
正常情况下,到这⼀步,程序已经可以运⾏了。⾄于运⾏期间(runtime)发⽣的事情,与编译器⼀概⽆关。但是,开发者可以在编译阶段选择可执⾏⽂件连接外部函数库的⽅式,到底是静态连接(编译时连接),还是动态连接(运⾏时连接)。所以,最后还要提⼀下,什么叫做动态连接。
前⾯已经说过,静态连接就是把外部函数库,拷贝到可执⾏⽂件中。这样做的好处是,适⽤范围⽐较⼴,不⽤担⼼⽤户机器缺少某个库⽂件;缺点是安装包会⽐较⼤,⽽且多个应⽤程序之间,⽆法共享
库⽂件。动态连接的做法正好相反,外部函数库不进⼊安装包,只在运⾏时动态引⽤。好处是安装包会⽐较⼩,多个应⽤程序可以共享库⽂件;缺点是⽤户必须事先安装好库⽂件,⽽且版本和安装位置都必须符合要求,否则就不能正常运⾏。
现实中,⼤部分软件采⽤动态连接,共享库⽂件。这种动态共享的库⽂件,Linux平台是后缀名为.so的⽂件,Windows平台是.dll⽂
件,Mac平台是.dylib⽂件。
⼆、Linux编译安装的具体实现
1.编译安装源程序的前提:
1).提供开发环境:开发⼯具和开发库
2).编译安装需要的包组:
Development Tools、Server Platform Development、Desktop Platform Development、Debug Tools
-
-help获取./configure脚本帮助
--prefix=: 指定安装路径;多数程序都有默认安装路径;
--sysconfidr=: 指定配置⽂件安装路径;
--with-PACKAGE[=ARG]:在⾃由软件社区⾥,有使⽤已有软件包和库的优秀传统.当⽤'configure'来配置⼀个源码树时,
可以提供其他已经安装的软件包的信息
--without-PACKAGE:有时候你可能不想让你的软件包与系统已有的软件包交互。例如,你可能不想让你的新编译器使⽤
GNU ld
--enable-FEATURE:⼀些软件包可能提供了⼀些默认被禁⽌的特性,可以使⽤'--enable-FEATURE'来起⽤它
--disable-EEATURE:关闭指定的默认特性
3.编译安装源程序⽅法:
1)、展开源代码,INSTALL、README;不存在此类⽂件时,项⽬官⽅⽂档;
2)、根据安装说明执⾏安装操作;
3.程序安装于专⽤⽬录时,安装后的配置:
1)、导出⼆进制程序所在路径⾄PATH环境中
# export PATH=/usr/local/nginx/sbin:$PATH
实现永久有效的办法:
/etc/profile.d/*.sh
2)、导出库⽂件给OS
OS查库⽂件⽅法:根据/etc/f配置⽂件指定的路径搜索,或搜索/lib, /lib64, /usr/lib, /usr/lib64,把查到的所有的库⽂件路径和其名称映射关系保存为⼀个缓存⽂件/etc/ld.so.cache;
/etc/f配置⽂件有其它组成部分:/etc/f.d/*.conf
假设nginx安装于/usr/local/nginx,此⽬录中有其库⽂件⼦⽬录lib,导出此⽬录中库⽂件:
(1)新建⽂件/etc/f.f,在⽂件添加如下⾏:
/usr/local/nginx/lib
(2) 运⾏命令:ldconfig
ldconfig的主要⽤途:
默认搜寻/lilb和/usr/lib,以及配置⽂件/etc/f内所列的⽬录下的库⽂件。
搜索出可共享的动态链接库,库⽂件的格式为:lib***.so.**,进⽽创建出动态装⼊程序(ld.so)所需的连接和缓存⽂件。
缓存⽂件默认为/etc/ld.so.cache,该⽂件保存已排好序的动态链接库名字列表。
ldconfig通常在系统启动时运⾏,⽽当⽤户安装了⼀个新的动态链接库时,就需要⼿⼯运⾏这个命令。
常⽤选项:
-v:⽤此选项时,ldconfig将显⽰正在扫描的⽬录及搜索到的动态链接库,还有它所创建的连接的名字.
-p: 显⽰当前OS已经加载到的所有库⽂件名称及其⽂件所在路径的映射关系;
ldconfig需要注意的地⽅:
(a)、往/lib和/usr/lib⾥⾯加东西,是不⽤修改/etc/f⽂件的,但是添加完后需要调⽤下ldconfig,不然添加的library会不到。
(b)、如果添加的library不在/lib和/usr/lib⾥⾯的话,就⼀定要修改/etc/f⽂件,往该⽂件追加library所在的路径,然后也需要重新调⽤下ldconfig命令。⽐如在安装mysql的时候,其库⽂件/usr/local/mysql/lib,就需要追加到/etc/f⽂件中。命令如下:
# echo "/usr/local/mysql/lib" >> /etc/f
# ldconfig -v | grep mysql
(c)、如果添加的library不在/lib或/usr/lib下,但是却没有权限操作写/etc/f⽂件的话,这时就需要往export⾥写⼀个全局变量LD_LIBRARY_PATH,就可以了。
(3)、帮助⽂件导出
mysql下载下来没安装包
man命令搜索特定路径查⼿册页⽂件,这些路径是定义在/fig中的MANPATH参数所指定的路径下的;
新增办法:编辑/fig⽂件,新增⼀个MANPATH参数,其值为新安装程序的man⼿册所在的⽬录;
/usr/local/nginx/share/man/{man1,man8}
man -M /path/to/man KEYWORD
(4)、头⽂件导出
有些程序安装后会⽣成对⾃⼰拥有库⽂件调⽤接⼝相关头⽂件系统查头⽂件的路径为/usr/include
导出独⽴安装应⽤程序的头⽂件⽅法:创建链接⾄/usr/include下即可;
例如:
/usr/local/nginx/include
# ln -sv /usr/local/nginx/include/* /usr/include/
# ln -sv /usr/local/nginx/include /usr/include/nginx
perl源程序的编译安装⽅法:
(1) perl Makefile.in
(2) make
(3) make install

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。