Top Banner
Linux Linux 操操操操操操操操操 操操操 [email protected] [email protected]
25

Linux 操作系统

Jan 20, 2016

Download

Documents

Miroslav Emling

Linux 操作系统. 裘江南 [email protected]. 第 6 章 Linux 下的 C 编程. 本章要点. 了解 Linux 下的 C 编程方法 gcc 和 make 工具的使用 编译内核的方法. C 是一种在 UNIX 操作系统的中被广泛使用的通用编程语言。 主要原因有: 它是一种非常通用的语言。 在任何一种计算机上都有至少一种能用的 C 编译器。 并且它的语法和函数库在不同的平台上都是统一的。 具有简洁、灵活,表达能力强、目标代码质量高等特点。 C 是所有版本的 UNIX 上的系统语言。 - PowerPoint PPT Presentation
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Linux 操作系统

LinuxLinux 操作系操作系统统

裘江南裘江南 [email protected][email protected]

Page 2: Linux 操作系统

第第 66 章 章 LinuxLinux 下的下的 CC 编程编程

了解 Linux 下的 C编程方法 gcc 和 make 工具的使用 编译内核的方法

本章要点本章要点

Page 3: Linux 操作系统

6.1 Linux6.1 Linux 下的下的 CC 编程编程 C C 是一种在 是一种在 UNIX UNIX 操作系统的中被广泛使用的通用编程语言。 主要原操作系统的中被广泛使用的通用编程语言。 主要原因有: 因有:

它是一种非常通用的语言。 在任何一种计算机上都有至少一种能用它是一种非常通用的语言。 在任何一种计算机上都有至少一种能用的 的 C C 编译器。 并且它的语法和函数库在不同的平台上都是统一的。 编译器。 并且它的语法和函数库在不同的平台上都是统一的。 具有简洁、灵活,表达能力强、目标代码质量高等特点。 具有简洁、灵活,表达能力强、目标代码质量高等特点。 C C 是所有版本的是所有版本的 UNIXUNIX 上的系统语言。 上的系统语言。

在在 8989 年美国国家标准协会年美国国家标准协会 ANSIANSI 发布了一个被称为 发布了一个被称为 ANSI C ANSI C 的 的 C C 语言标准。语言标准。 ANSI C ANSI C 的目标是为各种操作系统上的 的目标是为各种操作系统上的 C C 程序提供可移植性保程序提供可移植性保证。 该标准不仅定义了 证。 该标准不仅定义了 C C 编程语言的语发和语义,而且还定义了一个标编程语言的语发和语义,而且还定义了一个标准库。这个库可以根据头文件划分为准库。这个库可以根据头文件划分为 15 15 个部分,其中包括:字符类型个部分,其中包括:字符类型 <ct<ctype.h>ype.h> 、错误码 、错误码 <errno.h><errno.h> 、浮点常数 、浮点常数 <float.h><float.h> 、数学常数、数学常数 <math.<math.h>h> 、标准定义、标准定义 <stddef.h><stddef.h> 、标准 、标准 I/O<stdio.h>I/O<stdio.h> 、工具函数、工具函数 <stdlib.h><stdlib.h>、字符串操作、字符串操作 <string.h><string.h> 、时间和日期、时间和日期 <time.h><time.h> 、可变参数表、可变参数表 <stdarg.<stdarg.h>h> 、信号、信号 <signal.h><signal.h> 、非局部跳转、非局部跳转 <setjmp.h><setjmp.h> 、本地信息、本地信息 <local.h><local.h>、程序断言、程序断言 <assert.h> <assert.h> 等等。 等等。 在在 8080 年代还出现了一种 年代还出现了一种 C C 的面向对象的扩展称为 的面向对象的扩展称为 C++C++ 。 。 Linux Linux 上可用的 上可用的 C C 编译器是 编译器是 GNU C GNU C 编译器编译器 (gcc), (gcc), 它建立在自由软它建立在自由软件基金会的编程许可证的基础上件基金会的编程许可证的基础上 , , 因此可以自由发布。因此可以自由发布。

Page 4: Linux 操作系统

6.2 gcc6.2 gcc 简介(简介( 11 )) Linux Linux 中最重要的软件开发工具是 中最重要的软件开发工具是 gccgcc 。。 gcc gcc 是 是 GNU GNU 的 的 C C 和 和 C++ C++ 编译器。实际上,编译器。实际上, ggcc cc 能够编译三种语言:能够编译三种语言: CC 、、 C++ C++ 和 和 Object CObject C 。利用 。利用 gcc gcc 命令可同时编译并连接 命令可同时编译并连接 C C 和 和 C++ C++ 源程序。也可以对几个 源程序。也可以对几个 C C 源文件利用 源文件利用 gcc gcc 编译、连接并生成可执行文件。编译、连接并生成可执行文件。例如,有 例如,有 main.c main.c 和 和 factorial.c factorial.c 两个源文件,现在要编译生成一个计算阶乘的程序。 两个源文件,现在要编译生成一个计算阶乘的程序。 /*-- /*-- 清单 清单 factorial.h --*/ factorial.h --*/ #include <stdio.h> #include <stdio.h> #include <stdlib.h> #include <stdlib.h> int factorial (int n)int factorial (int n) { { if (n <= 1) return 1; if (n <= 1) return 1; else return factorial (n - 1) * n; else return factorial (n - 1) * n; } } /*-- main.c --*//*-- main.c --*/#include <stdio.h> #include <stdio.h> #include <stdlib.h>#include <stdlib.h>#include <factorial.h > #include <factorial.h > int factorial (int n); int factorial (int n); int main (int argc, char **argv)int main (int argc, char **argv){{ int n;int n; if (argc < 2) if (argc < 2) { printf ("Usage: %s n\n", argv [0]); { printf ("Usage: %s n\n", argv [0]); return -1; } return -1; } else else { n = atoi (argv[1]); { n = atoi (argv[1]); printf ("Factorial of %d is %d.\n", n, factorial (n));printf ("Factorial of %d is %d.\n", n, factorial (n)); } } return 0; }return 0; }

Page 5: Linux 操作系统

6.2 gcc6.2 gcc 简介(简介( 22 ))利用如下的命令可编译生成可执行文件,并执行程序: 利用如下的命令可编译生成可执行文件,并执行程序: $ gcc -o factorial main.c factorial.h $ gcc -o factorial main.c factorial.h $ ./factorial 5 $ ./factorial 5 Factorial of 5 is 120. Factorial of 5 is 120. gccgcc 编译器能将编译器能将 CC 、、 C++C++ 语言源程序、汇编序和目标程序编译、连接成可执行文件,若没有给语言源程序、汇编序和目标程序编译、连接成可执行文件,若没有给出可执行文件的名字,出可执行文件的名字, gccgcc 将生成一个名为将生成一个名为 a.outa.out 的文件。的文件。 gccgcc 通过后缀来区别输入文件的类别通过后缀来区别输入文件的类别,下面我们来介绍,下面我们来介绍 gccgcc 所遵循的部分所遵循的部分约定规则约定规则::.c.c 为后缀的文件,为后缀的文件, CC 语言源代码文件;语言源代码文件;.a.a 为后缀的文件,是由目标文件构成的档案库文件; 为后缀的文件,是由目标文件构成的档案库文件; .C.C ,, .cc.cc 或或 .cxx .cxx 为后缀的文件,是为后缀的文件,是 C++C++ 源代码文件; 源代码文件; .h.h 为后缀的文件,是程序所包含的头文件; 为后缀的文件,是程序所包含的头文件; .i .i 为后缀的文件,是已经预处理过的为后缀的文件,是已经预处理过的 CC 源代码文件; 源代码文件; .ii.ii 为后缀的文件,是已经预处理过的为后缀的文件,是已经预处理过的 C++C++ 源代码文件; 源代码文件; .m.m 为后缀的文件,是为后缀的文件,是 Objective-CObjective-C 源代码文件; 源代码文件; .o.o 为后缀的文件,是编译后的目标文件; 为后缀的文件,是编译后的目标文件; .s.s 为后缀的文件,是汇编语言源代码文件; 为后缀的文件,是汇编语言源代码文件; .S.S 为后缀的文件,是经过预编译的汇编语言源代码文件。 为后缀的文件,是经过预编译的汇编语言源代码文件。 gcc gcc 命令只能编译 命令只能编译 C++ C++ 源文件,而不能自动和 源文件,而不能自动和 C++ C++ 程序使用的库连接。因此,通常使用 程序使用的库连接。因此,通常使用 g+g++ + 命令来完成 命令来完成 C++ C++ 程序的编译和连接,该程序会自动调用 程序的编译和连接,该程序会自动调用 gcc gcc 实现编译。实现编译。//hello.C//hello.C#include <iostream.h>#include <iostream.h> void main (void) void main (void) { cout << "Hello, world!" << endl; }{ cout << "Hello, world!" << endl; } 则可以如下调用 则可以如下调用 g++ g++ 命令编译、连接并生成可执行文件: 命令编译、连接并生成可执行文件: $ g++ -o hello hello.C $ g++ -o hello hello.C $ ./hello Hello, world! $ ./hello Hello, world!

Page 6: Linux 操作系统

6.3 gcc6.3 gcc 的执行过程的执行过程 虽然我们称虽然我们称 gccgcc 是是 CC 语言的编译器,但使用语言的编译器,但使用 gccgcc 由由 CC 语言源代码文语言源代码文件生成可执行文件的过程不仅仅是编译的过程,而是要经历四个相互关联件生成可执行文件的过程不仅仅是编译的过程,而是要经历四个相互关联的步骤∶的步骤∶预处理预处理 (( 也称预编译,也称预编译, Preprocessing)Preprocessing)编译编译 (Compilation)(Compilation)汇编汇编 (Assembly)(Assembly)连接连接 (Linking) (Linking)

命令命令 gccgcc首先调用首先调用 cppcpp 进行预处理,在预处理过程中,对源代码文进行预处理,在预处理过程中,对源代码文件中的文件包含件中的文件包含 (include)(include) 、预编译语句、预编译语句 (( 如宏定义如宏定义 definedefine 等等 ))进行分析进行分析。。 接着调用接着调用 cc1cc1进行编译,这个阶段根据输入文件生成以进行编译,这个阶段根据输入文件生成以 .o.o 为后缀的为后缀的目标文件。目标文件。 汇编过程是针对汇编语言的步骤,调用汇编过程是针对汇编语言的步骤,调用 asas 进行工作,一般来讲,进行工作,一般来讲, ..SS 或或 .s.s 为后缀的汇编语言源代码文件汇编之后都生成以为后缀的汇编语言源代码文件汇编之后都生成以 .o.o 为后缀的目标文为后缀的目标文件。件。 当所有的目标文件都生成之后,当所有的目标文件都生成之后, gccgcc就调用就调用 ldld 来完成最后的关键性来完成最后的关键性工作,这个阶段就是连接。在连接阶段,所有的目标文件被安排在可执行工作,这个阶段就是连接。在连接阶段,所有的目标文件被安排在可执行程序中的恰当的位置,同时,该程序所调用到的库函数也从各自所在的函程序中的恰当的位置,同时,该程序所调用到的库函数也从各自所在的函数库中连到合适的地方。 数库中连到合适的地方。

Page 7: Linux 操作系统

6.4 Linux6.4 Linux 下函数库(下函数库( 11 )) 一个“程序函数库”就是一个文件包含了一些编译好的代码和数据一个“程序函数库”就是一个文件包含了一些编译好的代码和数据,这些编译好的代码和数据可以在事后供其他的程序使用。程序函数库可以,这些编译好的代码和数据可以在事后供其他的程序使用。程序函数库可以使整个程序更加模块化,更容易重新编译,而且更方便升级。可分为两种类使整个程序更加模块化,更容易重新编译,而且更方便升级。可分为两种类型:型:静态函数库静态函数库 (static libraries)(static libraries) :是一个普通的目标文件的集合,一般用:是一个普通的目标文件的集合,一般用““ .a”.a” 作为文件的后缀。静态函数库和共享函数库相比有很多的缺点,占作为文件的后缀。静态函数库和共享函数库相比有很多的缺点,占用内存空间多。但使用用内存空间多。但使用 ELFELF 格式的静态库函数生成的代码可以比使用共享格式的静态库函数生成的代码可以比使用共享函数库的程序运行速度上快一些。函数库的程序运行速度上快一些。 可以用可以用 arar 这个程序来创建一个静态函数库文件,或者往一个已经存这个程序来创建一个静态函数库文件,或者往一个已经存在地静态函数库文件添加新的目标代码。在地静态函数库文件添加新的目标代码。 例如例如 , , 把把 file1.ofile1.o 和和 file2.ofile2.o 加入到加入到 my_library.amy_library.a 这个函数库文件这个函数库文件: : ar rcs my_library.a file1.o file2.oar rcs my_library.a file1.o file2.o 然后运行 然后运行 ranlibranlib ,以给库加入一些索引信息。,以给库加入一些索引信息。

Page 8: Linux 操作系统

6.4 Linux6.4 Linux 下函数库(下函数库( 22 ))共享函数库共享函数库 (shared libraries)(shared libraries) :当一个可执行程序在启动的时候被加:当一个可执行程序在启动的时候被加载的函数。每个共享函数库都有个特殊的名字,称作“载的函数。每个共享函数库都有个特殊的名字,称作“ soname”soname” 。。 sonsonameame 名字命名必须以“名字命名必须以“ lib”lib” 作为前缀,然后是函数库的名字,然后是“作为前缀,然后是函数库的名字,然后是“.so”.so” ,最后是版本号信息。,最后是版本号信息。 优点:多进程使用同一函数库;修改函数库不需重新连编。 优点:多进程使用同一函数库;修改函数库不需重新连编。 安装一个新版本的函数库的时候,要先将这些函数库文件拷贝到一安装一个新版本的函数库的时候,要先将这些函数库文件拷贝到一些特定的目录中,运行些特定的目录中,运行 ldconfigldconfig 就可以。就可以。 ldconfigldconfig 检查已经存在的库文检查已经存在的库文件,然后创建件,然后创建 sonamesoname 的符号链接到真正的函数库,同时设置的符号链接到真正的函数库,同时设置 /etc/ld.so/etc/ld.so.cache.cache 这个缓冲文件。 这个缓冲文件。 例如,创建两个目标文件 例如,创建两个目标文件 (a.o(a.o 和和 b.o)b.o) ,然后创建一个包含,然后创建一个包含 a.oa.o 和和 bb.o.o 的共享函数库。的共享函数库。

gcc -fPIC -g -c -Wall a.cgcc -fPIC -g -c -Wall b.cgcc -shared -Wl,-soname,liblusterstuff.so.1 -o liblusterstuff.so.1.0.1 a.o b.o -lcgcc -fPIC -g -c -Wall a.cgcc -fPIC -g -c -Wall b.cgcc -shared -Wl,-soname,liblusterstuff.so.1 -o liblusterstuff.so.1.0.1 a.o b.o -lc注:” 注:” -fP-fPIC ”IC ” 是位置无关参数, ”是位置无关参数, ” -g”-g” 和“-和“- Wall”Wall” 参数不是必须的。参数不是必须的。

Page 9: Linux 操作系统

6.4 Linux6.4 Linux 下函数库(下函数库( 33 ))函数库和头文件的保存位置函数库和头文件的保存位置a. a. 函数库函数库/lib/lib :系统必备共享函数库 :系统必备共享函数库 /usr/lib/usr/lib :标准共享函数库和静态函数库 :标准共享函数库和静态函数库 /usr/i486-linux-libc5/lib/usr/i486-linux-libc5/lib :: libc5 libc5 兼容性函数库 兼容性函数库 /usr/X11R6/lib/usr/X11R6/lib :: X11R6 X11R6 的函数库 的函数库 /usr/local/lib/usr/local/lib :本地函数库 :本地函数库

b. b. 头文件头文件/usr/include/usr/include :系统头文件 :系统头文件 /usr/local/include/usr/local/include :本地头文件 :本地头文件

c. c. 共享函数库的相关配置和命令共享函数库的相关配置和命令/etc/ld.so.conf/etc/ld.so.conf :包含共享库的搜索位置 :包含共享库的搜索位置 ldconfigldconfig :共享库管理工具,一般在更新了共享库之后要运行该命令 :共享库管理工具,一般在更新了共享库之后要运行该命令 lddldd :可查看可执行文件所使用的共享函数库 :可查看可执行文件所使用的共享函数库

Page 10: Linux 操作系统

6.5 gcc6.5 gcc 的基本用法和选项 (的基本用法和选项 ( 11 )) gccgcc 最基本的语法∶最基本的语法∶ gcc [options] [filenames]gcc [options] [filenames] 其中编译器所需要的参数其中编译器所需要的参数 options options : : -c-c ,只编译,不连接成为可执行文件,编译器只是由输入的,只编译,不连接成为可执行文件,编译器只是由输入的 .c.c 等源代码文等源代码文件生成件生成 .o.o 为后缀的目标文件,通常用于编译不包含主程序的子程序文件。 为后缀的目标文件,通常用于编译不包含主程序的子程序文件。

-o output_filename-o output_filename ,确定输出文件的名称为,确定输出文件的名称为 output_filenameoutput_filename ,同,同时这个名称不能和源文件同名。如果不给出这个选项,时这个名称不能和源文件同名。如果不给出这个选项, gccgcc就给出预设的就给出预设的可执行文件可执行文件 a.outa.out 。 。 -g-g ,产生符号调试工具,产生符号调试工具 (GNU(GNU 的的 gdb)gdb) 所必要的调试符号信息,要想对源所必要的调试符号信息,要想对源代码进行调试,我们就必须加入这个选项。 代码进行调试,我们就必须加入这个选项。 -O-O ,对程序进行优化编译、连接,采用这个选项,整个源代码会在编译、,对程序进行优化编译、连接,采用这个选项,整个源代码会在编译、连接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,连接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,但是,编译、连接的速度就相应地要慢一些。 但是,编译、连接的速度就相应地要慢一些。 -O2-O2 ,比,比 -O-O 更好的优化编译、连接,当然整个编译、连接过程会更慢。更好的优化编译、连接,当然整个编译、连接过程会更慢。-O3 -O3 比 比 -O2 -O2 更进一步优化,包括 更进一步优化,包括 inline inline 函数。 函数。 -I dirname-I dirname ,将,将 dirnamedirname 所指出的目录加入到程序头文件目录列表中,所指出的目录加入到程序头文件目录列表中,是在预编译过程中使用的参数。是在预编译过程中使用的参数。

Page 11: Linux 操作系统

6.5 gcc6.5 gcc 的基本用法和选项 (的基本用法和选项 ( 22 ))-L dirname-L dirname ,将,将 dirnamedirname 所指出的目录加入到程序函数库文件的目录列所指出的目录加入到程序函数库文件的目录列表中,是在连接过程中使用的参数。 表中,是在连接过程中使用的参数。 -l name-l name ,在连接时,装载名字为“,在连接时,装载名字为“ libname.a”libname.a” 的函数库,该函数库位的函数库,该函数库位于系统预设的目录或者由于系统预设的目录或者由 -L-L 选项确定的目录下。例如,选项确定的目录下。例如, -l m-l m 表示连接名表示连接名为“为“ libm.a”libm.a” 的数学函数库。的数学函数库。-ansi -ansi 只支持 只支持 ANSI ANSI 标准的 标准的 C C 语法。这一选项将禁止 语法。这一选项将禁止 GNU C GNU C 的某些特的某些特色。 色。 -D MACRO -D MACRO 以字符串“以字符串“ 1”1” 定义 定义 MACRO MACRO 宏。 宏。 -D MACRO=DEFN -D MACRO=DEFN 以字符串“以字符串“ DEFN”DEFN” 定义 定义 MACRO MACRO 宏。 宏。 -E -E 只运行 只运行 C C 预编译器。 预编译器。 -m486 -m486 针对 针对 486 486 进行代码优化。 进行代码优化。 -O0 -O0 不进行优化处理。不进行优化处理。-shared -shared 生成共享目标文件。通常用在建立共享库时。 生成共享目标文件。通常用在建立共享库时。 -static -static 禁止使用共享连接。 禁止使用共享连接。 -U MACRO -U MACRO 取消对 取消对 MACRO MACRO 宏的定义。 宏的定义。 -w -w 不生成任何警告信息。 不生成任何警告信息。 -Wall -Wall 生成所有警告信息。生成所有警告信息。-S -S 编译选项告诉 编译选项告诉 gcc gcc 在为 在为 C C 代码产生了汇编语言文件后停止编译。代码产生了汇编语言文件后停止编译。

Page 12: Linux 操作系统

6.5 gcc6.5 gcc 的基本用法和选项 (的基本用法和选项 ( 33 ))例例 11 :一个程序名为:一个程序名为 test.ctest.c 源代码文件,要生成一个可执行文件,最简单的源代码文件,要生成一个可执行文件,最简单的办法就是∶ 办法就是∶ $gcc test.c $gcc test.c 预编译、编译连接一次完成,生成一个预编译、编译连接一次完成,生成一个 a.outa.out 的可执行文件。的可执行文件。

例例 22 :源程序由两个文件:源程序由两个文件 testmain.c testmain.c 和和 testsub.ctestsub.c 组成,程序中使用了系组成,程序中使用了系统提供的数学库,同时希望给出的可执行文件为统提供的数学库,同时希望给出的可执行文件为 testtest ,这时的编译命令可,这时的编译命令可以是∶ 以是∶ $gcc testmain.c testsub.c -lm -o test $gcc testmain.c testsub.c -lm -o test 其中,其中, -lm-lm 表示连接系统的数学库表示连接系统的数学库 libm.alibm.a 。 。

除了除了 gcc gcc 这个编译器之外,目前还有一个所谓这个编译器之外,目前还有一个所谓 egcsegcs 编译器。编译器。 egcsegcs 不不是一个全新设计的编译器,而是基于是一个全新设计的编译器,而是基于 gccgcc之上,它具有更先进的最优化特性之上,它具有更先进的最优化特性,同时对,同时对 c++c++ 的支持较好。的支持较好。

Page 13: Linux 操作系统

6.6 make6.6 make 命令的使用 (命令的使用 ( 11)) 在大型的开发项目中,人们通常利用 在大型的开发项目中,人们通常利用 make make 工具来自动完成编译工具来自动完成编译工作。这些工作包括:如果仅修改了某几个源文件,则只重新编译这几个工作。这些工作包括:如果仅修改了某几个源文件,则只重新编译这几个源文件;如果某个头文件被修改了,则重新编译所有包含该头文件的源文源文件;如果某个头文件被修改了,则重新编译所有包含该头文件的源文件。 利用这种自动编译可大大简化开发工作,避免不必要的重新编译。 件。 利用这种自动编译可大大简化开发工作,避免不必要的重新编译。 实际上,实际上, make make 工具通过一个称为 工具通过一个称为 makefile makefile 的文件来完成并自动的文件来完成并自动维护编译工作。维护编译工作。 makefile makefile 需要按照某种语法进行编写,其中说明了如何需要按照某种语法进行编写,其中说明了如何编译各个源文件并连接生成可执行文件,并定义了源文件之间的依赖关系编译各个源文件并连接生成可执行文件,并定义了源文件之间的依赖关系。 当修改了其中某个源文件时,如果其他源文件依赖于该文件,则也要重。 当修改了其中某个源文件时,如果其他源文件依赖于该文件,则也要重新编译所有依赖该文件的源文件。 默认情况下,新编译所有依赖该文件的源文件。 默认情况下, GNU make GNU make 工具在当前工具在当前工作目录中按如下顺序搜索 工作目录中按如下顺序搜索 makefilemakefile : : GNUmakefile GNUmakefile makefile makefile Makefile Makefile

6.6.1 GNU make 6.6.1 GNU make

Page 14: Linux 操作系统

6.6 make6.6 make 命令的使用 (命令的使用 ( 22)) 在在 UNIXUNIX 中,习惯使用中,习惯使用 makefile makefile 作为 作为 makfile makfile 文件文件。如果要使用。如果要使用其他文件作为 其他文件作为 makefilemakefile ,则可利用类 似下面的 ,则可利用类 似下面的 make make 命令选项指定 命令选项指定 makefile makefile 文件: 文件: $ make -f Makefile.debug $ make -f Makefile.debug 例例 11 :一个简单的:一个简单的 makefile makefile prog:prog1.o prog2.o prog:prog1.o prog2.o gcc prog1.o prog2.o -o prog gcc prog1.o prog2.o -o prog prog1.o:prog1.c lib.h prog1.o:prog1.c lib.h gcc -c -I. -o prog1.o prog1.c gcc -c -I. -o prog1.o prog1.c prog2.o:prog2.c prog2.o:prog2.c gcc -c prog2.c gcc -c prog2.c

Page 15: Linux 操作系统

6.6 make6.6 make 命令的使用 (命令的使用 ( 33))

makefile makefile 中一般包含如下内容: 中一般包含如下内容: 需要由 需要由 make make 工具创建的工具创建的项目项目,通常是目标文件和可执行文件。,通常是目标文件和可执行文件。 要创建的项目要创建的项目依赖于哪些文件依赖于哪些文件。 。 创建每个项目时需要创建每个项目时需要运行的命令运行的命令。 。 例如,有一个例如,有一个 C++ C++ 源文件 源文件 test.Ctest.C ,该源文件包含有自定义的头文件 ,该源文件包含有自定义的头文件 test.htest.h,则目标文件 ,则目标文件 test.o test.o 明确依赖于两个源文件:明确依赖于两个源文件: test.C test.C 和 和 test.htest.h 。另外,你。另外,你可能只希望利用 可能只希望利用 g++ g++ 命令来生成 命令来生成 test.o test.o 目标文件。 就可利用如下的 目标文件。 就可利用如下的 makmakefile efile 来定义规则: 来定义规则: # This makefile just is a example. # This makefile just is a example. test.o: test.C test.h test.o: test.C test.h g++ -c -g test.C g++ -c -g test.C 第一个非注释行指定 第一个非注释行指定 test.o test.o 为目标,并且依赖于 为目标,并且依赖于 test.C test.C 和 和 test.h test.h 文件。随后的行指定了如何从目标所依赖的文件建立目标。 当 文件。随后的行指定了如何从目标所依赖的文件建立目标。 当 test.C test.C 或 或 testest.h t.h 文件在编译之后又被修改,则 文件在编译之后又被修改,则 make make 工具可自动重新编译 工具可自动重新编译 test.otest.o 。这种。这种依赖关系在多源文件的程序编译中尤其重要。通过这种依赖关系的定义,依赖关系在多源文件的程序编译中尤其重要。通过这种依赖关系的定义, makmake e 工具可避免许多不必要的编译工作。而工具可避免许多不必要的编译工作。而 Shell Shell 脚本不能根据目标上一次编译脚本不能根据目标上一次编译的时间和目标所依赖的源文件的更新时间而自动判断应当编译哪个源文件。的时间和目标所依赖的源文件的更新时间而自动判断应当编译哪个源文件。

6.6.2 makefile 6.6.2 makefile 基本结构基本结构

Page 16: Linux 操作系统

6.6 make6.6 make 命令的使用 (命令的使用 ( 44))

GNU GNU 的 的 make make 工具还有许多便于表达依赖性关系以及建立目标的命令的工具还有许多便于表达依赖性关系以及建立目标的命令的特 色。其中之一就是变量或宏的定义能力: 特 色。其中之一就是变量或宏的定义能力: # Define macros for name of compiler # Define macros for name of compiler CC = gccCC = gcc# Define a macr o for the CC flags # Define a macr o for the CC flags CCFLAGS = -D_DEBUG -g -m486 CCFLAGS = -D_DEBUG -g -m486 # A rule for building a object file # A rule for building a object file test.o: test.c test.h test.o: test.c test.h $(CC) -c $(CCFLAGS) test.c $(CC) -c $(CCFLAGS) test.c

在上面的例子中,在上面的例子中, CC CC 和 和 CCFLAGS CCFLAGS 就是 就是 make make 的变量。的变量。 GNU makGNU make e 通常称之为变量,而其他 通常称之为变量,而其他 UNIX UNIX 的 的 make make 工具称之为宏。在 工具称之为宏。在 makefile makefile 中引用变量的值时,只需变量名之前添加 中引用变量的值时,只需变量名之前添加 $ $ 符号,如 符号,如 $(CC) $(CC) 和 和 $(CCFLAG$(CCFLAGS)S) 。 。

6.6.3 makefile 6.6.3 makefile 变量变量

一个 一个 makefile makefile 文件中可定义多个目标,利用 文件中可定义多个目标,利用 make make targettarget 命令可指定要命令可指定要编译的目标,如果不指定目标, 则使用第一个目标。通常,编译的目标,如果不指定目标, 则使用第一个目标。通常, makefile makefile 中定义有 中定义有 clean clean 目标,可用来清除编译过程中的中间文件,例如: 目标,可用来清除编译过程中的中间文件,例如: clean: rm -f *.o clean: rm -f *.o 运行 运行 make clean make clean 时,将执行 时,将执行 rm -f *.o rm -f *.o 命令。命令。

Page 17: Linux 操作系统

6.6 make6.6 make 命令的使用 (命令的使用 ( 55)) GNU make GNU make 有许多有许多预定义的变量预定义的变量 , GNU make , GNU make 还将所有的环境变量作为自己的还将所有的环境变量作为自己的预定义变量。下面是一些主要的 预定义预定义变量。下面是一些主要的 预定义 : : $* $* 不包含扩展名的目标文件名称。 不包含扩展名的目标文件名称。 $+ $+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件。所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件。$< $< 第一个依赖文件的名称。 第一个依赖文件的名称。 $? $? 所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚。所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚。$@ $@ 目标的完整名称。 目标的完整名称。 $^ $^ 所有的依赖文件,以空格分开,不包含重复的依赖文件。 所有的依赖文件,以空格分开,不包含重复的依赖文件。 $% $% 如果目标是归档成员,则该变量表示目标的归档成员名称。例如,如果目标名称 为 如果目标是归档成员,则该变量表示目标的归档成员名称。例如,如果目标名称 为 mytarget.so(image.o)mytarget.so(image.o) ,则 ,则 $@ $@ 为 为 mytarget.somytarget.so ,而 ,而 $% $% 为 为 image.oimage.o 。 。 AR AR 归档维护程序的名称,默认值为 归档维护程序的名称,默认值为 arar 。 。 ARFLAGS ARFLAGS 归档维护程序的选项。 归档维护程序的选项。 AS AS 汇编程序的名称,默认值为 汇编程序的名称,默认值为 asas 。 。 ASFLAGS ASFLAGS 汇编程序的选项。 汇编程序的选项。 CC C CC C 编译器的名称,默认值为 编译器的名称,默认值为 cccc 。 。 CFLAGS C CFLAGS C 编译器的选项。 编译器的选项。 CPP C CPP C 预编译器的名称,默认值为 预编译器的名称,默认值为 $(CC) -E$(CC) -E 。 。 CPPFLAGS C CPPFLAGS C 预编译的选项。 预编译的选项。 CXX C++ CXX C++ 编译器的名称,默认值为 编译器的名称,默认值为 g++g++ 。 。 CXXFLAGS C++ CXXFLAGS C++ 编译器的选项。 编译器的选项。 FC FORTRAN FC FORTRAN 编译器的名称,默认值为 编译器的名称,默认值为 f77f77 。 。 FFLAGS FORTRAN FFLAGS FORTRAN 编译器的选项。 编译器的选项。

6.6.4 GNU make 6.6.4 GNU make 的主要预定义变量的主要预定义变量

Page 18: Linux 操作系统

6.6 make6.6 make 命令的使用 (命令的使用 ( 66)) 直接在 直接在 make make 命令的后面键入目标名可建立指定的目标,如果直接运行 命令的后面键入目标名可建立指定的目标,如果直接运行 makmakee ,则建立第一个目标。还可以用 ,则建立第一个目标。还可以用 make -f mymakefile make -f mymakefile 这样的命令指定 这样的命令指定 make make 使使用特定的 用特定的 makefilemakefile ,而不是 默认的 ,而不是 默认的 GNUmakefileGNUmakefile 、、 makefile makefile 或 或 MakefileMakefile 。。 GNU make GNU make 命令还有一些其他选项,下面是 命令还有一些其他选项,下面是 GNU make GNU make 命令的常用命令行选项命令的常用命令行选项命令行选项含义:命令行选项含义: -C DIR -C DIR 在读取 在读取 makefile makefile 之前改变到指定的目录 之前改变到指定的目录 DIRDIR 。 。 -f FILE -f FILE 以指定的 以指定的 FILE FILE 文件作为 文件作为 makefilemakefile 。 。 -h -h 显示所有的 显示所有的 make make 选项。 选项。 -i -i 忽略所有的命令执行错误。 忽略所有的命令执行错误。 -I DIR -I DIR 当包含其他 当包含其他 makefile makefile 文件时,可利用该选项指定搜索目录。 文件时,可利用该选项指定搜索目录。 -n -n 只打印要执行的命令,但不执行这些命令。 只打印要执行的命令,但不执行这些命令。 -p -p 显示 显示 make make 变量数据库和隐含规则。 变量数据库和隐含规则。 -s -s 在执行命令时不显示命令。 在执行命令时不显示命令。 -w -w 在处理 在处理 makefile makefile 之前和之后,显示工作目录。 之前和之后,显示工作目录。 -W FILE -W FILE 假定文件 假定文件 FILE FILE 已经被修改。 已经被修改。

6.6.5 6.6.5 运行 运行 makemake

Page 19: Linux 操作系统

6.7 6.7 用 用 gdb gdb 调试 调试 gcc gcc 程序(程序( 11)) GNU GNU 的调试器称为 的调试器称为 gdbgdb ,该程序是一个交互式工具,工作在字符模式。在 ,该程序是一个交互式工具,工作在字符模式。在 X WiX Window ndow 系统中, 有一个 系统中, 有一个 gdb gdb 的前端图形工具,称为 的前端图形工具,称为 xxgdbxxgdb 。。 gdb gdb 是功能强大的是功能强大的调试程序,可完成如下的调试 任务: 调试程序,可完成如下的调试 任务: 设置断点; 设置断点; 监视程序变量的值; 监视程序变量的值; 程序的单步执行; 程序的单步执行; 修改变量的值。 修改变量的值。 在可以使用 在可以使用 gdb gdb 调试程序之前,必须使用 调试程序之前,必须使用 -g -g 选项编译源文件。可在 选项编译源文件。可在 makmakefile efile 中如下定义 中如下定义 CFLAGS CFLAGS 变量: 变量: CFLAGS = -g CFLAGS = -g 运行 运行 gdb gdb 调试程序时通常使用如调试程序时通常使用如下的命令:下的命令: gdb progname gdb progname 在 在 gdb gdb 提示符处键入提示符处键入 helphelp ,将列出命令的分类,主要的分类有: ,将列出命令的分类,主要的分类有: aliasesaliases :命令别名 :命令别名 breakpointsbreakpoints :断点定义; :断点定义; datadata :数据查看; :数据查看; filesfiles :指定并查看文件; :指定并查看文件; internalsinternals :维护命令; :维护命令; runningrunning :程序执行; :程序执行; stackstack :调用栈查看; :调用栈查看; statustatu :状态查看; :状态查看; tracepointstracepoints :跟踪程序执行。:跟踪程序执行。 后跟命令的分类名,可获得该类命令的详细清单。 后跟命令的分类名,可获得该类命令的详细清单。

6.7.1 gdb 6.7.1 gdb 简介简介

Page 20: Linux 操作系统

6.7 6.7 用 用 gdb gdb 调试 调试 gcc gcc 程序(程序( 22))break NUM break NUM 在指定的行上设置断点。 在指定的行上设置断点。 bt bt 显示所有的调用栈帧。该命令可用来显示函数的调用顺序。 显示所有的调用栈帧。该命令可用来显示函数的调用顺序。 clear clear 删除设置在特定源文件、特定行上的断点。其用法为:删除设置在特定源文件、特定行上的断点。其用法为: clear FILENAME:NUMclear FILENAME:NUM。 。 continue continue 继续执行正在调试的程序。该命令用在程序由于处理信号或断点而 导致停继续执行正在调试的程序。该命令用在程序由于处理信号或断点而 导致停止运行时。 止运行时。 display EXPR display EXPR 每次程序停止后显示表达式的值。表达式由程序定义的变量组成。 每次程序停止后显示表达式的值。表达式由程序定义的变量组成。 file FILE file FILE 装载指定的可执行文件进行调试。 装载指定的可执行文件进行调试。 help NAME help NAME 显示指定命令的帮助信息。 显示指定命令的帮助信息。 info break info break 显示当前断点清单,包括到达断点处的次数等。 显示当前断点清单,包括到达断点处的次数等。 info files info files 显示被调试文件的详细信息。 显示被调试文件的详细信息。 info func info func 显示所有的函数名称。 显示所有的函数名称。 info local info local 显示当函数中的局部变量信息。 显示当函数中的局部变量信息。 info prog info prog 显示被调试程序的执行状态。 显示被调试程序的执行状态。 info var info var 显示所有的全局和静态变量名称。 显示所有的全局和静态变量名称。 kill kill 终止正被调试的程序。 终止正被调试的程序。 list list 显示源代码段。 显示源代码段。 make make 在不退出 在不退出 gdb gdb 的情况下运行 的情况下运行 make make 工具。 工具。 next next 在不单步执行进入其他函数的情况下,向前执行一行源代码。 在不单步执行进入其他函数的情况下,向前执行一行源代码。 print EXPR print EXPR 显示表达式 显示表达式 EXPR EXPR 的值。 的值。

6.7.2 gdb 6.7.2 gdb 的常用命令的常用命令

Page 21: Linux 操作系统

6.7 6.7 用 用 gdb gdb 调试 调试 gcc gcc 程序(程序( 33))/* /* 一个有错误的 一个有错误的 C C 源程序 源程序 */ */ #include <stdio.h> #include <stdio.h> #include <stdlib.h> #include <stdlib.h> static char buff [256]; static char buff [256]; static char* string; static char* string; int main () int main () { printf ("Please input a string: "); { printf ("Please input a string: "); gets (string); gets (string); printf ("\nYour string is: %s\n", string); }printf ("\nYour string is: %s\n", string); }上面这个程序非常简单,其目的是接受用户的输入,然后将用户的输入打印出来。该程序使用了 上面这个程序非常简单,其目的是接受用户的输入,然后将用户的输入打印出来。该程序使用了 一个未经过初始化的字符串地址 一个未经过初始化的字符串地址 stringstring ,因此,编译并运行之后,将出现 ,因此,编译并运行之后,将出现 Segment Fault Segment Fault 错错误: 误: $ gcc -o test -g test.c $ gcc -o test -g test.c $ ./test Please input a string: asfd Segmentation fault (core dumped) $ ./test Please input a string: asfd Segmentation fault (core dumped) 为了查找该程序中出现的问题,我们利用 为了查找该程序中出现的问题,我们利用 gdbgdb ,并按如下的步骤进行: ,并按如下的步骤进行: 11.运行 .运行 gdb bugging gdb bugging 命令,装入 命令,装入 bugging bugging 可执行文件; 可执行文件; 22.执行装入的 .执行装入的 bugging bugging 命令; 命令; 33.使用 .使用 where where 命令查看程序出错的地方; 命令查看程序出错的地方; 44.利用 .利用 list list 命令查看调用 命令查看调用 gets gets 函数附近的代码; 函数附近的代码; 55.唯一能够导致 .唯一能够导致 gets gets 函数出错的因素就是变量 函数出错的因素就是变量 stringstring 。用 。用 print print 命令查看 命令查看 string string 的值; 的值; 66.在 .在 gdb gdb 中,我们可以直接修改变量的值,只要将 中,我们可以直接修改变量的值,只要将 string string 取一个合法的指针值就可以了,为 取一个合法的指针值就可以了,为 此,我们在第 此,我们在第 11 11 行处设置断点; 行处设置断点; 77.程序重新运行到第 .程序重新运行到第 11 11 行处停止,这时,我们可以用 行处停止,这时,我们可以用 set variable set variable 命令修改 命令修改 string string 的取的取值; 值; 88.然后继续运行,将看到正确的程序运行结果。 .然后继续运行,将看到正确的程序运行结果。

6.7.3 gdb 6.7.3 gdb 使用实例使用实例

Page 22: Linux 操作系统

6.8 6.8 编译内核(编译内核( 11 )) 如果只是想编译一个已安装内核的新版本,那不需要下载任何代码 。 如果只是想编译一个已安装内核的新版本,那不需要下载任何代码 。 可以在 可以在 http://www.kernel.org/pub/linux/kernel http://www.kernel.org/pub/linux/kernel 上找到内核代码。当进上找到内核代码。当进入到那后,将发现内核的源代码按内核版本,被组织到多个不同的目录中。在每个目录入到那后,将发现内核的源代码按内核版本,被组织到多个不同的目录中。在每个目录中,文件被冠以“中,文件被冠以“ linux-x.y.z.tar.gz”linux-x.y.z.tar.gz” 和“和“ linux-x.y.z.tar.bz2”linux-x.y.z.tar.bz2” 。这些就是 。这些就是 LinuLinux x 内核的源代码。也将看到冠以 内核的源代码。也将看到冠以 "patch-x.y.z.gz" "patch-x.y.z.gz" 和 和 "patch-x.y.z.bz2" "patch-x.y.z.bz2" 的文件的文件。这些是用来更新前面完整的内核源代码的补丁包。。这些是用来更新前面完整的内核源代码的补丁包。

6.8.1 6.8.1 下载内核下载内核

6.8.2 6.8.2 内核解包内核解包 从 从 kernel.org kernel.org 下载一个新的内核。首先,下载一个新的内核。首先, cd /usr/srccd /usr/src 。如果这里有一个。如果这里有一个存在的存在的 "linux""linux" 目录,将其改名为目录,将其改名为 "linux.old“"linux.old“ 。。 现在,可以解开新的内核包了。仍然在 现在,可以解开新的内核包了。仍然在 /usr/src /usr/src 目录下,输入目录下,输入 tar xzvf /path/to/my/kernel-x.y.z.tar.gztar xzvf /path/to/my/kernel-x.y.z.tar.gz 或者 或者 cat /path/to/my/kernel-x.y.z.tar.bz2 | bzip2 -d | tar xvf –cat /path/to/my/kernel-x.y.z.tar.bz2 | bzip2 -d | tar xvf – 在输入完此命令后,您下载的内核源代码会被释放到一个新的“在输入完此命令后,您下载的内核源代码会被释放到一个新的“ linux”linux” 目目录下。注意 录下。注意 : : 全套内核源代码通常将在硬盘上占用超过 全套内核源代码通常将在硬盘上占用超过 50 50 兆空间!兆空间!

Page 23: Linux 操作系统

6.8 6.8 编译内核(编译内核( 22 )) 在编译内核前,需要配置它,配置是精确控制在新内核中启用在编译内核前,需要配置它,配置是精确控制在新内核中启用 ((禁止禁止 ))哪哪些内核功能的机会。也将控制哪些会被编译到内核的二进制映像些内核功能的机会。也将控制哪些会被编译到内核的二进制映像 (( 在启动时被载在启动时被载入入 )) 而哪些被编译到需要时载入的内核模块文件。而哪些被编译到需要时载入的内核模块文件。 老式配置内核的方法是极为痛苦的过程,并涉及到进入 老式配置内核的方法是极为痛苦的过程,并涉及到进入 /usr/src/linux /usr/src/linux 目录并输入 目录并输入 make configmake config 命令。命令。 新式配置内核的方法输入 新式配置内核的方法输入 make menuconfig make menuconfig 或者 或者 make xconfigmake xconfig 。。如果想要配置您的内核,使用上述选择之一。如果输入 如果想要配置您的内核,使用上述选择之一。如果输入 make menuconfigmake menuconfig ,将,将使用一个漂亮的基于文本的彩色菜单系统来配置内核。如果您输入 使用一个漂亮的基于文本的彩色菜单系统来配置内核。如果您输入 make xconfimake xconfigg ,将使用一个更漂亮的基于 ,将使用一个更漂亮的基于 X-Window X-Window 的 的 GUI GUI 界面来配置内核的各种选项界面来配置内核的各种选项。。 当使用 当使用 "make menuconfig" "make menuconfig" 时,在左面出现一个 时,在左面出现一个 "< >" "< >" 的选项能被的选项能被编译成为一个模块。当选项被选中,按下空格键来循环选择选项是被选中或未选编译成为一个模块。当选项被选中,按下空格键来循环选择选项是被选中或未选中, 中, ("<*>")("<*>") 表示将被编译成内核映像而表示将被编译成内核映像而 ("<M>")("<M>") 表示将被编译成模块。表示将被编译成模块。

6.8.36.8.3配置内核配置内核

Page 24: Linux 操作系统

6.8 6.8 编译内核(编译内核( 33 ))1.1. make dep; make cleanmake dep; make clean :: 一旦的内核配置完毕,就可开始编译它了。在我们能编译它前,我们一旦的内核配置完毕,就可开始编译它了。在我们能编译它前,我们需要生成依赖需要生成依赖 (dependency)(dependency) 信息并清除任何老的“编译结果”。这可以通过信息并清除任何老的“编译结果”。这可以通过在 在 /usr/src/linux /usr/src/linux 下输入 下输入 make dep; make clean make dep; make clean 完成。完成。

2.2. make bzImage make bzImage 输入 输入 make bzImagemake bzImage 。过几分钟或几十分钟后,编译会结束而且您在 。过几分钟或几十分钟后,编译会结束而且您在 /us/usr/src/linux/arch/i386/boot(x86 PC r/src/linux/arch/i386/boot(x86 PC 内核内核 )) 目录下找到 目录下找到 bzImage bzImage 文件。文件。

3.3. 编译模块编译模块 现在我们有了 现在我们有了 bzImagebzImage ,下面要编译模块了。即使在配置内核时没,下面要编译模块了。即使在配置内核时没

有使用任何模块,也不要跳过此步骤 有使用任何模块,也不要跳过此步骤 -- -- 在编译完 在编译完 bzImage bzImage 后立刻编译模块后立刻编译模块是个好习惯。而且,如果您真的没有模块需要编译,这个步骤也非常快就结束了是个好习惯。而且,如果您真的没有模块需要编译,这个步骤也非常快就结束了。输入 。输入 make modules; make modules_installmake modules; make modules_install 。这将导致模块被编译而且。这将导致模块被编译而且被安装到 被安装到 /usr/lib/</usr/lib/< 内核版本号内核版本号 > > 目录下。目录下。

内核已经被编译完成了,内核模块也编译完成并被安装。现在是要重内核已经被编译完成了,内核模块也编译完成并被安装。现在是要重新配置 新配置 lilolilo ,这样您能使用新的内核。,这样您能使用新的内核。

6.8.4 6.8.4 编译和安装内核编译和安装内核

Page 25: Linux 操作系统

6.8 6.8 编译内核(编译内核( 44 )) 最后需重新配置 最后需重新配置 LILO LILO ,它将负责载入新的内核。,它将负责载入新的内核。 要配置 要配置 LILO LILO 来使用新的内核有两种选择。第一个是覆盖您现有的内核 来使用新的内核有两种选择。第一个是覆盖您现有的内核

,这很危险的方法; 更为安全的选择是配置 ,这很危险的方法; 更为安全的选择是配置 LILO LILO 是得它能从新的或旧的内核引是得它能从新的或旧的内核引导。导。

首先,拷贝 首先,拷贝 /usr/src/linux/arch/i386/boot/bzImage /usr/src/linux/arch/i386/boot/bzImage 到到 /boot/boot ,例如 ,例如 /boot/vmlinuz2/boot/vmlinuz2 。然后修改 。然后修改 lilo.conf lilo.conf 文件的最后三行并将它们添加到该文件的文件的最后三行并将它们添加到该文件的最后,现在,您的 最后,现在,您的 lilo.conf lilo.conf 文件应该看起来如下:文件应该看起来如下:

boot=/dev/hda boot=/dev/hda delay=20 delay=20 vga=normal vga=normal root=/dev/hda1 root=/dev/hda1 read-only read-only image=/boot/vmlinuz2 image=/boot/vmlinuz2 label=linux label=linux image=/boot/vmlinuz image=/boot/vmlinuz label=oldlinux label=oldlinux 作完这些修改后,您将需要以 作完这些修改后,您将需要以 root root 身份运行 身份运行 "lilo""lilo" 。这非常重要!。这非常重要!

如果您不执行此步,启动的过程无法继续。运行 如果您不执行此步,启动的过程无法继续。运行 "lilo" "lilo" 将给 将给 lilo lilo 一个机会来更一个机会来更新它的启动映射。新它的启动映射。

6.8.4 6.8.4 启动配置启动配置