linux软件安装与卸载


在linux系统上安装和升级软件的方法大概有两种:
编译程序源码Tarball安装与升级;
直接下载编译好的二进制可执行程序文件来安装与升级。

linux软件大都经过GPL授权,每个软件几乎都提供程序源代码! 最传统的软件安装方法就是由程序源码Tarball编译为二进制可执行文件!
该方法虽然有很高的弹性, 但毕竟比较麻烦。 如果linux发行版厂商能够针对自己的环境平台预先编译,将编译好的二进制可执行程序文件打包发布, 用户只需下载和自己平台对应的二进制可执行程序文件就可以直接安装,省去了用户自己检测环境与编译等繁杂的过程!
目前linux的大部分发行版都支持该安装机制,并由此衍生出了不同的软件安装包管理机制和在线安装方法:
Red Hat系统(含Fedora/CentOS系列)的RPM软件管理机制与yum在线安装模式;
Debian系统的dpkg软件管理机制与apt在线安装模式。
CentOS系统是依循标准的linux distribution,所以可以使用Tarball直接进行编译安装与升级,当然也可以使用RPM相关的机制来进行安装与升级!

使用源码安装与升级软件

回到顶部

由程序源码Tarball安装软件大致需要经过如下几个流程:

1. 软件官网下载源码Tarball文件;
2. 解压缩源码Tarball;
3. 使用gcc编译源码,产生目标文件object files;
4. 使用gcc链接函数库、主、子程序,生成二进制可执行文件;
5. 将生成的binary file及相关配置文件拷贝到相应的安装目录。

实际的安装步骤可能根据不同的软件有所不同,例如步骤3和步骤4可以通过make命令简化! 源码Tarball文件是以tar命令打包压缩的文件,需要先将Tarball解压缩,然后到源码所在的目录下建立makefile文件,再以make命令编译与安装!
通常软件源码目录下都会有一个INSTALL或README文件, 这些文件会记录软件一些说明信息,包括安装要求、工作环境、软件的安装参数设定及技巧等,只需按照文件中的安装指引进行安装即可。
实际的安装步骤如下:

1. 软件官网下载源码Tarball文件;
2. 将tarball文件拷贝到/usr/local/src目录下,并解压缩;
3. 进入解压后的源码目录,查看INSTALL或README相关文件(很重要的步骤!),找到软件的安装说明;
4. 根据INSTALL或README文件中的安装指引安装好一些依赖的软件(非必要);
5. 根据INSTALL或README文件中的安装指引安装软件;
大多数INSTALL或README文件中软件安装说明都会经过如下步骤:
1. ./configure
执行configure或config检测安装环境并建立makefile文件。 通常软件开发者会写一个scripts来检查linux系统、相关的软件属性等,这个步骤非常重要, 安装信息的建立都是在这一步骤完成的!
2. make clean
执行make clean清理上次编译生成的目标文件(*.o)。make会读取Makefile文件中的clean目标。
3. make
执行make命令根据生成的Makefile文件编译源码,生成可执行程序文件。make会依据Makefile文件中的预设工作进行编译! 编译的工作主要是调用gcc来将源码编译为可以被执行的object files,但是这些object files通常还需要link一些函数库,才能生成一个完整的可执行程序文件! 使用make就是要将源码编译成为可以被执行的可执行文件,而这个可执行文件会放置当前工作目录下, 尚未被安装到指定的目录中;
4. make install
执行make install将生成的可执行文件安装到指定的软件目录! 通常这就是最后的安装步骤了,make根据Makefile文件中的install目标,将make生成的可执行程序文件安装到指定的目录中!
makefile文件中有非常多的目标(target),最常见的就是install与clean! make clean将目标文件(object file)清除掉,make则进行源码编译。
编译生成的可执行文件与相关的配置文件还在源码所在的目录下!因此,最后要进行make install将编译编译生成的可执行文件与相关的配置文件安装到正确的路径,这样才可以使用该软件!
上面的步骤是一步一步进行的,需要确保每一步骤都是成功的。只要一个步骤无法成功,就无法进行后续的步骤! 例如./configure没有成功,就表示Makefile文件没有被建立起来,而后面的步骤都是根据Makefile文件来进行的,没有Makefile文件,后续的步骤就无法顺利执行。
make install是将编译生成的文件放置到文件系统指定的目录中,如果make无法成功执行,就不能生成可执行文件,也就不能执行make install了。
此外,如果安装成功, 并且是安装在独立的一个目录中,例如/usr/local/packages,那么就必需手动将这个软件的man page写入/etc/man_db.conf中。

默认情况下,随linux distribution发布安装的软件大多存放在/usr目录下, 为方便用户管理自己安装的软件,用户自行安装的软件建议放在/usr/local目录下。
几乎每个软件都会提供联机帮助的服务,那就是info与man的功能。 默认man会去搜寻/usr/local/man里面的说明文件, 如果我们将软件安装在/usr/local目录下,该软件的说明文件自然就可以被找到了。 通常建议用户将自己安装的软件放在/usr/local下,至于源码(Tarball)则建议放在/usr/local/src(src为source的缩写)目录下。
我们以apache这个软件来说明(apache是WWW服务器软件),linux distribution默认的软件安装会用到如下的路径:

              /etc/httpd
              /usr/lib
              /usr/bin
              /usr/share/man
            
分别存放配置文件、函数库、执行文件、联机帮助文件。
以tarball来安装就是放在预设的/usr/local里面:
              /usr/local/src
              /usr/local/etc
              /usr/local/lib
              /usr/local/bin
              /usr/local/man
            
但是如果每个软件都选择在这个默认的路径下安装的话, 未来想要升级或移除的时候,就会比较难以追查文件的来源! 所以最好在/usr/local/目录下为该软件再单独规划出一个目录,例如将apache安装在/usr/local/apache中,那么文件目录就会变成:
              /usr/local/apache/src
              /usr/local/apache/etc
              /usr/local/apache/bin
              /usr/local/apache/lib
              /usr/local/apache/man
            
这样和该软件相关的所有文件都在同一个目录下,要卸载该软件只需将该目录移除即可!
              rm -rf /usr/local/apache
            
我们在执行某些指令的时候,会去PATH环境变量指定的路径中查询相关的命令。 而/usr/local/apache/bin肯定是不在PATH里,这样执行apache的指令就得要利用绝对路径了。 需要将/usr/local/apache/bin加入PATH里面。/usr/local/apache/man也需要加入man page搜寻的路径当中!
为了方便Tarball的安装和卸载管理,通常建议用户:
1. 将tarball解压缩到/usr/local/src目录下;
2. 将软件安装到/usr/local/yourSoftware/路径下;
3. 将/usr/local/yourSoftware/bin目录加入PATH环境变量,将man page加入man path搜寻路径中
如果你安装的软件放置到 /usr/local/software/ ,那么 man page 搜寻的设定中,可能就得要在/etc/man_db.conf文件中加入一行:
MANPATH_MAP /usr/local/software/bin /usr/local/software/man这样才可以使用 man 来查询该软件的在线文件啰!
时至今日真的不太需要有tarball的安装了!CentOS/Fedora有个RPM补遗计划,就是俗称的EPEL计划, 相关网址说明如下:https://fedoraproject.org/wiki/EPEL~一般学界会用到的软件都在里头

ntp安装示例

回到顶部

我们以时间服务器(network time protocol)ntp的安装为例来说明tarball软件安装。
将ntp安装到/usr/local/ntp目录下:

              1.切换到/usr/local/src目录:
              [root@study ~]# cd /usr/local/src
              2.下载最新的ntp源码包:
              [root@study ~]# wget http://www.ntp.org/ntp-4.2.8p3.tar.gz
              3.解压缩源码包:
              [root@study src]# tar -zxvf ntp-4.2.8p3.tar.gz ntp-4.2.8p3/
              ntp-4.2.8p3/CommitLog
              ....(底下省略)....
              4.切换到源码目录:
              [root@study src]# cd ntp-4.2.8p3
              5.查看README/INSTALL文件。特别关注安装说明部分:
              [root@study ntp-4.2.8p3]# vi INSTALL
              6.检查configure支持参数,查询可用的参数有哪些:
              [root@study ntp*]# ./configure --help | more
              --prefix=PREFIX 
              install architecture-independent files in PREFIX
              --enable-all-clocks + include all suitable non-PARSE clocks:
              --enable-parse-clocks - include all suitable PARSE clocks:    这里列出的是比较重要的可能需要的参数功能! 
              7.建立makefile规则文件:
              [root@study ntp*]# ./configure --prefix=/usr/local/ntp \
              > --enable-all-clocks --enable-parse-clocks
              checking for a BSD-compatible install... /usr/bin/install -c #开始建立 makefile文件
              checking whether build environment is sane... yes
              ...
              checking for gcc... gcc #找到gcc编译程序了!
              ...
              config.status: creating Makefile
              config.status: creating config.h
              config.status: creating evconfig-private.h
              config.status: executing depfiles commands
              config.status: executing libtool commands
              8.开始编译并安装到/usr/local/ntp目录:
              [root@study ntp*]# make clean; make
              [root@study ntp*]# make check
              [root@study ntp*]# make install
            
一般来说configure参数较重要的就是--prefix=/path了,--prefix后面接的路径就是软件要安装的目录。 如果没有指定--prefix=/path参数,通常默认参数就是/usr/local。 其他参数的意义可以参考./configure --help! ./configure检查的过程会显示在屏幕上, 特别留意关于gcc的检查,执行完后会产生Makefile文件。

通过源码安装软件所需要的基础软件工具

回到顶部

上面从源码安装软件的过程中用到了很多相关的基础工具软件,包括解压缩工具、gcc和make等:
C语言编译程序(compiler)gcc或cc:
C compiler是一定要有的,linux上有众多的编译程序,其中最常用的就是GNU的gcc;
make及autoconfig等软件:
一般来说,以Tarball方式发布的软件当中,为了简化编译的流程,通常都是配合make命令来依据目标文件的相依性而进行编译。 make需要makefile文件的规则。由于不同的系统基础软件环境并不相同, 所以就需要检测用户的作业环境来自动建立makefile文件。 这个自行侦测的小程序也必须要藉由autoconfig这个相关的软件来辅助才行。
Kernel提供的Library以及相关的Include文件:
从前面的源码编译过程,我们晓得函数库 (library) 的重要性,同时也晓得有 include 文件的存在。 很多的软件都是直接取用系统核心提供的函数库与include文件的,这样才可以与这个操作系统兼容啊! 尤其是驱动程序方面的模块 ,例如网络卡、声卡、USB 等驱动程序在安装的时候,常常是需要核心提供的相关信息的。 在 Red Hat 的系统当中 (包含Fedora/CentOS 等系列) , 这个核心相关的功能通常都是被包含在 kernel-source 或 kernel-header 这些软件名称当中,所以记得要安装这些软件喔!

只要按照开发商提供的README与INSTALL文件说明的步骤,Tarball的安装是非常简单容易的。
目前使用最广泛的CentOS/Fedora或者是Red Hat大多是以RPM来安装软件的,所以,你只要拿出当初安装 linux 时的原版光盘,然 后以下一章介绍的 RPM 来一个一个的加入到你的 linux 主机里面就好啦!
在 CentOS 当中,如果你已经有网络可以连上 Internet 的话,那么就可以使用下一章会谈到的 yum啰! 透过 yum 的软件群组安装功能,你可以这样做:
使用 yum groupinstall "Development Tools"安装gcc等软件开发工具
若待安装的软件需要图形接口支持,一般还需要 yum groupinstall "X Software Development"
若安装的软件较旧,可能需要 yum groupinstall "Legacy Software Development"

什么是开放源码、编译程序与可执行文件

回到顶部

什么是可执行程序,编译程序,函数库(library)! 系统其实认识的可执行文件是binary program。
什么是二进制可执行文件?linux系统上,一个文件能不能被执行看的是有没有可执行权限(具有x permission), linux系统真正认识的可执行文件其实是二进制文件( binary program),例如/usr/bin/passwd, /bin/touch这些个文件即为二进制可执行程序文件。
或许你会说 shell scripts 不是也可以执行吗?其实 shell scripts 只是利用shell(例如 bash)程序的功能进行一些判断式, 而最终执行的除了bash提供的功能外,仍是调用一些已经编译好的二进制程序来执行! bash本身也是一支二进制程序!那么我怎么知道一个文件是否为binary呢? 使用file命令可以查看文件的类型:

              [root@study ~]# file /bin/bash
              /bin/bash: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked(uses shared libs), for GNU/linux 2.6.32, BuildID[sha1]=0x7e60e35005254...stripped
              [root@study ~]# file /etc/init.d/network
              /etc/init.d/network: Bourne-Again shell script, ASCII text executable
            
看到了吧!如果是 binary 而且是可以执行的时候,他就会显示执行文件类别 (ELF 64-bit LSB executable), 同时会说明是否使用动态函数库 (shared libs),而如果是一般的 script ,那他就会显示出 text executables 之类的字样!
事实上,network 的数据显示出 Bourne-Again ... 那一行,是因为你的 scripts 上面第一行声明了#!/bin/bash, 如果将script 的第一行去掉,那么不管 /etc/init.d/network 的权限为何,他其实显示的是 ASCII 文本文件的信息喔!
既然 linux 操作系统真正认识的其实是 binary program,那么我们是如何做出这样的一支 binary 的程序呢? 首先,我们必须要写程序,用什么东西写程序?就是一般的字处理器啊!都喜欢使 用 vim 来进行程序的撰写,写完的程序就是所谓的源代码啰! 这个程序代码文件其实就是一般的纯 文本档。 在完成这个源码文件的编写之后,再来就是要将这个文件编译成为操作系统看的懂 得 binary program 啰!而要编译自然就需要编译程序来动作, 经过编译程序的编译与连结之后, 就会产生一支可以执行的 binary program 啰。
举个例子来说,在 linux 上面最标准的程序语言为 C ,所以我使用 C 的语法进行源代码的书写, 写完之后,以 linux 上标准的 C 语言编译程序 gcc 这支程序来编译,就可以制作一支可以执行的 binary program 啰。整个的流程有点像这样:
图 21.1.1、利用 gcc 编译程序进行程序的编译流程示意图
事实上,在编译的过程当中还会产生所谓的目标文件 (Object file),这些文件是以 *.o 的扩展名样式 存在的!至于 C 语言的源码文件通常以 *.c 作为扩展名。此外,有的时候,我们会在程序当中引 用、调用 其他的外部子程序,或者是利用其他软件提供的函数功能,这个时候,我们就必须 要在编译的过程当中, 将该函数库给他加进去,如此一来,编译程序就可以将所有的程序代码与函 式库作一个连结 (Link) 以产生正确的执行文件啰。总之,我们可以这么说:
开放源码:就是程序代码,写给人类看的程序语言,但机器并不认识,所以无法执行;
编译程序:将程序代码转译成为机器看的懂得语言,就类似翻译者的角色;
可执行文件:经过编译程序变成二进制程序后,机器看的懂所以可以执行的文件。

什么是函数库

回到顶部

在前一小节的图 21.1.1 示意图中,在编译的过程里面有提到函数库这东西。 什么是函数库呢?先举 个例子来说:我们的 linux 系统上通常已经提供一个可以进行身份验证的模块, 就是在第十三章提 到的 PAM 模块。这个 PAM 提供的功能可以让很多的程序在被执行的时候,除了可以验证用户登 入的信息外, 还可以将身份确认的数据记录在登录档里面,以方便系统管理员的追踪!
既然有这么好用的功能,那如果我要编写具有身份认证功能的程序时,直接引用该 PAM 的功能就 好啦,如此一来,我就不需要重新设计认证机制啰!也就是说,只要在我写的程序代码里面,设定去 调用 PAM 的函式功能,我的程序就可以利用 linux 原本就有的身份认证的程序咯!除此之外,其 实我们的 linux 核心也提供了相当多的函数库来给硬件开发者利用喔。
函数库又分为动态与静态函数库,这两个咚咚的分别我们在后面的小节再加以说明。 这里我们以一 个简单的流程图,来示意一支有调用外部函数库的程序的执行情况。
图 21.1.2、程序执行时引用外部动态函数库的示意图 很简单的示意图啊!^_^!而如果要在程序里面加入引用的函数库,就需要如图 21.1.1 所示, 亦即 在编译的过程当中,就需要加入函数库的相关设定啰。
事实上, linux 的核心提供很多的核心相关 函数库与外部参数, 这些核心功能在设计硬件的驱动程序的时候是相当有用的信息,这些核心相关 信息大多放置在/usr/include, /usr/lib, /usr/lib64 里面哩!我们在本章的后续小节再来探讨。反正我们可 以简单的这么想:
函数库:就类似子程序的角色,可以被调用来执行的一段功能函数。

什么是 make 与 configure

回到顶部

事实上,使用类似 gcc 的编译程序来进行编译的过程并不简单,因为一套软件并不会仅有一支程序, 而是有一堆程序代码文件。所以除了每个主程序与子程序均需要写上一笔编译过程的指令外,还需要 写上最终的链接程序。 程序代码小的时候还好,如果是类似 WWW 服务器软件 (例如 Apache) , 或者是类似核心的源码,动则数百 MBytes 的数据量,编译指令会写到疯掉~这个时候,我们就 可以使用 make 这个指令的相关功能来进行编译过程的指令简化了!
当执行 make 时, make 会在当时的目录下搜寻 Makefile (or makefile) 这个文本文件,而 Makefile 里 面则记录了源码如何编译的详细信息! make 会自动的判别源码是否经过变动了,而自动更新 执行文件,是软件工程师相当好用的一个辅助工具呢!
咦!make 是一支程序,会去找 Makefile ,那 Makefile 怎么写? 通常软件开发商都会写一支侦测 程序来侦测用户的作业环境, 以及该作业环境是否有软件开发商所需要的其他功能,该侦测程序侦 测完毕后,就会主动的建立这个 Makefile 的规则文件啦!通常这支侦测程序的文件名为 configure 或 者是 config 。
咦!那为什么要侦测作业环境呢?在第一章当中, 不是曾经提过其实每个 linux distribution 都使用 同样的核心吗?但妳得要注意, 不同版本的核心所使用的系统调用可能不相同,而且每个软件所需 要的相依的函数库也不相同, 同时,软件开发商不会仅针对 linux 开发,而是会针对整个 Unix-Like 做开发啊! 所以他也必须要侦测该操作系统平台有没有提供合适的编译程序才行!所以当然要侦测 环境啊! 一般来说,侦测程序会侦测的数据大约有底下这些:
是否有适合的编译程序可以编译本软件的程序代码;
是否已经存在本软件所需要的函数库,或其他需要的相依软件;
操作系统平台是否适合本软件,包括 linux 的核心版本;
核心的表头定义档 (header include) 是否存在 (驱动程序必须要的侦测)。
至于 make 与 configure 运作流程的相关性,我们可以使用底下的图示来示意一下啊! 下图中,妳 要进行的任务其实只有两个,一个是执行 configure 来建立 Makefile , 这个步骤一定要成功!成功 之后再以 make 来调用所需要的数据来编译即可!非常简单!图 21.1.3、透过 configure 与 make 进行编译示意图 由于不同的 linux distribution 的函数库文件所放置的路径,或者是函数库的档名订定, 或者是预设 安装的编译程序,以及核心的版本都不相同,因此理论上,你无法在 CentOS 7.x 上面编译出 binary program 后,还将他拿到 SuSE 上面执行,这个动作通常是不可能成功的! 因为调用的目标函数库 位置可能不同 (参考图 21.1.2) , 核心版本更不可能相同!所以能够执行的情况是微乎其微!所以同 一套软件要在不同的平台上面执行时, 必须要重复编译!所以才需要源码嘛!了解乎!详细的 make 用法与 Makefile 规则,在后续的小节里面再探讨啰!

什么是 Tarball 的软件

回到顶部

从前面几个小节的说明来看,我们知道所谓的源代码,其实就是一些写满了程序代码的纯文本文件。 那我们在第八章压缩指令的介绍当中, 也了解了纯文本文件在网络上其实是很浪费带宽的一种文件 格式! 所以啦,如果能够将这些源码透过文件的打包与压缩技术来将文件的数量与容量减小, 不 但让用户容易下载,软件开发商的网站带宽也能够节省很多很多啊!这就是 Tarball 文件的由来啰!
想一想,一个核心的源码文件大约要 300~500 MB 以上,如果每个人都去下载这样
Tips 的一个核心文件, 呵呵!那么网络带宽不被吃的死翘翘才怪呢!
所谓的 Tarball 文件,其实就是将软件的所有源码文件先以 tar 打包,然后再以压缩技术来压缩, 通常最常见的就是以 gzip 来压缩了。因为利用了 tar 与 gzip 的功能,所以 tarball 文件一般的扩展 名就会写成 *.tar.gz 或者是简写为 *.tgz 啰!不过,近来由于 bzip2 与 xz 的压缩率较佳,所以 Tarball 渐渐的以 bzip2 及 xz 的压缩技术来取代 gzip 啰!因此档名也会变成 *.tar.bz2, *.tar.xz 之类的哩。
所以说, Tarball 是一个软件包, 妳将他解压缩之后,里面的文件通常就会有:
源代码文件;
侦测程序文件 (可能是 configure 或 config 等檔名);
本软件的简易说明与安装说明 (INSTALL 或 README)。
其中最重要的是那个 INSTALL 或者是 README 这两个文件,通常你只要能够参考这两个文件, Tarball 软件的安装是很简单的啦!我们在后面的章节会再继续介绍 Tarball 这个玩意儿。

使用gcc编译c语言程序

回到顶部

经过上面的介绍之后,你应该比较清楚的知道源码、编译程序、函数库与执行文件之间的相关性了。 不过,详细的流程可能还是不很清楚,所以,在这里我们以一个简单的程序范例来说明整个编译的过 程喔!赶紧进入 linux 系统,实地的操作一下底下的范例呢!

单一程序:印出 Hello World

回到顶部

我们以 linux 上面最常见的 C 语言来撰写第一支程序!第一支程序最常作的就是..... 在屏幕上面印 出Hello World!的字样~当然, 这里我们是以简单的 C 语言来撰写,如果你对于 C 有兴趣的 话,那么请自行购买相关的书籍喔! ^_^ 好了,不啰唆,立刻编辑第一支程序吧!请先确认你的 linux 系统里面已经安装了 gcc 了喔!如果尚未安装 gcc 的话,请先 Tips 参考下一节的 RPM 安装法,先安装好 gcc 之后,再回来阅读本章。 如果你已经有网络了,那么直接使用 yum groupinstall "Development Tools" 预安装好所需的所有软件即可。 rpm 与 yum 均会在下一章介绍。
编辑程序代码,亦即源码

              [root@study ~]# vim hello.c
              #用 C 语言写的程序扩展名建议用 .c
              #include <stdio.h>
              int main(void)
              {
              printf("Hello World\n");
              }
            
上面是用 C 语言的语法写成的一个程序文件。第一行的那个 # 并不是批注喔!如果你担心输 入错误, 请到底下的连结下载这个文件: http://linux.initroot.org/linux_basic/0520source/hello.c 开始编译与测试执行
              [root@study ~]# gcc hello.c
              [root@study ~]# ll hello.c a.out
              -rwxr-xr-x. 1 root root 8503 Sep 4 11:33 a.out
              -rw-r--r--. 1 root root 4 11:32 hello.c
              71 Sep
              #此时会产生这个档名
              [root@study ~]# ./a.out
              Hello World
            
#呵呵!成果出现了!
在预设的状态下,如果我们直接以 gcc 编译源码,并且没有加上任何参数,则执行文件的档名会被 自动设定为 a.out 这个文件名! 所以妳就能够直接执行 ./a.out 这个执行文件啦!上面的例子很简单吧!
那个 hello.c 就是源码,而 gcc 就是编译程序,至于 a.out 就是编译成功的可执行 binary program 啰! 咦!那如果我想要产生目标文件 (object file) 来进行其他的动作,而且执行文件的档名也不要用 预设的 a.out ,那该如何是好?其实妳可以将上面的第 2 个步骤改成这样:
              [root@study ~]# gcc -c hello.c
              [root@study ~]# ll hello*
              -rw-r--r--. 1 root root
              71 Sep 4 11:32 hello.c
              -rw-r--r--. 1 root root 1496 Sep 4 11:34 hello.o
              #就是被产生的目标文件[root@study ~]# gcc -o hello hello.o
              [root@study ~]# ll hello*
              -rwxr-xr-x. 1 root root 8503 Sep 4 11:35 hello
              -rw-r--r--. 1 root root 71 Sep 4 11:32 hello.c
              -rw-r--r--. 1 root root 1496 Sep 4 11:34 hello.o
              #这就是可执行文件! -o 的结果
              [root@study ~]# ./hello
              Hello World
            
这个步骤主要是利用 hello.o 这个目标文件制作出一个名为 hello 的执行文件,详细的 gcc 语法我 们会在后续章节中继续介绍!透过这个动作后,我们可以得到 hello 及 hello.o 两个文件, 真正可 以执行的是 hello 这个 binary program 喔! 或许你会觉得,咦!只要一个动作作出 a.out 就好了, 干嘛还要先制作目标文件再做成执行文件呢? 呵呵!透过下个范例,你就可以知道为什么啦!

主、子程序链接:子程序的编译

回到顶部

如果我们在一个主程序里面又调用了另一个子程序呢?这是很常见的一个程序写法, 因为可以简化 整个程序的易读性!在底下的例子当中,我们以 thanks.c 这个主程序去调用 thanks_2.c 这个子程序, 写法很简单:
撰写所需要的主、子程序
# 1. 编辑主程序:

              [root@study ~]# vim thanks.c
              #include <stdio.h>
              int main(void)
              {
              printf("Hello World\n");
              thanks_2();
              }
            
# 上面的 thanks_2(); 那一行就是调用子程序啦!
              [root@study ~]# vim thanks_2.c
              #include <stdio.h>
              void thanks_2(void)
              {
              printf("Thank you!\n");
              }
            
上面这两个文件你可以到底下下载:
http://linux.initroot.org/linux_basic/0520source/thanks.c
http://linux.initroot.org/linux_basic/0520source/thanks_2.c
进行程序的编译与链接 (Link)
# 2. 开始将源码编译成为可执行的 binary file :
              [root@study ~]# gcc -c thanks.c thanks_2.c
              [root@study ~]# ll thanks*
              -rw-r--r--. 1 root root
              75 Sep 4 11:43 thanks_2.c
              -rw-r--r--. 1 root root 1496 Sep 4 11:43 thanks_2.o
              -rw-r--r--. 1 root root 91 Sep 4 11:42 thanks.c
              -rw-r--r--. 1 root root 1560 Sep 4 11:43 thanks.o
              #编译产生的!
              #编译产生的!
              [root@study ~]# gcc -o thanks thanks.o thanks_2.o
              [root@study ~]# ll thanks*
              -rwxr-xr-x. 1 root root 8572 Sep
              4 11:44 thanks
            
#最终结果会产生这玩意儿
# 3. 执行一下这个文件:
              [root@study ~]# ./thanks
              Hello World
              Thank you!
            
知道为什么要制作出目标文件了吗?由于我们的源码文件有时并非仅只有一个文件,所以我们无法 直接进行编译。 这个时候就需要先产生目标文件,然后再以连结制作成为 binary 可执行文件。另外, 如果有一天,你更新了 thanks_2.c 这个文件的内容,则你只要重新编译 thanks_2.c 来产生新的 thanks_2.o ,然后再以连结制作出新的 binary 可执行文件即可!而不必重新编译其他没有更动过的 源码文件。 这对于软件开发者来说,是一个很重要的功能,因为有时候要将偌大的源码全部编 译完成,会花很长的一段时间呢!
此外,如果你想要让程序在执行的时候具有比较好的效能,或者是其他的除错功能时, 可以在编译 的过程里面加入适当的参数,例如底下的例子:
              [root@study ~]# gcc -O -c thanks.c thanks_2.c
              # -O 为产生优化的参数
              [root@study ~]# gcc -Wall -c thanks.c thanks_2.c
              thanks.c: In function ‘main’:
              thanks.c:5:9: warning: implicit declaration of function ‘thanks_2’
              [-Wimplicit-function-declaration]
              thanks_2();
              ^
              thanks.c:6:1: warning: control reaches end of non-void function [-Wreturn-type]
              }
              ^
            
# -Wall 为产生更详细的编译过程信息。上面的讯息为警告讯息 (warning) 所以不用理会也没有关系!
至于更多的 gcc 额外参数功能,就得要 man gcc 啰~呵呵!可多的跟天书一样~ 21.2.3 调用外部函数库:加入连结的函数库 刚刚我们都仅只是在屏幕上面印出一些字眼而已,如果说要计算数学公式呢?例如我们想要计算出三 角函数里面的 sin (90 度角)。要注意的是,大多数的程序语言都是使用径度而不是一般我们在计算的 角度, 180 度角约等于 3.14 径度!嗯!那我们就来写一下这个程序吧!
              [root@study ~]# vim sin.c
              #include <stdio.h>
              #include <math.h>
              int main(void)
              {
              float value;
              value = sin ( 3.14 / 2 );
              printf("%f\n",value);
              }
            
上面这个文件的内容可以在底下取得!
http://linux.initroot.org/linux_basic/0520source/sin.c
那要如何编译这支程序呢?我们先直接编译看看:
              [root@study ~]# gcc sin.c
            
# 新的 GCC 会主动将函数抓进来给你用,所以只要加上 include <math.h> 就好了!
新版的 GCC 会主动帮你将所需要的函数库抓进来编译,所以不会出现怪异的错误讯息! 事实上, 数学函数库使用的是 libm.so 这个函数库,你最好在编译的时候将这个函数库纳进去比较好~另外要 注意, 这个函数库放置的地方是系统默认会去找的 /lib, /lib64 ,所以你无须使用底下的 -L 去加入 搜寻的目录! 而 libm.so 在编译的写法上,使用的是 -lm (lib 简写为 l 喔!) 喔!因此就变成: 编译时加入额外函数库连结的方式:
              [root@study ~]# gcc sin.c -lm -L/lib -L/lib64
              [root@study ~]# ./a.out
            
#重点在 -lm
#尝试执行新文件!
1.000000
特别注意,使用 gcc 编译时所加入的那个 -lm 是有意义的,他可以拆开成两部份来看:
-l :是加入某个函数库(library)的意思,
m :则是 libm.so 这个函数库,其中, lib 与扩展名(.a 或 .so)不需要写
所以 -lm 表示使用 libm.so (或 libm.a) 这个函数库的意思~至于那个 -L 后面接的路径呢?这表示:
我要的函数库 libm.so 请到 /lib 或 /lib64 里面搜寻!上面的说明很清楚了吧!不过,要注意的是,由于 linux 预设是将函数库放置在 /lib 与 /lib64 当中, 所以你没有写 -L/lib 与 -L/lib64 也没有关系的!不过,万一哪天你使用的函数库并非放置在这两个 目录下,那么 -L/path 就很重要了!否则会找不到函数库喔!
除了连结的函数库之外,你或许已经发现一个奇怪的地方,那就是在我们的 sin.c 当中第一行 #include <stdio.h>,这行说的是要将一些定义数据由 stdio.h 这个文件读入,这包括 printf 的 相关设定。这个文件其实是放置在 /usr/include/stdio.h 的!那么万一这个文件并非放置在这里呢?那 么我们就可以使用底下的方式来定义出要读取的 include 文件放置的目录:
[root@study ~]# gcc sin.c -lm -I/usr/include
-I/path 后面接的路径( Path )就是设定要去搜寻相关的 include 文件的目录啦!不过,同样的,默认值 是放置在 /usr/include 底下,除非你的 include 文件放置在其他路径,否则也可以略过这个项目! 透过上面的几个小范例,你应该对于 gcc 以及源码有一定程度的认识了,再接下来,我们来稍微 整理一下 gcc 的简易使用方法吧!

gcc 的简易用法 (编译、参数与链结)

回到顶部

前面说过, gcc 为 linux 上面最标准的编译程序,这个 gcc 是由 GNU 计划所维护的,有兴趣的朋 友请自行前往参考。既然 gcc 对于 linux 上的 Open source 是这么样的重要,所以底下我们就列举 几个 gcc 常见的参数,如此一来大家应该更容易了解源码的各项功能吧!
# 仅将源码编译成为目标文件,并不制作链接等功能:

              [root@study ~]# gcc -c hello.c
            
# 会自动的产生 hello.o 这个文件,但是并不会产生 binary 执行文件。
# 在编译的时候,依据作业环境给予优化执行速度
              [root@study ~]# gcc -O hello.c -c
            
# 会自动的产生 hello.o 这个文件,并且进行优化喔!
# 在进行 binary file 制作时,将连结的函数库与相关的路径填入
              [root@study ~]# gcc sin.c -lm -L/lib -I/usr/include
            
# 这个指令较常执行在最终连结成 binary file 的时候,
# -lm 指的是 libm.so 或 libm.a 这个函数库文件;
# -L 后面接的路径是刚刚上面那个函数库的搜寻目录;
# -I 后面接的是源码内的 include 文件之所在目录。
# 将编译的结果输出成某个特定档名
              [root@study ~]# gcc -o hello hello.c
            
# -o 后面接的是要输出的 binary file 檔名
# 在编译的时候,输出较多的讯息说明[root@study ~]# gcc -o hello hello.c -Wall
# 加入 -Wall 之后,程序的编译会变的较为严谨一点,所以警告讯息也会显示出来!
比较重要的大概就是这一些。另外,我们通常称 -Wall 或者 -O 这些非必要的参数为旗标 (FLAGS),
因为我们使用的是 C 程序语言,所以有时候也会简称这些旗标为 CFLAGS ,这些变量偶尔会被使 用的喔!尤其是在后头会介绍的 make 相关的用法时,更是重要的很吶! ^_^

用 make 进行宏编译

回到顶部

在本章一开始我们提到过 make 的功能是可以简化编译过程里面所执行的指令,同时还具有很多很 方便的功能!那么底下咱们就来试看看使用 make 简化执行编译指令的流程吧!
21.3.1 为什么要用 make 先来想象一个案例,假设我的执行文件里面包含了四个源码文件,分别是 main.c haha.c sin_value.c
cos_value.c 这四个文件,这四个文件的目的是:
main.c :主要的目的是让用户输入角度数据与调用其他三支子程序;
haha.c :输出一堆有的没有的讯息而已;
sin_value.c :计算用户输入的角度(360) sin 数值;
cos_value.c :计算用户输入的角度(360) cos 数值。
这四个文件你可以到 http://linux.initroot.org/linux_basic/0520source/main.tgz 来下载。由于这四个文件里 面包含了相关性,并且还用到数学函式在里面,所以如果你想要让这个程序可以跑, 那么就需要这 样编译:
# 1. 先进行目标文件的编译,最终会有四个 *.o 的档名出现:

              [root@study ~]# gcc -c main.c
              [root@study ~]# gcc -c haha.c
              [root@study ~]# gcc -c sin_value.c
              [root@study ~]# gcc -c cos_value.c
            
# 2. 再进行连结成为执行文件,并加入 libm 的数学函式,以产生 main 执行文件:
              [root@study ~]# gcc -o main main.o haha.o sin_value.o cos_value.o -lm
            
# 3. 本程序的执行结果,必须输入姓名、360 度角的角度值来计算:
              [root@study ~]# ./main
              Please input your name: INITroot
              #这里先输入名字
              Please enter the degree angle (ex> 90): 30
              Hi, Dear INITroot, nice to meet you.
              The Sin is: 0.50
              The Cos is: 0.87
            
#输入以 360 度角为主的角度
#这三行为输出的结果喔!编译的过程需要进行好多动作啊!而且如果要重新编译,则上述的流程得要重新来一遍,光是找出这 些指令就够烦人的了! 如果可以的话,能不能一个步骤就给他完成上面所有的动作呢?那就利用 make 这个工具吧! 先试看看在这个目录下建立一个名为 makefile 的文件,内容如下:
# 1. 先编辑 makefile 这个规则文件,内容只要作出 main 这个执行文件
              [root@study ~]# vim makefile
              main: main.o haha.o sin_value.o cos_value.o
              gcc -o main main.o haha.o sin_value.o cos_value.o -lm
            
# 注意:第二行的 gcc 之前是 <tab> 按键产生的空格喔!
# 2. 尝试使用 makefile 制订的规则进行编译的行为:
              [root@study ~]# rm -f main *.o
              #先将之前的目标文件去除
              [root@study ~]# make
              cc -c -o main.o main.c
              cc -c -o haha.o haha.c
              cc -c -o sin_value.o sin_value.c
              cc -c -o cos_value.o cos_value.c
              gcc -o main main.o haha.o sin_value.o cos_value.o -lm
            
# 此时 make 会去读取 makefile 的内容,并根据内容直接去给他编译相关的文件啰!
# 3. 在不删除任何文件的情况下,重新执行一次编译的动作:
              [root@study ~]# make
              make: `main' is up to date.
            
# 看到了吧!是否很方便呢!只会进行更新 (update) 的动作而已。
或许你会说:如果我建立一个 shell script 来将上面的所有动作都集结在一起,不是具有同样的效 果吗?呵呵! 效果当然不一样,以上面的测试为例,我们仅写出 main 需要的目标文件,结果 make 会主动的去判断每个目标文件相关的源码文件,并直接予以编译,最后再直接进行连结的动作!
真 的是很方便啊!此外,如果我们更动过某些源码文件,则 make 也可以主动的判断哪一个源码 与相关的目标文件文件有更新过, 并仅更新该文件,如此一来,将可大大的节省很多编译的时间呢!
要知道,某些程序在进行编译的行为时,会消耗很多的 CPU 资源呢!所以说, make 有这些好处: 简化编译时所需要执行的指令;
若在编译完成之后,修改了某个源码文件,则 make 仅会针对被修改了的文件进行编译,其他的 object file 不会被更动;
最后可以依照相依性来更新 (update) 执行文件。
既然 make 有这么多的优点,那么我们当然就得好好的了解一下 make 这个令人关心的家伙啦!
而 make 里面最需要注意的大概就是那个规则文件,也就是 makefile 这个文件的语法啦!所以底下我 们就针对 makefile 的语法来加以介绍啰。 21.3.2 makefile 的基本语法与变量 make 的语法可是相当的多而复杂的,有兴趣的话可以到 GNU (注 1) 去查阅相关的说明,这里 仅列出一些基本的规则,重点在于让读者们未来在接触源码时,不会太紧张啊! 好了,基本的 makefile 规则是这样的:
目标(target): 目标文件 1 目标文件 2
<tab> gcc -o 欲建立的执行文件 目标文件 1 目标文件 2
那个目标 (target) 就是我们想要建立的信息,而目标文件就是具有相关性的 object files ,那建立执 行文件的语法就是以 <tab> 按键开头的那一行!特别给他留意喔,命令行必须要以 tab 按键作为 开头才行!他的规则基本上是这样的:
在 makefile 当中的 # 代表批注;
<tab> 需要在命令行 (例如 gcc 这个编译程序指令) 的第一个字符;
目标 (target) 与相依文件(就是目标文件)之间需以:隔开。
同样的,我们以刚刚上一个小节的范例进一步说明,如果我想要有两个以上的执行动作时, 例如下 达一个指令就直接清除掉所有的目标文件与执行文件,该如何制作呢?
# 1. 先编辑 makefile 来建立新的规则,此规则的目标名称为 clean :
              [root@study ~]# vi makefile
              main: main.o haha.o sin_value.o cos_value.o
              gcc -o main main.o haha.o sin_value.o cos_value.o -lm
              clean:
              rm -f main main.o haha.o sin_value.o cos_value.o
            
# 2. 以新的目标 (clean) 测试看看执行 make 的结果:
              [root@study ~]# make clean
            
#就是这里!透过 make 以 clean 为目标
rm -rf main main.o haha.o sin_value.o cos_value.o
如此一来,我们的 makefile 里面就具有至少两个目标,分别是 main 与 clean ,如果我们想要建立 main 的话,输入make main,如果想要清除有的没的,输入make clean即可啊!而如果想要 先清除目标文件再编译 main 这个程序的话,就可以这样输入:make clean main,如下所示:
              [root@study ~]# make clean main
              rm -rf main main.o haha.o sin_value.o cos_value.o
              cc -c -o main.o main.c
              cc -c -o haha.o haha.c
              cc -c -o sin_value.o sin_value.c
              cc -c -o cos_value.o cos_value.c
              gcc -o main main.o haha.o sin_value.o cos_value.o -lm
            
这样就很清楚了吧!但是,你是否会觉得,咦! makefile 里面怎么重复的数据这么多啊!没错!所 以我们可以再藉由 shell script 那时学到的变数来更简化 makefile 喔:
              [root@study ~]# vi makefile
              LIBS = -lm
              OBJS = main.o haha.o sin_value.o cos_value.o
              main: ${OBJS}
              gcc -o main ${OBJS} ${LIBS}
              clean:
              rm -f main ${OBJS}
            
与 bash shell script 的语法有点不太相同,变量的基本语法为:
1. 变量与变量内容以=隔开,同时两边可以具有空格;
2. 变量左边不可以有 <tab> ,例如上面范例的第一行 LIBS 左边不可以是 <tab>;
3. 变量与变量内容在=两边不能具有:
; 4. 在习惯上,变数最好是以大写字母为主;
5. 运用变量时,以 ${变量} 或 $(变量) 使用;
6. 在该 shell 的环境变量是可以被套用的,例如提到的 CFLAGS 这个变数!
7. 在指令列模式也可以给予变量。
由于 gcc 在进行编译的行为时,会主动的去读取 CFLAGS 这个环境变量,所以,你可以直接在 shell 定义出这个环境变量,也可以在 makefile 文件里面去定义,更可以在指令列当中给予这个咚咚呢!
例如:
              [root@study ~]# CFLAGS="-Wall" make clean main
            
# 这个动作在上 make 进行编译时,会去取用 CFLAGS 的变量内容! 也可以这样:
              [root@study ~]# vi makefile
              LIBS = -lm
              OBJS = main.o haha.o sin_value.o cos_value.o
              CFLAGS = -Wall
              main: ${OBJS}
              gcc -o main ${OBJS} ${LIBS}
              clean:
              rm -f main ${OBJS}
            
咦!我可以利用指令列进行环境变量的输入,也可以在文件内直接指定环境变量,那万一这个 CFLAGS 的内容在指令列与 makefile 里面并不相同时,以那个方式输入的为主?呵呵!
问了个好问 题啊! 环境变量取用的规则是这样的:
1. make 指令列后面加上的环境变量为优先;
2. makefile 里面指定的环境变量第二;
3. shell 原本具有的环境变量第三。
此外,还有一些特殊的变量需要了解的喔:
$@:代表目前的目标(target)
所以我也可以将 makefile 改成:
                  [root@study ~]# vi makefile
                  LIBS = -lm
                  OBJS = main.o haha.o sin_value.o cos_value.o
                  CFLAGS = -Wall
                  main: ${OBJS}
                  gcc -o $@ ${OBJS} ${LIBS}
                  #那个 $@ 就是 main !
                  clean:
                  rm -f main ${OBJS}
                
这样是否稍微了解了 makefile (也可能是 Makefile) 的基本语法?这对于你未来自行修改源码的编 译规则时,是很有帮助的喔!^_^!

利用 patch 更新源码

回到顶部

我们在本章一开始介绍了为何需要进行软件的升级,这是很重要的喔!那假如我是以 Tarball 来进行 某个软件的安装,那么是否当我要升级这个软件时,就得要下载这个软件的完整全新的 Tarball 呢? 举个例子来说,的讨论区 http://phorum.initroot.org 这个网址,这个讨论区是以 phpBB 这个软件来 架设的,而的讨论区版本为 3.1.4 ,目前 (2015/09) 最新发布的版本则是 phpbb 3.1.5 。那我是 否需要下载全新的 phpbb3.1.5.tar.gz 这个文件来更新原本的旧程序呢?
事实上,当我们发现一些软件的漏洞,通常是某一段程序代码写的不好所致。因此, 所谓的更新 源码常常是只有更改部分文件的小部分内容而已。既然如此的话, 那么我们是否可以就那些被 更动的文件来进行修改就可以咯?也就是说, 旧版本到新版本间没有更动过的文件就不要理他,仅 将有修订过的文件部分来处理即可。这有什么好处呢?首先,没有更动过的文件的目标文件 (object file) 根本就不需要重新编译,而且有 更动过的文件又可以利用 make 来自动 update (更新),如此一来,我们原先的设定 (makefile 文件里 面的规则) 将不需要重新改写或侦测!可以节省很多宝贵的时间呢 (例如后续章节会提到的核心的编 译!)
从上面的说明当中,我们可以发现,如果可以将旧版的源码数据改写成新版的版本, 那么就能直 接编译了,而不需要将全部的新版 Tarball 重新下载一次呢!可以节省带宽与时间说!那么如何改写 源码? 难道要我们一个文件一个文件去参考然后修订吗?当然没有这么没人性!
我们在第十一章、正规表示法的时候有提到一个比对文件的指令,那就是 diff,这个指令可以将两 个文件之间的差异性列出来呢!那我们也知道新旧版本的文件之间, 其实只有修改一些程序代码 而已,那么我们可以透过 diff 比对出新旧版本之间的文字差异,然后再以相关的指令来将旧版的文 件更新吗? 呵呵!当然可以啦!那就是 patch 这个指令啦!很多的软件开发商在更新了源码之后, 几乎都会发布所谓的 patch file,也就是直接将源码 update 而已的一个方式喔!我们底下以一个简 单的范例来说明给你了解喔!
关于 diff 与 patch 的基本用法我们在第十一章都谈过了,所以这里不再就这两个指令的语法进行介 绍, 请回去参阅该章的内容。这里我们来举个案例解释一下好了。假设我们刚刚计算三角函数的程 序 (main) 历经多次改版, 0.1 版仅会简单的输出, 0.2 版的输出就会含有角度值,因此这两个版 本的内容不相同。如下所示,两个文件的意义为:
http://linux.initroot.org/linux_basic/0520source/main-0.1.tgz :main 的 0.1 版;
http://linux.initroot.org/linux_basic/0520source/main_0.1_to_0.2.patch :main 由 0.1 升级到 0.2 的 patch file;
请您先下载这两个文件,并且解压缩到你的 /root 底下。你会发现系统产生一个名为 main-0.1 的目 录。 该目录内含有五个文件,就是刚刚的程序加上一个 Makefile 的规则文件。你可以到该目录下 去看看 Makefile 的内容, 在这一版当中含有 main 与 clean 两个目标功能而已。至于 0.2 版则加 入了 install 与 uninstall 的规则设定。 接下来,请看一下我们的作法啰:
测试旧版程序的功能

              [root@study ~]# tar -zxvf main-0.1.tgz
              [root@study ~]# cd main-0.1
              [root@study main-0.1]# make clean main
              [root@study main-0.1]# ./main
              version 0.1
              Please input your name: INITroot
              Please enter the degree angle (ex> 90): 45
              Hi, Dear INITroot, nice to meet you.
              The Sin is: 0.71
              The Cos is: 0.71
            
与之前的结果非常类似,只是将 Makefile 直接给您了!但如果你执行 make install 时,系统会 告知没有 install 的 target 啊!而且版本是 0.1 也告知了。那么如何更新到 0.2 版呢?透过这个 patch 文件吧!这个文件的内容有点像这样:
查阅 patch file 内容
              [root@study main-0.1]# vim ~/main_0.1_to_0.2.patch
              diff -Naur main-0.1/cos_value.c main-0.2/cos_value.c
              --- main-0.1/cos_value.c 2015-09-04 14:46:59.200444001 +0800
              +++ main-0.2/cos_value.c 2015-09-04 14:47:10.215444000 +0800
              @@ -7,5 +7,5 @@
              {
              float value;
              ....(底下省略)....
            
上面表格内有个底线的部分,那代表使用 diff 去比较时,被比较的两个文件所在路径,这个路径非 常的重要喔! 因为 patch 的基本语法如下:
patch -p 数字 < patch_file 特别留意那个 -p 数字,那是与 patch_file 里面列出的文件名有关的信息。假如在 patch_file 第 一行写的是这样:
*** /home/guest/example/expatch.old
那么当我执行 patch -p0 < patch_file 时,则更新的文件是 /home/guest/example/expatch.old , 如果 patch -p1 < patch_file,则更新的文件为home/guest/example/expatch.old,如果patch -p4 < patch_file则更新expatch.old,也就是说, -pxx 那个 xx 代表拿掉几个斜线(/)的意思!这 样可以理解了吗? 好了,根据刚刚上头的资料,我们可以发现比较的文件是在 main-0.1/xxx 与 main-0.2/xxx , 所以说,如果你是在 main-0.1 底下,并且想要处理更新时,就得要拿掉一个目录 (因 为并没有 main-0.2 的目录存在, 我们是在当前的目录进行更新的!),因此使用的是 -p1 才对喔!
所以:
更新源码,并且重新编译程序!
              [root@study main-0.1]# patch -p1 < ../main_0.1_to_0.2.patch
              patching file cos_value.c
              patching file main.c
              patching file Makefile
              patching file sin_value.c
            
# 请注意,目前所在目录是在 main-0.1 底下喔!注意与 patch 文件的相对路径!
# 虽然有五个文件,但其实只有四个文件有修改过喔!上面显示有改过的文件!
              [root@study main-0.1]# make clean main
              [root@study main-0.1]# ./main
              version 0.2
              Please input your name: INITroot
              Please enter the degree angle (ex> 90): 45Hi, Dear INITroot, nice to meet you.
              The sin(45.000000) is: 0.71
              The cos(45.000000) is: 0.71
            
# 你可以发现,输出的结果中版本变了,输出信息多了括号 () 喔!
              [root@study main-0.1]# make install

              #将他安装到 /usr/local/bin 给大家用
cp -a main /usr/local/bin [root@study main-0.1]# main #直接输入指令可执行! [root@study main-0.1]# make uninstall #移除此软件! rm -f /usr/local/bin/main
很有趣的练习吧!所以你只要下载 patch file 就能够对你的软件源码更新了!只不过更新了源码 并非软件就更新!你还是得要将该软件进行编译后,才会是最终正确的软件喔! 因为 patch 的功能 主要仅只是更新源码文件而已!切记切记!此外,如果你 patch 错误呢?没关系的!我们的 patch 是可以还原的啊!透过 patch -R < ../main_0.1_to_0.2.patch 就可以还原啦!很有趣吧!
例题:
如果我有一个很旧版的软件,这个软件已经更新到很新的版本,例如核心,那么我可以使用 patch file 来更 新吗?
答: 这个问题挺有趣的,首先,你必须要确定旧版本与新版本之间确实有发布 patch file 才行,以 kernel 2.2.xx 及 2.4.xx 来说,这两者基本上的架构已经不同了,所以两者间是无法以 patch file 来更新的。不过, 2.4.xx 与 2.4.yy 就可以更新了。不过,因为 kernel 每次推出的 patch 文件都仅针对前一个版本而已,所以假设要 由 kernel 2.4.20 升级到 2.4.26 ,就必须要使用 patch 2.4.21, 2.4.22, 2.4.23, 2.4.24, 2.4.25, 2.4.26 六个文件来 依序更新才行喔!当然,如果有朋友帮你比对过 2.4.20 与 2.4.26 ,那你自然就可以使用该 patch file 来 直接一次更新啰!

函数库管理

回到顶部

在我们的 linux 操作系统当中,函数库是很重要的一个项目。 因为很多的软件之间都会互相取用彼 此提供的函数库来进行特殊功能的运作, 例如很多需要验证身份的程序都习惯利用 PAM 这个模块 提供的验证机制来实作,而很多网络联机机制则习惯利用 SSL 函数库来进行联机加密的机制。
所以 说,函数库的利用是很重要的。不过, 函数库又依照是否被编译到程序内部而分为动态与静态函式 库,这两者之间有何差异?哪一种函数库比较好? 底下我们就来谈一谈先!

动态与静态函数库

回到顶部

首先我们要知道的是,函数库的类型有哪些?依据函数库被使用的类型而分为两大类,分别是静态 (Static) 与动态 (Dynamic) 函数库两类。底下我们来谈一谈这两种类行的函数库吧!
静态函数库的特色:
扩展名:(扩展名为 .a)
这类的函数库通常扩展名为 libxxx.a 的类型;
编译行为:
这类函数库在编译的时候会直接整合到执行程序当中,所以利用静态函数库编译成的文件会比较大一些喔;
独立执行的状态:
这类函数库最大的优点,就是编译成功的可执行文件可以独立执行,而不需要再向外部要求读取函数库的 内容 (请参照动态函数库的说明)。
升级难易度:
虽然执行文件可以独立执行,但因为函数库是直接整合到执行文件中, 因此若函数库升级时,整个执行文件必须 要重新编译才能将新版的函数库整合到程序当中。 也就是说,在升级方面,只要函数库升级了,所有将此 函数库纳入的程序都需要重新编译!
动态函数库的特色:
扩展名:(扩展名为 .so)
这类函数库通常扩展名为 libxxx.so 的类型;
编译行为:
动态函数库与静态函数库的编译行为差异挺大的。 与静态函数库被整个捉到程序中不同的,动态函数库在 编译的时候,在程序里面只有一个指向 (Pointer)的位置而已。也就是说,动态函数库的内容并没有被 整合到执行文件当中,而是当执行文件要使用到函数库的机制时, 程序才会去读取函数库来使用。由于执行文 件当中仅具有指向动态函数库所在的指标而已, 并不包含函数库的内容,所以他的文件会比较小一点。
独立执行的状态:
这类型的函数库所编译出来的程序不能被独立执行, 因为当我们使用到函数库的机制时,程序才会去读取 函数库,所以函数库文件必须要存在才行,而且,函数库的所在目录也不能改变,因为我们的可执 行文件里面仅有指标亦即当要取用该动态函数库时, 程序会主动去某个路径下读取,呵呵!所以动态 函数库可不能随意移动或删除,会影响很多相依的程序软件喔!
升级难易度:
虽然这类型的执行文件无法独立运作,然而由于是具有指向的功能, 所以,当函数库升级后,执行文件根本不 需要进行重新编译的行为,因为执行文件会直接指向新的函数库文件 (前提是函数库新旧版本的档名相同喔! )。
目前的 linux distribution 比较倾向于使用动态函数库,因为如同上面提到的最重要的一点, 就是函 式库的升级方便!由于 linux 系统里面的软件相依性太复杂了,如果使用太多的静态函数库,那么 升级某一个函数库时, 都会对整个系统造成很大的冲击!因为其他相依的执行文件也要同时重新编译 啊! 这个时候动态函数库可就有用多了,因为只要动态函数库升级就好,其他的软件根本无须变动。
那么这些函数库放置在哪里呢?绝大多数的函数库都放置在:/lib64, /lib 目录下! 此外,linux 系统 里面很多的函数库其实 kernel 就提供了,那么 kernel 的函数库放在哪里?呵呵!就是 在 /lib/modules 里面啦!里面的数据可多着呢!不过要注意的是, 不同版本的核心提供的函数库差异 性是挺大的,所以 kernel 2.4.xx 版本的系统不要想将核心换成 2.6.xx 喔! 很容易由于函数库的不 同而导致很多原本可以执行的软件无法顺利运作呢! 21.5.2 ldconfig 与 /etc/ld.so.conf 在了解了动态与静态函数库,也知道我们目前的 linux 大多是将函数库做成动态函数库之后,再来 要知道的就是,那有没有办法增加函数库的读取效能? 我们知道内存的访问速度是硬盘的好几倍, 所以,如果我们将常用到的动态函数库先加载内存当中 (快取, cache),如此一来,当软件要取用动态 函数库时,就不需要从头由硬盘里面读出啰! 这样不就可以增进动态函数库的读取速度?没错,是 这样的!这个时候就需要 ldconfig 与 /etc/ld.so.conf 的协助了。
如何将动态函数库加载高速缓存当中呢?
1. 首先,我们必须要在 /etc/ld.so.conf 里面写下 想要读入高速缓存当中的动态函数库所在的目录 ,注意 喔, 是目录而不是文件;
2. 接下来则是利用 ldconfig 这个执行文件将 /etc/ld.so.conf 的资料读入快取当中;
3. 同时也将数据记录一份在 /etc/ld.so.cache 这个文件当中吶!
图 21.5.1、使用 ldconfig 预加载动态函数库到内存中 事实上, ldconfig 还可以用来判断动态函数库的链接信息呢!赶紧利用 CentOS 来测试看看。
假设 妳想要将目前你系统下的 mariadb 函数库加入到快取当中时,可以这样做:

              [root@study ~]# ldconfig [-f conf] [ -C cache]
              [root@study ~]# ldconfig [-p]
            
选项与参数:
-f conf :那个 conf 指的是某个文件名,也就是说,使用 conf 作为 libarary 函数库的取得路径,而不以 /etc/ld.so.conf 为默认值
-C cache:那个 cache 指的是某个文件名,也就是说,使用 cache 作为快取暂存 的函数库资料,而不以 /etc/ld.so.cache 为默认值
-p :列出目前有的所有函数库资料内容 (在 /etc/ld.so.cache 内的资料!)
范例一:假设我的 Mariadb 数据库函数库在 /usr/lib64/mysql 当中,如何读进 cache ?
              [root@study ~]# vim /etc/ld.so.conf.d/initroot.conf
              /usr/lib64/mysql

              #这一行新增的啦!
              [root@study ~]# ldconfig
              #画面上不会显示任何的信息,不要太紧张!正常的!
              [root@study ~]# ldconfig -p924 libs found in cache `/etc/ld.so.cache'
              p11-kit-trust.so (libc6,x86-64) => /lib64/p11-kit-trust.so
              libzapojit-0.0.so.0 (libc6,x86-64) => /lib64/libzapojit-0.0.so.0
              ....(底下省略)....
              #
            
函数库名称 => 该函数库实际路径
透过上面的动作,我们可以将 Mariadb 的相关函数库给他读入快取当中,这样可以加快函数库读取 的效率呢! 在某些时候,你可能会自行加入某些 Tarball 安装的动态函数库,而你想要让这些动态 函数库的相关连结可以被读入到快取当中, 这个时候你可以将动态函数库所在的目录名称写入 /etc/ld.so.conf.d/yourfile.conf 当中,然后执行 ldconfig 就可以啦!

程序的动态函数库解析: ldd

回到顶部

说了这么多,那么我如何判断某个可执行的 binary 文件含有什么动态函数库呢?很简单,利用 ldd 就可以晓得了!例如我想要知道 /usr/bin/passwd 这个程序含有的动态函数库有哪些,可以这样做:

                  [root@study ~]# ldd [-vdr] [filename]
                
选项与参数:
-v :列出所有内容信息;
-d :重新将资料有遗失的 link 点秀出来!
-r :将 ELF 有关的错误内容秀出来!
找出 /usr/bin/passwd 这个文件的函数库数据
                  [root@study ~]# ldd /usr/bin/passwd
                  ....(前面省略)....
                  libpam.so.0 => /lib64/libpam.so.0 (0x00007f5e683dd000)
                  #PAM 模块
                  libpam_misc.so.0 => /lib64/libpam_misc.so.0 (0x00007f5e681d8000)
                  libaudit.so.1 => /lib64/libaudit.so.1 (0x00007f5e67fb1000) #SElinux
                  libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f5e67d8c000) #SElinux
                  ....(底下省略)....
                
# 我们前言的部分不是一直提到 passwd 有使用到 pam 的模块吗!怎么知道?
# 利用 ldd 察看一下这个文件,看到 libpam.so 了吧?这就是 pam 提供的函数库
找出 /lib64/libc.so.6 这个函式的相关其他函数库!
                  [root@study ~]# ldd -v /lib64/libc.so.6
                  /lib64/ld-linux-x86-64.so.2 (0x00007f7acc68f000)
                  linux-vdso.so.1 =>
                  (0x00007fffa975b000)
                  Version information:
                  #使用 -v 选项,增加显示其他版本信息!
                  /lib64/libc.so.6:
                  ld-linux-x86-64.so.2 (GLIBC_2.3) => /lib64/ld-linux-x86-64.so.2
                  ld-linux-x86-64.so.2 (GLIBC_PRIVATE) => /lib64/ld-linux-x86-64.so.2
                
未来如果你常常升级安装 RPM 的软件时 (下一章节会介绍),应该常常会发现那个 相依属性的 问题吧!没错!我们可以先以 ldd 来视察相依函数库之间的相关性!以先取得了解! 例如上面 的例子中,我们检查了 libc.so.6 这个在 /lib64 当中的函数库,结果发现他其实还跟 ld-linux-x86-64.so.2 有关!所以我们就需要来了解一下,那个文件到底是什么软件的函数库呀?使用 -v 这个参数还可以得知该函数库来自于哪一个软件!像上面的数据中,就可以得到该 libc.so.6 其实 可以支持 GLIBC_2.3 等的版本!

检验软件正确性

回到顶部

前面提到很多升级与安装需要注意的事项,因为我们需要克服很多的程序漏洞,所以需要前往 linux distribution 或者是某些软件开发商的网站,下载最新并且较安全的软件文件来安装才行。 好了,那 么有没有可能我们下载的文件本身就有问题? 是可能的!因为 cracker 无所不在,很多的软件 开发商已经公布过他们的网页所放置的文件曾经被窜改过! 那怎么办?连下载原版的数据都可能有 问题了?难道没有办法判断文件的正确性吗?
这个时候我们就要透过每个文件独特的指纹验证数据了!因为每个文件的内容与文件大小都不相同, 所以如果一个文件被修改之后,必然会有部分的信息不一样!利用这个特性,我们可以使用 MD5/sha1 或更严密的 sha256 等指纹验证机制来判断该文件有没有被更动过!举个例子来说,在每个 CentOS 7.x 原版光盘的下载点都会有提供几个特别的文件, 你可以先到底下的连结看看:
http://ftp.ksu.edu.tw/FTP/CentOS/7/isos/x86_64/ 仔细看喔,上述的 URL 里面除了有所有光盘的下载点之外,还有提供刚刚说到的 md5, sha1, sha256 等指纹验证机制喔!透过这个编码的比对, 我们就可以晓得下载的文件是否有问题。那么万一 CentOS 提供的光盘映象文件被下载之后,让有心人士偷偷修改过,再转到 Internet 上面流传,那么 你下载的这个文件偏偏不是原厂提供的,呵呵! 你能保证该文件的内容完全没有问题吗?当然不能 对不对!是的,这个时候就有 md5sum, sha1sum, sha256sum 这几文件指纹的咚咚出现啦!说说他的 用法吧!
21.6.1 md5sum / sha1sum / sha256sum
目前有多种机制可以计算文件的指纹码,我们选择使用较为广泛的 MD5, SHA1 或 SHA256 加密机 制来处理, 例如上面连结中 CentOS 7.x 的相关指纹确认。不过 ISO 文件实在太大了,下载来确认 实在很浪费带宽。 所以我们拿前一个小节谈到的 NTP 软件来检查看看好了。记得我们下载的 NTP 软件版本为 4.2.8p3 这一版, 在官网上面仅有提供 md5sum 的数据而已,在下载页面的 MD5 数据 为:
b98b0cbb72f6df04608e1dd5f313808b
ntp-4.2.8p3.tar.gz
如何确认我们下载的文件是正确没问题的呢?这样处理一下:

                  [root@study ~]# md5sum/sha1sum/sha256sum [-bct] filename
                  [root@study ~]# md5sum/sha1sum/sha256sum [--status|--warn] --check filename
                
选项与参数:
-b :使用 binary 的读档方式,默认为 Windows/DOS 文件型态的读取方式;
-c :检验文件指纹;
-t :以文字型态来读取文件指纹。
将刚刚的文件下载后,测试看看指纹码
                  [root@study ~]# md5sum ntp-4.2.8p3.tar.gz
                  b98b0cbb72f6df04608e1dd5f313808b
                  ntp-4.2.8p3.tar.gz
                
# 看!显示的编码是否与上面相同呢?赶紧测试看看!
一般而言,每个系统里面的文件内容大概都不相同,例如你的系统中的 /etc/passwd 这个登入信息文 件与我的一定不一样,因为我们的用户与密码、 Shell 及家目录等大概都不相同,所以由 md5sum 这 个文件指纹分析程序所自行计算出来的指纹表当然就不相同啰!
好了,那么如何应用这个东西呢?基本上,你必须要在你的 linux 系统上为你的这些重要的文件进 行指纹数据库的建立 (好像在做户口调查!),将底下这些文件建立数据库:
/etc/passwd
/etc/shadow (假如你不让用户改密码了)
/etc/group
/usr/bin/passwd
/sbin/rpcbind
/bin/login (这个也很容易被骇!)
/bin/ls
/bin/ps
/bin/top
这几个文件最容易被修改了!因为很多木马程序执行的时候,还是会有所谓的执行序, PID为了 怕被 root 追查出来,所以他们都会修改这些检查排程的文件,如果你可以替这些文件建立指纹数据 库 (就是使用 md5sum 检查一次,将该文件指纹记录下来,然后常常以 shell script 的方式由程序自 行来检查指纹表是否不同了!),那么对于文件系统会比较安全啦!
21.7 重点回顾
源码其实大多是纯文本档,需要透过编译程序的编译动作后,才能够制作出 linux 系统能够认识的可执 行的 binary file ;
开放源码可以加速软件的更新速度,让软件效能更快、漏洞修补更实时;
在 linux 系统当中,最标准的 C 语言编译程序为 gcc ;
在编译的过程当中,可以藉由其他软件提供的函数库来使用该软件的相关机制与功能; 为了简化编译过程当中的复杂的指令输入,可以藉由 make 与 makefile 规则定义,来简化程序的更新、编 译与连结等动作;
Tarball 为使用 tar 与 gzip/bzip2/xz 压缩功能所打包与压缩的,具有源码的文件; 一般而言,要使用 Tarball 管理 linux 系统上的软件,最好需要 gcc, make, autoconfig, kernel source, kernel header 等前驱软件才行,所以在安装 linux 之初,最好就能够选择 Software development 以及 kernel development 之类的群组;
函数库有动态函数库与静态函数库,动态函数库在升级上具有较佳的优势。动态函数库的扩展名为 *.so 而 静态则是 *.a ;
patch 的主要功能在更新源码,所以更新源码之后,还需要进行重新编译的动作才行;
可以利用 ldconfig 与 /etc/ld.so.conf /etc/ld.so.conf.d/*.conf 来制作动态函数库的链接与快取!
透过 MD5/SHA1/SHA256 的编码可以判断下载的文件是否为原本厂商所发布的文件。
21.8 本章习题 实作题部分: 请前往企鹅游戏网站 http://xpenguins.seul.org/ 下载 xpenguins-2.2.tar.gz 源码文件,并安装该软件。安装完 毕之后,请在 GNOME 图形接口执行 xpenguins , 看看有没有出现如同官网上面出现的小企鹅?(你有可 能需要安装 yum install libX*-devel 才行喔) 情境模拟题部分: 请依照底下的方式来建置你的系统的重要文件指纹码,并每日比对此重要工作。 1. 将 /etc/{passwd,shadow,group} 以及系统上面所有的 SUID/SGID 文件建立文件列表,该列表档名为 important.file ; [root@study ~]# ls /etc/{passwd,shadow,group} > important.file [root@study ~]# find /usr/sbin /usr/bin -perm /6000 >> important.file 2. 透过这个档名列表,以名为 md5.checkfile.sh 的档名去建立指纹码,并将该指纹码文件 finger1.file 设定成为不可修改的属性; [root@study ~]# vim md5.checkfile.sh #!/bin/bash for filename in $(cat important.file) do md5sum $filename >> finger1.file done [root@study ~]# sh md5.checkfile.sh [root@study ~]# chattr +i finger1.file3. 透过相同的机制去建立后续的分析数据为 finger_new.file ,并将两者进行比对,若有问题则提供 email 给 root 查阅: [root@study ~]# vim md5.checkfile.sh #!/bin/bash if [ "$1" == "new" ]; then for filename in $(cat important.file) do md5sum $filename >> finger1.file done echo "New file finger1.file is created." exit 0 fi if [ ! -f finger1.file ]; then echo "file: finger1.file NOT exist." exit 1 fi [ -f finger_new.file ] && rm finger_new.file for filename in $(cat important.file) do md5sum $filename >> finger_new.file done testing=$(diff finger1.file finger_new.file) if [ "$testing" != "" ]; then diff finger1.file finger_new.file | mail -s 'finger trouble..' root fi [root@study ~]# vim /etc/crontab 30 2 * * * root cd /root; sh md5.checkfile.sh 如此一来,每天系统会主动的去分析你认为重要的文件之指纹数据,然后再加以分析,看看有没有 被更动过。 不过,如果该变动是正常的,例如 CentOS 自动的升级时,那么你就得要删除 finger1.file , 再重新建置一个新的指纹数据库才行!否则你会每天收到有问题信件的回报喔!

本文由initroot编辑整理,转载请注明www.initroot.com

100次点赞 100次阅读