Stories

Detail Return Return

【Linux】《how linux work》第十五章 開發工具 - Stories Detail

第 15 章 開發工具

Linux and Unix are very popular with programmers, not just due to the overwhelming array of tools and environments available but also because the system is exceptionally well documented and transparent. On a Linux machine, you don’t have to be a programmer to take advantage of development tools, but when working with the system, you should know something about programming tools because they play a larger role in managing Unix systems than in other operating systems. At the very least, you should be able to identify development utilities and have some idea of how to run them.

Linux和Unix在程序員中非常受歡迎,不僅因為提供了豐富的工具和環境,還因為系統的文檔和透明度異常出色。

在Linux機器上,即使不是程序員,也可以利用開發工具,但是在使用系統時,你應該瞭解一些關於編程工具的知識,因為它們在管理Unix系統中起着比其他操作系統更重要的作用。

至少,你應該能夠識別開發工具,並且對如何運行它們有一些瞭解。

This chapter packs a lot of information into a small space, but you don’t need to master everything here. You can easily skim the material and come back later. The discussion of shared libraries is likely the most important thing that you need to know. But to understand where shared libraries come from, you first need some background on how to build programs.

本章節在一個小空間內提供了大量的信息,但你不需要完全掌握這裏的所有內容。

你可以簡單地瀏覽材料,然後稍後再回來。關於共享庫的討論可能是你需要了解的最重要的內容。

但是要理解共享庫的來源,你首先需要了解如何構建程序的一些背景知識。

15.1 C編譯器

Knowing how to run the C programming language compiler can give you a great deal of insight into the origin of the programs that you see on your Linux system. The source code for most Linux utilities, and for many applications on Linux systems, is written in C or C++. We’ll primarily use examples in C for this chapter, but you’ll be able to carry the information over to C++.

瞭解如何運行C編程語言編譯器可以讓您對在Linux系統上看到的程序的起源有很大的瞭解。

大多數Linux實用程序和許多Linux系統上的應用程序的源代碼都是用C或C++編寫的。

本章我們將主要使用C的示例,但您可以將這些信息應用到C++上。

C programs follow a traditional development process: You write programs, you compile them, and they run. That is, when you write a C program and want to run it, you must compile the source code that you wrote into a binary low-level form that the computer understands. You can compare this to the scripting languages that we’ll discuss later, where you don’t need to compile anything.

C程序遵循傳統的開發過程:您編寫程序,編譯它們,然後運行它們。

也就是説,當您編寫一個C程序並希望運行它時,您必須將您編寫的源代碼編譯成計算機理解的二進制低級形式。

您可以將此與我們稍後將討論的腳本語言進行比較,在那裏您不需要編譯任何東西。

NOTE By default, most distributions do not include the tools necessary to compile C code because these tools occupy a fairly large amount of space. If you can’t find some of the tools described here, you can install the build-essential package for Debian/Ubuntu or the Chapter 15 yum groupinstall for Fedora/CentOS. Failing that, try a package search for “C compiler.”

注意:

默認情況下,大多數發行版不包含編譯C代碼所需的工具,因為這些工具佔用了相當大的空間。

如果您找不到這裏描述的某些工具,可以為Debian/Ubuntu安裝build-essential軟件包,或者為Fedora/CentOS安裝Chapter 15 yum groupinstall軟件包。

如果這樣做不行,請嘗試搜索"C編譯器"。

The C compiler executable on most Unix systems is the GNU C complier, gcc, though the newer clang compiler from the LLVM project is gaining popularity. C source code files end with .c. Take a look at the single, self-contained C source code file called hello.c, which you can find in The C Programming Language, 2nd edition, by Brian W. Kernighan and Dennis M. Ritchie (Prentice Hall, 1988):

大多數Unix系統上的C編譯器可執行文件是GNU C編譯器gcc,儘管來自LLVM項目的較新的clang編譯器正在變得越來越受歡迎。

C源代碼文件以.c結尾。

請查看名為hello.c的單個、自包含的C源代碼文件,您可以在Brian W. Kernighan和Dennis M. Ritchie的

《C程序設計語言》 第2版(Prentice Hall,1988年)中找到。

#include <stdio.h>
main() {
 printf("Hello, World.\n");
}

Put this source code in a file called hello.c and then run this command:

將這段源代碼放入一個名為hello.c的文件中,然後運行以下命令:

$ cc hello.c

The result is an executable named a.out, which you can run like any other executable on the system. However, you should probably give the executable another name (such as hello). To do this, use the compiler’s -o option:

結果是一個名為a.out的可執行文件,您可以像系統上的其他可執行文件一樣運行它。

然而,您可能應該給可執行文件取另一個名字(比如hello)。

為此,請使用編譯器的-o選項:

$ cc -o hello hello.c

For small programs, there isn’t much more to compiling than that. You might need to add an extra include directory or library (see 15.1.2 Header (Include) Files and Directories and 15.1.3 Linking with Libraries), but let’s look at slightly larger programs before getting into those topics.

對於小型程序來説,編譯工作就沒什麼了。

在進入這些主題之前,您可能需要添加額外的包含目錄或庫(參見15.1.2 頭文件和目錄和15.1.3 鏈接庫),但讓我們先看一下稍微大一點的程序。

15.1.1 Multiple Source Files(多個源文件)

Most C programs are too large to reasonably fit inside a single source code file. Mammoth files become too disorganized for the programmer, and compilers sometimes even have trouble parsing large files. Therefore, developers group components of the source code together, giving each piece its own file.

大多數C程序太大了,無法合理地放在一個單獨的源代碼文件中。

龐大的文件會使程序員難以組織,而且編譯器有時甚至會在解析大文件時出現問題。

因此,開發人員將源代碼的組件分組在一起,每個組件都有自己的文件。

When compiling most .c files, you don’t create an executable right away. Instead, use the compiler’s -c option on each file to create object files. To see how this works, let’s say you have two files, main.c and aux.c. The following two compiler commands do most of the work of building the program:

在編譯大多數.c文件時,你不會立即創建一個可執行文件。相反,可以在每個文件上使用編譯器的-c選項來創建目標文件。

為了看清楚這是如何工作的,假設你有兩個文件,main.c和aux.c。

下面的兩個編譯器命令完成了構建程序的大部分工作:

$ cc -c main.c
$ cc -c aux.c

The preceding two commands compile the two source files into the two object files main.o and aux.o.

上述兩個命令將這兩個源文件編譯為兩個目標文件main.o和aux.o。

An object file is a binary file that a processor can almost understand, except that there are still a few loose ends. First, the operating system doesn’t know how to run an object file, and second, you likely need to combine several object files and some system libraries to make a complete program.

目標文件是處理器幾乎可以理解的二進制文件,只是還有一些鬆散的部分。

首先,操作系統不知道如何運行目標文件,其次,你可能需要將多個目標文件和一些系統庫組合起來,才能生成一個完整的程序。

To build a fully functioning executable from one or more object files, you must run the linker, the ld command in Unix. Programmers rarely use ld on the command line, because the C compiler knows how to run the linker program. So to create an executable called myprog from the two object files above, run this command to link them:

要從一個或多個目標文件構建一個完全運行的可執行文件,必須運行鏈接器,即Unix中的ld命令。

程序員很少在命令行上使用ld,因為C編譯器知道如何運行鏈接器程序。

因此,要從上述兩個目標文件中鏈接它們創建一個名為myprog的可執行文件,請運行以下命令:

$ cc -o myprog main.o aux.o

Although you can compile multiple source files by hand, as the preceding example shows, it can be hard to keep track of them all during the compiling process when the number of source files multiplies. The make system described in 15.2 make is the traditional Unix standard for managing compiles. This system is especially important in managing the files described in the next two sections.

雖然你可以手動編譯多個源文件,就像上面的例子所示,但是當源文件數量增多時,在編譯過程中跟蹤所有這些文件可能會很困難。

在第15.2節中描述的make系統是管理編譯的傳統Unix標準。這個系統在管理下面兩節中描述的文件時尤為重要。

15.1.2 Header (Include) Files and Directories(頭文件和目錄)

C header files are additional source code files that usually contain type and library function declarations. For example, stdio.h is a header file (see the simple program in 15.1 The C Compiler).

C頭文件是通常包含類型和庫函數聲明的附加源代碼文件。例如,stdio.h是一個頭文件(見第15.1節C編譯器中的簡單程序)。

Unfortunately, a great number of compiler problems crop up with header files. Most glitches occur when the compiler can’t find header files and libraries. There are even some cases where a programmer forgets to include a required header file, causing some of the source code to not compile.

不幸的是,使用頭文件時經常出現許多編譯器問題。大多數故障發生在編譯器找不到頭文件和庫時。

甚至有些情況下,程序員忘記包含所需的頭文件,導致部分源代碼無法編譯。

Fixing Include File Problems(解決頭文件問題)

Tracking down the correct include files isn’t always easy. Sometimes there are several include files with the same names in different directories, and it’s not clear which is the correct one. When the compiler can’t find an include file, the error message looks like this:

找到正確的頭文件並不總是容易的。

有時候在不同目錄中有幾個同名的頭文件,不清楚哪一個是正確的。

當編譯器找不到一個頭文件時,錯誤信息會像這樣:

badinclude.c:1:22: fatal error: notfound.h: No such file or directory

This message reports that the compiler can’t find the notfound.h header file that the badinclude.c file references. This specific error is a direct result of this directive on line 1 of badinclude.c:

這個錯誤報告了編譯器找不到badinclude.c文件引用的notfound.h頭文件。

這個具體的錯誤是由badinclude.c文件的第1行上的這個指令引起的:

#include <notfound.h>

The default include directory in Unix is /usr/include; the compiler always looks there unless you explicitly tell it not to. However, you can make the compiler look in other include directories (most paths that contain header files have include somewhere in the name).

在Unix中,默認的包含目錄是/usr/include;除非你明確告訴編譯器不要去找,否則編譯器總是在那裏查找。

然而,你可以讓編譯器在其他包含目錄中查找(大多數包含頭文件的路徑中都包含include這個關鍵字)。

NOTE You’ll learn more about how to find missing include files in Chapter 16.

注意

在第16章中,你將學習更多關於如何找到缺失的頭文件。

For example, let’s say that you find notfound.h in /usr/junk/include. You can make the compiler see this directory with the -I option:

例如,假設你在/usr/junk/include中找到了notfound.h。

你可以使用-I選項讓編譯器看到這個目錄:

$ cc -c -I/usr/junk/include badinclude.c

Now the compiler should no longer stumble on the line of code in badinclude.c that references the header file. You should also beware of includes that use double quotes (" ") instead of angle brackets (< >), like this:

現在,編譯器不應該再在badinclude.c中引用頭文件的那行代碼上出現問題了。

你還應該注意,有些#include語句使用雙引號(" ")而不是尖括號(< >),像這樣:

#include "myheader.h"

Double quotes mean that the header file is not in a system include directory but that the compiler should otherwise search its include path. It often means that the include file is in the same directory as the source file. If you encounter a problem with double quotes, you’re probably trying to compile incomplete source code.

雙引號意味着頭文件不在系統的包含目錄中,但編譯器應該在其包含路徑中搜索。

這通常意味着頭文件與源文件位於同一個目錄中。

如果你在雙引號中遇到問題,你可能在嘗試編譯不完整的源代碼。

What Is the C Preprocessor (cpp)?(C預處理器(cpp)是什麼?)

It turns out that the C compiler does not actually do the work of looking for all of these include files. That task falls to the C preprocessor, a program that the compiler runs on your source code before parsing the actual program. The preprocessor rewrites source code into a form that the compiler understands; it’s a tool for making source code easier to read (and for providing shortcuts).

事實上,C編譯器並不實際負責查找所有這些頭文件。這項任務由C預處理器完成,它是編譯器在解析實際程序之前在源代碼上運行的程序。

預處理器將源代碼重寫成編譯器能理解的形式;它是一種使源代碼更易讀(並提供快捷方式)的工具。

Preprocessor commands in the source code are called directives, and they start with the # character. There are three basic types of directives:

源代碼中的預處理器命令稱為指令,它們以#字符開頭。有三種基本類型的指令:

o Include files. An #include directive instructs the preprocessor to include an entire file. Note that the compiler’s -I flag is actually an option that causes the preprocessor to search a specified directory for include files, as you saw in the previous section.

  • 包含文件。#include指令指示預處理器包含整個文件。

    • 注意,編譯器的-I選項實際上是一個選項,它使預處理器在指定的目錄中搜索包含文件,就像你在前面的部分中看到的那樣。

o Macro definitions. A line such as #define BLAH something tells the preprocessor to substitute
something for all occurrences of BLAH in the source code. Convention dictates that macros appear in all uppercase, but it should come as no shock that programmers sometimes use macros whose names look like functions and variables. (Every now and then, this causes a world of headaches. Many programmers make a sport out of abusing the preprocessor.)

  • 宏定義。例如,#define BLAH something告訴預處理器在源代碼中將所有出現的BLAH替換為something。

    • 約定規定宏以全大寫形式出現,但程序員有時會使用看起來像函數和變量名的宏,這並不奇怪。
    • (偶爾會引起一系列的問題。許多程序員濫用預處理器,將其當作一種娛樂方式。)

o Conditionals. You can mark out certain pieces of code with #ifdef, #if, and #endif. The #ifdef MACRO directive checks to see whether the preprocessor macro MACRO is defined, and #if condition tests to see whether condition is nonzero. For both directives, if the condition following the “if statement” is false, the preprocessor does not pass any of the program text between the #if and the next #endif to the compiler. If you plan to look at any C code, you’d better get used to this.

  • 條件語句。你可以使用#ifdef、#if和#endif來標記出代碼的某些部分。

    • ifdef MACRO指令檢查預處理器宏MACRO是否已定義,#if condition測試條件是否為非零值。

    • 對於這兩個指令,如果“if語句”後面的條件為假,預處理器不會將位於#if和下一個#endif之間的任何程序文本傳遞給編譯器。
    • 如果你計劃查看任何C代碼,最好習慣於這個。

NOTE Instead of defining macros within your source code, you can also define macros by passing parameters to the compiler: -DBLAH=something works like the directive above.

注意

你可以通過向編譯器傳遞參數來定義宏,而不是在源代碼中定義宏:-DBLAH=something的效果類似於上面的指令。

An example of a conditional directive follows. When the preprocessor sees the following code, it checks to see whether the macro DEBUG is defined and, if so, passes the line containing fprintf() on to the compiler. Otherwise, the preprocessor skips this line and continues to process the file after the #endif:

以下是一個條件指令的示例。當預處理器看到下面的代碼時,它會檢查宏DEBUG是否已定義,如果是,則將包含fprintf()的那行傳遞給編譯器。

否則,預處理器跳過這行代碼,繼續處理#endif之後的文件:

#ifdef DEBUG
 fprintf(stderr, "This is a debugging message.\n");
#endif

NOTE The C preprocessor doesn’t know anything about C syntax, variables, functions, and other elements. It understands only its own macros and directives.

注意
C預處理器對C語法、變量、函數和其他元素一無所知。它只理解自己的宏和指令。

On Unix, the C preprocessor’s name is cpp, but you can also run it with gcc -E. However, you’ll rarely need to run the preprocessor by itself.

在Unix上,C預處理器的名稱是cpp,但你也可以使用gcc -E來運行它。然而,你很少需要單獨運行預處理器。

15.1.3 Linking with Libraries(鏈接庫)

The C compiler doesn’t know enough about your system to create a useful program all by itself. You need libraries to build complete programs. A C library is a collection of common precompiled functions that you can build into your program. For example, many executables use the math library because it provides trigonometric functions and the like.

C編譯器本身對於您的系統並不瞭解,無法單獨創建一個有用的程序。

您需要使用庫來構建完整的程序。C庫是一組常見的預編譯函數,您可以將其構建到程序中。

例如,許多可執行文件使用數學庫,因為它提供三角函數等功能。

Libraries come into play primarily at link time, when the linker program creates an executable from object files. For example, if you have a program that uses the gobject library but you forget to tell the compiler to link against that library, you’ll see linker errors like this:

庫主要在鏈接時起作用,鏈接程序將目標文件創建為可執行文件。

例如,如果您有一個使用gobject庫的程序,但忘記告訴編譯器鏈接該庫,您將看到如下的鏈接錯誤:

badobject.o(.text+0x28): undefined reference to 'g_object_new'

The most important parts of these error messages are in bold. When the linker program examined the badobject.o object file, it couldn’t find the function that appears in bold, and as a consequence, it couldn’t create the executable. In this particular case, you might suspect that you forgot the gobject library because the missing function is g_object_new().

這些錯誤消息中最重要的部分用粗體表示。

當鏈接程序檢查badobject.o目標文件時,它找不到出現在粗體中的函數,因此無法創建可執行文件。

在這種特殊情況下,您可能懷疑忘記了gobject庫,因為缺少的函數是g_object_new()。

NOTE Undefined references do not always mean that you’re missing a library. One of the program’s object files could be missing in the link command. It’s usually easy to differentiate between library functions and functions in your object files, though.

注意:未定義的引用並不總是意味着缺少庫。

鏈接命令中可能缺少程序的某個目標文件。

不過,通常很容易區分庫函數和目標文件中的函數。

To fix this problem, you must first find the gobject library and then use the compiler’s -l option to link against the library. As with include files, libraries are scattered throughout the system (/usr/lib is the system default location), though most libraries reside in a subdirectory named lib. For the preceding example, the basic gobject library file is libgobject.a, so the library name is gobject. Putting it all together, you would link the program like this:

要解決這個問題,首先必須找到gobject庫,然後使用編譯器的-l選項鍊接該庫。

與包含文件一樣,庫分散在整個系統中(/usr/lib是系統默認位置),儘管大多數庫位於名為lib的子目錄中。

對於前面的示例,基本的gobject庫文件是libgobject.a,因此庫名為gobject。

將所有內容組合起來,您可以像這樣鏈接程序:

$ cc -o badobject badobject.o -lgobject

You must tell the linker about nonstandard library locations; the parameter for this is -L. Let’s say that the badobject program requires libcrud.a in /usr/junk/lib. To compile and create the executable, use a command like this:

您必須告訴鏈接器非標準庫的位置;用於此的參數是-L。

假設badobject程序需要/usr/junk/lib中的libcrud.a。要編譯並創建可執行文件,請使用如下命令:

$ cc -o badobject badobject.o -lgobject -L/usr/junk/lib -lcrud

NOTE If you want to search a library for a particular function, use the nm command. Be prepared for a lot of output. For example, try this: nm libgobject.a. (You might need to use the locate command to find libgobject.a; many distributions now put libraries in architecture-specific subdirectories in /usr/lib.)

注意:如果要在庫中搜索特定函數,請使用nm命令。準備好大量輸出。

例如,嘗試執行此命令:nm libgobject.a(您可能需要使用locate命令來找到libgobject.a;許多發行版現在將庫放在/usr/lib的體系結構特定子目錄中)。

15.1.4 Shared Libraries(共享庫)

A library file ending with .a (such as libgobject.a) is called a static library. When you link a program against a static library, the linker copies machine code from the library file into your executable. Therefore, the final executable does not need the original library file to run, and furthermore, the executable’s behavior never changes.

以 .a 結尾的庫文件(例如 libgobject.a)被稱為靜態庫。

當你將程序與靜態庫進行鏈接時,鏈接器會將庫文件中的機器碼複製到可執行文件中。

因此,最終的可執行文件不需要原始庫文件來運行,而且可執行文件的行為也永遠不會改變。

However, library sizes are always increasing, as is the number of libraries in use, and this makes static libraries wasteful in terms of disk space and memory. In addition, if a static library is later found to be inadequate or insecure, there’s no way to change any executable linked against it, short of recompiling the executable.

然而,庫的大小一直在增加,使用的庫的數量也在增加,這使得靜態庫在磁盤空間和內存方面是一種浪費。

此外,如果後來發現靜態庫不足或存在安全問題,除非重新編譯可執行文件,否則無法更改任何與之鏈接的可執行文件。

Shared libraries counter these problems. When you run a program linked against one, the system loads the library’s code into the process memory space only when necessary. Many processes can share the same shared library code in memory. And if you need to slightly modify the library code, you can generally do so without recompiling any programs.

共享庫解決了這些問題。

當你運行與共享庫鏈接的程序時,系統只在必要時將庫的代碼加載到進程內存空間中。

許多進程可以在內存中共享相同的共享庫代碼。

如果需要稍微修改庫代碼,通常可以在不重新編譯任何程序的情況下完成。

Shared libraries have their own costs: difficult management and a somewhat complicated linking procedure. However, you can bring shared libraries under control if you know four things:

共享庫也有自己的成本:管理困難和相對複雜的鏈接過程。

但是,如果你瞭解以下四點,就可以控制共享庫:

o How to list the shared libraries that an executable needs
o How an executable looks for shared libraries
o How to link a program against a shared library
o The common shared library pitfalls

  • 如何列出一個可執行文件需要的共享庫
  • 可執行文件如何查找共享庫
  • 如何將程序與共享庫進行鏈接
  • 常見的共享庫陷阱

The following sections tell you how to use and maintain your system’s shared libraries. If you’re interested in how shared libraries work or if you want to know about linkers in general, you can check out Linkers and Loaders by John R. Levine (Morgan Kaufmann, 1999), “The Inside Story on Shared Libraries and Dynamic Loading” by David M. Beazley, Brian D. Ward, and Ian R. Cooke (Computing in Science & Engineering, September/October 2001), or online resources such as the Program Library HOWTO (http://dwheeler.com/program-library/). The ld.so(8) manual page is also worth a read.

接下來的章節將告訴你如何使用和維護系統的共享庫。

如果你對共享庫的工作原理感興趣,或者想了解鏈接器的一般情況,可以查閲

  • John R. Levine 的《Linkers and Loaders》(Morgan Kaufmann,1999)、David M. Beazley、Brian D. Ward
  • Ian R. Cooke 的《The Inside Story on Shared Libraries and Dynamic Loading》(Computing in Science & Engineering,2001 年 9 月/10 月),
  • 在線資源,如程序庫 HOWTO(http://dwheeler.com/program-library/)。

另外,也值得一讀的是 ld.so(8) 手冊頁。

Listing Shared Library Dependencies(列出共享庫依賴關係)

Shared library files usually reside in the same places as static libraries. The two standard library directories on a Linux system are /lib and /usr/lib. The /lib directory should not contain static libraries.

共享庫文件通常存放在與靜態庫相同的位置。

Linux系統上的兩個標準庫目錄是/lib和/usr/lib。

/lib目錄不應包含靜態庫。

A shared library has a suffix that contains .so (shared object), as in libc-2.15.so and libc.so.6. To see what shared libraries a program uses, run ldd prog, where prog is the executable name. Here’s an example for the shell:

共享庫的後綴包含.so(共享對象),如libc-2.15.so和libc.so.6。

要看程序使用的共享庫,運行ldd prog,其中prog是可執行文件名。

以下是一個關於shell的示例:

$ ldd /bin/bash
 linux-gate.so.1 => (0xb7799000)
 libtinfo.so.5 => /lib/i386-linux-gnu/libtinfo.so.5 (0xb7765000)
 libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xb7760000)
 libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75b5000)
 /lib/ld-linux.so.2 (0xb779a000)

In the interest of optimal performance and flexibility, executables alone don’t usually know the locations of their shared libraries; they know only the names of the libraries, and perhaps a little hint about where to find them. A small program named ld.so (the runtime dynamic linker/loader) finds and loads shared libraries for a program at runtime. The preceding ldd output shows the library names on the left—that’s what the executable knows. The right side shows where ld.so finds the library.

為了實現最佳性能和靈活性,可執行文件通常不知道其共享庫的位置;它們只知道庫的名稱,也許會有一點提示去找到它們。

一個名為ld.so(運行時動態鏈接器/加載器)的小型程序在運行時為程序找到並加載共享庫。

前述ldd輸出顯示了左側的庫名稱—這是可執行文件所知道的。右側顯示了ld.so找到庫的位置。

The final line of output here shows the actual location of ld.so: ld-linux.so.2.

這裏輸出的最後一行顯示了ld.so的實際位置:ld-linux.so.2。

How ld.so Finds Shared Libraries

One of the common trouble points for shared libraries is that the dynamic linker cannot find a library. The first place the dynamic linker should normally look for shared libraries is an executable’s preconfigured runtime library search path (rpath), if it exists. You’ll see how to create this path shortly.

Next, the dynamic linker looks in a system cache, /etc/ld.so.cache, to see if the library is in a standard location. This is a fast cache of the names of library files found in directories listed in the cache configuration file /etc/ ld.so.conf.

NOTE As is typical of many of the Linux configuration files that you’ve seen, ld.so.conf may include a number of files in a directory such as /etc/ld.so.conf.d.

為了實現最佳性能和靈活性,可執行文件通常不知道其共享庫的位置;它們只知道庫的名稱,可能還有一些關於如何找到它們的提示。

一個名為ld.so(運行時動態鏈接器/加載器)的小程序在運行時為程序找到並加載共享庫。

前面的ldd輸出顯示了左側的庫名稱——這是可執行文件所知道的。

右側顯示了ld.so找到庫的位置。

這裏的最後一行輸出顯示了ld.so的實際位置:ld-linux.so.2。

How ld.so Finds Shared Libraries(ld.so如何找到共享庫)

One of the common trouble points for shared libraries is that the dynamic linker cannot find a library. The first place the dynamic linker should normally look for shared libraries is an executable’s preconfigured runtime library search path (rpath), if it exists. You’ll see how to create this path shortly.

共享庫的常見問題之一是動態鏈接器找不到庫。

動態鏈接器通常應該首先查找共享庫的位置是可執行文件預配置的運行時庫搜索路徑(rpath),如果存在的話。您將在稍後看到如何創建此路徑。

Next, the dynamic linker looks in a system cache, /etc/ld.so.cache, to see if the library is in a standard location. This is a fast cache of the names of library files found in directories listed in the cache configuration file /etc/ ld.so.conf

接下來,動態鏈接器會在系統緩存/etc/ld.so.cache中查找,以查看庫是否位於標準位置。

這是一個快速緩存,其中包含在緩存配置文件/etc/ld.so.conf中列出的目錄中找到的庫文件的名稱。

NOTE As is typical of many of the Linux configuration files that you’ve seen, ld.so.conf may include a number of files in a directory such as /etc/ld.so.conf.d.

注意 與您看到的許多Linux配置文件一樣,ld.so.conf可能在目錄(如/etc/ld.so.conf.d)中包含多個文件。

ld.so.conf中的每一行都是您希望包含在緩存中的目錄。目錄列表通常很短,類似於以下內容:

···sh/lib/i686-linux-gnu
/usr/lib/i686-linux-gnu

The standard library directories /lib and /usr/lib are implicit, which means that you don’t need to include them in /etc/ld.so.conf

標準庫目錄/lib和/usr/lib是隱式的,這意味着您不需要將它們包含在/etc/ld.so.conf中。

If you alter ld.so.conf or make a change to one of the shared library directories, you must rebuild the /etc/ld.so.cache file by hand with the following command

如果您修改了ld.so.conf或對其中一個共享庫目錄進行更改,必須手動使用以下命令重新構建/etc/ld.so.cache文件:

#ldconfig -v

The -v option provides detailed information on libraries that ldconfig adds to the cache and any changes that it detects

-v 選項提供了ldconfig添加到緩存中的庫的詳細信息以及它檢測到的任何更改。

There is one more place that ld.so looks for shared libraries: the environment variable LD_LIBRARY_PATH. We’ll talk about this soon.

ld.so還會在另一個位置查找共享庫:環境變量LD_LIBRARY_PATH。我們很快會討論這個。

Don’t get into the habit of adding stuff to /etc/ld.so.conf. You should know what shared libraries are in the system cache, and if you put every bizarre little shared library directory into the cache, you risk conflicts and an extremely disorganized system. When you compile software that needs an obscure library path, give your executable a built-in runtime library search path. Let’s see how to do that

不要養成向/etc/ld.so.conf添加內容的習慣。

您應該知道系統緩存中有哪些共享庫,如果將每個奇怪的共享庫目錄都放入緩存中,會導致衝突和一個非常混亂的系統。

當您編譯需要一個奇怪的庫路徑的軟件時,請給您的可執行文件設置一個內置的運行時庫搜索路徑。讓我們看看如何做到這一點。

Linking Programs Against Shared Libraries(將程序與共享庫鏈接)

Let’s say you have a shared library named libweird.so.1 in /opt/obscure/lib that you need to link myprog against. Link the program as follows:

假設你有一個名為libweird.so.1的共享庫,位於/opt/obscure/lib目錄下,你需要將myprog與其進行鏈接。

按照以下方式鏈接該程序:

$ cc -o myprog myprog.o -Wl,-rpath=/opt/obscure/lib -L/opt/obscure/lib -lweird

The -Wl,-rpath option tells the linker to include a following directory into the executable’s runtime library search path. However, even if you use -Wl,-rpath, you still need the -L flag.

-Wl,-rpath選項告訴鏈接器將一個後續目錄包含到可執行文件的運行時庫搜索路徑中。但是,即使你使用了-Wl,-rpath,你仍然需要-L標誌。

If you have a pre-existing binary, you can also use the patchelf program to insert a different runtime library search path, but it’s generally better to do this at compile time.

如果你有一個現有的二進制文件,你也可以使用patchelf程序來插入一個不同的運行時庫搜索路徑,但通常最好在編譯時進行此操作。

Problems with Shared Libraries(共享庫的問題

Shared libraries provide remarkable flexibility, not to mention some really incredible hacks, but it’s also possible to abuse them to the point where your system becomes an utter and complete mess. Three particularly bad things can happen:

共享庫提供了非凡的靈活性,更不用説一些令人難以置信的技巧了,但是濫用它們可能會導致系統變得一團糟。

有三個特別糟糕的問題可能會發生:

o Missing libraries
o Terrible performance
o Mismatched libraries

o 缺少庫
o 性能糟糕
o 庫不匹配

The number one cause of all shared library problems is the environment variable named LD_LIBRARY_PATH. Setting this variable to a colon-delimited set of directory names makes ld.so search the given directories before anything else when looking for a shared library. This is a cheap way to make programs work when you move a library around, if you don’t have the program’s source code and can’t use patchelf, or if you’re just too lazy to recompile the executables. Unfortunately, you get what you pay for.

所有共享庫問題的頭號原因是一個名為LD_LIBRARY_PATH的環境變量。

將此變量設置為以冒號分隔的目錄名稱集合,使得ld.so在查找共享庫時首先搜索給定的目錄。

當你移動庫文件時,如果沒有程序的源代碼,無法使用patchelf,或者你懶得重新編譯可執行文件,這是一種廉價的方法使程序工作。

不幸的是,一分錢一分貨。

Never set LD_LIBRARY_PATH in shell startup files or when compiling software. When the dynamic runtime linker encounters this variable, it must often search through the entire contents of each specified directory more times than you’d care to know. This causes a big performance hit, but more importantly, you can get conflicts and mismatched libraries because the runtime linker looks in these directories for every program

永遠不要在shell啓動文件或編譯軟件時設置LD_LIBRARY_PATH。

當動態運行時鏈接器遇到這個變量時,它通常需要多次搜索每個指定目錄的全部內容,這會導致性能大幅下降,更重要的是,由於運行時鏈接器會在這些目錄中搜索每個程序,可能會出現衝突和不匹配的庫。

If you must use LD_LIBRARY_PATH to run some crummy program for which you don’t have the source (or an application that you’d rather not compile, like Mozilla or some other beast), use a wrapper script. Let’s say your executable is /opt/crummy/bin/crummy.bin and needs some shared libraries in /opt/crummy/lib. Write a wrapper script called crummy that looks like this

如果你必須使用LD_LIBRARY_PATH來運行一些糟糕的程序,而你沒有源代碼(或者你寧願不編譯某些應用程序,比如Mozilla或其他一些龐然大物),可以使用一個包裝腳本。

假設你的可執行文件是/opt/crummy/bin/crummy.bin,需要一些共享庫位於/opt/crummy/lib。

編寫一個名為crummy的包裝腳本,內容如下:

#!/bin/sh
LD_LIBRARY_PATH=/opt/crummy/lib
export LD_LIBRARY_PATH
exec /opt/crummy/bin/crummy.bin $@

Avoiding LD_LIBRARY_PATH prevents most shared library problems. But one other significant problem that occasionally comes up with developers is that a library’s application programming interface (API) may change slightly from one minor version to another, breaking installed software. The best solutions here are preventive: Either use a consistent methodology to install shared libraries with -Wl,-rpath to create a runtime link path or simply use the static versions of obscure libraries.

避免使用LD_LIBRARY_PATH可以避免大多數共享庫問題。
但是開發人員偶爾會遇到另一個重要的問題,即庫的應用程序編程接口(API)可能會在一個次要版本與另一個次要版本之間稍有變化,從而破壞已安裝的軟件。

在這裏,最好的解決方案是預防性的:要麼使用一致的方法來使用-Wl,-rpath安裝共享庫以創建運行時鏈接路徑,要麼簡單地使用這些庫的靜態版本。

15.2 make

A program with more than one source code file or that requires strange compiler options is too cumbersome to compile by hand. This problem has been around for years, and the traditional Unix compile management utility that eases these pains is called make. You should know a little about make if you’re running a Unix system, because system utilities sometimes rely on make to operate. However, this chapter is only the tip of the iceberg. There are entire books on make, such as Managing Projects with GNU Make by Robert Mecklenburg (O’Reilly, 2004). In addition, most Linux packages are built using an additional layer around make or a similar tool. There are many build systems out there; we’ll look at one named autotools in Chapter 16. make is a big system, but it’s not very difficult to get an idea of how it works. When you see a file named Makefile or makefile, you know that you’re dealing with make. (Try running make to see if you can build anything.)

一個有多個源代碼文件或需要奇怪編譯選項的程序太繁瑣了,無法手動編譯。

這個問題已經存在多年了,傳統的Unix編譯管理實用程序可以緩解這些痛苦,它被稱為make。

如果你在運行Unix系統,你應該對make有一些瞭解,因為系統工具有時依賴於make來運行。

然而,本章只是冰山一角。有很多關於make的書籍,比如Robert Mecklenburg的《使用GNU Make管理項目》(O'Reilly,2004)。

此外,大多數Linux軟件包都是使用make或類似工具的附加層構建的。

市面上有很多構建系統,我們將在第16章介紹一個名為autotools的構建系統。

make是一個龐大的系統,但是理解它的工作原理並不難。

當你看到一個名為Makefile或makefile的文件時,你就知道你正在處理make。

(嘗試運行make看看是否可以構建任何東西。)

The basic idea behind make is the target, a goal that you want to achieve. A target can be a file (a .o file, an executable, and so on) or a label. In addition, some targets depend on other targets; for instance, you need a complete set of .o files before you can link your executable. These requirements are called dependencies.

make的基本思想是目標,即你想要實現的目標。一個目標可以是一個文件(.o文件、可執行文件等)或一個標籤。

此外,一些目標依賴於其他目標;例如,在鏈接可執行文件之前,你需要一個完整的.o文件集。這些要求被稱為依賴關係。

To build a target, make follows a rule, such as a rule for how to go from a .c source file to a .o object file. make already knows several rules, but you can customize these existing rules and create your own.

為了構建一個目標,make遵循一個規則,比如從一個.c源文件到一個.o目標文件的規則。

make已經知道了幾個規則,但你可以自定義這些現有規則並創建自己的規則。

15.2.1 A Sample Makefile(Makefile 示例)

The following very simple Makefile builds a program called myprog from aux.c and main.c:

下面是一個非常簡單的 Makefile,它通過 aux.c 和 main.c 生成一個名為 myprog 的程序:

# object files
OBJS=aux.o main.o
all: myprog
myprog: $(OBJS)
 $(CC) -o myprog $(OBJS)

The # in the first line of this Makefile denotes a comment.

這個Makefile的第一行中的#表示註釋。

The next line is just a macro definition; it sets the OBJS variable to two object filenames. This will be important later. For now, take note of how you define the macro and also how you reference it later ($(OBJS)).

下一行只是一個宏定義;它將OBJS變量設置為兩個對象文件的文件名。這在後面會很重要。現在,請注意如何定義宏以及如何在後面引用它($(OBJS))。

The next item in the Makefile contains its first target, all. The first target is always the default, the target that make wants to build when you run make by itself on the command line.

Makefile 中的下一項包含第一個目標,即 all。第一個目標總是默認的,也就是當你在命令行上運行 make 時,make 希望構建的目標。

The rule for building a target comes after the colon. For all, this Makefile says that you need to satisfy something called myprog. This is the first dependency in the file; all depends on myprog. Note that myprog can be an actual file or the target of another rule. In this case, it’s both (the rule for all and the target of OBJS).

構建目標的規則在冒號後面。

對於all來説,這個Makefile表示你需要滿足一個叫做myprog的東西。

這是文件中的第一個依賴項;all依賴於myprog。

請注意,myprog可以是實際的文件,也可以是另一個規則的目標。

在這種情況下,它既是all的規則,也是OBJS的目標。

To build myprog, this Makefile uses the macro $(OBJS) in the dependencies. The macro expands to aux.o and main.o, so myprog depends on these two files (they must be actual files, because there aren’t any targets with those names anywhere in the Makefile).

為了構建myprog,這個Makefile在依賴項中使用了宏$(OBJS)。宏展開為aux.o和main.o,所以myprog依賴於這兩個文件(它們必須是實際的文件,因為在整個Makefile中沒有這些名稱的目標)。

This Makefile assumes that you have two C source files named aux.c and main.c in the same directory. Running make on the Makefile yields the following output, showing the commands that make is running:

這個Makefile假設你在同一個目錄中有兩個名為aux.c和main.c的C源文件。

在Makefile上運行make會產生以下輸出,顯示make正在運行的命令:

$ make
cc -c -o aux.o aux.c
cc -c -o main.o main.c
cc -o myprog aux.o main.o

圖 15-1 是相關性的示意圖。

Figure 15-1. Makefile dependencies

Figure 15-1. Makefile dependencies

15.2.2 Built-in Rules(內置規則)

So how does make know how to go from aux.c to aux.o? After all, aux.c is not in the Makefile. The answer is that make follows its built-in rules. It knows to look for a .c file when you want a .o file, and furthermore, it knows how to run cc -c on that .c file to get to its goal of creating a .o file.

那麼,make 是如何從 aux.c 到 aux.o 的呢?畢竟,aux.c 並不在 Makefile 中。

答案是 make 遵循其內置規則。它知道當你需要一個 .o 文件時,要查找一個 .c 文件,並且,它知道如何運行 cc -c 來編譯該 .c 文件,以達到創建 .o 文件的目標。

15.2.3 Final Program Build(最終程序構建)

The final step in getting to myprog is a little tricky, but the idea is clear enough. After you have the two object files in $(OBJS), you can run the C compiler according to the following line (where $(CC) expands to the compiler name):

獲取到 $(OBJS) 中的兩個目標文件後,要得到 myprog 的最後一步有點棘手,但思路是清晰的。你可以根據以下行運行 C 編譯器(其中 $(CC) 展開為編譯器名稱):

$(CC) -o myprog $(OBJS)

The whitespace before $(CC) is a tab. You must insert a tab before any real command, on its own line. Watch out for this:

$(CC) 前面的空格是一個製表符。在任何真正的命令之前,都必須在單獨的行上插入一個製表符。

注意這個問題:

Makefile:7: *** missing separator. Stop.

An error like this means that the Makefile is broken. The tab is the separator, and if there is no separator or there’s some other interference, you’ll see this error.

這樣的錯誤意味着 Makefile 有問題。製表符是分隔符,如果沒有分隔符或有其他干擾,你會看到這個錯誤。

15.2.4 Staying Up-to-Date(保持更新)

One last make fundamental is that targets should be up-to-date with their dependencies. If you type make twice in a row for the preceding example, the first command builds myprog, but the second yields this output:

最後一個 make 的基本原則是,目標應該與其依賴項保持更新。

如果你連續兩次輸入 make 命令來運行上述示例,第一次命令會構建 myprog,但第二次會輸出以下內容:

make: Nothing to be done for 'all'.

This second time through, make looked at its rules and noticed that myprog already exists, so it didn’t build myprog again because none of the dependencies had changed since it was last built. To experiment with this, do the following:

第二次運行時,make 查看其規則並注意到 myprog 已經存在,所以它不會再次構建 myprog,因為自上次構建以來,沒有任何依賴項發生變化。

為了進行實驗,請執行以下操作:

  1. Run touch aux.c.
  2. Run make again. This time, make determines that aux.c is newer than the aux.o already in the directory, so it compiles aux.o again.
  3. myprog depends on aux.o, and now aux.o is newer than the preexisting myprog, so make must create myprog again.
  4. 運行 touch aux.c。
  5. 再次運行 make。這次,make 確定 aux.c 比目錄中已存在的 aux.o 新,因此會重新編譯 aux.o。
  6. myprog 依賴於 aux.o,而現在 aux.o 比先前存在的 myprog 新,所以 make 必須重新創建 myprog。

This type of chain reaction is very typical

這種連鎖反應很常見。

15.2.5 命令行參數和選項

You can get a great deal of mileage out of make if you know how its command-line arguments and options work.

如果你瞭解make的命令行參數和選項的工作原理,你可以從中獲得很多好處。

One of the most useful options is to specify a single target on the command line. For the preceding Makefile, you can run make aux.o if you want only the aux.o file.

其中最有用的選項之一是在命令行上指定一個單獨的目標。對於前面的Makefile,如果你只想要aux.o文件,可以運行make aux.o。

You can also define a macro on the command line. For example, to use the clang compiler, try

你還可以在命令行上定義一個宏。

例如,要使用clang編譯器,可以嘗試執行以下命令:

make CC=clang

Here, make uses your definition of CC instead of its default compiler, cc. Command-line macros come in handy when testing preprocessor definitions and libraries, especially with the CFLAGS and LDFLAGS macros that we’ll discuss shortly.

在這裏,make使用你定義的CC而不是默認的編譯器cc。

命令行宏在測試預處理器定義和庫時非常方便,特別是在討論稍後的CFLAGS和LDFLAGS宏時。

In fact, you don’t even need a Makefile to run make. If built-in make rules match a target, you can just ask make to try to create the target. For example, if you have the source to a very simple program called blah.c, try make blah. The make run proceeds like this:

事實上,你甚至不需要一個Makefile來運行make。

如果內置的make規則匹配一個目標,你只需讓make嘗試創建該目標即可。

例如,如果你有一個名為blah.c的非常簡單的程序源代碼,可以嘗試運行make blah。make的運行過程如下:

$ make blah
cc blah.o -o blah

This use of make works only for the most elementary C programs; if your program needs a library or special include directory, you should probably write a Makefile. Running make without a Makefile is actually most useful when you’re dealing with something like Fortran, Lex, or Yacc and don’t know how the compiler or utility works. Why not let make try to figure it out for you? Even if make fails to create the target, it will probably still give you a pretty good hint as to how to use the tool

這種使用make的方式只適用於最基本的C程序;如果你的程序需要一個庫或特殊的包含目錄,你應該編寫一個Makefile。

在沒有Makefile的情況下運行make實際上在處理Fortran、Lex或Yacc等情況時最有用,因為你可能不知道編譯器或實用程序的工作原理。

為什麼不讓make試着為你找出來呢?即使make無法創建目標,它可能仍然會給你一個相當好的提示,告訴你如何使用該工具。

Two make options stand out from the rest:

有兩個make選項與其他選項不同:

o -n Prints the commands necessary for a build but prevents make from actually running any commands
o -f file Tells make to read from file instead of Makefile or makefile

  • -n 打印出構建所需的命令,但不實際運行任何命令
  • -f file 告訴make從file中讀取,而不是從Makefile或makefile中讀取

    15.2.6 標準宏和變量

make has many special macros and variables. It’s difficult to tell the difference between a macro and a variable, so we’ll use the term macro to mean something that usually doesn’t change after make starts building targets.

make有許多特殊的宏和變量。很難區分宏和變量的區別,所以我們將使用術語“宏”來表示在make開始構建目標後通常不會改變的東西。

As you saw earlier, you can set macros at the start of your Makefile. These are the most common macros:

正如你之前看到的,你可以在Makefile的開頭設置宏。以下是最常見的宏:

o CFLAGS C compiler options. When creating object code from a .c file, make passes this as an argument to the compiler.
o LDFLAGS Like CFLAGS, but they’re for the linker when creating an executable from object code. o LDLIBS If you use LDFLAGS but do not want to combine the library name options with the search path, put the library name options in this file.
o CC The C compiler. The default is cc.
o CPPFLAGS C preprocessor options. When make runs the C preprocessor in some way, it passes this macro’s expansion on as an argument.
o CXXFLAGS GNU make uses this for C++ compiler flags

  • CFLAGS C編譯器選項。當從.c文件創建目標代碼時,make將它作為一個參數傳遞給編譯器。
  • LDFLAGS 類似於CFLAGS,但用於鏈接器在從目標代碼創建可執行文件時。
  • LDLIBS 如果你使用LDFLAGS,但不想將庫名稱選項與搜索路徑結合在一起,可以將庫名稱選項放在這個文件中。
  • CC C編譯器。默認是cc。
  • CPPFLAGS C預處理器選項。當make以某種方式運行C預處理器時,它將將這個宏的展開作為一個參數傳遞。
  • CXXFLAGS GNU make在C++編譯器標誌中使用它。

A make variable changes as you build targets. Because you never set make variables by hand, the following list includes the $.

一個make變量在構建目標時會發生變化。因為你從不手動設置make變量,所以下面的列表包括$符號。

o $@ When inside a rule, this expands to the current target.
o $* Expands to the basename of the current target. For example, if you’re building blah.o, this expands to blah.

  • $@ 在規則內部,這個展開為當前目標。
  • $* 展開為當前目標的基本名稱。例如,如果你正在構建blah.o,這個展開為blah。

The most comprehensive list of make variables on Linux is the make info manual.

Linux上關於make變量的最全面列表可以在make info手冊中找到。

NOTE Keep in mind that GNU make has many extensions, built-in rules, and features that other variants do not have. This is fine as long as you’re running Linux, but if you step off onto a Solaris or BSD machine and expect the same stuff to work, you might be in for a surprise. However, that’s the problem that multi-platform build systems such as GNU autotools solve.

注意

請記住,GNU make具有許多其他變體沒有的擴展、內置規則和功能。

只要你在運行Linux,這沒有問題,但是如果你切換到Solaris或BSD機器並期望相同的東西能夠工作,你可能會感到驚訝。

然而,這就是GNU autotools等多平台構建系統解決的問題。

15.2.7 Conventional Targets(常規目標)

Most Makefiles contain several standard targets that perform auxiliary tasks related to compiles.

大多數Makefile包含幾個執行與編譯相關的輔助任務的標準目標。

o clean The clean target is ubiquitous; a make clean usually instructs make to remove all of the object files and executables so that you can make a fresh start or pack up the software. Here’s an example rule for the myprog Makefile:

  • clean clean目標是無處不在的;make clean通常指示make刪除所有的目標文件和可執行文件,以便你可以重新開始或打包軟件。

    • 以下是myprog Makefile的一個示例規則:
clean:
 rm -f $(OBJS) myprog

o distclean A Makefile created by way of the GNU autotools system always has a distclean target to remove everything that wasn’t part of the original distribution, including the Makefile. You’ll see more of this in Chapter 16. On very rare occasions, you might find that a developer opts not to remove the executable with this target, preferring something like realclean instead.

  • distclean 通過GNU autotools系統創建的Makefile始終有一個distclean目標,用於刪除所有不屬於原始分發的東西,包括Makefile。

    • 在第16章中你會看到更多關於這個的內容。
    • 在非常罕見的情況下,你可能會發現開發人員選擇不使用這個目標來刪除可執行文件,而是更喜歡使用類似realclean的目標。

o install Copies files and compiled programs to what the Makefile thinks is the proper place on the system. This can be dangerous, so always run a make -n install first to see what will happen without actually running any commands.

  • install 將文件和編譯好的程序複製到Makefile認為是系統上正確位置的地方。

    • 這可能是危險的,所以始終先運行make -n install來查看將會發生什麼而不實際運行任何命令。

o test or check Some developers provide test or check targets to make sure that everything works after performing a build.

  • test或check 一些開發人員提供了test或check目標,以確保在執行構建後一切正常工作。

o depend Creates dependencies by calling the compiler with -M to examine the source code. This is an unusual-looking target because it often changes the Makefile itself. This is no longer common practice, but if you come across some instructions telling you to use this rule, make sure to do so.

  • depend 通過調用帶有-M選項的編譯器來創建依賴項,以檢查源代碼。

    • 這是一個看起來不太常見的目標,因為它經常會改變Makefile本身。
    • 這種做法已經不再常見,但如果你遇到一些告訴你使用這個規則的指令,請確保這樣做。

o all Often the first target in the Makefile. You’ll often see references to this target instead of an actual executable.

  • 通常都是 Makefile 中的第一個目標。你經常會看到對該目標的引用,而不是實際的 可執行文件

15.2.8 組織一個Makefile

Even though there are many different Makefile styles, most programmers adhere to some general rules of thumb. For one, in the first part of the Makefile (inside the macro definitions), you should see libraries and includes grouped according to package:

儘管有許多不同的Makefile風格,大多數程序員都遵循一些通用的規則。

首先,在Makefile的第一部分(宏定義內部),你應該看到按照包進行分組的庫和包含文件:

MYPACKAGE_INCLUDES=-I/usr/local/include/mypackage
MYPACKAGE_LIB=-L/usr/local/lib/mypackage -lmypackage
PNG_INCLUDES=-I/usr/local/include
PNG_LIB=-L/usr/local/lib -lpng

Each type of compiler and linker flag often gets a macro like this:

每種編譯器和鏈接器標誌通常都有一個類似這樣的宏:

CFLAGS=$(CFLAGS) $(MYPACKAGE_INCLUDES) $(PNG_INCLUDES)
LDFLAGS=$(LDFLAGS) $(MYPACKAGE_LIB) $(PNG_LIB)

Object files are usually grouped according to executables. For example, say you have a package that creates executables called boring and trite. Each has its own .c source file and requires the code in util.c. You might see something like this:

目標文件通常按照可執行文件進行分組。

例如,假設你有一個創建名為boring和trite的可執行文件的包。

每個可執行文件都有自己的.c源文件,並且需要util.c中的代碼。你可能會看到類似這樣的內容:

UTIL_OBJS=util.o

BORING_OBJS=$(UTIL_OBJS) boring.o

TRITE_OBJS=$(UTIL_OBJS) trite.o

PROGS=boring trite

The rest of the Makefile might look like this:

Makefile的其餘部分可能如下所示:

all: $(PROGS)
boring: $(BORING_OBJS)
 $(CC) -o $@ $(BORING_OBJS) $(LDFLAGS)
trite: $(TRITE_OBJS)
 $(CC) -o $@ $(TRITE_OBJS) $(LDFLAGS)

You could combine the two executable targets into one rule, but it’s usually not a good idea to do so because you would not easily be able to move a rule to another Makefile, delete an executable, or group executables differently. Furthermore, the dependencies would be incorrect: If you had just one rule for boring and trite, trite would depend on boring.c, boring would depend on trite.c, and make would always try to rebuild both programs whenever you changed one of the two source files

您可以將兩個可執行文件目標合併為一條規則,但這樣做通常不是個好主意,因為
因為您不容易將規則移到另一個 Makefile 中、刪除可執行文件或以不同方式分組可執行文件。

不同。此外,依賴關係也是不正確的:如果只有一條規則用於 boring 和
trite,trite 會依賴 boring.c,boring 會依賴 trite.c,而且每當你修改了一個程序,make 總是會嘗試重建這兩個程序。

每當你修改這兩個源文件中的一個時,make 都會嘗試重建這兩個程序。

NOTE If you need to define a special rule for an object file, put the rule for the object file just above the rule that builds the executable. If several executables use the same object file, put the object rule above all of the executable rules.

注意

如果需要為對象文件定義特殊規則,請將對象文件的規則放在構建可執行文件的規則的上方。

如果多個可執行文件使用同一對象文件,則應將對象規則置於所有可執行文件規則之上。

置於所有可執行文件規則之上。

15.3 Debuggers(調試器)

The standard debugger on Linux systems is gdb; user-friendly frontends such as the Eclipse IDE and Emacs systems are also available. To enable full debugging in your programs, run the compiler with -g to write a symbol table and other debugging information into the executable. To start gdb on an executable named program, run

Linux系統上的標準調試器是gdb;還可以使用諸如Eclipse IDE和Emacs等用户友好的前端。

為了在程序中啓用完整的調試功能,可以使用-g選項運行編譯器,將符號表和其他調試信息寫入可執行文件中。

要在名為program的可執行文件上啓動gdb,運行以下命令:

$ gdb program

You should get a (gdb) prompt. To run program with the command-line argument options, enter this at the (gdb) prompt:

您應該會得到一個(gdb)提示符。要使用命令行參數選項運行程序,請在(gdb)提示符處輸入以下內容:

(gdb) run options

If the program works, it should start, run, and exit as normal. However, if there’s a problem, gdb stops, prints the failed source code, and throws you back to the (gdb) prompt. Because the source code fragment often hints at the problem, you’ll probably want to print the value of a particular variable that the trouble may be related to. (The print command also works for arrays and C structures.)

如果程序正常工作,它應該像平常一樣啓動、運行和退出。

然而,如果出現問題,gdb會停止運行,打印出錯誤的源代碼,並將您帶回(gdb)提示符。

由於源代碼片段通常暗示了問題所在,您可能希望打印與問題可能相關的特定變量的值。

(print命令也適用於數組和C結構。)

(gdb) print variable

To make gdb stop the program at any point in the original source code, use the breakpoint feature. In the following command, file is a source code file, and line_num is the line number in that file where gdb should stop:

要讓gdb在原始源代碼的任意位置停止程序,可以使用斷點功能。

在下面的命令中,file是源代碼文件,line_num是gdb應該停止的文件中的行號:

(gdb) break file:line_num

To tell gdb to continue executing the program, use

要告訴gdb繼續執行程序,請使用以下命令:

(gdb) continue

To clear a breakpoint, enter

要清除斷點,請輸入以下命令:

(gdb) clear file:line_num

This section has provided only the briefest introduction to gdb, which includes an extensive manual that you can read online, in print, or buy as Debugging with GDB, 10th edition, by Richard M. Stallman et al. (GNU Press, 2011). The Art of Debugging by Norman Matloff and Peter Jay Salzman (No Starch Press, 2008) is another guide to debugging.

本節只是對gdb進行了最簡單的介紹,它還包括一份詳盡的手冊,您可以在線閲讀,打印出來,或者購買Richard M. Stallman等人的《Debugging with GDB, 10th edition》(GNU Press,2011)。

Norman Matloff和Peter Jay Salzman的《The Art of Debugging》(No Starch Press,2008)也是一本關於調試的指南。

NOTE If you’re interested in rooting out memory problems and running profiling tests, try Valgrind ( http://valgrind.org/).

注意

如果您對查找內存問題和運行性能分析測試感興趣,請嘗試Valgrind(http://valgrind.org/)。

15.4 Lex and Yacc(Lex和Yacc)

You might encounter Lex and Yacc when compiling programs that read configuration files or commands. These tools are building blocks for programming languages.

當編譯讀取配置文件或命令的程序時,你可能會遇到Lex和Yacc。這些工具是編程語言的構建模塊。

o Lex is a tokenizer that transforms text into numbered tags with labels. The GNU/Linux version is named flex. You may need a -ll or -lfl linker flag in conjunction with Lex.

o Yacc is a parser that attempts to read tokens according to a grammar. The GNU parser is bison; to get Yacc compatibility, run bison -y. You may need the -ly linker flag.

  • Lex是一個將文本轉換為帶有標籤的編號標記的分詞器。GNU/Linux版本被稱為flex。你可能需要與Lex一起使用-ll或-lfl鏈接標誌。
  • Yacc是一個根據語法嘗試讀取標記的解析器。GNU解析器是bison;要獲得Yacc兼容性,請運行bison -y。你可能需要-ly鏈接標誌。

15.5 腳本語言

A long time ago, the average Unix systems manager didn’t have to worry much about scripting languages other than the Bourne shell and awk. Shell scripts (discussed in Chapter 11) continue to be an important part of Unix, but awk has faded somewhat from the scripting arena. However, many powerful successors have arrived, and many systems programs have actually switched from C to scripting languages (such as the sensible version of the whois program). Let’s look at some scripting basics.

很久以前,普通的Unix系統管理員對於除了Bourne shell和awk之外的腳本語言並不需要太多擔心。Shell腳本(在第11章討論)仍然是Unix的重要組成部分,但awk在腳本領域中有些衰落。

然而,許多強大的繼任者已經出現,許多系統程序實際上已經從C語言切換到腳本語言(例如whois程序的合理版本)。

讓我們來看一些腳本的基礎知識。

The first thing you need to know about any scripting language is that the first line of a script looks like the shebang of a Bourne shell script. For example, a Python script starts out like this:

關於任何腳本語言,你需要知道的第一件事是腳本的第一行看起來像Bourne shell腳本的shebang。

例如,Python腳本的開頭是這樣的:

#!/usr/bin/python

或者這樣:

#!/usr/bin/env python

In Unix, any executable text file that starts with #! is a script. The pathname following this prefix is the scripting language interpreter executable. When Unix tries to run an executable file that starts with a #! shebang, it runs the program following the #! with the rest of the file as the standard input. Therefore, even this is a script:

在Unix中,任何以#!開頭的可執行文本文件都被視為腳本。

在這個前綴之後的路徑名是腳本語言解釋器的可執行文件。當Unix嘗試運行以#!開頭的可執行文件時,它會將#!之後的程序作為標準輸入,並執行該程序。

因此,即使是這樣一個腳本:

#!/usr/bin/tail -2
This program won't print this line,
but it will print this line...
and this line, too.
這個程序不會打印這一行,
但它會打印這一行...
還有這一行。

The first line of a shell script often contains one of the most common basic script problems: an invalid path to the scripting language interpreter. For example, say you named the previous script myscript. What if tail were actually in /bin instead of /usr/bin on your system? In that case, running myscript would produce this error:

Shell腳本的第一行通常包含最常見的基本腳本問題之一:對腳本語言解釋器的路徑設置錯誤。例如,假設你將前面的腳本命名為myscript。如果tail實際上在你的系統上的/bin而不是/usr/bin中,那麼運行myscript將產生以下錯誤:

bash: ./myscript: /usr/bin/tail: bad interpreter: No such file or 
directory

Don’t expect more than one argument in the script’s first line to work. That is, the -2 in the preceding example might work, but if you add another argument, the system could decide to treat the -2 and the new argument as one big argument, spaces and all. This can vary from system to system; don’t test your patience on something as insignificant as this.

不要期望在腳本的第一行中使用多個參數能夠正常工作。

也就是説,前面的例子中的-2可能有效,但如果你添加另一個參數,系統可能會將-2和新的參數視為一個大參數,包括空格在內。這可能因系統而異;不要在這種微不足道的事情上浪費耐心。

Now, let’s look at a few of the languages out there.

現在,讓我們來看看一些現有的語言。

15.5.1 Python

Python is a scripting language with a strong following and an array of powerful features, such as text processing, database access, networking, and multithreading. It has a powerful interactive mode and a very organized object model.

Python是一種具有強大功能的腳本語言,擁有廣泛的用户羣體和一系列強大的功能,如文本處理、數據庫訪問、網絡和多線程。

它具有強大的交互模式和非常有組織的對象模型。

Python’s executable is python, and it’s usually in /usr/bin. However, Python isn’t used just from the command line for scripts. One place you’ll find it is as a tool to build websites. Python Essential Reference, 4th edition, by David M. Beazley (Addison-Wesley, 2009) is a great reference with a small tutorial at the beginning to get you started.

Python的可執行文件是python,通常位於/usr/bin目錄下。

然而,Python不僅僅用於命令行腳本。

你會發現它在構建網站的工具中也得到了應用。

《Python基礎參考》(第4版),作者David M. Beazley(Addison-Wesley,2009)是一本很好的參考書,書的開頭還有一個小教程,幫助你入門。

15.5.2 Perl

One of the older third-party Unix scripting languages is Perl. It’s the original “Swiss army chainsaw” of programming tools. Although Perl has lost a fair amount of ground to Python in recent years, it excels in particular at text processing, conversion, and file manipulation, and you may find many tools built with it. Learning Perl, 6th edition, by Randal L. Schwartz, brian d foy, and Tom Phoenix(O’Reilly, 2011) is a tutorialstyle introduction; a larger reference is Modern Perl by Chromatic (Onyx Neon Press, 2014)

Perl是較早的第三方Unix腳本語言之一。它是編程工具中最早的“瑞士軍刀”。

儘管Perl在最近幾年中失去了一定的市場份額,但在文本處理、轉換和文件操作方面表現出色,並且你可能會發現許多使用它構建的工具。

《學習Perl》(第6版),作者Randal L. Schwartz、brian d foy和Tom Phoenix(O'Reilly,2011)是一本以教程形式介紹的入門書籍;

更詳細的參考書是Chromatic的《現代Perl》(Onyx Neon Press,2014)。

15.5.3 Other Scripting Languages(其他腳本語言)

You might also encounter these scripting languages:

你還可能遇到以下腳本語言:

o PHP. This is a hypertext-processing language often found in dynamic web scripts. Some people use PHP for standalone scripts. The PHP website is at http://www.php.net/.
o Ruby. Object-oriented fanatics and many web developers enjoy programming in this language
(http://www.ruby-lang.org/).

  • PHP。這是一種在動態網頁腳本中經常使用的超文本處理語言。有些人也會將PHP用於獨立腳本。PHP的官方網站是http://www.php.net/。
  • Ruby。面向對象的愛好者和許多網頁開發人員都喜歡使用這種語言進行編程(http://www.ruby-lang.org/)。

o JavaScript. This language is used inside web browsers primarily to manipulate dynamic content. Most experienced programmers shun it as a standalone scripting language due to its many flaws, but it’s nearly impossible to avoid when doing web programming. You might find an implementation called Node.js with an executable name of node on your system

JavaScript.這種語言主要用於在Web瀏覽器內部操作動態內容。

大多數經驗豐富的程序員都不喜歡它作為一個獨立的腳本語言,因為它有很多缺點,但在Web編程中幾乎無法避免。

你可能會在你的系統上找到一個叫做Node.js的實現,可執行文件名為node。

o Emacs Lisp. A variety of the Lisp programming language used by the Emacs text editor.

Emacs Lisp.這是Emacs文本編輯器使用的一種Lisp編程語言的變體。

o Matlab, Octave. Matlab is a commercial matrix and mathematical programming language and library. There is a very similar free software project called Octave.

Matlab, Octave. Matlab是一種商業矩陣和數學編程語言和庫。

還有一個非常相似的免費軟件項目叫做Octave。

o R. A popular free statistical analysis language. See http://www.r-project.org/ and The Art of R Programming by Norman Matloff (No Starch Press, 2011) for more information.

R.一種流行的免費統計分析語言。更多信息請參見http://www.r-project.org/和Norman Matloff的《R編程藝術》(No Starch Press,2011)。

o Mathematica. Another commercial mathematical programming language with libraries. m4 This is a macro-processing language, usually found only in the GNU autotools.

Mathematica是另一種商業數學編程語言,帶有庫。m4是一種宏處理語言,通常只在GNU自動工具中找到。

o Tcl. Tcl (tool command language) is a simple scripting language usually associated with the Tk graphical user interface toolkit and Expect, an automation utility. Although Tcl does not enjoy the widespread use that it once did, don’t discount its power. Many veteran developers prefer Tk, especially for its embedded capabilities. See http://www.tcl.tk/ for more on Tk.

Tcl. Tcl(工具命令語言)是一種簡單的腳本語言,通常與Tk圖形用户界面工具包和Expect自動化工具相關聯。

儘管Tcl不再像過去那樣廣泛使用,但不要低估它的功能。許多資深開發人員喜歡Tk,特別是因為它的嵌入能力。

更多關於Tk的信息,請參見http://www.tcl.tk/。

15.6 Java

Java is a compiled language like C, with a simpler syntax and powerful support for object-oriented programming. It has a few niches in Unix systems. For one, it’s often used as a web application environment, and it’s popular for specialized applications. For example, Android applications are usually written in Java. Even though it’s not often seen on a typical Linux desktop, you should know how Java works, at least for standalone applications.

Java是一種編譯語言,類似於C語言,語法更簡單,支持面向對象編程。

它在Unix系統中有一些特定的應用場景。首先,它經常被用作Web應用程序開發環境,並且在專門的應用程序中很受歡迎。

例如,Android應用通常是用Java編寫的。

即使在典型的Linux桌面系統中不常見,你也應該瞭解Java的工作原理,至少對於獨立應用程序來説。

There are two kinds of Java compilers: native compilers for producing machine code for your system (like a C compiler) and bytecode compilers for use by a bytecode interpreter (sometimes called a virtual machine, which is different from the virtual machine offered by a hypervisor, as described in Chapter 17). You’ll practically always encounter bytecode on Linux.

有兩種類型的Java編譯器:本地編譯器用於生成適用於系統的機器代碼(類似於C編譯器),字節碼編譯器用於字節碼解釋器(有時稱為虛擬機,與第17章中描述的虛擬機不同)。

在Linux上,你幾乎總是會遇到字節碼。

Java bytecode files end in .class. The Java runtime environment (JRE) contains all of the programs you need to run Java bytecode. To run a bytecode file, use

Java 字節碼文件的擴展名是 .class。

Java 運行時環境(JRE)包含了運行 Java 字節碼所需的所有程序。要運行一個字節碼文件,可以使用以下命令:

$ java file.class

You might also encounter bytecode files that end in .jar, which are collections of archived .class files. To run a .jar file, use this syntax:

有時你可能會遇到以 .jar 結尾的字節碼文件,這些文件是包含歸檔的 .class 文件的集合。

要運行一個 .jar 文件,可以使用以下語法:

$ java -jar file.jar

Sometimes you need to set the JAVA_HOME environment variable to your Java installation prefix. If you’re really unlucky, you might need to use CLASSPATH to include any directories containing classes that your program expects. This is a colon-delimited set of directories like the regular PATH variable for executables.

有時候你需要設置 JAVA_HOME 環境變量為你的 Java 安裝前綴。

如果你很不幸,可能需要使用 CLASSPATH 來包含任何包含程序所需類的目錄。

這是一個由冒號分隔的目錄集合,類似於用於可執行文件的常規 PATH 變量。

If you need to compile a .java file into bytecode, you need the Java Development Kit (JDK). You can run the javac compiler from JDK to create some .class files:

如果你需要將一個 .java 文件編譯成字節碼,你需要使用 Java 開發工具包(JDK)。

你可以使用 JDK 中的 javac 編譯器來創建一些 .class 文件:

$ javac file.java

JDK also comes with jar, a program that can create and pick apart .jar files. It works like tar

JDK 還帶有 jar,一個可以創建和解壓 .jar 文件的程序。它的功能類似於 tar。

15.7 Looking Forward: Compiling Packages

The world of compilers and scripting languages is vast and constantly expanding. As of this writing, new compiled languages such as Go (golang) and Swift are gaining popularity.

編譯器和腳本語言的世界是廣闊的,而且不斷擴展。

截至本文撰寫時,新的編譯語言如Go(golang)和Swift正日漸流行。

The LLVM compiler infrastructure set (http://llvm.org/) has significantly eased compiler development. If you’re interested in how to design and implement a compiler, two good books are Compilers: Principles, Techniques, and Tools, 2nd edition, by Alfred V. Aho et al. (Addison-Wesley, 2006) and Modern Compiler Design, 2nd edition, by Dick Grune et al. (Springer, 2012). For scripting language development, it’s usually best to look for online resources, as the implementations vary widely.

LLVM編譯器基礎設施集(http://llvm.org/)大大簡化了編譯器開發。

如果你對如何設計和實現編譯器感興趣,可以參考Alfred V. Aho等人編寫的《編譯原理》第二版(Addison-Wesley, 2006)和Dick Grune等人編寫的《現代編譯器設計》第二版(Springer, 2012)這兩本好書。

對於腳本語言開發,最好查找在線資源,因為實現方式各不相同。

Now that you know the basics of the programming tools on the system, you’re ready to see what they can do. The next chapter is all about how you can build packages on Linux from source code.

現在你已經瞭解了系統上的編程工具基礎知識,接下來可以看看它們能做什麼了。下一章將詳細介紹如何在Linux上從源代碼構建軟件包。

user avatar xiaoniuhululu Avatar u_16769727 Avatar yizhidanshendetielian Avatar lvweifu Avatar junxiudedoujiang Avatar chaokunyang Avatar junction_640ae1a257911 Avatar
Favorites 7 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.