================
第一课  开发环境
================


一、课程内容
------------

开发环境 - 1天 -+
内存管理 - 1天  |
文件系统 - 2天  |
进程管理 - 1天  |
信号处理 - 1天  +- 共10天
进程通信 - 1天  |
网络通信 - 1天  |
线程管理 - 1天  |
线程同步 - 1天 -+

二、Unix操作系统
----------------

1. 简介
~~~~~~~

1971年,美国AT&T公司贝尔实验室,

肯.汤普逊、丹尼斯.里奇。

PDP-11,多用户、多任务、支持多种处理器架构。高安全性、高可靠性,高稳定性。既可构建大型关键业务系统的商业服务器应用,也可构建面向移动终端、手持设备等的嵌入式应用。

2. 三大派生版本
~~~~~~~~~~~~~~~

1) System V

AIX: IBM,银行
Solaris: SUN->Oracle,电信
HP-UX
IRIX

2) Berkley

FreeBSD
NetBSD
OpenBSD
Mac OS X

3) Hybrid

Minix: 迷你版的类Unix操作系统。

Linux: GPL,免费开源,商用服务器(RedHat)、
桌面(Ubuntu)、嵌入式(Android)。

3. Unix族谱
~~~~~~~~~~~

三、Linux操作系统
-----------------

1. 简介
~~~~~~~

类Unix操作系统,免费开源。不同发行版本使用相同内核。

手机、平板电脑、路由器、视频游戏控制台、台式计算机、
大型计算机、超级计算机。

严格意义上的Linux仅指操作系统内核。隶属于GNU工程。

发明人Linus Torvalds。

2. 标志
~~~~~~~
Tux (Tuxedo,一只企鹅)

 

 

 

 

3. 相关知识
~~~~~~~~~~~

1) Minix操作系统

荷兰阿姆斯特丹Vrije大学,
数学与计算机科学系,
Andrew S. Tanenbaum,
ACM和IEEE的资深会员。

2) GNU工程

Richard Stallman发起于1984年,
由自由软件基金会(FSF)提供支持。

GNU的基本原则就是共享,
其主旨在于发展一个有别于一切商业Unix系统的,
免费且完整的类Unix系统——GNU Not Unix。

3) POSIX标准

Portable Operating System Interface for
Computing Systems,
统一的系统编程接口规范。

由IEEE和ISO/IEC开发。

保证应用程序源代码级的可移植性。

Linux完全遵循POSIX标准。

4) GPL

通用公共许可证。

允许对某成果及其派生成果的重用、修改和复制,
对所有人都是自由的,但不能声明做了原始工作,
或声明由他人所做。

4. 版本
~~~~~~~

1) 早期版本:0.01, 0.02, ..., 0.99, 1.0

2) 旧计划:介于1.0和2.6之间,A.B.C

A: 主版本号,内核大幅更新。
B: 次版本号,内核重大修改,奇数测试版,偶数稳定版。
C: 补丁序号,内核轻微修订。

3) 2003年12月发布2.6.0以后:缩短发布周期,A.B.C-D.E

D: 构建次数,反映极微小的更新。
E: 描述信息。
   rc/r - 候选版本,其后的数字表示第几个候选版本,
          越大越接近正式版本
   smp  - 对称多处理器
   pp   - Red Hat Linux的测试版本
   EL   - Red Hat Linux的企业版本
   mm   - 测试新技术或新功能
   fc   - Red Hat Linux的Fedora Core版本

如:

# cat /proc/version
Linux version 3.6.11-4.fc16.i686

# cat /proc/version
Linux version 3.2.0-39-generic-pae

# cat /proc/version
Linux version 3.10.17 (root@hive64) (gcc version 4.8.2 (GCC) ) #2 SMP Fri Feb 14 16:45:28 CST 2014

# uname -a
Linux X200-LAPTOP 3.10.17 #2 SMP Fri Feb 14 16:45:28 CST 2014 x86_64 Intel(R) Core(TM)2 Duo CPU     P8600  @ 2.40GHz GenuineIntel GNU/Linux

# cat /etc/*release
NAME=Slackware
VERSION="14.1"
ID=slackware
VERSION_ID=14.1
PRETTY_NAME="Slackware 14.1"
ANSI_COLOR="0;34"
CPE_NAME="cpe:/o:slackware:slackware_linux:14.1"
HOME_URL="http://slackware.com/"
SUPPORT_URL="http://www.linuxquestions.org/questions/slackware-14/"
BUG_REPORT_URL="http://www.linuxquestions.org/questions/slackware-14/"

5. 特点
~~~~~~~

1) 遵循GNU/GPL
2) 开放性
3) 多用户
4) 多任务
5) 设备独立性
6) 丰富的网络功能
7) 可靠的系统安全
8) 良好的可移植性

6. 发行版本
~~~~~~~~~~~

1) 大众的Ubuntu
2) 优雅的Linux Mint
3) 锐意的Fedora
4) 华丽的openSUSE
5) 自由的Debian
6) 简洁的Slackware
7) 老牌的RedHat




---------------------------Unix Platform Programming------------------------------



四、GNU编译工具GCC

------------------

1. 支持多种编程语言
~~~~~~~~~~~~~~~~~~~

C、C++、Objective-C、Java、Fortran、Pascal、Ada

2. 支持多种平台
~~~~~~~~~~~~~~~

Unix、Linux、Windows。

3. 构建(Build)过程
~~~~~~~~~~~~~~~~~~

编辑 -> 预编译 -> 编译 -> 汇编 -> 链接

1) 编辑:  vi hello.c                                        -> hello.c
2) 预编译:gcc -E hello.c -o hello.i                   -> hello.i  -+
3) 编译:  gcc -S hello.i                                 -> hello.s    | GCC
4) 汇编:  gcc -c hello.s                                 -> hello.o   | 工具链
5) 链接:  gcc hello.o -o hello                         -> hello    -+

范例:hello.c

4. 查看版本
~~~~~~~~~~~

gcc -v

5. 文件后缀
~~~~~~~~~~~

.h  - C语言源代码头文件
.c  - 预处理前的C语言源代码文件
.i  - 预处理后的C语言源代码文件
.s  - 汇编语言文件
.o  - 目标文件
.a  - 静态库文件。*.a文件是静态链接库文件。静态链接库文件就是.o文件的归档文件,字母a取自archive.
.so - 共享库(动态库)文件



6. 编译单个源程序
~~~~~~~~~~~~~~~~~

gcc [选项参数] 文件

-c        - 只编译不链接
-o        - 指定输出文件
-E        - 预编译
-S        - 产生汇编文件
-pedantic - 对不符合ANSI/ISO C语言标准的
            扩展语法产生警告
-Wall     - 产生尽可能多的警告。
            范例:gcc -Wall wall.c
-Werror   - 将警告作为错误处理。
            范例:gcc -Werror werror.c
-x        - 指定源代码的语言。
            范例:gcc -x c++ cpp.c -lstdc++
-g        - 生成调试信息

-O1/O2/O3 - 优化等级

 1 /*
 2  *
 3  * 文件名: wall.c
 4  *
 5  * gcc选项练习
 6  * -Wall 可以尽可能多的报告警告
 7  *
 8  * gcc wall.c -Wall
 9  *
10 wall.c:13:1: 警告:返回类型默认为‘int’ [-Wreturn-type]
11  foo () {}
12  ^
13 wall.c: 在函数‘foo’中:
14 wall.c:13:1: 警告:在有返回值的函数中,控制流程到达函数尾 [-Wreturn-type]
15  foo () {}
16  ^
17  * 
18  *
19  * */
20 #include <stdio.h>
21 
22 foo () {}
23 
24 int main (void) {
25     printf ("%d\n", foo ());
26     return 0;
27 }

 

 1 /*
 2  * 文件名:werror.c
 3  *
 4  * gcc编译选项练习
 5  * 选项-Werror把警告视为error报告
 6  * 
 7  * gcc -Werror werror.c
 8  *
 9 werror.c: 在函数‘foo’中:
10 werror.c:17:2: 错误:函数返回局部变量的地址 [-Werror=return-local-addr]
11   return &local;
12   ^
13 cc1: all warnings being treated as errors
14  *
15  *
16  * */
17 #include <stdio.h>
18 
19 int* foo (void) {
20     int local = 10;
21     return &local;
22 }
23 
24 int main (void) {
25     printf ("%d\n", *foo ());
26     return 0;
27 }

 

 1 /*
 2  * 文件名: cpp.c
 3  *
 4  *
 5  * gcc 选项-x可以指定让编译器按照某种语言编译源文件
 6  *
 7  *
 8  * gcc -xc++ -c cpp.c -lstdc++//按照c++语言编译源文件cpp.c
 9  *
10  * */
11 
12 #include <iostream>
13 using namespace std;
14 
15 int main (void) {
16     cout << "Hello, C++ !" << endl;
17     return 0;
18 }

 

 

7. 编译多个源程序
~~~~~~~~~~~~~~~~~

gcc [选项参数] 文件1 文件2 ...

思考:头文件的作用是什么?

1) 声明外部变量、函数和类。
2) 定义宏、类型别名和自定义类型。
3) 包含其它头文件。
4) 借助头文件卫士,防止因同一个头文件被多次包含,
   而引发重定义错。

包含头文件时需要注意:

1) gcc的-I选项

指定头文件附加搜索路径。

2) #include <...>

先找-I指定的目录,再找系统目录。

3) #include "..."

先找-I指定的目录,再找当前目录,最后找系统目录。

4) 头文件的系统目录

/usr/include
/usr/local/include
/usr/lib/gcc/i686-linux-gnu/4.6.3/include
/usr/include/c++/4.6.3 (C++编译器优先查找此目录)

范例:calc.h、calc.c、math.c

 1 /*
 2 math.c中不包含calc.h,输出0.000000。
 3 参数和返回值均按int处理。
 4 
 5 math.c中包含calc.h,输出30.000000。
 6 参数和返回值均按double处理。
 7 */
 8 
 9 #include <stdio.h>
10 #include "calc.h"
11 
12 int main (void) {
13     printf ("%lf\n", add (10, 20));
14     return 0;
15 }
1 #ifndef _CALC_H
2 #define _CALC_H
3 
4 double add (double, double);
5 
6 #endif // _CALC_H
1 #include "calc.h"
2 
3 double add (double a, double b) {
4     return a + b;
5 }

 

 

8. 环境变量

~~~~~~~~~~~~

C_INCLUDE_PATH     - C头文件的附加搜索路径, 相当于gcc的-I选项
CPATH              - 同C_INCLUDE_PATH
CPLUS_INCLUDE_PATH - C++头文件的附加搜索路径
LIBRARY_PATH       - 链接时查找库(静态库/共享库)的路径
LD_LIBRARY_PATH    - 运行时查找共享库的路径

 1 /*
 2  * 头文件搜索环境变量设置练习
 3  * 头文件的三种定位方式:
 4  * (1)#include "directory"  (p.s. 头文件路径发生变化就需要修改源程序)
 5  *  (2) 设置环境变量CPATH或者C_INCLUDE_PATH (p.s. 同时构建多个项目、工程时候可能引起头文件搜索错误)
 6  *  (3) gcc -I指定头文件搜索路径 (p.s. 既不用改程序,也不会有冲突)
 7  * */
 8 #include <stdio.h>
 9 #include <calc.h>
10 
11 int main (void) {
12     printf ("%lf\n", add (10, 20));
13     return 0;
14 }

 

# gcc calc.c cpath.c
cpath.c:2:17: fatal error: calc.h: No such file or directory

通过gcc的-I选项指定C/C++头文件的附加搜索路径:

# gcc calc.c cpath.c -I.

将当前目录作为C头文件附加搜索路径,添加到CPATH环境变量中:

# export CPATH=$CPATH:. // export保证当前shell的子进程继承此环境变量
# echo $CPATH
# env | grep CPATH

也可以在~/.bashrc或~/.bash_profile配置文件中写环境变量,持久有效:
export CPATH=$CPATH:.

执行
# source ~/.bashrc

# source ~/.bash_profile
生效。以后每次登录自动生效。



9. 预处理指令
~~~~~~~~~~~~~

#include      // 将指定文件的内容插至此指令处
#include_next // 与#include一样,
              // 但从当前目录之后的目录查找,极少用
#define       // 定义宏
#undef        // 删除宏
#if           // 判定
#ifdef        // 判定宏是否已定义
#ifndef       // 判定宏是否未定义
#else         // 与#if、#ifdef、#ifndef结合使用
#elif         // else if多选分支
#endif        // 结束判定
##            // 连接宏内两个连续的字符串
#             // 将宏参数扩展成字符串字面值

#error   // 产生错误,结束预处理
#warning // 产生警告

范例:error.c

# gcc error.c -DVERSION=2
error.c:4:3: error: #error "Version too low !"

# gcc error.c -DVERSION=3

# gcc error.c -DVERSION=4
error.c:6:3: warning: #warning "Version too high !" [-Wcpp]

 1 /*
 2  * 文件名:error.c
 3  *
 4  * 编译预处理指令练习
 5  *
 6  * gcc -o error -c error.c -DVERSION=2
 7  *
 8    error.c:13:3: 错误:#error "Version too low !"
 9      #error "Version too low !"
10      ^
11  *
12  * */
13 #include <stdio.h>
14 
15 #if (VERSION < 3)
16     #error "Version too low !"
17 #elif (VERSION > 3)
18     #warning "Version too high !"
19 #endif
20 
21 int main (void) {
22     printf ("VERSION=%d\n", VERSION);
23     return 0;
24 }

 

 

#line // 指定行号

 1 /*
 2  *文件名:line.c
 3  * 编译预处理指令#line练习
 4  * */
 5 #include <stdio.h>
 6 
 7 int main (void) 
 8 {
 9 printf ("%d\n", __LINE__);//5
10 
11 #line 100//指定自下行的行号从100起始
12 printf ("%d\n", __LINE__);//100
13 printf ("%d\n", __LINE__);//101
14 printf ("%d\n", __LINE__);//102
15 return 0;
16 
17 }

 

 

#pragma // 提供额外信息的标准方法,可用于指定平台

#pragma GCC dependency <文件> // 若<文件>比此文件新则产生警告
#pragma GCC poison <标识>     // 若出现<标识> 则产生错误
#pragma pack(1/2/4/8)         // 按1/2/4/8字节对齐补齐

 1 /*
 2  * 
 3  *  编译预处理指令 #pragma 
 4  *
 5  *
 6  * p.s.  缺省的对齐补齐方式取决于系统位数,对于32位操作系统,一次读入4字节,所以缺省对齐补齐按照4字节计算,这样便于机器一次性读入目标数据,如果不做对齐补齐,机器一次性读入目标数据命中率降低
 7  *
 8 &nbsp;* */
 9 #include <stdio.h>
10 
11 
12 #pragma GCC dependency "error.c" // 若error.c比此文件新则产生警告
13 #pragma GCC poison goto float  // 若出现goto或float则产生错误
14 int main (void) {
15 //    goto escape;
16 //    float f;
17 
18 #pragma pack(1)//按1字节对齐补齐
19 
20     struct S1 {
21 double d;
22 char   c;
23 int    i;
24 short  h;
25 }; // DDDDDDDDCIIIIHH, 15
26 
27 
28 #pragma pack()//按照系统缺省方式对齐补齐
29     struct S2 {
30 double d;
31 char   c;
32 int    i;
33 short  h;
34 }; // DDDDDDDDCXXXIIIIHHXX, 20
35 
36 
37 #pragma pack(8)
38 
39     struct S3 {
40 double d;
41 char   c;
42 int    i;
43 short  h;
44 }; // DDDDDDDDCXXXIIIIHHXX, 20
45 
46 #pragma pack(4)
47 
48     struct S4 {
49 double d;
50 char   c;
51 int    i;
52 short  h;
53 }; // DDDDDDDDCXXXIIIIHH, 20 
54 
55 #pragma pack()
56 
57     printf ("S1: %u字节\n", sizeof (struct S1));
58     printf ("S2: %u字节\n", sizeof (struct S2));
59     printf ("S3: %u字节\n", sizeof (struct S3));
60     printf ("S4: %u字节\n", sizeof (struct S4));
61 return 0;
62 
63 escape:
64     printf ("goto到第%d行!\n", __LINE__);
65 return 0;
66 
67 }

 

 

 

10. 预定义宏
~~~~~~~~~~~

__BASE_FILE__      // 正在编译的源文件名
__FILE__                   // 所在文件名
__LINE__                  // 行号
__FUNCTION__      // 函数名
__func__                   // 同__FUNCTION__
__DATE__                 // 日期
__TIME__                  // 时间
__INCLUDE_LEVEL__    // 包含层数,从0开始
__cplusplus                        // C++编译器将其定义为1, C编译器不定义该宏

范例:print.h、predef.h、predef.c

 1 /*
 2  * 文件名:print.h
 3  *
 4 &nbsp;* 预编译宏练习
 5 &nbsp;* */
 6 #ifndef _PRINT_H
 7 #define _PRINT_H
 8 
 9 #include <stdio.h>
10 
11 void print (void) {
12     printf ("__BASE_FILE__     : %s\n", __BASE_FILE__);//输出当前编译文件
13     printf ("__FILE__          : %s\n", __FILE__);//输出__FILE__当前所在文件
14     printf ("__LINE__          : %d\n", __LINE__);
15     printf ("__FUNCTION__      : %s\n", __FUNCTION__);
16     printf ("__func__          : %s\n", __func__);
17     printf ("__DATE__          : %s\n", __DATE__);
18     printf ("__TIME__          : %s\n", __TIME__);
19     printf ("__INCLUDE_LEVEL__ : %d\n", __INCLUDE_LEVEL__);//输出被包含了多好层,从0开始计数
20 #ifdef __cplusplus
21     printf ("__cplusplus       : %d\n", __cplusplus);
22 #endif // __cplusplus
23 }
24 
25 #endif // _PRINT_H
1 /*
2  * 文件名:predef.h
3  * */
4 #ifndef _PREDEF_H
5 #define _PREDEF_H
6 
7 #include "print.h"
8 
9 #endif // _PREDEF_H
 1 /*
 2  * 文件名:predef.c
 3  *
 4  * */
 5 #include "predef.h"
 6 
 7 int main (void) {
 8     print ();
 9     return 0;
10 }

 

# gcc predef.c

__BASE_FILE__     : predef.c
__FILE__          : print.h
__LINE__          : 9
__FUNCTION__      : print
__func__          : print
__DATE__          : May 25 2013
__TIME__          : 07:31:39
__INCLUDE_LEVEL__ : 2

# g++ predef.c

__BASE_FILE__     : predef.c
__FILE__          : print.h
__LINE__          : 9
__FUNCTION__      : print
__func__          : print
__DATE__          : May 25 2013
__TIME__          : 07:32:33
__INCLUDE_LEVEL__ : 2
__cplusplus       : 1





五、库

------

1. 合久必分/*过大的源程序编译耗时不易维护*/——增量编译——易于维护。
   分久必合/*过多的.o文件链接阶段不易管理*/——库——易于使用。

2. 链接静态库是将库中的被调用代码复制到调用模块中,而链接共享库则只是在调用模块中,嵌入被调用代码在库中的(相对)地址。

3. 静态库占用空间非常大,不易修改但执行效率高。共享库占用空间小,易于修改但执行效率略低。

4. 静态库的缺省扩展名是.a,共享库的缺省扩展名是.so。

六、静态库

----------

(1)静态库是若干目标文件(*.o)归档打包后的文件,一般后缀为(*.a);链接阶段链接静态库是将库中的被调用代码复制到调用模块中,也因此得名"静态库"。

(2)静态库的制作步骤:

            step1: 编译出待打包成静态库的目标文件(*.o)

            step2: 使用打包归档命令ar将目标文件归档打包成静态库(libXXX.a)

(3)使用静态库(libXXX.a)的方法:

            方式一:gcc加编译选项-lXXX -L库路径

            方式二:gcc直接加上库,例如gcc -o a.out -c main.c libXXX.a

            方式三:使得环境变量LIBRARY_PATH加有库所在目录,然后使用gcc加编译选项-lXXX

(4)链接阶段链接了静态库而成的可执行文件运行方法:

            直接运行即可!在链接阶段已将所调用的函数的二进制代码复制到可执行程序中,因此运行时不需要依赖静态库。



1. 创建静态库
~~~~~~~~~~~~~

1) 编辑源程序:.c/.h
2) 编译成目标文件:gcc -c xxx.c -> xxx.o
3) 打包成静态库文件:ar -r libxxx.a xxx.o ...

# gcc -c calc.c
# gcc -c show.c
# ar -r libmath.a calc.o show.o

ar指令:ar [选项] 静态库文件名 目标文件列表
-r - 将目标文件插入到静态库中,已存在则更新
-q - 将目标文件追加到静态库尾
-d - 从静态库中删除目标文件
-t - 列表显示静态库中的目标文件
-x - 将静态库展开为目标文件

注意:提供静态库的同时也需要提供头文件。

2. 调用静态库
~~~~~~~~~~~~~

# gcc main.c libmath.a (直接法)

或通过LIBRARY_PATH环境变量指定库路径:

# export LIBRARY_PATH=$LIBRARY_PATH:.
# gcc main.c -lmath (环境法)

或通过gcc的-L选项指定库路径:

# unset LIBRARY_PATH
# gcc main.c -lmath -L. (参数法)

一般化的方法:gcc .c/.o -l<库名> -L<库路径>

3. 运行
~~~~~~~

# ./a.out

在可执行程序的链接阶段已将所调用的函数的二进制代码复制到可执行程序中,因此运行时不需要依赖静态库。

范例:static/

七、共享库
----------

(1)共享库,共享库是将若干具有位置无关码(PIC)特性的目标文件(*.o)链接而成的文件,一般文件后缀是*.so 。 任何时候,嵌入被调用代码在库中的(相对)地址就可以使用库中代码,库中代码可以同时被多进程使用,也因此得名"共享库"。链接可执行文件时立刻嵌入被调 用代码在库中的(相对)地址称为"静态加载共享库",而程序中通过使用<dlfcn.h>里声明的dlopen(), dlclose(), dlsym(), dlerror()实现在运行期动态加载共享库中被调用代码称为"动态加载共享库"。显然,无论是"静态加载共享库"还是"动态加载共享库"都没有将库中 被调用代码复制到可执行文件中,所以调用了共享库中代码的可执行文件都无法脱离共享库单独运行。

(2)动态库的制作步骤:

            step1: 编译出具有位置无关码(PIC)特性的目标文件(*.o)

            step2: 使用gcc加选项-shared将目标文件链接成共享库(libXXX.so)

(3)"静态加载共享库"方式使用共享库(libXXX.so)的方法:

            方式一:gcc加编译选项-lXXX -L库路径

            方式二:gcc直接加上库,例如gcc -o a.out -c main.c libXXX.so

            方式三:使得环境变量LIBRARY_PATH加有库所在目录,然后使用gcc加编译选项-lXXX

(4)"动态加载共享库"方式使用共享库(libXXX.so)的方法:

 1 /*
 2  * 动态加载共享库例程
 3 &nbsp;* 
 4  * 例程中的共享库说明:
 5  * 共享库名称:libXXX.so
 6  * 共享库中的函数:add()/sub()/show()
 7 &nbsp;* */
 8 #include <stdio.h>
 9 #include <dlfcn.h>
10 
11 typedef int (*PFUNC_CALC) (int, int);
12 typedef void (*PFUNC_SHOW) (int, char, int, int);
13 
14 int main (void) {
15     void* handle = dlopen ("shared/libXXX.so", RTLD_NOW);
16     if (! handle) {
17         fprintf (stderr, "dlopen: %s\n", dlerror ());
18         return -1;
19     }
20 
21     PFUNC_CALC add = (PFUNC_CALC)dlsym (handle, "add");
22     if (! add) {
23         fprintf (stderr, "dlsym: %s\n", dlerror ());
24         return -1;
25     }
26 
27     PFUNC_CALC sub = (PFUNC_CALC)dlsym (handle, "sub");
28     if (! sub) {
29         fprintf (stderr, "dlsym: %s\n", dlerror ());
30         return -1;
31     }
32 
33     PFUNC_SHOW show = (PFUNC_SHOW)dlsym (handle, "show");
34     if (! show) {
35         fprintf (stderr, "dlsym: %s\n", dlerror ());
36         return -1;
37     }
38 
39     show (30, '+', 20, add (30, 20));
40     show (30, '-', 20, sub (30, 20));
41 
42     if (dlclose (handle)) {
43         fprintf (stderr, "dlclose: %s\n", dlerror ());
44         return -1;
45     }
46 
47     return 0;
48 }

 

因动态加载共享库时候使用了<dlfcn.h>里面声明的dlopen(), dlclose(), dlsym(), dlerror()才实现了在运行期动态加载共享库中被调用代码,而这几个函数在库llibdl.so里。所以上述例程 编译链接时候应使用命令: gcc main.c -ldl


(5)链接阶段链接了共享库的可执行文件运行方法:

            链接可执行文件时立刻嵌入被调用代码在库中的(相对)地址称为"静态加载共享库",而程序中通过使用<dlfcn.h>里声明的 dlopen(), dlclose(), dlsym(), dlerror()实现在运行期动态加载共享库中被调用代码称为"动态加载共享库"。显然,无论是"静态加载共享库"还是"动态加载共享库"都没有将库中 被调用代码复制到可执行文件中,所以调用了共享库中代码的可执行文件都无法脱离共享库单独运行。所以,运行时需要保证 LD_LIBRARY_PATH 环境变量中包含共享库所在的路径!(p.s.   LD_LIBRARY_PATH是供进程加载器loader用的,如果loader找不到共享库文件会报出No such file or director)

 


1. 创建共享库

~~~~~~~~~~~~~

1) 编辑源程序:.c/.h
2) 编译成目标文件:gcc -c -fpic xxx.c -> xxx.o
3) 链接成共享库文件:gcc -shared xxx.o ... -o libxxx.so

# gcc -c -fpic calc.c
# gcc -c -fpic show.c
/*
   选项-fpic/-fPIC可以使得目标文件中地址采用相对地址, loader可以将其映射到内存的任何位置,更便于多程序共享。

PIC (Position Independent Code):位置无关代码。
可执行程序加载它们时,可将其映射到其地址空间的
任何位置。

-fPIC : 大模式,生成代码比较大,运行速度比较慢,
        所有平台都支持。

-fpic : 小模式,生成代码比较小,运行速度比较快,
        仅部分平台支持。
*/

# gcc -shared calc.o show.o -o libmath.so
/*
   -shared的实质是在进行链接,与普通链接不一样的是普通链接需要主函数。
 
(p.s.共享库libXXX.so生成后linux操作系统会默认给执行权限,这是因为共享库本身就具有可运行特质。可以粗略地说给共享库加上main就可以运行。)

*/
另外,也可以一次性完成编译和链接:
# gcc -shared -fpic calc.c show.c -o libmath.so
注意:提供共享库的同时也需要提供头文件。

2. 调用共享库
~~~~~~~~~~~~~

# gcc main.c libmath.so (直接法)

或通过LIBRARY_PATH环境变量指定库路径:

# export LIBRARY_PATH=$LIBRARY_PATH:.
# gcc main.c -lmath (环境法)

或通过gcc的-L选项指定库路径(一般化的方法(参数法) ):gcc .c/.o -l<库名> -L<库路径>

# unset LIBRARY_PATH
# gcc main.c -lmath -L.   //-l<库名>选项默认先去找叫做该名称的共享库,找不到的情况下再找该名称的静态库。


3. 运行
~~~~~~~

运行时需要保证 LD_LIBRARY_PATH 环境变量中包含共享库所在的路径:
(LD_LIBRARY_PATH是供进程加载器loader用的,如果loader找不到共享库文件会报出No such file or director)
举例:
# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
# ./a.out

在可执行程序的链接阶段,
并不将所调用函数的二进制代码复制到可执行程序中,
而只是将该函数在共享库中的地址嵌入到可执行程序中,
因此运行时需要依赖共享库。

范例:shared/

在有同名静态库和共享库的情况下,gcc缺省链接共享库,可通过-static选项强制链接静态库。
如:gcc -static hello.c

八、动态加载共享库

程序自身通过函数调用来实现共享库中被调用代码的加载称为"动态加载共享库"。
------------------

#include <dlfcn.h>

1. 加载共享库
~~~~~~~~~~~~~

void* dlopen (//这里返回值类型是void*,只是将返回的句柄当void*来看待了
    const char* filename, // 共享库路径,若只给文件名,则根据LD_LIBRARY_PATH环境变量搜索
    int         flag      // 加载方式 (RTLD_LAZY和RTLD_NOW) flag取值:
                  //RTLD_LAZY - 延迟加载,使用共享库中的符号(如调用函数)时才加载。
                  //RTLD_NOW  - 立即加载。
);
成功返回共享库句柄,失败返回NULL。
(p.s. 句柄是操作系统内核维护的一个句柄表或句柄数组的键或句柄数组下标。 往往操作系统内核为了防应用程序修改目标存储区都提供的是句柄。)

               交给
           应用程序
               使用
                   ^      -----------------------------------------------
              handle |     1     |     2     |     3     |     4     | ...
                           ----------句柄表----------------------------
              pointer |     *     |     *      |     *      |     *      | ...    
                 v         ----------------------------------------------
              指向
           共享库
        所在存储区




2. 获取函数地址
~~~~~~~~~~~~~~~

void* dlsym (
    void*       handle, // 共享库句柄
    const char* symbol  // 函数名
);

成功返回函数地址,失败返回NULL。

3. 卸载共享库
~~~~~~~~~~~~~
int dlclose ( void* handle/*共享库句柄*/);
成功返回0,失败返回非零。

4. 获取错误信息
~~~~~~~~~~~~~~~
char* dlerror (void);
有错误发生则返回错误信息字符串指针,否则返回NULL。

范例:load.c

注意:链接时不再需要-lmath,但需要-ldl。/*程序中使用的动态加载函数dlopen dlclose dlsym dlerror在库dl中*/

九、辅助工具
------------

nm: 查看目标文件、可执行文件、静态库、
共享库中的符号列表。
举例:nm hello.o
0000000000000000 T main
                 U puts   /*这里的U代表puts是标准库的函数。T表示是自定义的函数*/

hexdump可以查看通过十六进制文本显式二进制文件。


ldd:
查看可执行文件和共享库的动态依赖。如果该可执行文件不依赖共享库,那么返回"不是动态可执行文件"。

ldconfig: 共享库管理。

事先将共享库的路径信息写入/etc/ld.so.conf配置文件中,
ldconfig根据该配置文件生成/etc/ld.so.cache缓冲文件,
并将该缓冲文件载入内存,借以提高共享库的加载效率。

系统启动时自动执行ldconfig,但若修改了共享库配置,
则需要手动执行该程序。

strip: 减肥。去除目标文件、可执行文件、静态库和共享库中的符号列表、调试信息等。 减肥后nm命令就无法再显式可执行文件里面的符号信息了。因为符号信息被strip删除了。

objdump: 显示二进制模块的反汇编信息。

# objdump -S a.out

指令地址    机器指令                汇编指令
--------    --------------------    ---------------------
8048514:    55                      push %ebp
8048515:    89 e5                   mov  %esp,%ebp
8048517:    83 e4 f0                and  $0xfffffff0,%esp
804851a:    83 ec 20                sub  $0x20,%esp
804851d:    c7 44 24 04 02 00 00    movl $0x2,0x4(%esp)

作业:编写一个函数diamond(),打印一个菱形,其高度、
宽度、实心或者空心以及图案字符,均可通过参数设置。
分别封装为静态库libdiamond_static.a和
动态库libdiamond_shared.so,并调用之。

代码:diamond/

posted on 2015-08-21 04:50  來時的路  阅读(250)  评论(0编辑  收藏  举报