Linux 软件安装与管理

编译安装

编译程序是将源代码编译成机器语言生成可执行二进制程序的过程。

在 Linux 中,可以使用 configure 命令对系统环境进行检测和收集,生成 Makefile 文件,make 命令根据 Makefile 文件的配置来对源文件进行编译,生成可执行二进制程序。

简单来说,通过源代码安装程序的步骤如下:

  1. 从网上下载包含源代码的压缩包;
  2. 解压压缩包;
  3. 使用 gcc 进行源代码编译,生成目标文件(object files);
  4. 使用 gcc 进行函数库、主程序和辅助程序的链接,形成二进制文件;
  5. 将二进制文件及相关配置文件安装到指定目录。

编译安装需要用到的程序包括:gccmakeautoconfig,以及相关的库文件和头文件。通常可以使用 yum groupinstall "Development Tools" 命令来安装所需套件。

单一程序

编写一个简单的 C 语言源代码进行编译测试:

[root@101c7 ~]$ vi hello.c
#include <stdio.h>
int main (void)
{
        printf("hello\n");
}
"hello.c" [New] 5L, 58C written

使用 gcc 进行编译并运行:

[root@101c7 ~]$ gcc hello.c 
[root@101c7 ~]$ ll
total 20
-rw-------. 1 root root 1260 Sep  8 01:38 anaconda-ks.cfg
-rwxr-xr-x. 1 root root 8360 Sep 21 20:33 a.out
-rw-r--r--. 1 root root   59 Sep 21 20:32 hello.c
[root@101c7 ~]$ ./a.out 
hello

若没有指定输出文件名,则可执行文件名为 a.out。使用 -o 参数指定输出文件名为 hello.exe:

[root@101c7 ~]$ gcc -c hello.c
[root@101c7 ~]$ gcc -o hello.exe hello.o
[root@101c7 ~]$ ./hello.exe 
hello

主副程序链接

如果在主程序里又调用了另一个副程序,例如新建一个world.c,再到hello.c调用它:

[root@234c8 ~]$ vi world.c
#include <stdio.h>
void world (void)
{
        printf("world!\n");
}
"world.c" [New] 5L, 62C written
[root@234c8 ~]$ vi hello.c 
#include <stdio.h>
int main (void)
{
        printf("hello ");
        world ();
}
"hello.c" 6L, 69C written

同样执行编译,最终生成二进制文件helloworld

[root@234c8 ~]$ gcc -c hello.c world.c 
[root@234c8 ~]$ ll
total 20
-rw-------. 1 root root 1260 Sep  8 01:38 anaconda-ks.cfg
-rw-r--r--. 1 root root   69 Sep 21 20:43 hello.c
-rw-r--r--. 1 root root 1560 Sep 21 20:44 hello.o
-rw-r--r--. 1 root root   62 Sep 21 20:42 world.c
-rw-r--r--. 1 root root 1488 Sep 21 20:44 world.o
[root@234c8 ~]$ gcc -o helloworld hello.o world.o
[root@234c8 ~]$ ./helloworld 
hello world!

这个生成的二进制文件helloworld包含了两个源代码里的内容。如果修改了源文件world.c内容,只需要重新编译world.c文件,将新的world.ohello.o链接制作出修改过后的二进制可执行文件。

调用外部函数库

如果调用的是系统函数库,直接在源文件里include进来。也可以使用-I参数来指定include文件位置:

[root@234c8 ~]$ vi sin.c
#include <stdio.h>
#include <math.h>
int main (void)
{
        float value;
        value = sin (3/2);
        printf ("%f\n",value);
}
"sin.c" 8L, 115C written
[root@234c8 ~]$ gcc sin.c -I/usr/include     
[root@234c8 ~]$ ./a.out 
0.841471

如果要指定函数库位置可以使用-L参数。另外,-参数表示加入某函数库,m代表libm.so函数库:

[root@234c8 ~]$ gcc sin.c -lm -L/lib -L/lib64

创建 Makefile

Makefile文件作用为简化整个编译流程。可以手动创建一个Makefile来测试下:

[root@234c8 ~]$ vi makefile
main: hello.o world.o
        gcc -o main hello.o world.o
"makefile" [New] 2L, 51C written
[root@234c8 ~]$ make
cc    -c -o hello.o hello.c
cc    -c -o world.o world.c
gcc -o main hello.o world.o
[root@234c8 ~]$ ll
total 40
-rw-r--r--. 1 root root   69 Sep 21 20:43 hello.c
-rw-r--r--. 1 root root 1560 Sep 21 21:05 hello.o
-rwxr-xr-x. 1 root root 8472 Sep 21 21:05 main
-rw-r--r--. 1 root root   51 Sep 21 21:05 makefile
-rw-r--r--. 1 root root   62 Sep 21 20:42 world.c
-rw-r--r--. 1 root root 1488 Sep 21 21:05 world.o
[root@234c8 ~]$ make
make: `main' is up to date.
[root@234c8 ~]$ ./main 
hello world!

目标(target)与相关文件之间以分号:隔开,gcc行前必须使用 [Tab] 按键来缩进。

如果更新了源文件,只需要再次执行 make 命令就可以将生成的可执行文件更新。

当有两个以上执行动作时,例如编译完成后删除生成的 .o 文件,可以直接在后面接自定义阶段名:

[root@234c8 ~]$ vi makefile 
main: hello.o world.o
        gcc -o main hello.o world.o
clean:
        rm -f hello.o world.o
"makefile" 4L, 81C written
[root@234c8 ~]$ make clean
rm -f hello.o world.o

运行时,在 make 后面接阶段名即可执行那一阶段定义的命令。

默认不接参数时执行 main 中命令,想要先执行 main 段再执行 clean 段,可以把两个阶段名都写出来:

[root@234c8 ~]$ make main clean
cc    -c -o hello.o hello.c
cc    -c -o world.o world.c
gcc -o main hello.o world.o
rm -f hello.o world.o

Makefile 中可以使用变量来简化内容,例如将 hello.o world.o 定义为 OBJS

[root@234c8 ~]$ vi makefile 
OBJS = hello.o world.o
main: ${OBJS}
        gcc -o main ${OBJS}
clean:
        rm -f ${OBJS}

另外可以用 $@ 代表目前的 target(也就是 main)。

从源代码安装程序

通常建议将源代码放在 /usr/local/src 目录下,软件安装到 /usr/local 目录下。

一般从网上下载的源码包安装实际操作步骤如下:

  1. 取得原始文件,一般是 tar 压缩包;
  2. tar 解压缩,读取里面的 INSTALLREADME 等文档;
  3. 根据文档的要求安装好一些依赖软件;
  4. 创建 makefile,一般用 ./configure 脚本来检测系统与相关软件属性;
  5. 运行 make clean; makemakefile 作为配置来编译;
  6. 运行 make install 将目标文件安装到指定路径。

以上每个步骤都必须执行成功,才能进行下一步骤。

下面以安装 NTP 举例。先到官网 http://www.ntp.org/downloads.html 找到最新的安装包用 wget 下载:

[root@234c8 ~]$ cd /usr/local/src
[root@234c8 src]$ wget http://www.eecis.udel.edu/~ntp/ntp_spool/ntp4/ntp-4.2/ntp-4.2.8p15.tar.gz
100%[=================================================================>] 7,015,970   23.5KB/s   in 6m 2s  

2021-09-21 22:14:23 (18.9 KB/s) - ‘ntp-4.2.8p15.tar.gz’ saved [7015970/7015970]

验证下载的 tar 文件 MD5 值是否正确:

[root@234c8 src]$ wget http://www.eecis.udel.edu/~ntp/ntp_spool/ntp4/ntp-4.2/ntp-4.2.8p15.tar.gz.md5
100%[=================================================================>] 61          --.-K/s   in 0s      

2021-09-21 23:01:20 (9.29 MB/s) - ‘ntp-4.2.8p15.tar.gz.md5’ saved [61/61]
[root@234c8 src]$ md5sum ntp-4.2.8p15.tar.gz --check ntp-4.2.8p15.tar.gz.md5 
md5sum: ntp-4.2.8p15.tar.gz: no properly formatted MD5 checksum lines found
ntp-4.2.8p15.tar.gz: OK

将其解压并阅读 README 文件:

[root@234c8 src]$ tar -zxv -f ntp-4.2.8p15.tar.gz 
ntp-4.2.8p15/lib/isc/ia64/include/isc/
ntp-4.2.8p15/lib/isc/ia64/include/isc/atomic.h
ntp-4.2.8p15/lib/isc/alpha/include/
ntp-4.2.8p15/lib/isc/alpha/include/isc/
ntp-4.2.8p15/lib/isc/alpha/include/isc/atomic.h
[root@234c8 src]$ cd ntp-4.2.8p15 ; less README
Submit patches, bug reports, and enhancement requests via

                        http://bugs.ntp.org

README 文件解释了每个文件的作用。然后使用./configure创建 makefile,并加入--prefix参数指定安装位置:

[root@234c8 ntp-4.2.8p15]$ ./configure --help
[root@234c8 ntp-4.2.8p15]$ ./configure --prefix=/usr/local/ntp --enable-all-clocks --enable-parse-clocks
config.status: creating Makefile
config.status: creating config.h
config.status: creating evconfig-private.h
config.status: evconfig-private.h is unchanged
config.status: executing depfiles commands
config.status: executing libtool commands
[root@234c8 ntp-4.2.8p15]$ cat Makefile | grep /usr/local/ntp
NTP_KEYSDIR = /usr/local/ntp/etc
PERLLIBDIR = /usr/local/ntp/share/ntp/lib
prefix = /usr/local/ntp

最后使用make来安装:

[root@234c8 ntp-4.2.8p15]$ make clean; make
make[2]: Entering directory `/usr/local/src/ntp-4.2.8p15'
make[2]: Leaving directory `/usr/local/src/ntp-4.2.8p15'
make[1]: Leaving directory `/usr/local/src/ntp-4.2.8p15'
[root@234c8 ntp-4.2.8p15]$ make check
make[2]: Leaving directory `/usr/local/src/ntp-4.2.8p15'
make[1]: Leaving directory `/usr/local/src/ntp-4.2.8p15'
[root@234c8 ntp-4.2.8p15]$ make install
Installing stand-alone HTML documentation
make[3]: Leaving directory `/usr/local/src/ntp-4.2.8p15'
make[2]: Leaving directory `/usr/local/src/ntp-4.2.8p15'
make[1]: Leaving directory `/usr/local/src/ntp-4.2.8p15'
[root@234c8 ntp-4.2.8p15]$ ll /usr/local/ntp/
total 0
drwxr-xr-x. 2 root root 189 Sep 21 22:26 bin
drwxr-xr-x. 2 root root   6 Sep 21 22:26 libexec
drwxr-xr-x. 2 root root   6 Sep 21 22:26 sbin
drwxr-xr-x. 5 root root  39 Sep 21 22:26 share

使用patch命令来对源码进行更新后,还需要再次编译才能生效。

函数库管理

在 Linux 下依据函数库是否被编译到程序内部分为动态与静态函数库:

  • 静态(Static)

    一般扩展名为 .a,在编译时直接整合到执行程序中。

  • 动态(Dynamic)

    扩展名为 .so,在编译时程序只有一个指向(Pointer)位置,需要用到时才会去读取。

现在软件偏向使用动态函数库,这样方便升级函数库后不需重新编译程序。

将函数库常驻内存

可以将常用的动态函数库载入内存中,这样程序调用函数库时比从硬盘读取速度快。方法如下:

  • /etc/ld.so.conf 里写明要读入内存中的函数库目录;
  • 利用 ldconfigld.so.conf 中配置的函数库读入内存;
  • 同时也将数据记录一份在 /etc/ld.so.cache 这个文件中。

假设要把 MySQL 数据库的函数库载入内存(实际上系统默认已经这么做了):

[root@234c8 ~]$ vi /etc/ld.so.conf.d/mariadb-x86_64.conf 
/usr/lib64/mysql
[root@234c8 ~]$ ldconfig
[root@234c8 ~]$ ldconfig -p | grep mysql
        libmysqlclient.so.18 (libc6,x86-64) => /usr/lib64/mysql/libmysqlclient.so.18

程序的动态函数库解析

可以使用ldd命令来查询,例如查询df命令使用的动态函数库:

[root@234c8 ~]$ ldd /usr/bin/df
        linux-vdso.so.1 =>  (0x00007ffe7de99000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fec1f049000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fec1f417000)

也可以继续拿ldd来查询某个函数的相关函数:

[root@234c8 ~]$ ldd -v /lib64/libc.so.6 
        /lib64/ld-linux-x86-64.so.2 (0x00007f894f632000)
        linux-vdso.so.1 =>  (0x00007ffecdfd8000)

        Version information:
        /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

这个命令可以用来检查程序安装时的依赖性。

软件管理

通常不同的分发商会将一些软件编译好成二进制,并通过软件管理平台提供给用户。Fedora/CentOS 系列使用 RPM 软件管理机制和 yum 线上更新模式,而 Debian/Ubuntu 使用 dpkg 软件管理机制和 APT 线上更新模式。

RPM 与 SRPM

RPM 的全名是 RedHat Package Manager,它要求软件文件安装环境与打包时一致,并且需要满足软件依赖性需求。

SRPM 是 Source RPM,也就是提供的 RPM 包的源文件,通常扩展名为 *.src.rpm。与普通源代码安装不同的是,要将它以 RPM 管理的方式编译成 RPM 文件,再安装到系统中。

RPM 包文件名通常有固定格式,例如:cronolog-1.6.2-14.el7.x86_64.rpm

  • cronolog:软件名
  • 1.6.2:软件版本。主版本为 1,在主版本架构下的功能更新变动次版本号 6,小修补则用动用再次版本号 2 来表示。
  • 14:释出版本次数,也就是编译次数。可能由于一些 bug 或安全原因,重新调整过编译参数。
  • el7.x86_64:适合的硬件平台。如果没有硬件等级限制,用 noarch 表示。
  • rpm:固定扩展名

RPM 的特点:

  • RPM 内含已经编译过的程序与配置文件,不需要使用者重新编译。
  • RPM 在被安装之前,会检查系统版本、剩余容量等,可避免被错误安装。
  • RPM 文件本身提供软件版本信息、软件依赖信息、用途说明、文件内容等信息。
  • RPM 管理的方式使用数据库记录 RPM 文件相关参数,方便升级、卸载、查询等。

RPM 使用 YUM 服务器提供下载,每次安装软件前,yum 都会先在线更新本地软件数据库,得出需要安装的软件列表。再到 YUM 服务器去获取软件,通过 RPM 的机制开始安装。使用 RPM 安装软件的信息会记录在 /var/lib/rpm/ 目录下的数据库内。

SRPM 文件编译打包使用 rpmbuild --rebuild 命令。配置文件一般是 SPECS 目录下的 .spec 文件,修改完成后可以用 rpmbuild -bb spec 文件来编译成 RPM 文件。

RPM 命令

安装使用 -ivh 参数,比如安装 cronolog 软件:

[root@234c8 ~]$ rpm -ivh cronolog-1.6.2-14.el7.x86_64.rpm 
Preparing...                          ################################# [100%]
Updating / installing...
   1:cronolog-1.6.2-14.el7            ################################# [100%]

参数后面可以接多个 rpm 包来一次安装,也可以指定网络上的地址来安装。

其他一些有用的参数如下:

参数 说明
--nodeps 强制忽略软件依赖性来安装
--replacefiles 直接覆盖掉原先安装过的软件
--replacepkgs 直接重装 rpm 包
--force 等于 --replacefiles--replacepkgs
--test 测试 rpm 包是否能正确安装
--justdb 在数据库有错误时,更新此软件在数据库内的信息
--nosignature 忽略数字签名检查
--prefix 路径 指定软件安装到自定义目录
--noscripts 禁止软件在安装时执行的命令

如果要升级软件使用 -Uvh 参数:

[root@234c8 ~]$ rpm -Uvh cronolog-1.6.2-14.el7.x86_64.rpm 
Preparing...                          ################################# [100%]
        package cronolog-1.6.2-14.el7.x86_64 is already installed

查询本机已安装的软件或 RPM 包可以使用以下命令及参数:

参数 说明
-q 仅查询软件是否有安装
-qa 列出所有已经安装的软件
-qi 列出软件详细信息
-ql 列出软件的所有文件与目录路径
-qc 列出软件的所有配置文件
-qd 列出软件的说明文档
-qR 列出软件的依赖情况
-qf 找出查询文件属于哪一个已经安装的软件
-q --script 列出是否含有安装后需要执行的脚本
-qp[icdlR] 查询 RPM 文件内的信息

例如,查询 cronolog 软件安装的文件:

[root@234c8 ~]$ rpm -ql cronolog
/usr/bin/cronosplit
/usr/sbin/cronolog
/usr/share/doc/cronolog-1.6.2

查询 cronolog 软件相关说明:

[root@234c8 ~]$ rpm -qi cronolog
Name        : cronolog
Version     : 1.6.2
Release     : 14.el7
Architecture: x86_64

查询文件 /bin/rpcgen 属于哪个软件包。如果文件被误删了也可以查询:

[root@234c8 ~]$ rpm -qf /bin/rpcgen 
glibc-common-2.17-324.el7_9.x86_64

可以使用 -V 参数来验证某个软件是否缺失或更改了文件,例如查询 rootfiles:

[root@234c8 ~]$ rpm -V rootfiles

卸载软件使用 -e 参数,当软件没有依赖关系时才能卸载。如果强制移除软件造成数据库错误,可以使用 --rebuilddb 来重建数据库:

[root@234c8 ~]$ rpm --rebuilddb

YUM 命令

yum (Yellowdog Updater Modified) 是一种通过线上服务器安装软件的工具,常用参数包括 -y 用于自动确认,以及 --installroot 用于指定安装路径。

使用 list 选项查询软件列表,使用 search 选项在线上搜索软件,例如搜索和 vim 有关的软件:

[root@234c8 ~]$ yum search vim
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirrors.tuna.tsinghua.edu.cn
 * epel: mirrors.tuna.tsinghua.edu.cn
 * extras: mirrors.tuna.tsinghua.edu.cn
 * updates: mirrors.tuna.tsinghua.edu.cn
============================== N/S matched: vim ========================================
beakerlib-vim-syntax.noarch : Files for syntax highlighting BeakerLib tests in VIM editor
boxes-vim.noarch : Vim plugin for boxes
fluxbox-vim-syntax.noarch : Fluxbox syntax scripts for vim
geany-plugins-vimode.x86_64 : Vim-mode plugin for Geany

使用 info 选项搜索软件信息,例如搜索 gdisk 软件的信息:

[root@234c8 ~]$ yum info gdisk
Available Packages
Name        : gdisk
Arch        : x86_64
Version     : 0.8.10
Release     : 3.el7
Size        : 190 k

列出可升级的软件使用 list updates 命令:

[root@234c8 ~]$ yum list updates
Updated Packages
epel-release.noarch                     7-14                                   epel   
kernel.x86_64                           3.10.0-1160.42.2.el7                   update

安装软件使用 install 命令,升级使用 update 命令,例如安装 agrep 软件:

[root@234c8 ~]$ yum install -y agrep
Installed:
  agrep.x86_64 0:0.8.0-18.20140228gitc2f5d13.el7                                                           

Dependency Installed:
  tre.x86_64 0:0.8.0-18.20140228gitc2f5d13.el7     tre-common.noarch 0:0.8.0-18.20140228gitc2f5d13.el7    

Complete!

也可以只下载 RPM 包但不安装(软件已安装的情况下使用 reinstall 代替 install)。例如下载 agrep/root/dl/ 目录:

[root@server1 ~]$ yum install -y agrep --downloadonly --downloaddir=/root/dl/
[root@server1 ~]$ ll /root/dl/
total 96
-rw-r--r--. 1 root root 19924 Nov  4  2016 agrep-0.8.0-18.20140228gitc2f5d13.el7.x86_64.rpm
-rw-r--r--. 1 root root 40868 Nov  4  2016 tre-0.8.0-18.20140228gitc2f5d13.el7.x86_64.rpm
-rw-r--r--. 1 root root 32920 Nov  4  2016 tre-common-0.8.0-18.20140228gitc2f5d13.el7.noarch.rpm

软件删除使用 remove 命令,例如移除掉 agrep

[root@234c8 ~]$ yum remove agrep
Remove  1 Package

Installed size: 22 k
Is this ok [y/N]: y
Downloading packages:
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Erasing    : agrep-0.8.0-18.20140228gitc2f5d13.el7.x86_64                                            1/1 
  Verifying  : agrep-0.8.0-18.20140228gitc2f5d13.el7.x86_64                                            1/1 

Removed:
  agrep.x86_64 0:0.8.0-18.20140228gitc2f5d13.el7                                                           

Complete!

如果想要加入自定义仓库地址,可以修改 yum 配置文件 /etc/yum.repos.d/CentOS-Base.repo

[root@234c8 ~]$ cat /etc/yum.repos.d/CentOS-Base.repo
[base]
name=CentOS-$releasever - Base
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os&infra=$infra
#baseurl=http://mirror.centos.org/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

其中以中括号代表软件库名称,可以自己取名,其他设置如下:

  • name:自定义仓库名,不要重名即可;
  • mirrorlist:列出这个软件仓库可用的映射站点,可以不需要;
  • baseurl:后面接实际软件仓库网址;
  • enable:设置是否要启用此仓库;
  • gpgcheck:是否要检查数字签名;
  • gpgkey:数字签名文件位置。

设置好以后可以使用 repolist all 来查询:

[root@234c8 ~]$ yum repolist all
repo id                       repo name                                status
C7.8.2003-updates/x86_64      CentOS-7.8.2003 - Updates                disabled
base/7/x86_64                 CentOS-7 - Base                          enabled: 10,072

yum 支持软件群组功能,可以使用下面选项来操作:

  • grouplist:列出所有可用的软件群组;
  • groupinfo:查询软件群组内容;
  • groupinstall:安装一组软件群组;
  • groupremove:删除某软件群组。

软件群组内软件分主要和可选,使用 groupinstall 只会安装主要软件,可以修改 /etc/yum.conf 配置,在 distroverpkg 下面加入一行配置 group_package_types=default, mandatory, optional 来安装所有软件群组的软件。

另外,想要通过代理连接下载,可以在配置文件中新增一行 proxy=代理服务器地址 来启用。