Chương 15 · Công cụ phát triển Chapter 15 · Development Tools
Mọi chương trình trên máy bạn đều có một tiểu sử: mã nguồn, object file, rồi một lần liên kết Chương trình không mọc ra từ hư không — có người viết, rồi máy dịch Every program on your machine has a life story: source code, object files, then one act of linking
Brian Ward mở chương bằng một nhận xét nghe có vẻ lạ với người quản trị: trên một máy Linux, bạn không cần là lập trình viên mới cần tới công cụ phát triển.1 Lý do là các công cụ này đóng vai trò lớn hơn trong việc quản trị Linux so với ở các hệ điều hành khác; tối thiểu, bạn nên nhận ra được đâu là một tiện ích phát triển và mường tượng được cách chạy nó.1 Ông cũng nói thẳng rằng chương này nhồi rất nhiều thông tin vào một không gian nhỏ, nhưng bạn không cần thành thạo mọi thứ ở đây.1
Bạn không cần biết lập trình mới đọc được chương này. Nhưng nếu bạn dùng máy Linux, bạn sẽ liên tục gặp những cái tên như gcc, make, .so. Biết chúng là gì giúp bạn đỡ hoảng khi một chương trình báo lỗi thiếu thư viện. Nhớ nhé: chương trình nào cũng từng là chữ do người viết ra.
Brian Ward opens the chapter with a claim that sounds odd to an administrator: on a Linux machine, you don't have to be a programmer to take advantage of development tools.1 They play a larger role in managing Linux 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.1 He is also candid that the chapter packs a lot of information into a small space, but that you don't need to master everything in it.1
Ward tự xếp hạng nội dung chương giúp bạn: phần bàn về thư viện chia sẻ (shared libraries) có lẽ là điều quan trọng nhất bạn cần biết ở đây — nhưng để hiểu thư viện chia sẻ đến từ đâu, trước hết bạn cần chút nền tảng về cách dựng chương trình.1 Trang này giữ nguyên thứ tự đó: trình biên dịch trước, thư viện sau, rồi mới tới make.
Luận đề của chương: chương trình C đi theo một quy trình phát triển truyền thống — bạn viết, bạn biên dịch, rồi chúng chạy; mã người đọc được phải được dịch sang dạng nhị phân mức thấp mà bộ xử lý hiểu.1 Hiểu ba mốc của hành trình đó — biên dịch, liên kết lúc dựng, liên kết lúc chạy — là đủ để giải mã phần lớn thông báo lỗi bạn gặp khi cài phần mềm.
Ý chính: chữ người viết → máy dịch thành mảnh nhị phân → ghép các mảnh lại thành chương trình chạy được. Ba bước. Nhớ nhé: viết, dịch, ghép.
The chapter's thesis: C programs follow a traditional development process — you write them, you compile them, and they run; human-readable code must be compiled into a binary low-level form that the processor understands.1 Grasping the three milestones of that journey — compiling, link time, and run time — is enough to decode most of the error messages you meet while installing software.
.c thành object file .o bằng -c; -o đặt tên đầu ra
Dịch từng tệp chữ thành một mảnh nhị phân
Compiles .c into a .o object file with -c; -o names the output
.so mà chương trình cần (run time)
Lúc chạy mới đi tìm các thư viện dùng chung
The runtime dynamic linker/loader: finds and loads the .so files a program needs (run time)
Cụm khái niệm 01 Concept cluster 01
Trình biên dịch C: từ một tệp chữ tới một chương trình chạy được Máy dịch chữ thành chương trình như thế nào The C compiler: from a text file to a running program
Ward nói rằng biết dùng trình biên dịch C cho bạn rất nhiều hiểu biết về nguồn gốc của các chương trình bạn thấy trên hệ thống, bởi phần lớn tiện ích Linux và nhiều ứng dụng được viết bằng C hoặc C++.1 Trên đa số hệ Unix, trình biên dịch C là GNU C compiler — gcc, thường được gọi theo tên truyền thống cc — dù clang mới hơn từ dự án LLVM đang phổ biến dần.126
Máy tính không đọc được chữ bạn viết. Có một chương trình chuyên đi dịch, tên là gcc (hay cc). Bạn đưa cho nó một tệp chữ, nó trả lại một tệp máy chạy được. Nhớ nhé: gcc là người phiên dịch giữa bạn và máy.
Ward says that knowing how to use the C compiler gives you a great deal of insight into the origin of the programs on your system, because most Linux utilities and many applications are written in C or C++.1 On most Unix systems the C compiler is the GNU C compiler — gcc, often referred to by the traditional name cc — though the newer clang from the LLVM project is gaining popularity.126
Một lưu ý thực tế mà Ward đặt ngay đầu chương. Phần lớn bản phân phối không cài sẵn công cụ biên dịch C. Nếu thiếu, ông chỉ cách cài gói build-essential trên Debian/Ubuntu, hoặc yum groupinstall nhóm “Development Tools” trên Fedora/CentOS; không được nữa thì tìm gói theo từ khoá “gcc” hoặc “C compiler”.1 Đây là chi tiết hay bị bỏ qua nhưng lại là nguyên nhân số một khiến người mới nghĩ rằng “máy mình hỏng”.
Định nghĩa. Mã nguồn (source code) là đoạn mã bạn viết ra, và nó có thể trải trên nhiều tệp.1 Tệp mã nguồn C kết thúc bằng .c.1 Ward đối chiếu điều này với các ngôn ngữ kịch bản bàn ở cuối chương, nơi bạn không phải biên dịch gì cả.1
Nói đơn giản. Mã nguồn là tệp chữ bạn gõ ra, đuôi .c. Một chương trình lớn thì có nhiều tệp như vậy. Nhớ nhé: đuôi .c là chữ của người, không phải của máy.
Definition. Source code is the code you write, and it can encompass many files.1 C source files end in .c.1 Ward contrasts this with the scripting languages discussed later in the chapter, where you don't need to compile anything.1
$ cc hello.c
→ a.out
$ cc -o hello hello.c
→ hello
$ cc -c main.c
→ main.o
cc hello.c → a.out
cc -o hello hello.c → hello
cc -c main.c → main.o
$ cc hello.c
→ a.out
$ cc -o hello hello.c
→ hello
$ cc -c main.c
→ main.o
-
object file
Object file — mảnh nhị phân còn dang dở Object file — mảnh ghép chưa hoàn chỉnh Object file — a binary that is almost finished
Phần lớn chương trình C quá lớn để nhét vừa một tệp: tệp khổng lồ trở nên lộn xộn với lập trình viên, và đôi khi chính trình biên dịch cũng chật vật xử lý.1 Vì thế người ta tách mã nguồn thành từng mảnh, mỗi mảnh một tệp, rồi dùng tuỳ chọn
-ctrên từng tệp để tạo object file chứa mã nhị phân sẽ đi vào chương trình cuối cùng.12Một chương trình lớn không viết trong một tệp — sẽ rối lắm. Người ta chia ra nhiều tệp, dịch riêng từng tệp thành một mảnh nhị phân gọi là object file (đuôi
.o). Nhớ nhé: mỗi tệp chữ cho ra một mảnh ghép.Most C programs are too large to fit reasonably in a single file: mammoth files become disorganized for the programmer, and compilers sometimes have trouble processing them.1 So developers split the source into pieces, one file each, and use the compiler's
-coption on each file to create object files containing the binary object code that will eventually go into the final executable.12Bạn gặp nó ở đâuWhere you meet it Mỗi khi bạn chạymaketrên một gói phần mềm và thấy hàng chục dòngcc -c ...lướt qua, đó chính là giai đoạn này. Khi màn hình chạy một loạt dòng chữ lúc cài phần mềm. Every time you runmakeon a package and dozens ofcc -c ...lines scroll past, that is this stage.Vì sao “còn dang dở”
Ward định nghĩa object file là tệp nhị phân mà bộ xử lý gần như hiểu được, trừ vài đầu mối còn lỏng: thứ nhất, hệ điều hành không biết cách khởi động một object file; thứ hai, bạn thường cần ghép vài object file cùng một số thư viện hệ thống mới thành chương trình hoàn chỉnh.1
Mặc định,
ccđặt tên tệp object theo tên nguồn với đuôi.o; nếu bạn không dùng-okhi tạo chương trình, kết quả là một tệp têna.out.12By default
ccnames the object file after the source with a.osuffix; if you don't pass-owhen building a program, the result is a file calleda.out.12 -
linker (ld)
Trình liên kết — người ghép các mảnh lại Trình liên kết — người dán các mảnh The linker — the one who joins the pieces
Để dựng một chương trình chạy được từ một hoặc nhiều object file, bạn phải chạy trình liên kết, lệnh
ldtrong Unix.1 Tuy nhiên lập trình viên hiếm khi gọildtrực tiếp trên dòng lệnh, vì trình biên dịch C đã biết cách chạy nó giúp:cc -o myprog main.o aux.o.1Có các mảnh rồi thì cần người dán chúng lại. Người đó tên là
ld. Nhưng bạn không phải gọi nó —cctự gọi hộ. Nhớ nhé: cc vừa dịch vừa gọi thợ dán.To build a fully functioning executable from one or more object files, you must run the linker, the
ldcommand in Unix.1 Programmers rarely useldon the command line, though, because the C compiler knows how to run the linker for them:cc -o myprog main.o aux.o.1Bạn gặp nó ở đâuWhere you meet it Đây là chỗ bạn dễ gặp vấn đề nhất khi dựng phần mềm: Ward nói rõ rằng thư viện chủ yếu vào cuộc ở link time, khildtạo chương trình từ các object file.1 Lỗi cài phần mềm hay xảy ra đúng ở bước dán này. This is where you're most likely to have a problem: Ward notes that libraries come into play primarily at link time, whenldcreates the executable from object files.1Đọc một lỗi “undefined reference”
Ward đưa nguyên văn thông báo lỗi khi bạn quên liên kết với thư viện curses:
badobject.o(.text+0x28): undefined reference to 'initscr'.1 Hai phần quan trọng nhất là tên tệp object và tên hàm: khi trình liên kết xembadobject.o, nó không tìm thấy hàm đó nên không tạo được chương trình.1 Mẹo của ông: tra tên hàm trên web, gần như lúc nào bạn cũng tìm ra trang man hoặc tài liệu chỉ đúng thư viện chứa nó.1 -
-l and -L
Thư viện C — một bó object file đã dịch sẵn Thư viện — hộp đồ dùng chung A C library — a bundle of precompiled object files
Chạy trình biên dịch trên mã nguồn thường không cho đủ mã object để tự nó thành một chương trình hữu ích; bạn cần thư viện.1 Ward định nghĩa gọn: thư viện C là tập hợp các thành phần dùng chung đã biên dịch sẵn, thực chất không hơn một bó object file kèm vài tệp header.1 Ví dụ quen thuộc là thư viện toán chuẩn, nơi nhiều chương trình lấy các hàm lượng giác.1
Không ai tự làm lại mọi thứ. Có sẵn những hộp đồ dùng chung — gọi là thư viện — chứa các mảnh đã dịch sẵn, ai cần thì lấy ra ghép vào. Nhớ nhé: thư viện là hộp đồ nghề đi mượn.
Running the compiler on source code usually doesn't produce enough object code to make a useful executable on its own; you need libraries.1 Ward's definition is compact: a C library is a collection of common precompiled components, really not much more than a bundle of object files along with some header files.1 The familiar example is the standard math library, which many executables draw on for trigonometric functions.1
Bạn gặp nó ở đâuWhere you meet it Với thư viện curses, tệp cơ bản làlibcurses.a, nên tên thư viện làcursesvà bạn liên kết bằngcc -o badobject badobject.o -lcurses.13 Tên tệp làlibcurses.athì cờ để gọi nó là-lcurses. For the curses library the basic file islibcurses.a, so the library name iscursesand you link withcc -o badobject badobject.o -lcurses.13Khi thư viện không nằm chỗ mặc định
Thư viện rải rác khắp hệ thống, phần lớn nằm trong một thư mục con tên
lib, với/usr/liblà vị trí mặc định của hệ thống.1 Với đường dẫn khác thường, bạn phải báo cho trình liên kết bằng tham số-L: ví dụ của Ward làcc -o badobject badobject.o -lcurses -L/usr/junk/lib -lcrudkhi cầnlibcrud.atrong/usr/junk/lib.14Tìm một hàm nằm trong thư viện nào
Ward gợi ý dùng
nmvới bộ lọc ký hiệu--defined-only— ví dụnm --defined-only libcurses.a— và dặn trước rằng đầu ra sẽ rất dài.17 Ông cũng lưu ý nhiều bản phân phối nay đặt thư viện trong thư mục con theo kiến trúc bên dưới/usr/lib, chẳng hạn/usr/lib/x86_64-linux-gnu/.1Có một thư viện luôn được gộp vào trừ khi bạn cố tình loại ra: thư viện chuẩn C, tệp cơ bản là
libc.a.1 Nhưng hầu hết chương trình trên máy bạn dùng bản chia sẻ của nó — đó là cầu nối sang mục sau.1One library is always included unless you specifically exclude it: the C standard library, whose basic file is
libc.a.1 But most programs on your system use its shared version — which is the bridge to the next section.1 -
header files & cpp
Tệp header và bộ tiền xử lý C Tệp khai báo và người dọn đường Header files and the C preprocessor
Header C là các tệp mã nguồn bổ sung, thường chứa khai báo kiểu và khai báo hàm thư viện —
stdio.htrong chương trình mẫu là một ví dụ.1 Ward nói thẳng: rất nhiều vấn đề biên dịch liên quan tới header, phần lớn xảy ra khi trình biên dịch không tìm thấy header hoặc thư viện.1Ngoài tệp chữ chính, còn có những tệp nhỏ chuyên đi khai báo trước: “có hàm tên này, nhận tham số kiểu kia”. Chúng đuôi
.h. Máy không tìm thấy chúng là báo lỗi ngay. Nhớ nhé: đuôi .h là tờ khai, đuôi .c là việc làm thật.C header files are additional source files that usually contain type and library function declarations —
stdio.hin the sample program is one.1 Ward is blunt: a great number of compilation problems are related to header files, most of them occurring when the compiler can't find headers or libraries.1Bạn gặp nó ở đâuWhere you meet it Lỗi điển hình có dạngbadinclude.c:1:22: fatal error: notfound.h: No such file or directory; cách sửa là chỉ thêm thư mục bằng-I, chẳng hạncc -c -I/usr/junk/include badinclude.c.14 Thấy dòng “No such file or directory” kèm tên tệp.hlà máy đang than không tìm ra tờ khai. The typical error readsbadinclude.c:1:22: fatal error: notfound.h: No such file or directory; the fix is to add the directory with-I, e.g.cc -c -I/usr/junk/include badinclude.c.14Ngoặc nhọn hay ngoặc kép
Thư mục include mặc định trong Unix là
/usr/include, và trình biên dịch luôn tìm ở đó trừ khi bạn bảo nó đừng.1 Ward dặn để ý các#includedùng ngoặc kép" "thay vì ngoặc nhọn< >: ngoặc kép nghĩa là header không nằm trong thư mục include hệ thống, thường là cùng thư mục với tệp nguồn — gặp trục trặc với dạng này thì nhiều khả năng bạn đang biên dịch mã nguồn không đầy đủ.1Ba loại chỉ thị tiền xử lý
Việc đi tìm header không phải do trình biên dịch làm, mà do bộ tiền xử lý C, một chương trình chạy trên mã nguồn trước khi trình biên dịch phân tích chương trình thật.1 Chỉ thị bắt đầu bằng
#và có ba loại cơ bản: include (#includechèn nguyên một tệp), định nghĩa macro (#define BLAH somethingthay mọi chỗ xuất hiệnBLAH), và điều kiện (#ifdef,#if,#endif).1Ward khép lại phần này bằng một lời nhắc đáng nhớ: bộ tiền xử lý C không biết gì về cú pháp C, biến, hàm hay các thành phần khác — nó chỉ hiểu macro và chỉ thị của chính nó.1
Ward closes the section with a line worth remembering: the C preprocessor knows nothing about C syntax, variables, functions or other elements — it understands only its own macros and directives.1
Cụm khái niệm 02 Concept cluster 02
Thư viện chia sẻ: thứ quan trọng nhất trong chương này Thư viện dùng chung và người đi tìm chúng Shared libraries: the most important thing in this chapter
Ward tự xếp hạng: phần bàn về thư viện chia sẻ có lẽ là điều quan trọng nhất bạn cần biết trong chương.1 Ông cũng khẳng định bạn có thể kiểm soát được chúng nếu nắm bốn điều: cách liệt kê thư viện chia sẻ mà một chương trình cần; cách một chương trình đi tìm thư viện chia sẻ; cách liên kết chương trình với thư viện chia sẻ; và cách tránh những cạm bẫy thường gặp.1
Đây là phần quan trọng nhất chương. Bốn câu hỏi thôi: chương trình cần những hộp đồ nào? Nó tìm chúng ở đâu? Làm sao nối nó với hộp đồ? Và tránh hỏng bằng cách nào? Nhớ nhé: cần gì, tìm ở đâu, nối thế nào, tránh hỏng ra sao.
Ward ranks it himself: the discussion of shared libraries is likely the most important thing you need to know in the chapter.1 He also insists you can bring them under control if you know four things: how to list the shared libraries an executable needs; how an executable looks for shared libraries; how to link a program against one; and how to avoid the common pitfalls.1
Nếu bạn muốn đi sâu hơn chương này. Ward giới thiệu ba nguồn đọc thêm: quyển Linkers and Loaders của John R. Levine (Morgan Kaufmann, 1999); bài “The Inside Story on Shared Libraries and Dynamic Loading” của David M. Beazley, Brian D. Ward và Ian R. Cooke (Computing in Science & Engineering, số tháng 9–10 năm 2001); và các tài nguyên trực tuyến như Program Library HOWTO.114 Ông nói thêm rằng trang man ld.so(8) cũng đáng đọc.19
Định nghĩa đối lập. Tệp thư viện kết thúc bằng .a (như libcurses.a) gọi là thư viện tĩnh: khi liên kết, trình liên kết sao chép mã máy cần thiết từ tệp thư viện vào chương trình của bạn.1 Thư viện chia sẻ có đuôi chứa .so (shared object), như libc-2.15.so hoặc libc.so.6: liên kết với nó không sao mã vào chương trình, mà chỉ thêm tham chiếu tới tên trong mã của tệp thư viện.1
Hai kiểu. Kiểu .a: chép hẳn đồ vào ba lô của mình, nặng nhưng độc lập. Kiểu .so: chỉ ghi lại địa chỉ kho, lúc cần mới tới mượn. Nhớ nhé: .a là chép, .so là mượn.
A definition by contrast. A library file ending in .a (like libcurses.a) is a static library: at link time the linker copies the necessary machine code from the library file into your executable.1 A shared library has a suffix containing .so (shared object), as in libc-2.15.so or libc.so.6: linking against it does not copy the code into the executable, it just adds references to names in the library file's code.1
.a → chép vào chương trình
.so → chỉ ghi tham chiếu
link time → ld
run time → ld.so
$ ldd /bin/bash
.a → chép
.so → mượn
ld → lúc dựng
ld.so → lúc chạy
.a → copied in
.so → referenced only
link time → ld
run time → ld.so
$ ldd /bin/bash
-
static library
Thư viện tĩnh — độc lập nhưng đông cứng Chép hẳn vào — chắc chắn nhưng nặng Static libraries — self-contained but frozen
Khi đã sao mã vào, chương trình cuối cùng không còn cần tệp thư viện gốc lúc chạy nữa, và hành vi của nó không đổi ngay cả khi tệp
.athay đổi.1 Đó là ưu điểm — và cũng chính là nhược điểm.Chép hẳn vào ba lô thì đi đâu cũng dùng được, không sợ kho đóng cửa. Nhưng ba lô nặng, và nếu kho sửa lại món đồ thì ba lô của bạn vẫn giữ món cũ. Nhớ nhé: chép vào thì an toàn nhưng không cập nhật được.
Once the code is copied in, the final executable no longer needs the original library file when it runs, and its behaviour is not subject to change if the
.afile changes.1 That is the advantage — and equally the drawback.Cái giá phải trảThe price Kích thước thư viện luôn tăng, số thư viện dùng cũng tăng, khiến thư viện tĩnh lãng phí cả dung lượng đĩa lẫn bộ nhớ.1 Càng nhiều chương trình chép vào thì càng tốn chỗ. Library sizes keep increasing, as does the number of libraries in use, which makes static libraries wasteful of both disk space and memory.1Vấn đề an ninh nghiêm trọng hơn
Ward nêu một hệ quả ít người nghĩ tới: nếu về sau người ta phát hiện một thư viện tĩnh là thiếu sót hoặc không an toàn, thì không có cách nào thay đổi các chương trình đã liên kết với nó, ngoài việc đi tìm và biên dịch lại từng chương trình một.1
Nhưng đôi khi tĩnh vẫn là lời giải
Ở cuối mục về cạm bẫy, chính Ward đề xuất dùng bản tĩnh của các thư viện ít tên tuổi như một trong hai giải pháp phòng ngừa, khi API của thư viện có thể đổi nhẹ giữa các phiên bản phụ và làm hỏng phần mềm đã cài.1
-
ldd
ldd — liệt kê thư viện mà một chương trình cần ldd — hỏi xem chương trình cần mượn gì ldd — listing what a program needs
Để xem một chương trình dùng những thư viện chia sẻ nào, chạy
ldd prog.18 Ward lấy chính shell làm ví dụ,ldd /bin/bash, và đọc kết quả rất rõ ràng: phía trái dấu=>là tên thư viện — thứ mà chương trình biết; phía phải là nơild.sotìm thấy thư viện đó.1Gõ
lddrồi tên chương trình, máy sẽ liệt kê nó cần mượn những gì và mượn ở đâu. Nhớ nhé: ldd là bảng kê đồ đi mượn.To see which shared libraries a program uses, run
ldd prog.18 Ward uses the shell itself,ldd /bin/bash, and reads the output plainly: the left of the=>is the library name — what the executable knows; the right is whereld.sofound it.1Điều bất ngờ trong đầu raThe surprise in the output Vì hiệu năng và tính linh hoạt, bản thân chương trình chạy thường không biết vị trí thư viện chia sẻ của nó; nó chỉ biết tên thư viện, và có lẽ một gợi ý nhỏ về chỗ tìm.1 Chương trình chỉ nhớ tên món đồ, chứ không nhớ kho nào có. For performance and flexibility, executables usually don't know the locations of their shared libraries; they know only the library names, and perhaps a small hint about where to find them.1Thư viện nằm ở đâu
Tệp thư viện chia sẻ thường ở cùng những chỗ với thư viện tĩnh. Hai thư mục thư viện chuẩn trên hệ Linux là
/libvà/usr/lib, dù còn nhiều chỗ khác rải rác — và Ward nhấn mạnh/libkhông nên chứa thư viện tĩnh.1Dòng cuối của ldd
Dòng cuối trong đầu ra
lddcho thấy vị trí thật của chínhld.so.1 Trong sách, đoạn văn ghi vị trí đó là/lib/ld-linux.so.2, trong khi đầu ra mẫu ngay phía trên lại in/lib64/ld-linux-x86-64.so.2— khác biệt phản ánh kiến trúc 32-bit hay 64-bit của máy, không phải mâu thuẫn.1 Trangld.so(8)xác nhận trên một số kiến trúc 64-bit, đường dẫn mặc định là/lib64rồi/usr/lib64.9Một chương trình nhỏ tên
ld.so— trình liên kết/nạp động lúc chạy — mới là bên tìm và nạp thư viện chia sẻ cho chương trình tại thời điểm chạy.1A small program named
ld.so— the runtime dynamic linker/loader — is what actually finds and loads shared libraries for a program at run time.1 -
ld.so search path
ld.so tìm thư viện theo thứ tự nào Máy đi tìm hộp đồ ở đâu trước The order in which ld.so searches
Ward nói đây là một trong những điểm rắc rối phổ biến nhất: trình liên kết động không tìm ra thư viện.1 Theo cách trình bày của ông, chỗ đầu tiên nó nên tìm là đường dẫn tìm kiếm thư viện lúc chạy (rpath) được cấu hình sẵn trong chương trình, nếu có; kế đó là bộ nhớ đệm hệ thống
/etc/ld.so.cache; và còn một chỗ nữa là biến môi trườngLD_LIBRARY_PATH.1Khi chạy, máy phải đi tìm các hộp đồ dùng chung. Nó tìm theo thứ tự: chỗ ghi sẵn trong chương trình trước, rồi tới sổ tay tra nhanh của hệ thống. Nhớ nhé: có thứ tự tìm, và thứ tự đó quyết định bạn lấy trúng bản nào.
Ward calls this one of the common trouble points: the dynamic linker being unable to find a library.1 In his account, the first place it should normally look is the executable's preconfigured runtime library search path (rpath), if it exists; next a system cache,
/etc/ld.so.cache; and there is one more place, the environment variableLD_LIBRARY_PATH.1Bộ nhớ đệm đến từ đâuWhere the cache comes from/etc/ld.so.cachelà bộ đệm nhanh chứa tên các tệp thư viện tìm thấy trong những thư mục liệt kê ở tệp cấu hình/etc/ld.so.conf; hai thư mục chuẩn/libvà/usr/liblà ngầm định nên không cần liệt kê.110 Sổ tay tra nhanh được dựng từ danh sách thư mục ghi trong/etc/ld.so.conf./etc/ld.so.cacheis a fast cache of the names of library files found in the directories listed in the configuration file/etc/ld.so.conf; the standard directories/liband/usr/libare implicit and need not be listed.110Dựng lại bộ đệm bằng tay
Nếu bạn sửa
ld.so.confhoặc thay đổi một trong các thư mục thư viện chia sẻ, bạn phải dựng lại tệp/etc/ld.so.cachebằng tay với lệnhldconfig -v; tuỳ chọn-vcho thông tin chi tiết về các thư viện được thêm vào bộ đệm và những thay đổi nó phát hiện.111 Ward cũng lưu ý rằngld.so.confcó thể lần lượt nạp thêm nhiều tệp từ một thư mục như/etc/ld.so.conf.d.110Lời khuyên bị bỏ qua nhiều nhất
“Đừng tập thói quen nhét thêm thứ vào
/etc/ld.so.conf” — Ward viết rằng bạn nên biết rõ những thư viện chia sẻ nào đang nằm trong bộ đệm hệ thống; nếu tống mọi thư mục thư viện kỳ quặc vào đó, bạn chuốc lấy rủi ro xung đột và một hệ thống cực kỳ mất tổ chức.1Bản thứ tự chính xác của ld.so(8) — và vì sao nó khác
Cách kể của sách gộp
LD_LIBRARY_PATHvào cuối như “một chỗ nữa”, rồi ở mục sau lại nói biến này khiếnld.sotìm các thư mục được chỉ định trước mọi thứ khác.1 Trang manld.so(8)cho thứ tự chuẩn xác gồm năm bước, áp dụng khi tên phụ thuộc không chứa dấu gạch chéo: (1) các thư mục trong thuộc tínhDT_RPATHnếu có và không cóDT_RUNPATH; (2) biếnLD_LIBRARY_PATH, trừ khi chương trình chạy ở chế độ thực thi an toàn; (3) các thư mục trongDT_RUNPATH; (4) bộ đệm/etc/ld.so.cache; (5) đường dẫn mặc định/librồi/usr/lib.9 Trang này theo cách trình bày của sách ở phần thân và ghi lại biến thể chính xác ở đây, thay vì lặng lẽ chọn một bên. -
-Wl,-rpath
rpath và cạm bẫy mang tên LD_LIBRARY_PATH Cách làm đúng và cách làm sai rpath, and the trap called LD_LIBRARY_PATH
Khi bạn biên dịch phần mềm cần một đường dẫn thư viện ít gặp, Ward khuyên cấp cho chương trình một đường dẫn tìm kiếm thư viện lúc chạy dựng sẵn bên trong.1 Ví dụ của ông: với
libweird.so.1nằm trong/opt/obscure/lib, hãy liên kết bằngcc -o myprog myprog.o -Wl,-rpath=/opt/obscure/lib -L/opt/obscure/lib -lweird— và ông nhấn mạnh rằng dù đã dùng-Wl,-rpath, bạn vẫn cần cờ-L.136Nếu hộp đồ để ở chỗ lạ, cách đúng là ghi luôn địa chỉ đó vào trong chương trình lúc dựng. Cách sai là mỗi lần chạy lại hô to “tìm ở đây này!”. Nhớ nhé: ghi địa chỉ vào chương trình, đừng hô mỗi lần chạy.
When you compile software that needs an obscure library path, Ward advises giving the executable a built-in runtime library search path.1 His example: for
libweird.so.1in/opt/obscure/lib, link withcc -o myprog myprog.o -Wl,-rpath=/opt/obscure/lib -L/opt/obscure/lib -lweird— and he stresses that even when you use-Wl,-rpath, you still need the-Lflag.136Ba điều tệ có thể xảy raThree bad things Ward liệt kê ba hậu quả khi lạm dụng thư viện chia sẻ: thiếu thư viện, hiệu năng tệ hại, và thư viện không khớp.1 Ba kiểu hỏng: thiếu đồ, chậm, và lấy nhầm bản. Ward lists three consequences of abusing shared libraries: missing libraries, terrible performance, and mismatched libraries.1Nguyên nhân số một
Ward gọi tên thủ phạm không úp mở: nguyên nhân số một của mọi vấn đề thư viện chia sẻ là biến môi trường
LD_LIBRARY_PATH.1 Đặt biến này thành một tập thư mục ngăn cách bằng dấu hai chấm khiếnld.sotìm trong các thư mục đó trước mọi thứ khác.1 Đó là cách rẻ tiền để chữa cháy khi bạn di chuyển thư viện, không có mã nguồn, không dùng đượcpatchelf, hoặc đơn giản là lười biên dịch lại — và theo lời ông, “tiền nào của nấy”.113Đừng bao giờ đặt nó ở đâu
Lời khuyên dứt khoát: không bao giờ đặt
LD_LIBRARY_PATHtrong các tệp khởi động shell hay khi biên dịch phần mềm.1 Khi gặp biến này, trình liên kết động thường phải quét toàn bộ nội dung từng thư mục được chỉ định nhiều lần hơn bạn muốn biết; điều đó gây sụt hiệu năng lớn, nhưng quan trọng hơn là gây xung đột và thư viện không khớp, vì trình liên kết tìm trong các thư mục ấy cho mọi chương trình.1Nếu buộc phải dùng: kịch bản bao
Với một chương trình tồi mà bạn không có mã nguồn — hoặc một ứng dụng bạn không muốn biên dịch lại — Ward chỉ cách gói nó trong một kịch bản bao (wrapper script): một tệp
#!/bin/shđặtLD_LIBRARY_PATH,exportnó, rồiexectệp nhị phân thật.1 Như vậy biến chỉ sống trong một tiến trình, không lan ra cả hệ thống.Sửa rpath của tệp nhị phân đã có
Nếu cần đổi đường dẫn tìm kiếm thư viện lúc chạy của một tệp nhị phân sẵn có, bạn dùng được chương trình
patchelf, nhưng nhìn chung nên làm việc này lúc biên dịch thì hơn.113 Nhân tiện, Ward định nghĩa luôn: ELF — Executable and Linkable Format — là định dạng chuẩn dùng cho tệp thực thi và thư viện trên hệ Linux.112Hai giải pháp phòng ngừa tốt nhất, theo Ward: hoặc dùng một phương pháp nhất quán để cài thư viện chia sẻ kèm
-Wl,-rpathtạo đường dẫn liên kết lúc chạy, hoặc đơn giản là dùng bản tĩnh của những thư viện ít tên tuổi.1The two best preventive solutions, per Ward: either use a consistent methodology to install shared libraries with
-Wl,-rpathto create a runtime link path, or simply use the static versions of obscure libraries.1
Cụm khái niệm 03 Concept cluster 03
make: mục tiêu, phụ thuộc, quy tắc — và một ký tự tab make: bảo máy tự làm phần việc lặp lại make: targets, dependencies, rules — and one tab character
Một chương trình có nhiều hơn một tệp mã nguồn, hoặc đòi hỏi những tuỳ chọn biên dịch kỳ lạ, thì quá cồng kềnh để biên dịch bằng tay.1 Ward nói bạn nên biết chút ít về make nếu đang chạy một hệ Unix, vì đôi khi chính các tiện ích hệ thống dựa vào nó để hoạt động; khi bạn thấy một tệp tên Makefile hoặc makefile, bạn biết mình đang làm việc với make.1
Khi chương trình có nhiều tệp, gõ tay từng lệnh dịch rất mệt và dễ sai. make đọc một tờ hướng dẫn tên Makefile rồi làm thay bạn. Nhớ nhé: thấy tệp tên Makefile là biết có thể gõ make.
A program with more than one source file, or one that requires strange compiler options, is too cumbersome to compile by hand.1 Ward says you should know a little about make if you run a Unix system, because system utilities sometimes rely on it to operate; when you see a file named Makefile or makefile, you know you're dealing with make.1
Phạm vi của chương này, theo lời tác giả. Ward nói rõ chương này chỉ là phần nổi của tảng băng: có hẳn những quyển sách viết riêng về make, chẳng hạn Managing Projects with GNU Make, tái bản lần thứ 3, của Robert Mecklenburg (O’Reilly, 2005).1 Ngoài ra, phần lớn gói phần mềm Linux được dựng bằng một lớp bổ sung nằm trên make hoặc một công cụ tương tự; sách sẽ xem xét một lớp như vậy — autotools — ở Chương 16.1
Ba danh từ. Ý tưởng cơ bản của make là mục tiêu (target) — một cái đích bạn muốn đạt tới, có thể là một tệp (.o, một chương trình chạy được…) hoặc một nhãn.1 Một số mục tiêu cần mục tiêu khác trước: những yêu cầu đó gọi là phụ thuộc (dependencies).1 Để dựng một mục tiêu, make đi theo một quy tắc (rule), chẳng hạn quy tắc đi từ tệp nguồn .c sang tệp object .o.1
Ba từ thôi. Đích cần đạt, những thứ phải có trước, và cách làm. Nhớ nhé: đích — cần gì trước — làm thế nào.
Three nouns. The basic idea behind make is the target — a goal you want to achieve, which can be a file (a .o, an executable, and so on) or a label.1 Some targets depend on other targets; those requirements are called dependencies.1 To build a target, make follows a rule, such as one specifying how to go from a .c source file to a .o object file.1
# object files
OBJS=aux.o main.o
all: myprog
myprog: $(OBJS)
→ $(CC) -o myprog $(OBJS)
→ = một ký tự tab
all: myprog
myprog: aux.o main.o
→ cc -o myprog ...
→ phải là tab
# object files
OBJS=aux.o main.o
all: myprog
myprog: $(OBJS)
→ $(CC) -o myprog $(OBJS)
→ = one tab character
-
a sample Makefile
Đọc một Makefile năm dòng Tờ hướng dẫn ngắn nhất có thể Reading a five-line Makefile
Ward dựng tiếp ví dụ
aux.c+main.cở mục 15.1.1 thành một Makefile rất đơn giản tạo ra chương trìnhmyprog.1 Dấu#ở dòng đầu là chú thích; dòng kế là định nghĩa macro đặt biếnOBJSthành hai tên tệp object, và bạn tham chiếu lại nó bằng cú pháp$(OBJS).1Tờ hướng dẫn chỉ có vài dòng: dòng đầu là ghi chú, dòng sau đặt tên tắt cho danh sách tệp, rồi tới các đích cần làm. Nhớ nhé: Makefile chỉ là tờ ghi “muốn gì, cần gì, làm sao”.
Ward builds the
aux.c+main.cexample from 15.1.1 into a very simple Makefile that produces the programmyprog.1 The#on the first line denotes a comment; the next line is a macro definition settingOBJSto two object filenames, which you reference later as$(OBJS).1Mục tiêu đầu tiên là mặc địnhThe first target is the default Mục tiêu đầu tiên trong tệp — ở đây làall— luôn là mặc định, tức mục tiêumakemuốn dựng khi bạn chỉ gõmaketrần trụi.115 Gõmakekhông kèm gì thì nó làm đích đầu tiên trong tờ hướng dẫn. The first target in the file — hereall— is always the default, the onemakewants to build when you runmakeby itself.115Đọc dây chuyền phụ thuộc
Quy tắc dựng một mục tiêu nằm sau dấu hai chấm. Với
all, Makefile nói rằng bạn cần thoả mãn thứ gọi làmyprog— phụ thuộc đầu tiên trong tệp.1 Ward lưu ýmyprogcó thể là một tệp thật hoặc mục tiêu của một quy tắc khác; ở đây nó là cả hai.1 Còn$(OBJS)nở ra thànhaux.ovàmain.o, và chúng buộc phải là tệp thật vì không có mục tiêu nào mang tên đó trong Makefile.1 -
built-in rules
Quy tắc dựng sẵn — và ký tự tab khét tiếng make đã biết sẵn vài việc Built-in rules — and the notorious tab
Làm sao
makebiết cách đi từaux.csangaux.o, khiaux.cthậm chí không xuất hiện trong Makefile? Câu trả lời làmakecó sẵn một số quy tắc dựng sẵn: nó biết đi tìm tệp.ckhi bạn muốn một tệp.o, và biết chạycc -ctrên tệp.cđó.116makeđã học thuộc vài việc thường làm, nên bạn không phải dặn. Muốn tệp.oà? Nó tự đi tìm tệp.ccùng tên. Nhớ nhé: có những việc make tự biết, bạn không cần viết ra.How does
makeknow how to get fromaux.ctoaux.o, whenaux.cisn't even in the Makefile? Becausemakehas built-in rules: it knows to look for a.cfile when you want a.ofile, and knows to runcc -con that.cfile.116Lỗi kinh điểnThe classic error Khoảng trắng trước$(CC)là một ký tự tab, vàmakerất nghiêm khắc với tab: bạn phải chèn tab trước mọi lệnh hệ thống nằm trên dòng riêng của nó.1 Đầu dòng lệnh phải là phím Tab, không phải dấu cách. The whitespace before$(CC)is a tab character, andmakeis very strict about tabs: you must insert a tab before any system command on its own line.1Thông báo lỗi nếu bạn dùng dấu cách
Ward đưa nguyên văn dòng lỗi bạn sẽ gặp:
Makefile:7: *** missing separator. Stop.1 Lỗi kiểu này nghĩa là Makefile hỏng — tab chính là dấu phân tách, và nếu không có dấu phân tách hoặc có thứ gì khác chen vào, bạn sẽ thấy đúng thông báo đó.1Bước liên kết cuối cùng
Bước cuối để có
myproghơi rối nhưng ý tưởng thì rõ: sau khi đã có hai tệp object trong$(OBJS), bạn chạy trình biên dịch C theo dòng$(CC) -o myprog $(OBJS), trong đó$(CC)nở ra thành tên trình biên dịch.1 Trong bộ quy tắc mặc định củamake,CCchính là trình biên dịch C và mặc định làcc.115 -
dependency updates
Cập nhật theo phụ thuộc — phản ứng dây chuyền Chỉ làm lại phần nào đã đổi Dependency updates — the chain reaction
Nguyên tắc cuối cùng cần nắm: mục đích của
makenói chung là đưa các mục tiêu về trạng thái cập nhật so với phụ thuộc của chúng, và nó được thiết kế để chỉ làm số bước tối thiểu cần thiết — điều có thể tiết kiệm rất nhiều thời gian.1 Gõmakehai lần liên tiếp, lần thứ hai bạn nhận được:make: Nothing to be done for 'all'.1Nếu không có gì đổi,
makesẽ không làm lại. Nó chỉ làm lại đúng phần nào có tệp mới hơn. Nhớ nhé: đổi tới đâu, làm lại tới đó thôi.The last fundamental: in general
make's goal is to bring targets up to date with their dependencies, and it is designed to take only the minimum steps necessary — which can mean considerable time savings.1 Typemaketwice in a row and the second run printsmake: Nothing to be done for 'all'.1Thí nghiệm ba bước của tác giảThe author's three-step experiment Chạytouch aux.c; chạy lạimake: nó xác địnhaux.cmới hơnaux.onên biên dịch lạiaux.o; và vìmyprogphụ thuộcaux.o, màaux.ogiờ mới hơnmyprog, nênmakephải tạo lạimyprog.1 Chạm vào một tệp cho nó “mới hơn”, thế là cả dây chuyền làm lại. Runtouch aux.c; runmakeagain: it determinesaux.cis newer thanaux.oso it recompilesaux.o; and sincemyprogdepends onaux.o, which is now newer thanmyprog,makemust createmyprogagain.1Chỉ định một mục tiêu, hoặc một macro
Một trong những tuỳ chọn hữu ích nhất là nêu đúng một mục tiêu trên dòng lệnh: với Makefile ở trên,
make aux.onếu bạn chỉ muốn tệpaux.o.1 Bạn cũng định nghĩa được macro ngay trên dòng lệnh — ví dụmake CC=clangđể dùng trình biên dịchclangthay cho mặc địnhcc.115 Ward nói macro dòng lệnh rất tiện khi thử nghiệm định nghĩa tiền xử lý và thư viện, nhất là vớiCFLAGSvàLDFLAGS.1Chạy make mà không cần Makefile
Nếu quy tắc dựng sẵn khớp với một mục tiêu, bạn có thể nhờ
maketạo mục tiêu đó mà không cần Makefile nào: với mã nguồnblah.c, thửmake blah.1 Cách này chỉ hợp với chương trình C sơ đẳng nhất; nó hữu dụng nhất khi bạn đụng tới thứ như Fortran, Lex hay Yacc và chưa biết trình biên dịch hoặc tiện ích đó hoạt động ra sao — kể cả khi thất bại,makethường vẫn gợi ý khá tốt về cách dùng công cụ.1 -
macros, variables, targets
Macro, biến và các mục tiêu quy ước Những cái tên bạn sẽ gặp đi gặp lại Macros, variables and conventional targets
Ward phân biệt: macro là thứ thường không đổi sau khi
makebắt đầu dựng mục tiêu, còn biến thì thay đổi trong lúc dựng và bắt đầu bằng dấu$.1 Các macro phổ biến nhất:CFLAGS(tuỳ chọn trình biên dịch C),LDFLAGS(tuỳ chọn cho trình liên kết),LDLIBS(tên thư viện, khi bạn không muốn gộp chung với đường dẫn tìm kiếm trongLDFLAGS),CC(trình biên dịch C, mặc địnhcc),CPPFLAGS(tuỳ chọn tiền xử lý C) vàCXXFLAGS(GNU make dùng cho cờ trình biên dịch C++).116Trong Makefile có những cái tên viết hoa hay gặp:
CClà trình dịch,CFLAGSlà các tuỳ chọn cho nó. Biết vài cái tên này là đọc được phần lớn Makefile. Nhớ nhé: chữ hoa là chỗ để chỉnh, không phải chỗ để sợ.Ward draws the line this way: a macro usually doesn't change after
makestarts building targets, while a variable changes as you build and begins with a dollar sign.1 The most common macros:CFLAGS(C compiler options),LDFLAGS(options for the linker),LDLIBS(library-name options, when you don't want to combine them with the search path inLDFLAGS),CC(the C compiler, defaultcc),CPPFLAGS(C preprocessor options) andCXXFLAGS(GNU make uses this for C++ compiler flags).116Ba biến tự độngThree automatic variables$@nở ra thành mục tiêu hiện tại;$<thành phụ thuộc đầu tiên của mục tiêu;$*thành phần gốc của tên mục tiêu — nếu bạn đang dựngblah.othì$*làblah.115 Ba ký hiệu tắt: đích đang làm, thứ đầu tiên nó cần, và phần tên không có đuôi.$@expands to the current target;$<to the target's first dependency;$*to the basename or stem of the current target — buildingblah.o,$*isblah.115Sáu mục tiêu quy ước
Hầu hết nhà phát triển thêm vài mục tiêu chung vào Makefile của họ.1
cleangần như luôn có mặt:make cleanthường bảomakexoá mọi tệp object và chương trình để bạn khởi đầu lại từ đầu hoặc đóng gói phần mềm.1distclean— luôn có trong Makefile do autotools sinh ra — xoá mọi thứ không thuộc bản phân phối gốc, kể cả chính Makefile.117installchép tệp và chương trình đã biên dịch tới nơi Makefile cho là đúng chỗ trên hệ thống — việc này có thể nguy hiểm, nên Ward dặn luôn chạymake -n installtrước để xem điều gì sẽ xảy ra.1Ba mục tiêu còn lại
testhoặccheckđược một số nhà phát triển cung cấp để chắc rằng mọi thứ hoạt động sau khi dựng.117dependtạo các phụ thuộc bằng cách gọi trình biên dịch với-Mđể xem xét mã nguồn — một mục tiêu trông lạ vì nó thường sửa chính Makefile; Ward nói cách làm này không còn phổ biến, nhưng nếu gặp hướng dẫn bảo dùng thì hãy dùng.15 Cònallthường là mục tiêu đầu tiên trong Makefile, và bạn sẽ thấy người ta tham chiếu tới nó thay vì tới một tệp thực thi cụ thể.1Tổ chức một Makefile
Ward đưa vài quy tắc ngón tay cái: ở phần đầu Makefile, trong các định nghĩa macro, thư viện và include nên được nhóm theo gói; mỗi loại cờ biên dịch và cờ liên kết thường có macro riêng; và tệp object thường được nhóm theo tệp thực thi.1 Ông cảnh báo không nên gộp hai mục tiêu thực thi vào một quy tắc: ngoài chuyện khó tách rời về sau, phụ thuộc sẽ sai — chương trình này sẽ phụ thuộc vào mã nguồn của chương trình kia, và
makesẽ luôn dựng lại cả hai mỗi khi bạn sửa một trong hai tệp nguồn.1Một quy ước về vị trí
Nếu bạn cần định nghĩa quy tắc riêng cho một tệp object, Ward khuyên đặt quy tắc đó ngay trên quy tắc dựng tệp thực thi; nếu nhiều tệp thực thi cùng dùng một tệp object, hãy đặt quy tắc cho object đó phía trên tất cả các quy tắc thực thi.1
Ward khép mục bằng một cảnh báo về tính di động: GNU make có nhiều phần mở rộng, quy tắc dựng sẵn và tính năng mà các biến thể khác không có — ổn khi bạn chạy Linux, nhưng bước sang một máy Solaris hay BSD thì bạn có thể bất ngờ; đó chính là bài toán mà một hệ thống dựng đa nền tảng như GNU autotools sinh ra để giải.1
Ward closes the section with a portability warning: GNU make has many extensions, built-in rules and features other variants lack — fine while you're on Linux, but step onto a Solaris or BSD machine and you may be in for a surprise; that is exactly the problem a multiplatform build system such as GNU autotools is designed to solve.1
Cụm khái niệm 04 Concept cluster 04
Lex, Yacc, ngôn ngữ kịch bản và Java Những công cụ và ngôn ngữ khác trên máy Lex, Yacc, scripting languages and Java
Ba mục cuối của chương ngắn hơn nhưng không kém hữu dụng: chúng giúp bạn nhận diện những thứ sẽ hiện ra trong quá trình biên dịch gói phần mềm, hoặc trong các tệp thực thi bạn mở ra xem.1
Ba phần cuối ngắn thôi: hai công cụ để làm ra ngôn ngữ, một nhóm ngôn ngữ chạy thẳng không cần dịch, và Java. Nhớ nhé: biết mặt là đủ, chưa cần biết dùng.
The chapter's last three sections are shorter but no less useful: they help you recognise things that turn up while compiling packages, or inside executables you open to inspect.1
Dòng đầu tiên nói lên tất cả. Điều đầu tiên cần biết về bất kỳ ngôn ngữ kịch bản nào là dòng đầu của một kịch bản trông giống shebang của kịch bản Bourne shell.1 Một kịch bản Python bắt đầu bằng #!/usr/bin/python, hoặc bản #!/usr/bin/env python chạy phiên bản Python đầu tiên tìm thấy trong đường dẫn lệnh thay vì luôn vào /usr/bin.1
Nhìn dòng đầu là biết. Kịch bản nào cũng mở đầu bằng #! rồi tên chương trình sẽ chạy nó. Nhớ nhé: dòng #! là tấm biển chỉ “ai sẽ đọc tệp này”.
The first line says it all. The first thing to know about any scripting language is that a script's first line looks like the shebang of a Bourne shell script.1 A Python script starts with #!/usr/bin/python, or the variant #!/usr/bin/env python, which runs the first Python in the command path instead of always going to /usr/bin.1
#!/usr/bin/python
#!/usr/bin/env python
$ java file.class
$ java -jar file.jar
$ javac file.java
#!/usr/bin/python
java file.class
javac file.java
#!/usr/bin/python
#!/usr/bin/env python
$ java file.class
$ java -jar file.jar
$ javac file.java
-
Lex and Yacc
Lex và Yacc — những viên gạch dựng nên ngôn ngữ Hai công cụ để chế ra ngôn ngữ Lex and Yacc — building blocks for languages
Bạn có thể gặp Lex và Yacc khi biên dịch các chương trình đọc tệp cấu hình hoặc đọc lệnh; Ward gọi chúng là những viên gạch xây nên ngôn ngữ lập trình.1 Lex là bộ tách token, biến văn bản thành các thẻ được đánh số kèm nhãn; bản GNU/Linux tên là
flex, và bạn có thể cần cờ liên kết-llhoặc-lflđi kèm.119Hai công cụ này dùng để làm ra ngôn ngữ mới. Một cái cắt văn bản thành từng từ, cái kia xem các từ đó ghép lại có đúng ngữ pháp không. Nhớ nhé: một cái cắt từ, một cái soát ngữ pháp.
You might encounter Lex and Yacc while compiling programs that read configuration files or commands; Ward calls them building blocks for programming languages.1 Lex is a tokenizer that transforms text into numbered tags with labels; the GNU/Linux version is named
flex, and you may need a-llor-lfllinker flag alongside it.119Yacc và bisonYacc and bison Yacc là bộ phân tích cú pháp, cố đọc các token theo một văn phạm; bộ phân tích của GNU làbison, và để có tính tương thích Yacc thì chạybison -y. Bạn có thể cần cờ liên kết-ly.12120 Bản GNU của Yacc tên làbison; thêm-yđể nó cư xử như Yacc cũ. Yacc is a parser that attempts to read tokens according to a grammar; the GNU parser isbison, and for Yacc compatibility you runbison -y. You may need the-lylinker flag.12120Vì sao lại cần một cờ thư viện lạ
Sách chỉ nói bạn “có thể cần” các cờ đó mà không giải thích. Tài liệu flex cho biết lý do: bạn phải cung cấp hàm
yywrap()của riêng mình, hoặc liên kết vớilibfl.a(thư viện này cung cấp sẵn một bản), hoặc dùng%option noyywraptrong mã nguồn để nói rằng bạn không cần hàm đó.19Thứ tự của hai cờ có ý nghĩa
Chuẩn POSIX nêu một chi tiết đáng nhớ: cả thư viện lex lẫn thư viện yacc đều chứa hàm
main(), nên khi dùng cả hai, thứ tự-l y -l lbảo đảm thư viện yacc được tìm trước vàmain()của nó được dùng.20 Đây là kiểu chi tiết khiến một lỗi liên kết trông khó hiểu bỗng trở nên hiển nhiên. -
scripting languages
Ngôn ngữ kịch bản — chạy thẳng, không qua biên dịch Ngôn ngữ chạy thẳng Scripting languages — run directly, no compiling
Đã có thời người quản trị Unix trung bình không phải bận tâm gì ngoài Bourne shell và
awk; kịch bản shell vẫn là phần quan trọng của Unix, nhưngawkđã phai nhạt phần nào, trong khi nhiều hậu duệ mạnh mẽ nổi lên — thậm chí nhiều chương trình hệ thống đã chuyển từ C sang ngôn ngữ kịch bản.1Có những ngôn ngữ không cần dịch trước: bạn viết xong là chạy được luôn. Python và Perl là hai cái tên quen nhất. Nhớ nhé: viết xong chạy ngay, không qua bước dịch.
There was a time when the average Unix systems manager didn't have to worry about much beyond the Bourne shell and
awk; shell scripts remain an important part of Unix, butawkhas faded somewhat, while many powerful successors have emerged — and many system programs have actually switched from C to scripting languages.1Hai ngôn ngữ Ward dành hẳn một mụcThe two that get their own sections Python có lượng người dùng đông đảo và nhiều tính năng mạnh — xử lý văn bản, truy cập cơ sở dữ liệu, mạng, đa luồng — cùng chế độ tương tác mạnh và mô hình đối tượng rất có tổ chức; tệp thực thi của nó làpython, thường nằm trong/usr/bin.1 Perl là một trong những ngôn ngữ kịch bản Unix bên thứ ba lâu đời nhất, “cưa xích đa năng” của giới lập trình; dù đã mất khá nhiều chỗ đứng vào tay Python những năm gần đây, nó vẫn xuất sắc ở xử lý văn bản, chuyển đổi và thao tác tệp.1 Python thì hiện đại và dễ học; Perl thì cũ hơn nhưng rất giỏi cắt gọt văn bản. Python has a strong following and an array of powerful features — text processing, database access, networking, multithreading — plus a powerful interactive mode and a very organized object model; its executable ispython, usually in/usr/bin.1 Perl is one of the older third-party Unix scripting languages, the original "Swiss army chainsaw" of programming tools; although it has lost ground to Python in recent years, it still excels at text processing, conversion and file manipulation.1Cơ chế phía sau dòng #!
Một tệp văn bản thực thi bắt đầu bằng
#!là một kịch bản; đường dẫn theo sau tiền tố đó là tệp thực thi của trình thông dịch.1 Khi Unix cố chạy một tệp thực thi bắt đầu bằng#!, nó chạy chương trình đứng sau#!với phần còn lại của tệp làm đầu vào chuẩn.122 Ward minh hoạ bằng một kịch bản chỉ có#!/usr/bin/tail -2ở dòng đầu — và nó vẫn là một kịch bản hợp lệ.1Lỗi cơ bản hay gặp nhất
Dòng đầu của một kịch bản shell thường chứa một trong những lỗi cơ bản phổ biến nhất: đường dẫn tới trình thông dịch không hợp lệ.1 Ward đưa nguyên văn thông báo:
bash: ./myscript: /usr/bin/tail: bad interpreter: No such file or directory.1 Ông cũng dặn đừng trông đợi nhiều hơn một tham số ở dòng đầu hoạt động đúng:-2có thể chạy được, nhưng thêm một tham số nữa thì hệ thống có thể gộp cả hai thành một tham số lớn, kể cả khoảng trắng — điều này khác nhau giữa các hệ thống.122Danh sách “bạn có thể gặp”
Ward liệt kê thêm: PHP (xử lý siêu văn bản, thường thấy trong kịch bản web động), Ruby, JavaScript (trong đó bản cài đặt Node.js ngày càng phổ biến ở lập trình phía máy chủ, tên tệp thực thi là
node), Emacs Lisp, MATLAB và Octave, R, Mathematica, m4 (ngôn ngữ xử lý macro, thường chỉ gặp trong GNU autotools) và Tcl — ngôn ngữ đơn giản thường đi cùng bộ công cụ giao diện Tk và tiện ích tự động hoá Expect.125 -
Java bytecode
Java — biên dịch, nhưng không ra mã máy Java — dịch ra một thứ ở giữa Java — compiled, but not to machine code
Java là ngôn ngữ biên dịch như C, với cú pháp đơn giản hơn và hỗ trợ mạnh cho lập trình hướng đối tượng; nó có vài chỗ đứng riêng trên hệ Unix — thường làm môi trường ứng dụng web, và ứng dụng Android thường được viết bằng Java.1 Có hai loại trình biên dịch Java: trình biên dịch bản địa sinh mã máy cho hệ thống của bạn (như trình biên dịch C), và trình biên dịch bytecode dành cho một trình thông dịch bytecode — và trên Linux bạn gần như luôn gặp bytecode.1
Java cũng phải dịch, nhưng không dịch thẳng ra tiếng của máy. Nó dịch ra một thứ ở giữa, rồi một chương trình khác đọc thứ ở giữa đó mà chạy. Nhớ nhé: Java dịch ra “tiếng trung gian”, chạy được trên nhiều loại máy.
Java is a compiled language like C, with simpler syntax and powerful support for object-oriented programming; it has a few niches on Unix systems — often as a web application environment, and Android applications are usually written in Java.1 There are two kinds of Java compiler: native compilers producing machine code for your system (like a C compiler), and bytecode compilers for use by a bytecode interpreter — and on Linux you'll practically always encounter bytecode.1
Bốn lệnh cần nhớFour commands to remember Tệp bytecode Java kết thúc bằng.class, và Java Runtime Environment (JRE) chứa mọi chương trình cần để chạy chúng:java file.class.123 Với tệp.jar— vốn là tập hợp các tệp.classđược lưu trữ — cú pháp làjava -jar file.jar.123 Muốn biên dịch.javathành bytecode, bạn cần Java Development Kit (JDK) và chạyjavac file.java.124javađể chạy,javacđể dịch,-jarđể chạy cả gói. Java bytecode files end in.class, and the Java Runtime Environment (JRE) contains everything needed to run them:java file.class.123 For.jarfiles — collections of archived.classfiles — the syntax isjava -jar file.jar.123 To compile.javainto bytecode you need the Java Development Kit (JDK) and runjavac file.java.124Hai biến môi trường
Đôi khi bạn cần đặt biến
JAVA_HOMEtrỏ tới tiền tố cài đặt Java.1 Nếu thật sự xui, bạn có thể phải dùngCLASSPATHđể bao gồm các thư mục chứa lớp mà chương trình của bạn cần — đây là một tập thư mục ngăn cách bằng dấu hai chấm, giống biếnPATHthông thường cho tệp thực thi.123jar làm việc như tar
JDK cũng đi kèm
jar, một chương trình tạo và tháo rời tệp.jar; Ward nói ngắn gọn rằng nó hoạt động giốngtar.124Một phân biệt dễ nhầm
Trình thông dịch bytecode đôi khi được gọi là máy ảo (virtual machine) — và Ward dặn rõ rằng đây không phải loại máy ảo do một hypervisor cung cấp như mô tả ở Chương 17.1 Hai khái niệm trùng tên nhưng ở hai tầng hoàn toàn khác nhau.
Ward kết chương bằng cái nhìn tới trước: thế giới trình biên dịch và ngôn ngữ kịch bản rất rộng và không ngừng mở rộng; tại thời điểm ông viết, các ngôn ngữ biên dịch mới như Go (golang) và Rust đang phổ biến dần, còn bộ hạ tầng trình biên dịch LLVM đã giúp việc phát triển trình biên dịch dễ đi đáng kể.126
Ward ends the chapter looking forward: the world of compilers and scripting languages is vast and constantly expanding; as of his writing, newer compiled languages such as Go (golang) and Rust were gaining popularity, while the LLVM compiler infrastructure has significantly eased compiler development.126
Đính chính Corrections
Ba ngộ nhận mà người đọc lần đầu hay mắc Ba chỗ hay hiểu nhầm Three misconceptions first readers hit
Phần này thuộc mức đọc Đầy đủ: nó đối chiếu điều người đọc thường nghĩ với điều bản văn thật sự nói.
Phần này hơi khó, bạn có thể bỏ qua và quay lại sau. Nhớ nhé: hiểu chậm mà chắc vẫn hơn.
This block belongs to the Đầy đủ (full) reading level: it sets what readers usually assume against what the text actually says.
“Tệp thực thi biết thư viện của nó nằm ở đâu” "An executable knows where its libraries live"
Ngộ nhận: đường dẫn tuyệt đối tới từng .so được ghi sẵn trong chương trình lúc biên dịch.
Bản văn nói: vì hiệu năng và tính linh hoạt, tệp thực thi thường không biết vị trí thư viện chia sẻ của nó — nó chỉ biết tên thư viện, và có lẽ một gợi ý nhỏ về chỗ tìm; chính ld.so mới đi tìm và nạp lúc chạy.1 Đó cũng là lý do đầu ra ldd có hai phía: tên ở bên trái, nơi tìm thấy ở bên phải.18
Misconception: the absolute path to each .so is baked into the program at compile time.
The text says: for performance and flexibility, executables usually do not know the locations of their shared libraries — they know only the library names, and perhaps a small hint about where to look; it is ld.so that finds and loads them at run time.1 That is also why ldd output has two sides: the name on the left, where it was found on the right.18
“Thiếu thư viện thì cứ đặt LD_LIBRARY_PATH là xong” "Missing a library? Just set LD_LIBRARY_PATH"
Ngộ nhận: thêm một dòng export LD_LIBRARY_PATH=... vào tệp khởi động shell là cách sửa hợp lệ.
Bản văn nói: biến môi trường này là nguyên nhân số một của mọi vấn đề thư viện chia sẻ, và Ward nói thẳng: không bao giờ đặt nó trong tệp khởi động shell hay khi biên dịch phần mềm.1 Lý do: trình liên kết động thường phải quét toàn bộ nội dung mỗi thư mục nhiều lần, gây sụt hiệu năng lớn, và quan trọng hơn là gây xung đột cùng thư viện không khớp vì nó tìm trong các thư mục đó cho mọi chương trình.1 Nếu buộc phải dùng, hãy giới hạn nó trong một kịch bản bao.1
Misconception: adding an export LD_LIBRARY_PATH=... line to a shell startup file is a legitimate fix.
The text says: this environment variable is the number one cause of all shared library problems, and Ward is explicit: never set it in shell startup files or when compiling software.1 The reason: the dynamic linker often must search the entire contents of each specified directory many times over, causing a big performance hit, and more importantly conflicts and mismatched libraries, because it looks in those directories for every program.1 If you must use it, confine it to a wrapper script.1
“Khoảng trắng nào ở đầu dòng lệnh cũng được” "Any whitespace at the start of a command line will do"
Ngộ nhận: Makefile chỉ cần thụt đầu dòng cho đẹp, dùng dấu cách hay tab đều như nhau.
Bản văn nói: khoảng trắng trước $(CC) là một tab, và make rất nghiêm khắc với tab; bạn phải chèn tab trước mọi lệnh hệ thống trên dòng riêng của nó.1 Dùng dấu cách, bạn nhận đúng dòng lỗi Makefile:7: *** missing separator. Stop. — tab chính là dấu phân tách.1
Misconception: the indent in a Makefile is cosmetic, so spaces and tabs are interchangeable.
The text says: the whitespace before $(CC) is a tab, and make is very strict about tabs; you must insert a tab before any system command on its own line.1 Use spaces and you get exactly the error Makefile:7: *** missing separator. Stop. — the tab is the separator.1
Tự kiểm tra Self-check
Bạn đã nắm hành trình từ mã nguồn tới chương trình chưa? Thử trả lời xem nào Do you have the source-to-program journey yet?
Trả lời bằng lời của bạn, không tra sách. Nếu một câu khiến bạn ngập ngừng, đó chính là mục bạn nên đọc lại.
Thử trả lời miệng thôi, không cần giở lại. Câu nào bí thì quay lại đọc phần đó nhé. Nhớ nhé: chỗ bí chính là chỗ cần học.
Answer in your own words, without looking back. Whichever question makes you hesitate points at the section you should reread.
-
Tuỳ chọn
-ckhác tuỳ chọn-oở chỗ nào, và mỗi cái cho ra loại tệp gì?Gợi ý sâu hơn: nếu bạn không dùng
-okhi dựng chương trình thì tệp kết quả tên là gì? -
Vì sao một object file chưa phải là chương trình chạy được? Kể hai lý do Ward nêu.
Gợi ý sâu hơn: ai là bên ghép chúng lại, và bạn thường gọi bên đó qua chương trình nào?
-
Khác biệt cốt lõi giữa một tệp
.avà một tệp.solà gì?Gợi ý sâu hơn: nếu một thư viện bị phát hiện không an toàn, kiểu nào sửa được mà không phải biên dịch lại?
-
Trong đầu ra của
ldd, phía trái và phía phải dấu=>nói lên điều gì khác nhau?Gợi ý sâu hơn: chương trình biết gì và không biết gì về thư viện của chính nó?
-
ld.sotìm thư viện chia sẻ ở những chỗ nào, và vì sao Ward gọiLD_LIBRARY_PATHlà nguyên nhân số một của rắc rối?Gợi ý sâu hơn: cách làm đúng khi thư viện nằm ở đường dẫn lạ là gì, và bạn vẫn cần thêm cờ nào bên cạnh
-Wl,-rpath? -
Ba danh từ nền tảng của
makelà gì, và cái nào là mặc định khi bạn chỉ gõmake?Gợi ý sâu hơn: vì sao
makebiết cách tạoaux.odùaux.ckhông có trong Makefile? -
Dòng lỗi
missing separatornghĩa là gì, và bạn sửa nó bằng cách nào?Gợi ý sâu hơn: thử nói xem vì sao ký tự đó lại được chọn làm dấu phân tách trong Makefile.
-
Trước khi chạy
make install, Ward khuyên bạn làm gì và vì sao?Gợi ý sâu hơn: cùng tuỳ chọn đó còn giúp bạn hiểu một Makefile lạ như thế nào?
-
Một kịch bản Python và một chương trình C khác nhau thế nào ở khâu “chuẩn bị để chạy”?
Gợi ý sâu hơn: dòng
#!làm gì, và lỗibad interpreternói lên điều gì? -
Vì sao trên Linux bạn gần như luôn gặp bytecode Java thay vì mã máy bản địa?
Gợi ý sâu hơn:
java,javacvàjarmỗi lệnh làm gì?
Máy có đọc được tệp
.ckhông? Phải làm gì trước đã?Object file là gì? Vì sao chưa chạy được ngay?
Thư viện là gì? Hai kiểu
.avà.sokhác nhau ra sao?Muốn biết một chương trình cần mượn những gì, bạn gõ lệnh nào?
makedùng để làm gì? Nó đọc tệp tên gì?Trong Makefile, đầu dòng lệnh phải gõ phím nào?
Kịch bản là gì? Dòng đầu tiên của nó trông thế nào?
Kể tên hai ngôn ngữ kịch bản mà sách nói tới.
-
How does the
-coption differ from-o, and what kind of file does each produce? -
Why isn't an object file a runnable program yet? Give the two reasons Ward states.
-
What is the core difference between a
.afile and a.sofile? -
In
lddoutput, what do the left and the right of the=>each tell you? -
Where does
ld.solook for shared libraries, and why does Ward callLD_LIBRARY_PATHthe number one cause of trouble? -
What are
make's three foundational nouns, and which target is the default when you typemakealone? -
What does a
missing separatorerror mean, and how do you fix it? -
What does Ward advise you to do before running
make install, and why? -
How do a Python script and a C program differ in what must happen before they run?
-
Why do you practically always meet Java bytecode on Linux rather than native machine code?
Khép lại chương Closing the chapter
Chương này nhồi rất nhiều thông tin vào một không gian nhỏ, nhưng bạn không cần thành thạo mọi thứ ở đây.1 Nếu chỉ giữ lại một điều, hãy giữ điều Ward tự xếp hạng: phần về thư viện chia sẻ là thứ quan trọng nhất bạn cần biết — và để hiểu chúng đến từ đâu, trước hết bạn cần chút nền tảng về cách dựng chương trình.1
Chương này nhiều chữ, nhưng bạn không cần nhớ hết. Nhớ một điều thôi cũng được: thư viện dùng chung là phần quan trọng nhất. Nhớ nhé: nhớ ít mà đúng còn hơn nhớ nhiều mà loạn.
This chapter packs a lot of information into a small space, but you don't need to master everything here.1 If you keep only one thing, keep the one Ward ranks himself: the discussion of shared libraries is the most important thing you need to know — and to understand where they come from, you first need some background on how to build programs.1