Do C là ngôn ngữ lập trình được dùng để viết hệ điều hành Linux, nên với việc sử dụng ngôn ngữ C/C++ chúng ta có thể sử dụng các lời gọi hệ thống của Linux một cach dé dang.. Sử dụng tuỳ
Trang 1NGUYỄN TRÍ THÀNH
0002056 38%
Trang 2
NGUYỄN TRÍ THÀNH
GIÁO TRÌNH
LAP TRINH C/C++ TREN LINUX
ALG BI RUC saa
h ÔNG Đi Bót Nà
THU VIER
NHA XUAT BAN GIAO DUC VIET NAM
Trang 3MỤC LỤC
Chương 1 MÔI TRƯỜNG VÀ CÁC CÔNG CỤ LẶP TRÌNH Giới thiệu về hệ điều hành Linux
Ngôn ngữ lập trinh trén Linux
Thiết lập môi trường lập trình
Chương 2 THAO TÁC VÀO / RA VO! HE THONG
1 Hệ thống file của Linux
2 Thao tác với thư mục
3 Thao tác với bàn phíim/màn hình
4, Bat lỗi
BAI TAP
Chương 3 LÀM VIỆC VỚI TIÊN TRÌNH
1 Khái niệm tiến trình
2 Cơ chế truyền thông đường ống (pipe
3 Hàng đợi thông điệp (mesage queues
4 Vùng bộ nhớ chia sẻ (shared memory)
5 File ảnh xạ bộ nhớ
6 Semaphores
7 Phương pháp truyền thông sử dụng file vật lý
8 Phương pháp truyền thang Socket
BÀI TẬP .oinhớh
Chương 5 LÀM VIỆC VỚI LUỒNG
1 Giới thiéu ludng (thread)
2 Xây dựng tiến trình đa luồng
Chương 6 THAO TÁC HỆ THÓNG -
1 Thao tác với bộ nhớ vê
2 Thao tác với ngày/giờ hệ thông
Trang 4
2 Viết trang hướng dẫn sử dụng
3 Tạo ứng dụng có khả năng tự cấu hình
4 Đóng gói sản phẩm dưới dạng file nhị phan
BAI TAP
Xử lý tham số dòng lệnh 254
Thao tác với môi trường
Phát triển ứng dụng trên kiến trúc 32 bịt và 64 bit
Ghi log va str dung dịch vụ log của hệ thống
Viết ứng dụng chạy dưới dang dich vu (service)
BÀI TẶP “
CHÚ THÍCH MỘT SÓ THUẬT NGỮ 270
TÀI LIỆU THAM KHẢO
Onhona
Trang 5-“ời nói đầu
Kế từ khi ra doi dén nay đã hơn 10 năm, do chứng tỏ được hiệu quả và độ an toàn,
Linux ngày càng được sử dụng rộng rãi và thu hút được sự quan tâm của rất nhiều người từ đội ngũ phải triển hệ điều hành, đến những người dùng hệ điều hành Linux và đặc biệt là những người phát triển ứng dụng trên Linux Bên cạnh những ngôn ngữ lập trình mới và tô ra hiệu quả như ngôn ngữ lập trình PHP (Hypertext PreProcessor) ding
để phát triển ứng dụng Web, ngôn ngữ đã từng được sử dụng rấi nhiều là Java, thì ngôn ngữ khá cỗ điển nhưng vẫn đóng một vai trò rất quan trọng trên nhiều hệ điều hành đó
là ngôn ngữ C và C++ do nó có tốc độ vượt trội so với các ngôn ngữ lập trình khác Một mình chứng rõ rùng để chứng mình cho lầm quan trọng của ngôn ngữ C là nó đã được dùng để viết nhân của hệ điều hành Limax Do đó việc làm chủ ngôn ngữ này không chỉ giúp ta có thể phát triển những ứng dụng cân tốc độ cao, tính toán nhiều, mà
nó côn có thể thao tác với hệ điều hành Limux một cách dễ dàng và có thé ở mức độ sâu (do nhân của hệ điều hành Linux cũng được viết bằng C) Cuốn sách này được xáy dựng nhằm giúp người đọc mới làm quen với Linux cé thể bước đâu làm chủ ngôn ngữ lập trình C trên Linux, sau đó có thể có những hiểu biết sâu hơn về Linux va từ đó có thể phát triển những ứng dụng doi hỏi những hiểu biết sâu vé Linux Cuốn sách này được viết cho những độc giả đã có hiểu biết nhất định về Linux và bước đầu đã sử dụng thành thạo các công cụ cơ bản trên Linux như shell, trình soạn thảo, các lệnh thao tác với hệ thống, Với những độc giả chưa có kiến thức về Linux thi co thé tim doc cuỗn giáo trình "Hệ điều hành Lunix ~ Unlx" của nhóm tác giả Hà Quang Thụy và Nguyễn Trí Thành (tai liệu a 1}) trước khi làm quen với cuốn sách này Ngoài kiến thức co bản
về Linux, độc gia can có kiến thức cơ bản về ngôn ngữ lập trình C và C++ Những người chưa có kiến thức về ngôn ngữ lập trình này cần bồ sung kiến thức về C (có thể tham khảo tài liệu [18]) và C++ (có thé tham khảo tài liệu [18]) trước khi đọc cuốn sách này,
Cuốn sách bao gém 12 chương Chương đầu tiên giới thiéu khdi qudt vé Linux cũng như cách thiết lập môi trường lập trình cho ngôn ngữ C/C++ trén Linux cùng với các công cụ cần thiết dùng cho việc phát triển ứng dung bang C/C++ Cac chương tiếp theo lần luot dé cập đến các nội dụng từ dễ đến khó Chương 2 giới thiệu các phương pháp VÀO/RA với hệ thông Chương 3 giới thiệu về cách làm việc với tiến trình Chương 4 nêu chỉ tiết về các cơ chế giao tiếp giữa các tiễn trình Chương 5 để cập đến nội dụng liên quan đến luỗng và cơ chế đông bộ luéng Chương 6 đề cập đẩn các nội dụng thao tác sâu với hệ thống Chương 7 giới thiệu về các thự viện trong Linux, cách tạo và cách sử dụng các loại thư viện Chương ð giới thiệu về cách phát triển các ứng dụng có sử dụng các hệ quan trị cơ sở dữ liệu Chương 9 giới thiệu về các cách phái triển các ứng dụng có giao điện dé hoa Chương 10 ban về vẫn dé bao mật và cách phát triển các ứng dụng có tính bảo mật Chương 11 bàn về vấn dé đóng gói sản phẩm
Trang 6
Các vấn đề quan trọng khác được đề cập ở chương 12 Cuốn sách cũng có phân giải
thích một số thuật ngữ phố biến khi làm việc với hệ điều hành Limux
Cuốn sách này được biên soạn dựa trên một SỐ tài liệu có giá trị công với kinh
nghiệm lập trình/phát triển ứng dụng khá lâu năm bằng ngôn ngữ C và C++ trên Linux của tác giả Trên cơ sở kinh nghiệm của mình, tác giả đã trình bày những nội dụng
quan trong nhất, hay gặp nhất, phục vụ cho việc phát triển các ứng dụng thông thường, ngoài ra cũng trình bày một số nội dung nâng cao Một số nội dung rong, chẳng hạn
như lập trình giao diện trên Linux, do khuôn khổ cuốn sách này không thể trình bày
toàn bộ chỉ tiết hết được, nên chỉ trình bày dưới dạng giới thiệu Còn một SỐ nội dung
mạng tính chuyên dụng, chẳng hạn như xử lý âm thanh, hình ảnh, cuốn sách này sẽ không đề cập đến Ly vọng cách tiếp cận của mình sẽ giúp độc giả làm quen với việc học tập phát triển ứng dụng trên Linux dugc dễ dàng và có thể tự mở rỘng kiến thức của mình để giải quyết các vấn đề phức tạp hơn Cuối cùng, do hiểu biết của tác giả
côn hạn chế, cuỗn sách được viết một cách cô đọng nhất có thể Đặc biệt Linux là hệ điều hành vẫn đang tiếp tục được cải tiễn, sửa đối, cho nên có không ít nội dụng được
trình bày chưa thật đầy đủ, chưa thật chỉ tiết và chắc chấn khó tránh khỏi thiếu sói Tác giả rất mong nhận được các ý kiến đóng góp, bổ sung (xin gửi về địa chỉ email
nithanh@vnu.edu.vn) dé gitip cho cuỗn sách ngày càng tắt hơn
Xin chân thành cảm on!
TÁC GIÁ
Trang 7Chương 1 MÔI TRƯỜNG VÀ CÁC CÔNG CỤ LẬP TRÌNH
———————ễ=.——£ẽẼẺ
1 Giới thiệu về hệ điều hành Linux
Xuất xứ của hệ điều hành Linux là dựa trên hệ điều hành nhỏ có tên là Minix — hệ điều
hành được viết bởi Giáo sư Andy §tuwart Tanenbaum! nhằm mục đích giảng dạy trong trường đại học Minix được lấy làm một ví dụ sống cho môn học Các hệ điều hành Cụ thể có
thể coi nó là phần phụ lục cho cuến sách liên quan đến khoá học vé mén hoc nay "Operating Systems: Design and Implementation” Nó đủ lớn để có đầy đủ các chức năng mà bắt kỳ một
hệ điều hành nào cũng phải có, và cũng đủ nhỏ để có thể học trong một môn học Nhân của hệ điều hành Minix 1 có khoảng 1400 dòng mã C và Assembly, cộng thêm các dòng mã lệnh cho
các trình điều khiển thiết bị, do đó tổng số đỏng mã để cho hệ điều hành này chạy được là
khoảng 5000 dòng mã lệnh Phiên bản Minix 1 được hoàn thành vào năm 1987 Minix sau đó
đã trở nên rất phô biến, sau khi tạo một newsgroup trên Usenet nó đã thu hút được 40000
khách thăm chỉ trong 3 tháng Phiên bản Minix 2 được hoàn thiện vào năm 1997 ải kèm với
lần tái bản thứ hai cuốn sách của tác giả Phiên bản ôn định hiện tại là Minix 3 (số phiên bản 3.1.2) xuất hiện cùng với lần tái bản thứ ba cuốn sách của tác giả Hiện giờ trang web chính thức của Minix là http:/www.minix3.org/ dang thu hút được rất nhiều người, với khoảng
1400 lượt người/ngày
Một tác giả đặc biệt của newsgroup Minix là Linus Torvalds, người đã cài đặt và nghiên cứu Minix trên máy tính của mình, sau đó muốn cải tiền hệ điều hành này Do vậy, Linux là hệ
điều hành được bất đầu phát triển kế tử khi xuất hiện một đoạn thông báo của một sinh viên
người Phần Lan trên Usenet newsgroup comp os.minix vao thang 8 nam 1991:
"Xin chao moi người đang sử dụng hệ điều hành Mimix, tôi đang viết mội hệ điều hành
miễn phí (chỉ viết theo sở thích chứ không đồ sô và chuyên nghiệp như GNU?) cho cde dòng máy tính AT 386 (486)"
Nhân hệ điều hành do Torvalds viết có tên là Linux có đặc điểm giếng với hệ điều hành
Unix — một hệ điều hành nổi tiếng về tính ôn định và được thiết kế chuẩn Phiên bản nhân 1.0
của Linux được hoàn thiện vào ngày 14 tháng 3 năm 1994 Phiên bản 2.2 được hoàn thiện vào ngày 25 tháng giêng năm 1999 Linux được phát triên tuân theo chuẩn giao điện hệ điều hành
khả chuyén POSIX? — một chuẩn được phát triển bởi Viện Công nghệ điện và điện tử
(Institute of Electrical and Electronic Engineers, viet tit la IEEE) Chuan POSIX đã định
nghĩa các giao điện hệ điều hành có tính khả chuyển, điều này làm cho Linux trở thành một hệ điều hành có chất lượng cao và mang đầy đủ các tính chất của hệ điều hành Unix như:
— ———————
Amsterdam, Ha Lan
2 Dy an mang tén GNU được tiến hành vào năm 1984 dưới sự tài trợ của tổ chức phần mềm miễn phí (Free
Sofware Foundation-FSF) với mục đích xây dựng một hệ điều hành giống Unix nhưng hoàn toàn miễn phí
btip:/www.gnU,0fg/
3 POSIX 1a viét tat cia cum tir Portable Operating System Interface [for Unix].
Trang 8~_ Hỗ trợ tốt cho xử lý song song (parallel processing) và máy tính cụm (cluster)
Ngoài ra, Linux con có thêm một số các tính năng khác như: có thể cài đặt trên nhiều chủng loại máy tính khác nhau, chẳng hạn PC, mini Khác với Unix, Linux là một hệ điều
hành hoàn toàn miễn phí, ai cũng có thể tải về dùng Điều đặc biệt nhất, Linux là một hệ điều
hành với mã nguồn mở, được phát triển qua Free SoftWare Foundation nén Linux phat trién
nhanh và là một hệ điều hành được quan tâm nhiều nhất trên thể giới hiện nay Ngay sau khi
Linux xuất biện, nó đã thu hút được một số lượng lớn các nhà lập trình trên thể giới cùng tham gia vào dé phát triển hệ điều hành này ngày một mạnh hơn Chẳng hạn, có nhóm tham gia tập trung viết giao diện đồ hoạ (như KDE* và GNOME”), nhóm phát triển các web server (như Apache"), hệ quản trị cơ sở dữ liệu MySQL”, PostgreSQL”, Và Linux ngày cảng được
bổ sung thêm các phần mềm tng dụng như soạn thảo, quản lý mạng, quản trị cơ sở đữ liệu, bảng tính Do các phần mềm cho Linux được phát triển bởi rất nhiều nhóm khác nhau, nên
cần có một tổ chức sẽ tập hợp, đóng gói các phần mềm trên để tạo thành một hệ điều hành hoàn chỉnh và có thể đem cài đặt một cách dễ đàng Tổ chức đứng ra làm công việc tập hợp,
đóng gói này được gọi là nhà phân phối Linux (Linux distributor) Hé diéu hanh được phân
phối bởi một nhà phân phối nảo đó thường được đặt tên riêng và được gọi là một bản phân
phối (Linux distribution) Hiện tại đang có rất nhiều tổ chức khác nhau đã đóng gói hệ điều hành Linux này theo các triết lý khác nhau và một số chuẩn khác nhau Bang 1.1 liệt kê một
số bản phân phối Linux và được sử dụng phỏ biến nhất được bình bầu vào quý II năm 2009
Bảng 1.1 Một số bản phân phối Linux phổ biến được bình bau quy Ill năm 2009?
Tên hệ điều hành Dia chi website Tên hệ điều hành Địa chỉ website
Trang 92 Ngôn ngữ lập trình trên Linux
Hiện tại trên Linux đã hỗ trợ rất nhiễu ngôn ngữ lập trình phục vụ phát triển các ứng dụng, khác nhau như C, C++, PHP, Perl, Python, Trong khuôn khổ của cuốn sách này, tác giả muốn tập trung vào ngôn ngữ lập trình C và C++ Do C là ngôn ngữ lập trình được dùng để viết hệ điều hành Linux, nên với việc sử dụng ngôn ngữ C/C++ chúng ta có thể sử dụng các
lời gọi hệ thống của Linux một cach dé dang Hơn nữa, chúng ta cũng có thể khai thác được
sâu hơn vào nhân của hệ điều hành Linux
3 Thiết lập môi trường lập trình
Để có thể phát triển được ứng dụng trén Linux bằng ngôn ngữ C/C++ cần phải cài đặt
một số gói phần mềm cần thiết Số lượng các gói này là khá lớn và tên cụ thể của các gói phần mềm này là không giống nhau trên các bản phân phối Linux khác nhau, nên tác giả sẽ không
liệt kê ra Nói một cách tổng quát, các gói phần mềm này được phân ra làm ba loại chính: các
gói thư viện; các gói chứa các file header cho các lời gọi thư viện; các gói chứa các tiện ích (biên dịch, đóng gói, gỡ rối ) Để thuận tiện cho việc cài đặt, ở các bản phân phối Linux đều
nhóm các gói này lại thành các nhóm Ví dụ, ở hệ điều hành CentOS, để cài các gói cần thiết cho việc phát triển bằng C/C++ ta dùng lệnh (từ cửa số dòng lệnh):
#yum groupinstall "Development Libraries"
#yum groupinstall "Development Tools"
Chú ý rằng, hai câu lệnh ở trên chỉ cài đặt cho ta các thư viện cơ bản nhất để phục vụ cho quá trình phát triển ứng dụng trên nền Linux Khi sử dụng các thư viện đặc biệt khác phải cài đặt thêm Chẳng hạn, khi muốn sử dụng thư viện của hệ quản trị cơ sở dữ liệu mysql thi phải cài đặt thêm gói phần mềm mysql-devel-xyz.rpm, trong đó xyz!° là tên phiên bản của mysdl
Trang 1010
oman VU lecckenogtu eee sateen
Gast “Wundat -Waigurcompare “Beaawaraion “Wpoiabar-arith -padaayve -05 fare Wanda “al top ~f,/Ancluds -paDax00
Hình 1.3 Giao diện môi trường phát triển tích hợp của NetBeans
Trang 11Sau khi cài đặt xong các thư viện, công việc tiếp theo là cài đặt một môi trường phát triển tích hợp (Integrated Development Environment — IDE) nào đó Hiện tại có khá nhiều môi trường phát triển tích hợp dành cho ngôn ngữ C/C++ trên Lìnux Một số IDE phổ biến được nhiều người dùng là CodeBlock (hình 1.1), NetBeans (hinh 1.2) va KDevelop (hinh 1,3) Việc
sử dụng các IDE để phát triển ứng dụng rất thuận tiện vì nó giúp người dùng rất nhiều việc Trong khuôn khổ cuỗn sách này, chúng ta muốn tìm hiểu sâu về việc lập trình trên Linux nên tác giả sẽ không hướng dẫn sử dụng bất kỳ IDE nào, thay vào đó sẽ sử dụng một trình soạn thảo văn bản bắt kỳ để lập trình nhằm giúp độc giả hiểu rõ hơn về các bước phải làm Một trình soạn thảo rất nỗi tiếng và mạnh trên Linux 1a trinh soan thao vim (hinh 1.4) Khi hiểu rõ tất cả các khâu trong quá trình phát triển ứng dụng trên Linux, khi đỏ có thể lựa chọn sử dụng một [DE nào đó để giúp làm việc được nhanh và thuận tiện hơn
4.1 Trình biên dịch cua GNU
Sau khi cài đặt xong các gói phần mềm phục vụ việc phát triển img dung bang C/C++ thi trình biên dịch ngôn ngữ lập trình C với tên gọi là gcc (việt tắt của cụm từ GWVU C Compiler)
sẽ được cài vào hệ thống, Trong hệ điều hành CentOS, gói phần mềm chứa trình biên dịch này có tên là gcc-xyz.rpm
Trinh biên dich gec hỗ trợ 3 phiên bản của chuẩn C (C stanđard):
- Chuẩn ANSI C (X3.159- 1989) được phê chuẩn vào năm 1989 và công bố vào năm
1990 Sau đó chuẩn này cũng được coi là chuẩn ISO (ISO/IEC 9899:1990) Chuẩn này còn được biết đến với tên C§9 và C90 Đề biên dịch chương trình C được viết theo chuẩn này ta sử dụng một trong các tham số sau để truyền vao cho gee: -ansi, -std = c90 hay -std = iso9899:1990
Trang 12~ Một bản sửa đổi của chuẩn C90 được công bố vào năm 1995 Bản sửa đổi này được biết đến với tên là AMDI, C94 hay C95 Đề biên dịch chương trình C viết theo chuẩn
này, ta dùng tham số sau để truyền cho gee: -std = iso9899:199409
~ Phién bản mới nhất của ISO C được công bố vào năm 1999 là ISO/IEC 9899:1999, sau này còn được biết đến với tên là C99 Để biên dịch chương trình C viết theo chuẩn
này, ta dùng tham số sau để truyền cho gec: -std = c99 or -std = iso9899:1999
Tuy nhiên, tại thời điểm hiện tai thi gcc chua hé tro hoàn toàn chuẩn này
Ngoài ra, ở chế độ ngầm định gcc bổ sung thêm một số mở rộng!” vào ngôn ngữ C
(nhưng không mâu thuẫn với chuẩn C) Khi sử dụng các tuỳ chọn cho các phiên bản cụ thể đã
trình bày ở trên thì các mở rộng này sẽ không được sử dụng
Trong khuôn khổ cuốn sách này chúng ta sẽ không đi tìm biểu sâu về sự khác nhau giữa các chuẩn này Chỉ tiết thông tin về gec có thể tham khảo tại địa chỉ
có tên là ISO/IEC 14882:1998 hay còn được biết đến với tên C++98; sau đó được bổ sung vào năm 2003 với tên là (ISO/IEC 14882:2003) hay còn được biết với tên là C++03 g++ hỗ trợ
những phân quan trọng nhất của chuẩn C++98 và phần lớn những thay đổi trong chuẩn
C++03 Để báo cho g++ biên dịch chương trình C++ được viết theo chuẩn C++98, ta truyền
thêm tham số dòng lệnh: -ansi hay -std = c++98 Hiện tại hội đồng ISO C++ đang làm việc để
tạo ra một chuẩn mới cho C++ được đặt tên dưới dạng C++0x, hy vọng sẽ sớm công bố C++0x chứa khá nhiều thay đổi cho ngôn ngữ C++, một số thay đổi này đã được cài đặt thử nghiệm trong g++ Thông tin mới nhất về những thay đổi này có thể xem tại htIp://www.open-std.org/jtc1/sc22/wg2l/ Những thông tin về những đặc trưng mới của
C++0x được cài dit trong g++ cd thé xem tai http://gee.gnu.org/gec-4.3/cxxOx_status.html
Để biên dịch chương trình C++ theo chudn C++0x ta dua thêm tham số dòng lệnh sau cho
@++: -std = c++0x
Ngắm định gtt+ cung cấp thêm một số ở rong’? vao cho C++ cho hai chudn C++, Dé tit
việc sử dụng các mở rộng nay ta sử dụng các tuỳ chọn tường minh nhự vừa trình bay Tuy
nhiên, nêu muốn sử dụng chuân C++98 cùng với các mở rộng của gt+ ta đưa thêm tuỳ chọn
sau vào tham số dòng lệnh của g++: -std = gnu++98; để sử dụng chuẩn C++0x cùng với các
mở rộng cua g++ ta đưa thêm tuỳ chọn sau: -std = gnu++0x Khi không chỉ rõ tuỳ chọn một cách tường minh thì tuỳ chọn -std = gnu++98 sẽ được sử dụng
Ngoài C và C++ thì trình biên dịch của GNU còn hỗ trợ biên dịch chương trình viết bằng
ngôn ngữ Objective-C và Objective-C++
4.2 Quả trình biên dịch
Quá trình biên dịch một chương trình nguồn thành file khả thi bao gồm bến giai đoạn:
!! Thông tin chí tiết về những mở rộng có thê xem tại địa chỉ: http://gcc gnu.org/onlinedocs/gce/C-Extensions.htmt
'? Xem chỉ tiết tại http://gcc.gnu.org/onlinedocs/gce/C_002b_002b-Extensions.html
12
Trang 13
Tiền xử lý Biên dịch Tậphợp l ——ạ| Liên kết
Nếu chỉ biên dịch một chương trình đơn giản viết trọn trong một file nguồn thì bốn quá
trình này được thực hiện một cách an chi thông qua một lệnh biên dịch Khi biên dịch một
chương trình ứng dụng lớn gồm nhiều module/thư viện được viết trên nhiều file nguồn thi thông thường chúng ta thấy có hai bước được thực hiện một cách tường minh: quá trình tiền
xử lý và biên dịch được thực hiện cùng trong một bước; quá trình tập hợp và liên kết được thực hiện trong bước thứ hai Tuy nhiên, gcc cho phép người lập trình kiểm tra/theo dõi từng bước trong quá trinh biên dịch Ta có thể dừng quá trình sau một trong những giai đoạn dé
kiểm tra kết quả biên dịch tại giai đoạn ấy và cũng có thể kiểm soát số lượng cũng như kiểu thông tin cần cho quá trình gỡ lỗi (debug) được nhúng trong mã nhị phân Giống như hầu hết
các trình biên dịch, gcc cũng thực hiện tôi ưu hoá mã (xem Bảng 1.3)
Trước khi bắt đầu đi sâu vào nghiên cứu gcc, ta xét ví dụ sau:
#include<stdio.h>
int main (void)
fprintf(stdout, "Hello Linux Programming World!\n");
Hello Linux Programming World!
Dòng lệnh đầu tiên chỉ cho gee phai bién dich va lién két file nguén hello.c, tao ra tap tin
thuc thi, bang cach chi dinh str dung tham sé -o hello Dòng lệnh thứ hai thực hiện chương trình, và kết quả cho ra trên đòng thứ 3
Có nhiều chỗ mà ta không nhìn thấy được, gec trước khi chạy hello.e thông qua bộ tiền
xử lý của cpp, để mở rộng bất kỳ một macro nào và chèn thêm vào nội dung của những file
#include Tiếp đến, nó biên dịch mã nguồn tiên xử lý sang mã obj Cuối cùng, trình liên kết, tạo ra mã nhị phân cho chương trình hello
Ta có thể tạo lại từng bước này bằng tay, chia thành từng bước qua tiến trình biên dịch
Để chỉ cho gec biết phải đừng việc biên dịch sau khi tiên xử lý, ta sử đụng tuỳ chọn —E của gcc như sau:
$ gcc -E hello.e -o hello.cpp
Xem xét hello.cpp và ta có thể thấy nội dung của stdio.h duoc chén vao file, cing với
những mã thông báo tiền xử lý khác Bước tiếp theo là biên dịch hello.cpp sang mã obj Sử dụng tuỳ chọn —c của gee để hoàn thành:
$ gee -x cpp-output -c hello.cpp -o hello.o
Trong trường hợp này, không cần chỉ định tên của file output bởi vì trình biên địch tạo một tên file obj bằng cách thay thế.e bởi.o Tuỳ chọn -x chỉ cho gcc biết ngôn ngữ của file
đầu vào cần được biên dịch
Làm thế nào gec biết phân loại ñle? Nó dựa vào đuôi mở rộng của file ở trên để xác định
rõ phải xử lý file như thế nào cho đúng Hầu hết những đuôi mở rộng thông thường và ý nghĩa của chúng được liệt ké trong bang 1.2
13
Trang 14Liên kết file đối tượng, và cuối cùng tạo ra mã nhị phân:
Trong trường hợp chỉ muốn tạo ra các file obj thì bước liên kết là không cần thiết
Bằng 1.2 Các phần mở rộng của tên file đối với gcc
di Mã nguồn C++ tiền xử lý
.8, Mã nguồn hợp ngữ 0 Mã đối tượng biên dịch (obi)
Hầu hết các chương trình C chứa nhiều file nguồn thì mỗi file nguồn đó đều phải được
biên dịch sang mã obj trước khi tới bước liên kết cuối cùng Giả sử ta đang làm việc trên killerapp.c là chương trình sử dụng phần mã của helper.c Như vậy, để biên dịch kiHerapp.c
ta dùng dòng lệnh sau:
g&cc qua lần lượt các bước tiền xử lý — biên dịch — liên kết, lúc nảy tạo ra các file obj cho mỗi file nguồn trước khi tạo ra mã nhị phân cho killerapp
Một số tuỳ chọn dòng lệnh của gcc:
Bằng 1.3 Một số tuỳ chọn cửa lệnh gcc
-0 FILE Chỉ định tên file đầu ra; không cần thiết khi biên địch sang mã obj Nếu FILE không được chỉ
rõ thì tên mặc định sẽ là a.out
dựa trên những thư viện dùng chung nằm trong một số thư mục chuẩn như /ib hay /usrfib, -Static Liên kết dựa trên những thư viện tĩnh
-ggdb Thêm tất cả thông tin mã nhị phân ma chỉ có chương trình gỡ rối GNU-gdb mới có thé
hiểu được,
-O Tối ưu hoá mã biên dịch
-ON Chỉ định một mức tôi ưu hoá mã N, 0 <= N <= 3,
-ANSI
Hỗ trợ chuẫn ANSI/ISO của C, loại bỏ những mở rộng của GNU mả xung đột với chuẩn (tuỷ
chon nay không bảo đảm mã theo ANSI),
14
Trang 15
-pedantic In ra tắt cả những cảnh báo quy định chuẩn
-pedantic-erors _ | In ra tắt cả các lỗi quy định chuẩn ANSI/ISO cua C
hàm kiểu cũ)
Ww Chan tắt cả thông điệp cảnh báo
-Wall Thông báo ra tắt cả những cảnh báo hữu ích thông thường mà gcc có thễ cung cắp
-werror Chuyển đối tắt cả những cảnh bảo sang lỗi mã sẽ làm ngưng tiền trình biên dịch
-~ Hiện ra tắt cả các lệnh đã sử dụng trong mỗi bước của tiền trình biên dịch
Khi lập trình bang C++ có thé dùng trình biên dich g++ thay cho gec, cách dùng g++ coi
như là bài tập dành cho bạn đọc
5 Công cụ GNU make
Trong trường hợp viết một chương trình rất lớn, được cấu thành từ nhiều file, việc biên dịch sẽ rất phức tạp vì phải viết các dòng lệnh gec rất là dài, Để khắc phục tình trang nay,
công cụ GNU make đã được đưa ra GNU make được giải quyết bằng cách chứa tât cả các
dòng lệnh phức tạp đó trong một file gọi là Makefile No cing lam tối ưu hoá quá trình dịch bằng cách phát hiện ra những file nảo có thay đổi thì nó mới dich lại, còn file nào không bị thay đổi thì nó sẽ không làm gì cả, vì vậy thời gian dịch sẽ được rút ngắn
5.1 Makefile
Một makefile là một cơ sở dữ liệu văn bản chứa các luật, các luật này sẽ báo cho chương trình make biết phải làm gì và làm như thế nào Một luật bao gồm các thành phần như sau:
—_ Đích (target) — cái mà make phải làm
~_ Một danh sách các thành phần phụ thuộc (dependencies) cần để tạo ra đích
-_ Một danh sách các câu lệnh đề thực thi trên các thành phần phụ thuộc
Khi được gọi, GNU make sẽ tìm các file có tên là GNUmakefile, makefile hay Makefile
Các luật sẽ có cú pháp như sau:
target: dependency1, dependency2,
Lệnh _1 Lệnh _2
Target thường là một file như file khả thi hay file objec† ta muốn tạo ra Dependency là một danh sách các file cần thiết như là đầu vào để tạo ra target Lệnh _¡ là các bước cần thiết (chẳng hạn như gọi chương trình dịch) để tạo ra target
Dưới đây là một ví dụ về một Makefile để tạo ra một chương trình khả thi có tên là editor (số hiệu dong chỉ đưa vào để tiện theo dõi, còn nội dung của Makefile không chứa số hiệu dòng) Chương trình này được tạo ra bởi một số các file nguồn: editor.c, editor.h, keyboard.h,
screen.h, screen.c, keyboard.c,
Trang 16editor : editor.o screen.o keyboard.o
gcc -o editor.o screen.o keyboard.o editor.o : editor.c editor.h keyboard.h screen.h
rm *.o editor *~
Để biên dich chương trình này ta chỉ cần ra lệnh make trong thu muc cha file nay
Trong Makefile này chứa tất cả 5 luật, luật đầu tiên có đích là eđør được gọi là đích ngầm định Đây chính 1a file ma make sé phai tao ra, editor 06 ba dependencies là editor.o, sereen.o, keyboard.o Tất cả các file này phải tổn tại thì mới tạo ra được đích trên Dòng thứ
2 là lệnh mà make sẽ gọi thực hiện để tạo ra đích trên Các dòng tiếp theo là các đích và các lệnh tương ứng để tạo ra các file đối tượng (object)
Đề biên dịch chương trình, ta chỉ cần chuyển đến thư mục có chứa file Makefile và gỗ make, ngầm định nó sẽ tìm cái target có tên là ALL, nếu không có thì nó sẽ tìm target đầu tiên
để thực hiện Do đó, nêu muốn cho zz&e thực hiện cái target clean ta phải gọi tường minh
make clean
Giống như chương trình shell, để viết đòng chú thích trong Makefile ta dimg dấu # trước đoạn cân chú thich GNU make sé bỏ qua toàn bộ các ký tự kể từ dấu # trở đi Mỗi một dấu # chỉ có tác dụng trên một dòng đơn (Ví dụ về lời chú thích xin xem ở phần đưới)
5.2 Sử dụng biến trong Makefile
Việc sử dụng biến hay hằng trong chương trình đã tạo rất nhiều thuận lợi, vì chỉ cần thay
đổi giá trị của biến là nó sẽ có ảnh hưởng đến toàn bộ những nơi biến được sử dụng Do đó
GNỦ make cũng cho phép tạo và sử dụng biến ở trong Makefile Dé tao một biến ta chỉ cần gan gid trị cho biến đó, ví đụ OBJ = editor.o screen.o keyboard.o
Khi sử dụng biến ta chỉ cần báo tên biến bằng $(bien), dưới đây là một ví dụ về cách sử
dụng biến trong Makefile:
CC = gcc
CC = $(CC) -g
OBJS = editor.o screen.o keyboard.o
HDRS = editor.h screen.h keyboard.h
Trang 17
Có một số quy tắc liên quan đến biến trong Makefile như Sau:
~ Một biến chỉ được khai báo trên một dòng, do đó nếu giá trị của biến dài quá và ta muốn cắt dòng thì thêm dấu Ñ' vào vị trí cần ngắt xuống dòng Ví dụ:
OBJS = editor.c screen.o keyboard.o\
utility.o library.o mail.o
- Nếu không muốn cất đòng theo cơ chế trên, ta có cơ chế thêm giá trị vào một biến
băng một trong các cách sau:
cc = gee
ŒC = $(CC) -g
CC + = -g Ngoài các biến do người dùng định nghĩa, GNU make cũng định nghĩa sẵn một số biến với các giá trị ngầm định Tuy nhiên, cũng có thể thay đổi các giá trị ngâm định của các biến này, danh sách các biến được định nghĩa sẵn liệt kê ở Bảng 1.4
Bảng 1.4 Một số biến định nghĩa sẵn của GNU make
ASFLAGS Tham số cho tiện ích AS, giá trị ngằm định là rỗng
CFLAGS Tham số cho trình biên dịch CC, giá trị ngâm định là rỗng
CPPFLAGS Tham số cho trình biên dịch CPP, giá trị ngầm định là rỗng
LDFLAGS Tham số cho trình liên kết Id, giá trị ngảm định là rỗng
Quay lai file Makefile ở trên, ta thấy cần phải khai báo target cho từng file đối tượng
object Nếu làm việc với một ứng dụng có số lượng file nguồn lớn (chẳng hạn 300 file.e) thì
việc tao Makefile sẽ là một công việc rất vat và Đề giải quyết hạn ché nay, GNU make cung cấp một số luật mẫu (pattern rule) va một số biến tự động (automatic variable) Vi du, tao mét luật chung để tạo các file đối tượng từ các file.e ở trên ta sử dụng luật mẫu sau:
%,o : %.C
Khi gặp luật này, make sé tu déng tim dén file keyboard.c để biên dich khi nó cần file keyboard.o Tuy nhiên khi biên dịch file keyboard.c, nó sẽ sử dụng các giá trị ngầm định của các biến CC, CFLAGS Do đó, nếu không muốn sử dụng các giá trị ngâm định này ta có thể cung cấp cho nó các lệnh biên địch theo ý mình Ví dụ, có thê cung cấp lệnh biên dịch cho
mẫu luật trên như sau:
Trang 18Ở trên ta thấy xuất hiện một số biến mới là các biến tự động Biến tự động là biến có gia trị thay đổi tuỳ theo từng ngữ cảnh Ví dụ, khi cần biên địch file đối tượng keyboard.o thì biến
$< sẽ có giá trị là keyboard.c, con $@ sẽ có giá trị là keyboard.o Ý nghĩa của các biến này
cùng với một số biển khác được đề cập ở Bảng 1.5
Bảng 1.5 Một số biến tự động của GNU make
$< Tên của thành phần phụ thuộc (dependency) đầu tiên của luật
$? Danh sách toàn bộ các thành phần phụ thuộc của luật mới hơn target (được phân cách bởi
dẫu cách)
$(@D) Phần đường dẫn đến thư mục chứa file target (néu target nằm ở trong thư myc con)
$(@F) Phan tén file target (néu target nam ở trong thư mục con)
5.3 Một số vấn đề khác liên quan đến Makefile
> Cac file Makefile ở các thư mục con
Trong các ứng dụng lớn có thể có nhiều module, mỗi module có thể được lưu ở một thư mnục con (trong cây thư mục của ứng dụng), mỗi module sẽ có một Makefile riêng, để thuận tiện cho quá trình biên dịch, người dùng rất muốn chỉ cần (26 mét lénh make là nó sẽ tự động chay make cho tất cả các file Makefile cho các module rồi cuối cùng là liên kết với chương trình chính Để báo cho make thực hiện các Makefile ở các thư mục con, có thể sử dụng lệnh
cả để chuyển đến thư mục con rồi thực hiện make, sau do lai dimg lénh ed dé trở về thư mục cha và xử lý thư mục con khác Tuy nhiên, GNU make cho một cơ chế giống như sub-shell
trong lập trình shell để thực hiện công việc trên một cách đơn giản hơn, như vị dụ sau:
mylib.a:
(ed mylibdirectory; $ (MAKE) }
Khi sử dụng subshel! như trên không cần phải dùng thêm lệnh ed để chuyển về thư mục
cha nữa
> Một số thông báo lỗi thông dụng của make
Một số thông báo lỗi ở đưới là các thông báo lỗi thường gặp nhất khi làm việc với Makefile, do đó xin giới thiệu để giúp độc giả xử lý nhanh hơn khi gặp phải:
— No rule to make target “target” Stop: Makefile không chứa luật để tạo ra target va không
có luật ân (implieit) nào có thể sử dụng để tạo ra target đó
"target” is up lo date: _ các thành phần phụ thuộc cho target không thay đổi
- Target “target” not remade because of errors: có lỗi xảy ra khi bién dich target, thông báo
lỗi này chỉ hiện ra khi sử dụng tham số -k
18
Trang 19~ command: Command not found make could not find command: tén lệnh sai hoặc không chỉ
đầy đủ đường dẫn đến lệnh khi nó không nằm trong thư mục
được liệt kê ở biến môi trường PATH
— illegal option - option: tham số truyền cho make không hợp lệ
> M6ét sé target théng dung ctia Makefile
GNU make đã định nghĩa trước một sé target va gin cho chúng ý nghĩa cụ thể, trong
trường hợp phát triển ứng dụng mà †a muốn tạo ra một farget nào đó có ý nghĩa trùng với danh sách các target nảy thì có thể sử dụng luôn chứ không nên tạo ra target mới Bảng 1.6
Hệt kê một sé target thong dụng
Bảng 1.6 Một số target thông dụng trong Makefile
Biên dịch toàn bộ chương trình (là target ngam dinh được thực thi khi ta không chỉ rõ cho
check Biên dịch các chương trình dùng để kiểm tra ứng dụng
6.1 Trình gỡ lỗi gdb
Công cụ đầu tiên có thể kể đến là các trình gỡ lỗi (debugger) Trong Linux có khá nhiều trình gỡ lỗi, ở đây chỉ xin giới thiệu trình gỡ lỗi phổ biến nhất là gdb (The GNU Project Debugger! 3) gdb c6 giao diện dòng lệnh nên cỏ thể sẽ gây khó khăn cho những người lần đầu
3 nạp:/www.gnu.org/software/gdb/
19
Trang 20làm quen với nó Để khởi động gdb ta gõ lệnh gđð và truyền cho nó chương trình ta cần gỡ lỗi
(cũng có thể bỏ qua tham số này nếu muốn gỡ lỗi một tiến trình đang chạy) như sau:
$ gdb debug
<http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistxibute it
There is NO WARRANTY, to the extent permitted by law Type "show copying” and "show warranty" for details
This GDB was configured as "x86_64-redhat-linux-gnu".,
List of classes of commands:
aliases Aliases of other commands
breakpoints Making program stop at certain points
data Examining data
files Specifying and examining files
internals Maintenance commands
obscure Obscure features
running Running the program
stack Examining the stack
status Status inquiries
tracepoints Tracing of program execution without stopping the program user-defined User-defined commands
Type "help" followed by a class name for a list of commands in that class
Type "help" followed by command name for full documentation
Command name abbreviations are allowed if unambiguous
Kết quả của lệnh help liệt kê ra một danh sách các chủ để Để xem thông tin tóm tắt về một chủ để hay một lệnh nào đó, ta gõ lệnh help và sau đó là tên lệnh hoặc tên chu dé Vi du:
20
Examining data
List of commands:
append Append target code/data to a local file
print Print value of expression EXP
print-object Ask an Objective-C object to print itself
printf Printf "printf format stxing®
ptype Print definition of type TYPE
set Evaluate expression EXP and assign result to variable VAR
Set annotate Set annotation_level
set architecture Set architecture of target
set args Set argument list to give Program being debugged when it is started
set auto-solib-add Set autoloading of shared library symbols
Set backtrace Set backtrace specific variables
set backtrace limit Set an upper bound on the number of backtrace levels
Trang 21set backtrace past-entry Set whether backtraces should continue past the entry point of a program
Như đã thấy, danh sách các lệnh của gđb là rất lớn, nên không thể giới thiệu toàn bộ trong
nội dung của chương này Do dé ở đây chỉ đề cập một số lệnh thông dụng Quá trình gỡ lỗi
đòi hỏi rất nhiều kinh nghiệm, chương trình gỡ lỗi chỉ là một công cụ giúp người dùng đỏ tìm
ra được nguyên nhân lỗi chứ nó không tự động chỉ rõ cho ta nguyên nhân lỗi là do đâu Độc giả có thể tham khảo thêm nội dung gỡ lỗi ở tài liệu [27] Trước khi đi vào chỉ tiết các lệnh,
xét thử một chương trình gây lỗi và dùng gđb để tìm nguyên nhân gây lỗi như sau;
ali} = ali+1},
alj+1} = t,
sS++, } }
Trang 22$ gcc -g -o debug đebug.c
$./debug
Segmentation fault
Có thể thấy chương trình trên có vấn đề và cần tìm hiểu ra nguyên nhân ở đâu Sau khi
khởi động chương trình gđ6 bằng lệnh, ta cho chương, trình chạy bắng lệnh r (run):
$ gdb debug
License GPLv3+: GNU GPL version 3 or later
<http://gnu.org/licenses/gp1.html>
and "show warranty" for details
This GDB was configured as "x86_64-redhat-linux-gnu"
(gấb) r
Starting program: /home/thanhnt/c/debug
24 if(alj].key > alj+1].key) {
(gdb)
Lệnh tiếp theo có thể sử dụng để tìm ra nơi xây ra lỗi (hữu ích khi chương trình lớn có
nhiều thủ tục gọi lồng nhau) la bt (back trace):
(gdb) help breakpoint
Making program stop at certain points
List of commands:
awatch Set a watchpoint for an expression
break Set breakpoint at specified line or function
catch Set catchpoints to catch events
clear Clear breakpoint at specified line or function
commands Set commands to be executed when a breakpoint is hit
condition Specify breakpoint number N to break only if COND is true delete Delete some breakpoints or auto-display expressions
delete breakpoints Delete some breakpoints or auto-display
expressions
delete mem Delete memory region
delete tracepoints Delete specified tracepoints
disable Disable some breakpoints
disable breakpoints Disable some breakpoints
disable display Disable some expressions to be displayed when program stops
disable mem Disable memory region
disable tracepoints Disable specified tracepoints
enable Enable some breakpoints
enable delete Enable breakpoints and delete when hit
enable display Enable some expressions to be displayed when program stops
22
Trang 23enable mem Enable memory region
enable once Enable breakpoints for one hit
enable tracepoints Enable specified tracepoints
hbreak Set a hardware assisted breakpoint
ignore Set ignore-count of breakpoint number N to COUNT
rwatch Set a read watchpoint for an expression
De dat điểm dừng ta dùng lệnh b (break) tại vị trí xây ra lỗi, dòng 34, sau đó chạy chương
trình băng lệnh r (run):
gdb) b 24
{gdb} x
The program being debugged has been started already
Breakpoint 1, sort (a = 0x600b20, n = 5) at debug.c:24
Sau một hỗi tìm hiểu sẽ tìm ra lý do chương trình bị lỗi là đo chúng ta tham chiếu đến vị
trí nằm ngoài mảng (giá trị của j+1 = 5), do đó ta sẽ biết cách để sửa lại chương trình Đề thoát khỏi gdb ta dùng lệnh q (quit)
gdb 1a m6t trình gỡ lỗi mạnh trong Linux, tuy nhiên việc sử dụng giao diện dòng lệnh sẽ
có thể gây khó khăn cho một số người mới làm quen với việc phát triền ứng dụng trên Linux
Rất may là đã có một số môi trường, tích hợp phát triển (DE), chẳng hạn như KDevelop đã
tích hợp gdb và cho phép người phát triển gỡ lỗi thông qua giao diện đồ hoạ Việc sử dụng
chức năng gỡ lỗi trong các môi trường phát triển tích hợp được coi là bài tập cho độc giá ` 6.2 Một số kỹ thuật gỡ lỗi khác
> Gỡ lỗi bằng cách in thông tin của tiễn trình ra màn hình
Trong một số ứng dụng, đặc biệt ứng dụng liên quan đến các giao thức truyền thông mang, viéc ding gdb để go lỗi đôi khi là không khả thị vì việc dừng chương trình để gỡ lỗi quá lâu sẽ làm cho kết nối giữa ứng dụng với tiến trình khách của nó bị quá thời gian (timeout) và bị ngất Trong trường hợp này, ta sử dụng phương pháp chèn thêm các dòng lệnh
in ra trang thai cua tién trinh (gia tri cha các biến) tại vị trí ta nghi ngờ Tuy nhiên, việc chèn thêm các đòng lệnh in ra trạng thái của tiến trình có thể làm cho chương trình không tôi wu vi
có các dòng mã dư thừa trên Để tạo co chế vừa có khả năng gỡ lỗi vừa tạo ứng dụng tối ưu,
23
Trang 24
ta sử dụng các chỉ thị tiền xử lý để hướng dẫn cho trình biên địch có thé dịch hay không dịch
các lệnh phục vụ gỡ lỗi Cụ thể, nếu ta định nghĩa một hằng DEBUG trong quá trình biên dịch
thì trình biên dịch sẽ dịch các lệnh gỡ lỗi, nếu hằng trên không được định nghĩa thì các lệnh
gỡ lỗi sẽ không được biên dịch Chương trình sau là một ví dụ :
printf("This is line %d of file %sìn", LINE_ ,_ FILE );
#endif printf("Hello Worldin");
exit(0);
}
Chỉ thị tiền xử lý #fdef DEBUG #endif sẽ báo cho trình biên dịch rằng, nếu hing DEBUG được định nghĩa thì đoạn mã nằm ở trong chỉ thị này sẽ được biên dịch; ngược lại, trình biên
dịch sẽ bỏ qua đoạn mã này Có 2 cách đẻ định nghĩa hằng DEBUG, cách thứ nhất là thêm chỉ
thị #define DEBUG 1 vào chương trình Tuy nhiên, cách này không được thuận tiện, vì cần dam
bảo tắt cả file chương trình nguồn (có các đòng mã gỡ lỗi) của ứng dụng đều "nhìn" thấy hẳng
DEBUG Khi không muốn biên dịch chương trình ở chế độ gỡ lỗi ta cần phải bỏ dòng lệnh
#define đi Cách thứ hai có lẽ đơn giản hơn là thêm -DDEBUG vào biến CFLAGS trong file
Makefile, còn ở chương trình trên (giả sử được đặt tên là debugl.e) thì chi cần đưa thêm
-DDEBUG vào trong danh sách các tham số của gec như sau:
$ gece -o cinfo -DDEBUG debugl.c
Trong lénh printfQ) & trong chi thị tiền xử lý ở trên có một số macro được định nghĩa
trước là DATE_,_ TIME_, LINE và FILE Mục đích của các macro này là
cung cấp thêm các thông tin tại thời điểm lệnh trên được thực hiện Ý nghĩa của các macro
này như sau:
- _DATE_: la một chuỗi biểu diễn ngày, lệnh trên được thực hiện ở định dạng
“mmm đả yyyy",
_—TIME : là chuỗi biểu diễn thời gian, lệnh trên được thực hiện ở định dạng
“hh:mm:ss”
- _LINE_: làmột số nguyên biểu thị dòng lệnh chứa nó
— FILE_ : là chuỗi chứa tên file (chứa lệnh trên)
Kết quả chạy thử chương trình trên là:
$./cinfo Run: Jun 17 2010 at 09:33:48 This is line 7 of file debugl.c Hello World
> Gỡ lỗi ở nhiều mức khác nhau
_Trong nhiêu trường hợp, khi muốn gỡ lỗi ở nhiều mức khác nhau (số lượng thông tin in
ra là khác nhau) thì không chỉ dừng ở mức định nghĩa mét hing DEBUG mà có thể gán cho
nó một giá trị nào đó chỉ mức độ gỡ lỗi Ví dụ sau minh hoạ phương pháp trên:
24
Trang 25Để gán giá trị cho hằng DEBUG, sử dụng cú pháp -DDEBUG = n, trong đó n là giá trị
cần gán Ta có thể gán giá trị khác nhau cho DDEBUG để xem kết quả (giả sử chương trình
trên có tên là debug2.c):
> Gỡ lỗi bằng cách giủ thông tin ra file log
Trong một số ứng dụng chạy ngầm, việc in thông tin ra màn hình là không khả thi Dé
khắc phục điều này, một cơ chế đơn gian 1a in ra file log thay vi in ra man hình Việc tạo file
log này là khá đơn giản nên được coi là bải tập cho độc giả Ta cũng có thể sử dụng dịch vụ
log hệ thống để gửi thông tin này vào log hệ thống thay vi tao file log riéng (xem chỉ tiết ở
mục 5, Chương 12)
> Cách in thông tin của tiến trình không sử dụng hằng DEBUG
Việc gỡ lỗi sử dụng hằng DEBUG như trên đôi khi cũng bất tiện vì nếu muốn gỡ bỏ
thông tin gỡ lỗi ta lại phải biên dịch lại chương trình Để làm cho chương trình i in các thông
tin gỡ lỗi khi cần hoặc không in thông tin gỡ lỗi khi chạy thực mà không cần biên dịch lại
chương trình, thay vì sử dụng chỉ thị tiền xử lý #ifdef như trên, có thể sử dụng một biến toàn
cục, chăng hạn int gDebug, có vai trò tương tự như hằng DEBUG Khi đó thay vì dùng #ifdef,
ta có thể dùng lệnh bình thường if (gDebug) hay if(gDebug>1) Dé +tắt/bật việc in ra các
25
Trang 26thông tin gỡ lỗi chỉ cần truyền tham số đòng lệnh tương ứng để gán giá trị cho biến toàn cục
gDebug Ví dụ sau sé minh hoạ phương pháp này:
if (gDebug>= 1) printf("Debug level 1\n");
if (gDebug>= 2) printf("Debug levei 2\n");
if (gDebug>= 3) printf("Debug level 3\n");
Tuy nhiên, khi chọn phương pháp này phải chấp nhận nhược điểm là chương trình không
tôi ưu Do đó, tuỳ từng ứng dụng cụ thể mà nên quyết định lựa chọn phương pháp in thông tin
gỡ lôi cho phủ hợp
> Gỡ lỗi với thư viện assert
Một trong những hảm gỡ lỗi và bắt lỗi hiệu quả nhất là ham assert() trong thu vién assert
Mục đích của hàm assertQ là kiểm tra điều kiện của một biến hay một biêu thức nảo đó có
thoả mãn không trước khi thực hiện các thao tác tiếp theo Ví dụ, trước khi tính căn bậc hai
của một sô cân kiêm tra điêu kiện của nó có lớn hơn hoặc bằng 0, trước khi chia một sô cho
một số khác thì ta cân kiểm tra xem mẫu số có khác 0 hay không, Khi điều kiện kiểm tra
không thoá mãn, thì đừng chương trình và in ra lỗi Chỉ tiết về hàm này có thê xem ở mục 4
Trang 273,5, 8 Dùng sec biên dịch chương trình trên và chạy thử chương trình Sử dụng cơ chế
gỡ lỗi bằng chỉ thị tiền xử lý để in ra giá trị delia
Thực hiện công việc tương tự như bài tập 2, nhưng sử dụng biến toàn cục gDebug để in ra
thông tin gỡ lỗi
Tách thủ tục giải phương trình bậc hai ở trên thành một file độc lập với chương trình
chính, sau đó viết Makefile dé bién dich chương trình từ hai file chương trình nguồn ở trên
Sửa chương trình ở bài tập 4, viết lại hàm căn bậc hai Giả sử hàm có tên là mysqrtQ, sử
dụng ham assert() dé dam bao ham sqrt() sẽ luôn được gọi nếu tham số truyền vào là một
số không âm
27
Trang 28
Chương 2
THAO TÁC VÀO / RA VỚI HỆ THÓNG
1 Hệ thống file của Linux
1.1 Giới thiệu về hệ thống file của Linux
Hiện tại có khá nhiều định đạng hệ thống file được hỗ trợ trên Linux, ví dụ một số loại:
Minix, ext2, ext3, ext4, XFS, GPFS, GFS, ReiserFS, OCFS, Reiser4, Dinh dang hé thống
file ban đầu khi hệ điều hành Linux xuất hiện là ex(2 (hiện giờ là ext3 và phiên bản ext4 đang
được thử nghiệm) Vì vậy, cuốn sách này sẽ tập trung dé cập vào hệ thống file ext2 Hệ điều
hành quản lý file theo tên gọi của file (iên file) và một số thuộc tính liên quan đến file Một số
đặc điểm sau đây liên quan đến tên file (bao gồm cả tên thư mục): Tên file trong Linux có thể
đài tới 255 ký tự, bao gồm các chữ cái, chữ số, dấu gạch nối, gạch chân, dấu chấm Tên thự
mue/file trong Linux cé thé cé nhiều hơn một đầu chấm, ví dụ: wpget.tar.gz Nếu trong tên file
có dấu chấm "." thì xâu con của tên file từ dấu chấm cuối cùng được gọi là phần mở rộng của
tên file, ví dụ, file wget.tar,gz có phần mở rộng là.gz Chú ý rằng, khái niệm phần mở rộng ở đây không mang ý nghĩa như một số hệ điều hành khác chẳng hạn như Windows (Windows
quy định ñile có đuôi.exe là các file khả thị, file có đuôi.dlI là file thư viện, ) Tuy nhiên,
phân mở rộng của file lại có ý nghĩa đối với một số ứng dụng, chẳng hạn đuôi file.gz là có ý nghĩa đối với lệnh gz Nhung Linux lai phan biệt chữ hoa và chữ thường đối với tên thư
mục/fñle, ví dụ hai file có tên wget.tar.gz và Wget.tar.gz là hai file khác nhau Một số ký tự
sau không được sử dụng trong tên thư mục/fle (vì nó được quy định dùng với ý nghĩa khác) là: !,*, $, &, #,
Thư mục (directory) là đối tượng được dùng để chứa thông tin về các file, hay nói theo một cách khác, thư mục "chứa" các file Hệ thống sử dụng cấu trúc dữ liệu gọi la file entry
để quản lý một file/thư mục con được lưu trữ trong thư mục, nội dung của một file entry là như sau:
mới, hệ thông cần tìm một inode rỗi dé cung cấp cho file cần tạo mới Do vậy, nếu không còn
inode nào rối thì đồng nghĩa với việc không thể tạo ra được file mới Trên Linux số lượng tối
đa (và tôi thiêu) các inode, trong một hệ thông file, được tính toán dựa trên công thức sau:
nưu max inode = V/ min(2!?2,number o£ block)
num main inode = V/2??
28
Trang 29Trong đó V là kích thước của hệ thong file tinh bang don vi byte, number_of block la
tông số lượng các khối trong hệ thống file đó Thực tế đã chứng minh giá trị tính toán trên là phủ hợp (đủ) với rất nhiều hệ thống,
Ở các phiên bản đầu của Linux, hệ thông đủng 2 bytes để lưu trữ số hiệu của inode Với
cách lưu trữ số hiệu như thế, không có nhiều hơn 65535 inode trong một hệ thống file Đây là con số khá khiêm tốn, do đó với các hệ điều hành Linux hiện nay thì hệ thống dùng nhiều hơn
2 bytes (4 hoặc 8 bytes) để lưu trữ chỉ số inode Độ đài entry có thể được coi là con trỏ đến
file entry tiép theo Cha ý, khi chúng ta tạo một thư mục thì luôn có hai thư mục con ngầm
định, thư mục thứ nhất có tên là ".", nó là một thư mục trỏ đến chính thư mục hiện tại; thư
mục thử hai có tên là ° ", nó là thư mục con trỏ đến thư mục cha của thư mục hiện tại Cách
tổ chức nảy có ưu điểm là khá đơn giản và đặc biệt là tiện lợi khi thao tác Ví dụ, khi file b.gz
bị xoá thì hệ thống chỉ cần thay đổi giá trị của trường "độ dai entry” tro đến phan tử nằm sau
b.gz (xem hình 2.1) Minh hoạ các file được lưu trong thư mục được thể hiện ở hình 2.1
Hình 2.1 Minh hoạ cách tổ chức dữ liệu của thư mục
Cũng theo cách tổ chức như trên, có thể thấy nó cho phép hai (hay nhiều) file entry có thể
trùng số hiệu inode; nói cách khác, một file vật lý (tương ứng với một inode) có thể có nhiều
tên khác nhau Đây chính là khái niệm liên kết cứng trong Linux Việc một file vật lý có nhiều
tên, nhưng về bản chất, trong Linux thư mục cũng là một file có dữ liệu và được quản lý như
một file bình thường, chỉ có trường #iểu file (file type) là khác /ife thông thường (regular file),
và các xử lý nội dung cua thư mục là do hệ điều hành quy định Do đó, để duyệt nội dung của
thư mục ta cần sử dụng các hàm riêng như: readdirQ, scandirQ (trình bảy ở phần sau) chứ
không thể dùng các hàm đọc/ghi dữ liệu với file thông thường
Khi duyệt nội dung của thư mục bằng hàm readdirQ) nó sẽ trả về thông tin của một file
entry chứa trong cấu trúc dirent có nội dung như sau (một số trường không hoàn toàn giống
với cầu trúc của file entry):
#include <linux/types.h>
#include <linux/dirent.h>
struct dirent
‘ long d_ino, # inode number */
off_td_ off; ? ofiset to this dirent */
unsigned short d_reclen; # length of this d_name */
char d_name [NAME_MAX+1]; /* filename (null-terminated) */
}
Định dang ext3 giới hạn số lượng thư mục con trong một thư mục là 31998, điều này xuất
phát từ lý do là sô lượng liên kết tôi đa đến một thư mục là 32000 (xem câu trúc inode)
29
Trang 30> Cấu trúc Inode
Câu trúc cla inode có thể chia lam hai phần: phần thứ nhất dùng để chứa các thông tin liên quan đến thuộc tính của file, phần thứ hai chứa thông tin liên quan đến các khối dữ liệu của file
Phân thứ nhất của inode gém các thông tìn sau:
Quyền truy nhập file: được chia thành ba nhóm quyền đành cho: người chủ của file
(ký hiệu là w: từ chữ user), nhóm người dùng” (ký hiệu là ø: từ chữ group) và người dùng khác (ký hiệu là œ: từ chữ all) Mỗi nhóm quyển được tạo bởi tô hợp
của ba quyên là đọc (read), ghi (write) và thực thi (execute)
Số lượng liên kết đối với inode chính là số lượng các tên file trên các thư mục được liên kết với inode này
Định danh chủ nhân của inode
Định danh nhóm chủ nhân là xác định tên nhóm người dùng mà chủ file là một thành viên của nhóm này,
Độ dài của file tính theo byte
Thời gian truy nhập file gồm 3 thông tin về thời gian là
~ Thời gian file được sửa đối muộn nhất;
~— Thời gian file được truy nhập muộn nhất;
— Thời gian file được tạo
thứ hai của inode là thông tin liên quan đến địa chỉ của các khối đĩa chứa dữ liệu
13 con trỏ (tạm gọi là con trỏ đữ liệu), chia làm 4 loại:
Con trỏ trực tiếp: gồm 10 con trỏ đầu tiên, mỗi con trỏ sẽ lưu trữ địa chỉ của khối
đĩa chứa dữ liệu của file (theo đúng thứ tự)
Con trỏ gián tiếp bậc ! (là con trỏ thứ 11): khối đĩa được trỏ bởi con trỏ này không chứa dữ liệu của ñle mà chứa một danh sách các con trỏ, mỗi con trô trong khỗi đĩa nảy mới thực sự trỏ đến các khối dữ liệu của file
Con trỏ gián tiếp bậc 2 (là con trỏ thứ 12): có cơ chế giống con trỏ gián tiếp bậc nhất nhưng số mức gián tiếp là 2
Con trỏ gián tiếp bậc 3 (con trỏ thứ 13): có cơ chế giống con trỏ gián tiếp bậc 2 nhưng thêm một mức gián tiếp nữa
Vị dụ về nội dung một inode như sau:
Trang 31Hình ảnh các con trỏ dữ hiệu này được minh hoạ trên hình 2.2
Triple indirect biock
Addresses of data blocks
Indevidval file systems
Butter cache
Hình 2.3 Kiến trúc hệ điều hành Linux liên quan đến hé théng file
Phần thứ nhất của thông tin về inode có thê lẫy được thông qua lời gọi hệ thông statQ, kết
quả thu được sẽ được chứa trong cấu trúc struct stat (sẽ được trình bày chỉ tiết ở
phan sau)
Như đã trình bày ở trên, Linux hỗ trợ rất nhiều định dạng hệ thống file khác nhau, do đó
để tạo một giao diện déng nhất cho tất cá các hệ thống file này, Linux đã tạo thêm một tầng
trung gian có tên là "Virtual File System" sẽ chịu trách nhiệm giao tiếp với từng hệ thống file
cụ thể tương ứng với một lời gọi thao tác file Trước khi đi sâu vào chi tiết các lời gọi hệ
Trang 32thống liên quan đến hệ thống file, ta xem kiến trúc của Linux trong hình 2.3 Tầng cuối cùng trong kiến trúc này là các trình điều khiển thiết bị, tiếp theo là từng module quản lý các hệ
thống file khác nhau "Individual File Systems" Tất cả các module điều khiển file này đều được quản lý bởi hệ thống file ảo Các lời gọi hệ thống (liên quan đến file) đều giao tiếp với
hệ thống file ảo Chương trình người dùng có thê sử dụng các lời gọi hệ thống để giao tiếp với
file, hoặc cũng có thể giao tiếp với file thông qua thư viện C
Chúng ta sẽ lần lượt tìm hiểu các cách thao tác với file thông qua lời gọi hệ thống hay
thông qua thư viện C (cụ thể là thư viện stdio)
1.2 Thao tác với file thông qua các hàm hệ thống
Trong Linux, để làm việc với file ta sử dụng biến mô tả file (file đescriptor) Một trong
những thuận lợi trong Linux và các hệ thông Unix khác là giao diện file tương tự nhau đối với nhiều loại thiết bị như: đĩa từ, các thiết bị vào/ra, cổng song song, giả máy trạm (pseudo-
terminal), cổng máy in, card âm thanh, chuột đều được quản lý như các thiết bị đặc biệt giống
như các file thông thường dé lap trình ứng dung Cac socket TCP/IP và miễn khi kết nổi được thiết lập, đều sử dụng mô tả file giống như là các file chuẩn Các đường ống (pipe) cũng
tương tự như các fñle chuẩn
Một mô tả file đơn giản chỉ là một số nguyên được sử dụng như chỉ số (index) của một
bảng các file được mở và đang liên kết với từng tiến trình Các mô tả file có giá trị 0, 1 và 2
liên quan đến các dong (streams) VAO/RA chuan: stdin, stderr va stdout; ba đòng đó thường
kết nối với may của người sử dụng và có thể chuyên hướng (redirect) được
Một số lời gọi” hệ thông thao tác với file thông qua mô ta file Hầu hết các lời gọi đó trả về giá trị -1 khi có lỗi Xây ra và biến hệ thống errno ghi mã lỗi Mã lỗi được ghi có ý nghĩa tuỳ
theo từng lời gọi hệ thống Hàm perror() được sử dụng để hiến thị nội dụng thông báo lỗi dựa
trên mã lỗi (xem chỉ tiết ở phần bát lỗi ở phần sau) Các lời gọi thao tác với file thông qua mô
tả file dưới đây:
int open(const char *pathname, int flags);
int open(const char “pathname, int flags, mode_t mode);
int creat(const char *pathname,mode_t mode);
int close(int fd);
ssize_t read{int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
int ftruncate(int fd, size_t length);
#include <sys/types.h>
#include <unistd.h>
off_t Iseek(int fildes, off_t offset, int whence);
int fstat(int filedes, struct stat *buf);
int fchown(int fd, uid_t owner, gid_t group);
int fchmod(int fildes, mode_t mode);
'? Trong cuốn sách này, khái niệm đi gọi và hàm được sử dụng với ý nghĩa như nhau
32
Trang 33> MéAgo méi mét file
Để mở một file đã tồn tại hay tạo mới một file ta sử dụng hàm openQ, trong đó:
— Tham sé pathname 1a mét xâu chỉ ra đường dẫn đến file sẽ được mở
— Tham sé mode xdc định quyền truy nhập của file: gồm các quyển đọc/ghi/thực thi tương ứng với ba đôi tượng người dùng Giá trị của tham số này có thể nhận một trong
các giá trị sau (hoặc tô hợp (OR) giữa chúng):
+§ IRUSR: quyền đọc cho chủ sở hữu;
+§ IWUSR: quyền ghi cho chủ sở hữu;
+§ IXUSR: quyền thực thi cho chủ sở hữu;
+S IRGRP: quyền đọc cho nhóm người dùng;
+§ IWGRP: quyển ghi cho nhóm người dùng;
+§ IXGRP: quyền thực thi cho nhóm người dùng;
+§ IROTH: quyền đọc cho những người dùng khác;
+§ _IWOTH: quyền ghi cho những người dùng khác;
+§_IXOTH: quyền thực thi cho những người dùng khác
Một điều chú ý khi tạo file và gán cho nó luôn quyền truy nhập thì ñle đó chưa chắc đã
có quyền như giá trị truyền vào Lý do là quyền truy nhập của file còn bị khống chế bởi giá trị của biến hệ thông umask (có thể thay đổi giá trị của biến này thông qua lệnh umask) Cũng giống như tham số quyền truy nhập file, giá trị của biến môi trường wask cũng gồm ba số hệ
bát phân (hệ 8) quy định quyên truy nhập của ba đổi tượng là chủ sở hữu, nhóm người dùng
Giá trị của số thứ nhất (từ trái sang) quy định quyền truy nhập của chủ sở hữu, số này có
thể nhận một trong các giá trị cờ sau (hoặc tổ hợp OR giữa chúng):
0: không cấm quyền nao;
Lời gọi openQ trả về một mô tả file nếu không có lỗi xảy ra, ngược lại nó trả về giá trị —1 và gán mã lỗi cho biến hệ thống errno
Lời gọi creatQ cho phép ta tạo một file mới, nó có tác dụng tương tự như hàm openQ với các cờ O_CREATE, O_WRONLY, O_TRUNC
33
Trang 34Bang 2.1 Các giá trị cờ của hàm open()
O_RDONLY Mở file để đọc
O_WRONLY Mở file để ghi
O_RDWR Mở file để đọc và ghi
O CREAT Tạo file nều chưa tồn tại file đó
O_EXCL Trả về lỗi nêu file đã tồn tại
O_NOCTTY Không điều khiển fy nêu fty đã mở và tiền trình không điều khiển đy,
O_TRUNC Cắt file (xoá nội dung file) nếu nó tổn tại
O_APPEND Nối thêm vào cuối file và đặt con trỏ ở cuối file,
O_NONBLOCK Nếu một thao tác không thể hoàn thành mà không phải chờ, thoát ra trước khi hoàn
` thành thao tác đó
O_NODELAY Tương tự O_NONBLOGK
O_SYNC Thao tác sẽ không trả về cho đến khi dữ liệu được ghi vào đĩa hoặc thiết bị khác
> Đọc dữ liệu từ file
Lời gọi hệ thông read( cho phép đọc đữ liệu từ file, trong đó:
— Tham sé fd li mé ta file can đọc (thụ được về từ lời gọi openQ)
~_ Tham số #zƒ là con trỏ tới bộ đệm chứa dữ liệu
— Tham sé count la sé byte can doc
Nếu thành công read() tra vé s6 byte được đọc, ngược lại nó trả về gia tri -1 va gan ma
lỗi vào biên hé théng errno
> Ghỉ dữ liệu vào file
Lời gọi writeQ cho phép ghi đữ liệu vào file, trong đó:
- Tham số /Z là mô tả file cần ghỉ (thu được về từ lời gọi open())
— Tham sé buf la con trỏ tới bộ đệm chứa dữ liệu
— Tham sé count 1a sé byte can ghi
Nếu thành công readQ trả về số byte được phi, ngược lại nó trả về giá trị ~1 và gán mã lỗi
vào biên hệ thông errno
> Cắt bó phan cuối file
Lời gọi ftruncateQ) cho phép cắt phần đuôi của file, trong đó:
- Tham sé fd la mé ta file can thao tac
~ Tham sé /ength xác định chiều dài (đơn vị là byte) còn lại của file (tính từ đầu file)
- Néu thanh công hàm ftruncate() tra về giá trị 0, ngược lại nó trả về giá trị —1 và gán mã lỗi vào biến hệ théng errno
> Di chuyén con tré trong file
Lời gọi IseekQ cho phép di chuyên con trỏ trong file, trong đó:
~ Tham sé fa là mô tả file cần thao tác
~ Tham số 9/Set xác định vị trí (đơn vị là byte) của con trỏ trong file mà ta cần đặt
— Tham số whence xác định vị trí tuyệt đối của con trỏ, nó có thê nhận các giá trị sau:
+ SEEK_SET: vị tri offset dugc tinh từ đầu file;
34
Trang 35+ SEEK_CUR: vi tri offset duge tinh tir vị trí hiện tại;
+ SEEK_END: vi tri offser duoc tính từ cuối file (ngược trở lên)
Nếu thành công hàm lseekQ trả lại vị trí của con tré tinh tir đầu file, ngược lại nó trả về -1
và gán mã lỗi vào biên hệ thông errno
> Lay thong tin cia file
Để lấy thông tin của file ta dùng hàm fstatQ, trong đó:
— Tham sé fd 14 mé ta file cdn thao tac
— Tham s6 buf’ la con trỏ tỏ đến một biến có cấu trúc sức siaf được dùng để chứa
dev_t st_dev: # Thiết bị */
int_t sUino ; / inode */
mode_t st_mode; /“ Chế độ bảovệ/quyền truy nhập */
nlink_t st_nlink; /* Số lượng các liên kết cứng *
uid_t st_uid; /* Số hiệu của người chủ */
gid_t st_gid; / Số hiệu nhóm của người chủ"/
dev_t st_rdev; /* Kiểu thiết bị */
of t st_size; /" Kích thước bytes */
unsigned long — st_biksize; /* Kích thước khối"/
unsigned long _ st_blocks; /* Số lượng các khối da str dung*/
time_t st_atime; ?* Thời gian truy cập cuối cuing*/
time _t st_mtime; /* Thời gian cập nhật cuối cùng */
time_t st_ctime; /* Thai gian thay đổi cuối cùng */
}
Nếu thành công fstatQ trả về giá trị 0, ngược lại nó trả về giá trị —1 và gắn mã lỗi vào
bién hé théng errno
> Thay déi quyén truy nhép file
Dé thay đổi quyền truy nhập của file ta sử dụng hàm fchmod(), trong đó:
— Tham sé fa la mô tả file cần thao tác
— Tham sé mode quyền truy nhập ñle (xem thêm về các giá trị của quyền truy nhập file
ở hàm open())
Nếu thành công fchmodQ trả về giá trị 0, ngược lại nó trả về giá trị —1 va gan ma lỗi vào biến hệ thống errno
> Thay déi chủ sở hitu/nhém người dùng của file
Để thay đổi chủ sở hữu/nhóm người dùng của file ta sử dụng ham fchown(), trong đó:
— Tham sé fd la mé ta file can thao tac
—_ Tham số owner là số hiệu của người chủ sở hữu của file
Tham số group là số hiệu của nhóm người dùng của file
Trong trường hợp chỉ muốn thay đổi chủ sở hữu, còn giữ nguyên nhóm người dùng ta truyền giá trị —l cho tham sé group Nếu chỉ muốn thay đổi nhóm người dùng, còn giữ nguyên chủ sở hữu ta truyền giá trị =1 cho tham sé owner
35
Trang 36Nếu thành công fchownQ) trả về giá trị 0, ngược lại nó trả về giả trị —l và gán mã lỗi vào biến hé théng errno
Chú ý: Người dùng có thể thay đổi nhóm người dùng của các file họ sở hữu, con root thi
có quyền thay đổi người chủ sở hữu cũng như nhóm người dùng của file
> Dong file
Chúng ta nên đóng file khi đã thao tác xong với nó thông qua hàm closeQ, trong đó tham
sé fd chính là mô tả file cần đóng Khi file bị đóng thì tật cả các khoá (lock) do tién trinh tao
ra trên file (xem Chương 4) đều được giải phóng, Nếu quá trình đóng file làm cho bộ đếm liên kết bằng 0 thì file sẽ bị xoá Nếu đây là mô tả file cuối cùng liên kết đến file thì bản ghí ở bang file mé (open file table) được giải phóng Nếu không phải là một file bình thường thì các hiệu ứng không mong muôn có thể xảy T8, chẳng hạn khi đóng một file đường ống thì sẽ ảnh
hưởng đến tiến trình ở đầu bên kia đường 6 ống (xem thêm Chương 4)
Chương trình sau minh hoạ cách sử dụng một số hàm được để cập ở trên:
char samplet "This is sample data 1\n";
char sample2[] = "This is sample data 2\n";
printf{" Appending to file\n");
Íd = open("iunk.out", 0_WRONLY| 0_APPEND);
Trang 37printf (” Fiddling with inode\n");
fd = open (" junk.out", O_RDONLY);
printf (" file mode = 0% (octal) \n", statbuf.st_mode);
printf(°Owner uid = %d \n", statbuf.st_uid);
printf” Owner gid = %d \n", statbuf.st_uid);
close(fd);
}
1.3 Thao tác với file thông qua con trỏ file
Chúng ta có cách khác thao tác với file thông qua cơ chế sử dụng con trỏ file (FILE*)
Các lời gọi này được cung cấp bởi thư viện stdio — một thư viện được cài đặt khá phổ biển
trên các máy tính đa mục đích Khác với các lời gọi hệ thông thao tác với file ở phần trước, thư viện stdio này đã có một số cải tiến, tuy nhiên những cải tiên này trong một số trường hợp
lại trở thành nhược điểm, Cài tiễn ở đây là việc sử dụng bộ đệm (buffer) cho thao tac vao/ra,
do đó làm tăng được tốc độ Việc sử dụng bộ đệm này cũng có thể gây ra những hiệu ứng
không mong muốn, lý do là khi ghi đữ liệu ra ñle thì dữ liệu vẫn nằm ở bộ đệm cho đến khi
đầy một khôi đĩa mới được ghỉ ra đĩa Trong thời điểm này nêu có thao tác đọc thì có thê thu
37
Trang 38được các kết quả không chính xác Ngoài việc sử dụng bộ đệm thư viện này cũng cung cấp
thêm nhiều lời gọi để thao tác với file thuận tiện hơn
Tương tự như phương pháp truy cập file ở trên là thông qua biến mô tả file, với thư viện
stdio tất cả các lời gọi thao tác với file đều thông qua con trỏ file Để sử dụng những lời gọi nay chỉ cần thêm dòng #include <stdio.h> vào dau file chương trình nguồn chứa lời gọi Và
ứng với mỗi một file cần thao tác phải khai báo một biến kiểu FILE *, ví dụ FILE *fpDataFile
> Cac ham mé va déng file
Hàm fopenQ`'® mở file có tên chứa trong biến path theo chế độ mở ghi ở biến mode Các chế độ mở file được trình bày trong Bảng 2.2 Kết quả trả về của hàm mở file này là con trỏ
kiểu FILE đến cấu trúc đữ liệu ghi trạng thái của file Nếu có lỗi thì hàm sẽ trả về giá trị
NULL và đặt số hiệu lỗi ở biến hệ thống errno
#include <stdio.h>
FILE “fopen(char *path, char *mode),
FILE *fdopen(int fildes, char *mode);
FILE “freopen(char “path, char *mode, FILE*stream):
Bang 2.2 Cac ché d6 mé file
"rb" Có Không Đầu file Không Không Nhị phân
"heb" Có Có Đầu file Không Không Nhị phân
"ab" Không Có Cuỗi file Không Có Nhị phân
Nhị phản
Hàm fdopen() được sử dụng để tạo một con trỏ file dựa trên một biến mô tả file tạo bởi
các lời gọi openQ, pipeQ, hay acceptQ Hàm freopenO được sử dụng để mở lại một file đã
được mở trước đó Sau lệnh này thì file được mở bởi lời gọi mở file trước đó sẽ bị đóng Hàm fcloseQ sẽ đóng một file đã mở có con trỏ kiểu FILE* được truyền vào dưới dạng tham
sé stream
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb,FILE*stream);
size_t fwrite(void “ptr, size_t size, size_t nmemb,FILE*stream);
'S Hàm fopenQ có thể bị khai thác để tấn công hệ thông nó được dùng trong môi trường xảy ra tỉnh huống đua
tranh (race condition), xem thêm ở Chương 10
38
Trang 39Tham số đầu tiên của hàm ghỉ là con trỗ đến một vùng đệm, tham số thứ hai là kích thước của vùng đữ liệu (ban ghi) cần được ghi, tham số thứ ba là số bản ghi của vùng dữ liệu cần được ghi, tham sơ cuối cùng là con trỏ FILE* của đle ta muốn ghi vào Giá trị trả về của hàm ghi là số lượng bản phí (khơng phải là đơn vị bytes) đã được ghỉ Với hàm đọc thì các tham số
cũng cĩ ý nghĩa tương tự
> Các hàm kiểm tra trạng thái
“Trong quá trình thao tác với fle, chúng ta cĩ thể kiểm tra trạng thái của file thơng qua các hàm sau:
#include <stdio.h>
void clearerr(FILE *stream);
int feof(FILE "stream),
int ferror(FILE *stream),
int fileno(FILE “stream),
Ham feof() trả lại giá trị khác 0 (true) nếu con trỏ file đã đi đến cuối file Như vậy, để đọc
tồn bộ nội dung của một file cĩ con trỏ chứa trong biển stream ta cĩ thể sử dụng một vịng
lặp while (feofstream)) { }
Hàm ferrorQ sẽ trả lại giá trị khác 0 (true) nếu cờ báo lỗi thao tác với file được bật Các
lỗi sẽ được đặt lại (reset) khi ta goi ham clearerr()
Ham fileno() tra lai bién mé ta file (file descriptor) cia file duge mo Ta cĩ thể lẫy giá trị
mơ tả file để dùng các hàm thao tác với file như đã để cập ở mục trước
Ð> Các hàm thao tác với con trơ file
“Trong trường hợp nếu file là đle cĩ cấu trúc thì việc truy cập trực tiếp đến một phần tử
nào đĩ trong đle thì cĩ thể sử dụng các hàm sau:
#include <stdio.h>
int fseek(FILE “stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);
int fgetpos(FILE “stream, fpos_t *pos);
int fsetpos(FILE “stream, fpos_t *pos),
Để di chuyển con trỏ đến một vị trí nào đĩ trong file ta sử dụng hàm fseekQ, trong đĩ:
~_ Tham số sream là con trồ file (đã được mở) cần thao tác
— Tham sé offset chiza vị trí cần di chuyên dén
— Tham số cuối cùng whence xác định vị trí tương đối của vj tri offser trong file N6 cĩ
thể nhận một trong các giá trị sau:
+§EEK_SET: vị trí bắt đầu của vùng nhớ sẽ là byte thứ aØS tính từ dau file
+ SBEK_CUR: vi trí bắt đầu của vùng nhớ sẽ là byte thứ offset tinh tir vj tri hiện tại
của con trỏ file
+SEEK_END: vị trí bắt đầu của vùng nhớ sẽ là byte thir offset tinh từ cuối file
Hàm ftellQ trả lại vị trí hiện tại của con trỏ trong file
Một hàm rewindQ là hàm di chuyển con trỏ vẽ đầu file, đây là hàm giúp người lập trình
xử lý nhanh hơn, vì người dùng hồn tồn cĩ thÊ sử dụng hàm fseekQ đề làm việc này
39
Trang 40Hai hàm còn lại cho phép di chuyển và lấy vị trí con trỏ trong file bằng cách khác, độc
giả có thể tự tìm hiểu
> Các hàm điều khiển bộ đệm
Thư viện stdio thao tác với file có sử dụng bộ đệm, ngầm định nó sẽ tự tạo bộ đệm cũng như quản lý bộ đệm, tuy nhiên thư viện này cũng cung cấp cho người dùng một số hàm thao tác với bộ đệm theo ý như sau:
#include <stdio.h>
int fflush(FILE *stream);
int setbuf(FILE *stream, char *buf);
int setbuffer(FILE *stream, char *buf, size _ size);
int setlinebuf(FILE *stream);
int setvbuf(FILE ‘stream, char “buf, int mode, size_t size);
theo dong (line), bộ đệm này phủ hợp với file đữ liệu văn bản; _IOFBF nếu ta muốn sử dụng
toản bộ vùng đệm
Hàm setbufferQ cung cấp cách thao tác ngắn, gọn hơn tương ứng với giá trị mode _IOFBF cia hàm setvbufQ, hàm setlinebuf() cung cap thao tac ngắn gọn hơn tương ứng với giá trị mode là _IOLBE, và setbuf() nêu được sử dụng dưới dang setbuf(stream, NULL) tuong ứng với giá trị mode _IONBF cua ham setvbuf()
Chuong trinh sau minh hoa cach str dung ham setbuf():
int printf(const char *format, );
int fprintf(FILE *stream, const char *format, );
int sprintf(char *str, const char *format, );
int snprintf(char *str, size_t size, const char *format, );
#include <stdarg.h>
int vprintf(const char “*format,va_list ap):
ant vfprintf(FILE “stream, const char *format,va_list ap);
int vsprintf(char “str, char “format, va_list ap);
int vsnprintf(char “str, size_t size, char *format,va_list ap);
40