Linux学习笔记(二十二)

来源:互联网 发布:电脑刷机软件 编辑:程序博客网 时间:2024/04/28 19:19

源码与Tarball套件管理程序  

通过Linux操作系统里面的执行问价,来理解什么时候可执行程序,什么是编译器,以及与程序息息相关的函数库。本章中,希望读者可以了解如何将开放源码的程序设计、加入函数库的原理、编译源码成二进制文件,最后执行该文件可被我们所用的一连串的过程。

了解这些的好处:在Linux的世界里,我们常常需要Linux系统自行安装套件,所以,如果有简单的程序编译概念,就会很容易地进行套件的安装,甚至是在套件编译过程出现错误时,可以自行做一些简单的修改。最传统的套件安装过程是由源码编译而来的,所以本章最后介绍原始的套件管理方式:使用Traball来安装与升级管理套件。

什么是开放源码。编译器与可执行文件

在Linux上,一个文件能不能执行看的是有没有可执行权限(x权限),不过,Linux系统真正认识的可执行文件其实是二进制文件(binary file)

Shell只是利用Shell这个程序的功能进行一些判断,而最终执行的除了bash提供的功能外,仍是调用一些已编译好的二进制文件(bash本身就是二进制文件)。利用“file”命令来查看一个文件到到底是什么类型。

# 先以系统的档案测试看看: 

[root@www ~]# file /bin/bash 

/bin/bash: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for 

GNU/ 

Linux 2.6.9, dynamically linked (uses shared libs), for GNU/Linux 2.6.9, 

stripped 

# 如果是系统提供的 /etc/init.d/syslog 呢? 

[root@www ~]# file /etc/init.d/syslog 

/etc/init.d/syslog: Bourne-Again shell script text executable 

看到了吧!如果是 binary 而且是可以执行的时候,他就会显示执行文件类别 (ELF 32-bit LSB executable), 同时会说明是否使用动态函式库 (shared libs),而如果是一般的 script ,那他就会显示出 text executables 之类的字样! 

利用一般的文字处理程序进行程序的编写,写完之后的程序就是源码(纯文本文件),利用编译器将这个纯文本文件编译成操作系统能看懂的二进制文件。在编译的过程中,会产生目标文件(扩展名字为*.o),有时候会在程序中“引用,条用”其他的外部程序或者其他套件提供的函数,这时在编译过程中就要将该函数库加进去。

什么是函数库

函数库分为动态的和静态的。

大多放在/usr/include,/lib,/usr/lib中

什么是make与configure

实际上,使用gcc的编译器进行编译的过程并不简单,每个主程序与子程序均需要写上编译过程的命令外,还需要写上最终的连接程序。代码少时候还可以,多的话就不容易控制了。这时候可以利用make命令的相关功能来简化编译过程的命令。

执行make时,mak会在当前目录下搜索Makefile文件,Makefile文件中记录了源码如何编译的详细信息。Make会自动识别源码是否经过变动,自动更新文件。

Make是一个程序,会去找Makefile,Makefile怎么写的呢?软件开发商会写一个检测用户工作环境以及该工作环境是否有软件开发商所需要的其他功能的检测程序。该检测程序检测完后,会主动建立Makefile文件。通常检测程序的文件名为configure或config。

下图中,你要进行的任务其实只有两个,一个是执行 configure 来建立 Makefile , 这个步骤一定要成功!成功之后再以 make 来呼叫所需要的数据来编译即可!非常简单! 

什么是Tarball的套件

Tarball文件,其实就是将套件的所有源码文件先以tar打包,在压缩。最常见的是gzip来压缩。Tarball套件解压后,里面的文件通常会有:源码文件,检测程序文件(可能是configure或config),本套件的简单说明或按照说明(INSTALL或README)。

通常只要能够参考INSTALL或README这两个文件,Tarball的套件就很容易安装了。

安装与升级套件

一个套件的Tarball的基本安装流程是:

   1:从厂商的处下载Tarball;

   2: 将Tarball解开,产生很多的源码文件;

   3:开始以gcc进行源码的编译(会产生目标文件)

   4:用gcc进行函数库,主,子程序的连接,形成主要的二进制文件;

   5:将上述二进制文件以及相关的设置文件安装到自己的主机上。

上面第 3, 4 步骤当中,我们可以透过 make 这个指令的功能来简化他, 所以整个步骤其实是很简单的啦!只不过你就得需要至少有 gcc 以及 make 这两个软件在你的 Linux 系统里面才行喔!

 

一个简单的例子

使用传统程序语言进行编译的简单范例  

编辑程序代码,亦即原始码 

[root@www ~]# vim hello.c   <==用 C 语言写的程序扩展名建议用 .c 

#include <stdio.h> 

int main(void) 

        printf("Hello World\n"); 

开始编译与测试执行 

[root@www ~]# gcc hello.c 

[root@www ~]# ll hello.c a.out 

-rwxr-xr-x 1 root root 4725 Jun  5 02:41 a.out   <==此时会产生这个档名 

-rw-r--r-- 1 root root   72 Jun  5 02:40 hello.c 

[root@www ~]# ./a.out 

Hello World  <==呵呵!成果出现了! 

在预设的状态下,如果我们直接以 gcc 编译原始码,并且没有加上任何参数,则执行档的档名会被自动设定为 a.out 这个文件名! 所以你就能够直接执行 ./a.out 这个执行档啦!

那如果我想要产生目标文件 (object file) 来进行其他的动作,而且执行档的档名也不要用预设的a.out ,那该如何是好?其实你可以将上面的第 2 个步骤改成这样: 

[root@www ~]# gcc -c hello.c 

[root@www ~]# ll hello* 

-rw-r--r-- 1 root root  72 Jun  5 02:40 hello.c 

-rw-r--r-- 1 root root 868 Jun  5 02:44 hello.o  <==就是被产生的目标文件 

 

[root@www ~]# gcc -o hello hello.o 

[root@www ~]# ll hello* 

-rwxr-xr-x 1 root root 4725 Jun  5 02:47 hello  <==这就是可执行文件! -o 的结果 

-rw-r--r-- 1 root root   72 Jun  5 02:40 hello.c 

-rw-r--r-- 1 root root  868 Jun  5 02:44 hello.o 

[root@www ~]# ./hello 

Hello World 

有多个源码文件的情况:

#gcc –c file1.c file2.c file3.c(编译.c文件)

#gcc –o file file1.o file2.o file3.o(编译.o文件)

#./file(执行文件)

在编译的时候加入 参数 “-O”可以优化编译环境。

 

加入函数库

#gcc file.c –lm –L/lib –L/usr/lib

注意:–lm可以拆分为两部分来看:

-l:表示:“加入某个函数库(library)”

-m:表示libm.so函数库,其中lib与扩展名(.a或者.so)不需要写,所以 –lm 表示使用libm.so(或libm.a)函数库的意思。 -L后面接的路径上面的内容表示为:“要用到的函数库libm.so请到/lib或/usr/lib中搜索”

补充: #gcc file.c –lm –I/usr/inlcude  -I/path后面的路径(path)就是相关包含文件的目录。

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

# 仅将原始码编译成为目标文件,并不制作链接等功能: 

[root@www ~]# gcc -c hello.c 

# 会自动的产生 hello.o 这个档案,但是并不会产生 binary 执行档。 

 

# 在编译的时候,依据作业环境给予优化执行速度 

[root@www ~]# gcc -O hello.c -c 

# 会自动的产生 hello.o 这个档案,并且进行优化喔! 

 

# 在进行 binary file 制作时,将连结的函式库与相关的路径填入 

[root@www ~]# gcc sin.c -lm -L/usr/lib -I/usr/include 

# 这个指令较常下达在最终连结成 binary file 的时候, 

# -lm 指的是 libm.so 或libm.a 这个函式库档案; 

# -L 后面接的路径是刚刚上面那个函式库的搜寻目录; 

# -I 后面接的是原始码内的 include 档案之所在目录。 

 

# 将编译的结果输出成某个特定档名 

[root@www ~]# gcc -o hello hello.c 

# -o 后面接的是要输出的 binary file 档名 

 

# 在编译的时候,输出较多的讯息说明 

[root@www ~]# gcc -o hello hello.c -Wall 

# 加入 -Wall 之后,程序的编译会变的较为严谨一点, 

# 所以警告讯息也会显示出来! 

make的简单用法

为什么要用 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 数值。

传统编译过程需要好多动作而且如果要重新编译,则上述的流程得要重新来一遍,光是找出这些指令就够烦人的了! 如果可以的话,能不能一个步骤就给他完成上面所有的动作呢?那就利用 make这个工具吧!

# 1. 先编辑 makefile 这个规则文件,内容只要作出 main 这个执行档 

[root@www ~]# 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@www ~]# rm -f main *.o   <==先将之前的目标文件去除 

[root@www ~]# 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@www ~]# make 

make: `main' is up to date. 

# 看到了吧!是否很方便呢!只会进行更新 (update) 的动作而已。 

Make的基本语法与变量

标的(target): 目标文件1 目标文件2 

<tab>   gcc -o 欲建立的执行文件 目标文件1 目标文件2 

那个标的 (target) 就是我们想要建立的信息,而目标文件就是具有相关性的 object files ,那建立执行文件的语法就是以 <tab> 按键开头的那一行!特别给他留意喔,『命令行必须要以 tab 按键作为开头』才行!他的规则基本上是这样的: 

·  在 makefile 当中的 # 代表批注; 

·  <tab> 需要在命令行 (例如 gcc 这个编译程序指令) 的第一个字符; 

·  标的 (target) 与相依档案(就是目标文件)之间需以『:』隔开。 

同样的,我们以刚刚上一个小节的范例进一步说明,如果我想要有两个以上的执行动作时, 例如下达一个指令就直接清除掉所有的目标文件与执行文件,该如何制作呢? 

# 1. 先编辑 makefile 来建立新的规则,此规则的标的名称为 clean : 

[root@www ~]# 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@www ~]# 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』,

但是,你是否会觉得,咦! makefile 里面怎么重复的数据这么多啊!没错!所以我们可以再藉由 shell script 那时学到的『变数』来更简化 makefile 喔: 

[root@www ~]# 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.  在指令列模式也可以给予变量。

 

环境变量取用规则:

1.  make 指令列后面加上的环境变量为优先; 

2.  makefile 里面指定的环境变量第二; 

3.  shell 原本具有的环境变量第三。 

Tarball的安装基本步骤

1:将tarball文件在/usr/local/src 目录下解压缩;

2:进入新建立的目录,查看INSTALL与README等相关文件的内容(很重要的步骤);

3:根据INSTALL与README的内容查看并安装好相关的套件;

4:以自动检测程序(configure或config)检测工作环境,并建立Makefile文件;

5:使用make以及该目录下的Makefile文件作为参数设置文件,来进行make;

6:以make程序和Makefile参数设置文件,根据install目标来指定安装到正确的路径。

我们底下约略提一下大部分的 tarball 软件的安装的指令下达方式: 

1.  ./configure 这个步骤就是在建立 Makefile 这个档案啰!通常程序开发者会写一支 scripts 来检查你的 Linux 系统、相关的软件属性等等,这个步骤相当的重要

2.  make clean make 会读取 Makefile 中关于 clean 的工作。

3.  make make 会依据 Makefile 当中的预设工作进行编译的行为!编译的工作主要是进行 gcc 来将原始码编译成为可以被执行的 object files ,但是这些 object files 通常还需要一些函式库之类的link 后,才能产生一个完整的执行档!使用 make 就是要将原始码编译成为可以被执行的可执行文件,而这个可执行文件会放置在目前所在的目录之下, 尚未被安装到预定安装的目录中;

4.  make install通常这就是最后的安装步骤了,make 会依据 Makefile 这个档案里面关于install 的项目,将上一个步骤所编译完成的数据给他安装到预定的目录中,就完成安装啦!  

为了方便 Tarball 的管理,通常鸟哥会这样建议使用者: 

1.  最好将 tarball 的原始数据解压缩到 /usr/local/src 当中; 

2.  安装时,最好安装到 /usr/local 这个默认路径下; 

3.  考虑未来的反安装步骤,最好可以将每个软件单独的安装在 /usr/local 底下; 

4.  为安装到单独目录的软件的 man page 加入 man path 搜寻:如果你安装的软件放置到 /usr/local/software/ ,那么 man page 搜寻的设定中,可能就得要在 

/etc/man.config 内的 40~50 行左右处,写入如下的一行:  

MANPATH /usr/local/software/man 

这样才可以使用 man 来查询该软件的在线文件啰! 

一个简单的范例、利用 ntp 来示范 

假设我对这个软件的要求是这样的: 

·  假设 ntp-4.2.4p7.tar.gz 这个档案放置在 /root 这个目录下; 

·  原始码请解开在 /usr/local/src 底下; 

·  我要安装到 /usr/local/ntp 这个目录中;  

·  解压缩下载的 tarball ,并参阅 README/INSTALL 档案 

[root@www ~]# cd /usr/local/src   <==切换目录 

[root@www src]# tar -zxvf /root/ntp-4.2.4p7.tar.gz  <==解压缩到此目录 

ntp-4.2.4p7/         <==会建立这个目录喔! 

ntp-4.2.4p7/libopts/ 

....(底下省略).... 

[root@www src]# cd ntp-4.2.4p7/ 

[root@www ntp-4.2.4p7]# vi INSTALL  <==记得 README 也要看一下! 

# 特别看一下 28 行到 54 行之间的安装简介!可以了解如何安装的流程喔! 

检查 configure 支持参数,并实际建置 makefile 规则文件 

[root@www 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: 

# 上面列出的是比较重要的,或者是你可能需要的参数功能! 

 

[root@www ntp*]# ./configure --prefix=/usr/local/ntp \ 

>  --enable-all-clocks --enable-parse-clocks  <==开始建立makefile 

checking for a BSD-compatible install... /usr/bin/install -c 

checking whether build environment is sane... yes 

....(中间省略).... 

checking for gcc... gcc           <==也有找到 gcc 编译程序了! 

....(中间省略).... 

config.status: creating Makefile  <==现在知道这个重要性了吧? 

config.status: creating config.h 

config.status: executing depfiles commands 

一般来说 configure 设定参数较重要的就是那个 --prefix=/path 了,--prefix 后面接的路径就是『这个软件未来要安装到那个目录去?』如果你没有指定 --prefix=/path 这个参数,通常预设参数就是/usr/local 至于其他的参数意义就得要参考 ./configure --help 了! 这个动作完成之后会产生makefile 或 Makefile 这个档案。当然啦,这个侦测检查的过程会显示在屏幕上, 特别留意关于 gcc的检查,还有最重要的是最后需要成功的建立起 Makefile 才行! 

最后开始编译与安装噜! 

[root@www ntp*]# make clean; make 

[root@www ntp*]# make check 

[root@www ntp*]# make install 

# 将数据给他安装在 /usr/local/ntp 底下 

函数库的管理

1 动态函数库与静态函数库

 静态函数库 

扩展名类似于libxxx.a

编译行为:整个函数库的所有数据都整合到执行文件中(比较大)。

独立执行的状态:编译成功的可执行文件可以独立执行。

升级难易度:当函数库升级时,执行文件也要重新编译;

动态函数

     扩展名类似于libxxx.so

     编译行为 在程序中只有一个指向的文字而已,动态函数没有整合到执行文件中(小)

     独立执行的状态:不能独立执行,函数库必须要存在。

     升级难易度:无法独立执行

当前Linux大多是将函数库做成动态函数库。

ldconfig与/etc/ld.so.conf

将动态函数库加载到高速缓存的过程如下:

1:要在/etc/ld.so.conf中写下“想要读入高速缓存中的动态函数库所在的目录”,注意是目录而不是文件。

2:利用ldconfig执行文件将/etc/ld.so.conf的数据读入高速缓存中。

3:同时在/etc/ld.so.conf文件中记录数据。

[root@www ~]# ldconfig [-f conf] [ -C cache] 

[root@www ~]# ldconfig [-p] 

选项与参数: 

-f conf :那个 conf 指的是某个文件名,也就是说,使用 conf 作为 libarary 函式库的取得路径,而不以 /etc/ld.so.conf 为默认值 

-C cache:那个 cache 指的是某个文件名,也就是说,使用 cache 作为快取暂存的函式库资料,而不以 /etc/ld.so.cache 为默认值 

-p  :列出目前有的所有函式库资料内容 (在 /etc/ld.so.cache 内的资料!) 

 

范例一:假设我的 MySQL 数据库函式库在 /usr/lib/mysql 当中,如何读进

cache ? 

[root@www ~]# vi /etc/ld.so.conf 

include ld.so.conf.d/*.conf 

/usr/lib/mysql   <==这一行新增的啦! 

 

[root@www ~]# ldconfig  <==画面上不会显示任何的信息,不要太紧张!正

常的! 

 

[root@www ~]# ldconfig -p 

530 libs found in cache `/etc/ld.so.cache' 

        libz.so.1 (libc6) => /usr/lib/libz.so.1 

        libxslt.so.1 (libc6) => /usr/lib/libxslt.so.1 

....(底下省略).... 

#       函式库名称 => 该函式库实际路径 

透过上面的动作,我们可以将 MySQL 的相关函式库给他读入快取当中,这样可以加快函式库读取的效率呢! 在某些时候,你可能会自行加入某些 Tarball 安装的动态函式库,而你想要让这些动态函式库的相关连结可以被读入到快取当中, 这个时候你可以将动态函式库所在的目录名称写入 /etc/ld.so.conf当中,然后执行 ldconfig 就可以啦! 

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

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

[root@www ~]# ldd [-vdr] [filename] 

选项与参数: 

-v :列出所有内容信息; 

-d :重新将资料有遗失的 link 点秀出来! 

-r :将 ELF 有关的错误内容秀出来! 

 

范例一:找出 /usr/bin/passwd 这个档案的函式库数据 

[root@www ~]# ldd /usr/bin/passwd 

....(前面省略).... 

        libaudit.so.0 => /lib/libaudit.so.0 (0x00494000)     <==SELinux 

        libselinux.so.1 => /lib/libselinux.so.1 (0x00101000) <==SELinux 

        libc.so.6 => /lib/libc.so.6 (0x00b99000) 

        libpam.so.0 => /lib/libpam.so.0 (0x004ab000)         <==PAM 模块 

....(底下省略).... 

# 我们前言的部分不是一直提到 passwd 有使用到 pam 的模块吗!怎么知道? 

# 利用 ldd 察看一下这个档案,看到 libpam.so 了吧?这就是 pam 提供的凼式

库 

 

范例二:找出 /lib/libc.so.6 这个函式的相关其他函式库! 

[root@www ~]# ldd -v /lib/libc.so.6 

        /lib/ld-linux.so.2 (0x00ab3000) 

        linux-gate.so.1 =>  (0x00636000) 

 

        Version information:  <==使用 -v 选项,增加显示其他版本信息! 

        /lib/libc.so.6: 

                ld-linux.so.2 (GLIBC_PRIVATE) => /lib/ld-linux.so.2 

                ld-linux.so.2 (GLIBC_2.3) => /lib/ld-linux.so.2 

                ld-linux.so.2 (GLIBC_2.1) => /lib/ld-linux.so.2 

原创粉丝点击