Kĩ Thuật Lập Trình Hệ Thống Linux [Chương 4]

Chương 4  File  I/O: The Universal I/O Model

     Bây giờ chúng ta bắt đầu tìm kiếm một cách nghiêm túc API lời gọi hệ thống. Các tệp là một nơi tốt để bắt đầu, vì chúng là trung tâm của triết lý UNIX. Trọng tâm của chương này là các lời gọi hệ thống được sử dụng để thực hiện nhập và xuất tệp.
     Chúng ta giới thiệu khái niệm về bộ mô tả tệp và sau đó xem các lời gọi hệ thống tạo thành cái gọi là mô hình I/ O phổ quát. Đây là những hệ thống gọi là mở và đóng tệp, đọc và ghi dữ liệu.
     Chúng ta  tập trung vào I/O trên các tệp đĩa. Tuy nhiên, phần lớn tài liệu được đề cập ở đây là có liên quan cho các chương sau, vì các cuộc gọi hệ thống giống nhau được sử dụng để thực hiện I/O trên tất cả các loại tệp, chẳng hạn như đường ống và thiết bị đầu cuối.
     Chương 5 mở rộng cuộc thảo luận trong chương này với các chi tiết khác về tệp I/O. Một khía cạnh khác của tệp I/O, buffering, là đủ phức tạp để xứng đáng với chương riêng của nó. Chương 13 bao gồm I/O buffering trong hernel và trong thư viện stdio.

4.1 Overview

     Tất cả các lời gọi hệ thống để thực hiện I/O đều đề cập đến các tệp đang mở bằng cách sử dụng một bộ mô tả tệp, một (thường là nhỏ) số nguyên không âm. Mô tả tệp được sử dụng để chỉ tất cả các loại mở tệp, bao gồm đường ống, FIFO, socket, thiết bị đầu cuối, thiết bị và tệp thông thường. Mỗi
process có bộ mô tả tệp riêng. Theo quy ước, hầu hết các chương trình mong đợi có thể sử dụng ba tệp chuẩn mô tả được liệt kê trong Bảng 4-1. Ba bộ mô tả này được mở trên chương trình thay mặt cho shell, trước khi chương trình bắt đầu. Hoặc, chính xác hơn, chương trình kế thừa các bản sao của các bộ mô tả tệp của trình shell và trình shell thường hoạt động với ba bộ mô tả tập tin này luôn mở. (Trong một trình shell tương tác, ba bộ mô tả tệp này thường tham chiếu đến terminal mà shell đang chạy.) Nếu I / O chuyển hướng được chỉ định trên một dòng lệnh, sau đó trình shell đảm bảo  bằng tệp mô tả được sửa đổi phù hợp trước khi bắt đầu chương trình.
Khi đề cập đến các bộ mô tả tệp này trong một chương trình, chúng ta có thể sử dụng các số
(0, 1, hoặc 2) hoặc, tốt hơn là, các tên chuẩn POSIX được định nghĩa trong <unistd.h>.
Mặc dù các biến stdin, stdout và stderr ban đầu tham chiếu đến quá trình đầu vào, đầu ra và lỗi chuẩn, chúng có thể được thay đổi để chỉ bất kỳ tệp nào theo bằng cách sử dụng hàm freopen (). Là một phần của hoạt động của nó, freopen () có thể thay đổi bộ mô tả tập tin nằm bên dưới luồng được mở lại. Nói cách khác, sau khi một freopen () trên stdout, ví dụ, nó không còn an toàn để giả định rằng
mô tả tập tin cơ bản vẫn là 1.

Sau đây là bốn lời gọi hệ thống chính để thực hiện file I / O (các gói ngôn ngữ lập trình  và phần mềm thường chỉ sử dụng các lời gọi này một cách gián tiếp, thông qua thư viện I / O ):

  • fd = open (tên đường dẫn, cờ, chế độ) mở tệp được xác định theo tên đường dẫn, trả về một bộ mô tả tệp được sử dụng để chỉ tệp mở trong các lời gọi tiếp theo. Nếu tập tin không tồn tại, open() có thể tạo nó, tùy thuộc vào các thiết lập của đối số bitmask cờ. Đối số cờ cũng chỉ định liệu tệp có phải là được mở để đọc, viết hoặc cả hai. Đối số chế độ chỉ định các quyền được đặt trên tệp nếu nó được tạo bởi cuộc gọi này. Nếu lời gọi open() không phải được sử dụng để tạo một tệp, đối số này bị bỏ qua và có thể bỏ qua.
  • numread = read (fd, buffer, count) đọc tối đa số byte từ tệp đang mở được tham chiếu bởi fd và lưu trữ chúng trong bộ đệm. Lời gọi read () trả về số lượng byte thực sự đọc. Nếu không thể đọc thêm byte nào (tức là, gặp phải kí tự kết thúc tập tin), read () trả về 0.
  • numwritten = write (fd, buffer, count)  để đếm byte từ bộ đệm ghi lên tập tin đang mở  được gọi bởi fd. Cuộc gọi write () trả về số byte thực sự được viết, có thể ít hơn count.
  • status = close (fd) được gọi sau khi tất cả I / O đã được hoàn thành, để giải phóng fd mô tả tập tin và các tài nguyên kernel liên quan của nó. 

Trước khi chúng ta giới thiệu chi tiết về các lời gọi hệ thống này, chúng tôi cung cấp một minh họa ngắn về việc sử dụng chúng trong Liệt kê 4-1. Chương trình này là một phiên bản đơn giản của cp (1)
chỉ huy. Nó sao chép nội dung của tệp hiện có có tên trong đối số dòng lệnh đầu tiên của nó vào tệp mới có tên trong đối số dòng lệnh thứ hai của nó
$ ./copy oldfile newfileListing 4-1: Using I/O system calls–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– fileio/copy.c#include <sys/stat.h>
#include <fcntl.h>
#include "tlpi_hdr.h"
#ifndef BUF_SIZE /* Allow "cc -D" to override definition */
#define BUF_SIZE 1024
#endif
int
main(int argc, char *argv[])
{
int inputFd, outputFd, openFlags;
mode_t filePerms;
ssize_t numRead;
char buf[BUF_SIZE];
if (argc != 3 || strcmp(argv[1], "--help") == 0)
usageErr("%s old-file new-file\n", argv[0]);
/* Open input and output files */
inputFd = open(argv[1], O_RDONLY);
if (inputFd == -1)
errExit("opening file %s", argv[1]);
openFlags = O_CREAT | O_WRONLY | O_TRUNC;
filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
S_IROTH | S_IWOTH; /* rw-rw-rw- */
outputFd = open(argv[2], openFlags, filePerms);
if (outputFd == -1)
errExit("opening file %s", argv[2]);
/* Transfer data until we encounter end of input or an error */
while ((numRead = read(inputFd, buf, BUF_SIZE)) > 0)
if (write(outputFd, buf, numRead) != numRead)
fatal("couldn't write whole buffer");
if (numRead == -1)
errExit("read");
if (close(inputFd) == -1)
errExit("close input");
if (close(outputFd) == -1)
errExit("close output");
exit(EXIT_SUCCESS);
}
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– fileio/copy.c



4.2 Universality of I/O

Một trong những đặc điểm phân biệt của mô hình UNIX I / O là khái niệm tính phổ quát của I / O. Điều này có nghĩa là cùng một hệ thống bốn lời gọi - open (), read (), write(), và close () - được sử dụng để thực hiện I / O trên tất cả các loại tệp, bao gồm các thiết bị như thiết bị đầu cuối. Do đó, nếu chúng ta viết một chương trình chỉ sử dụng các lời gọi hệ thống này, chương trình sẽ hoạt động trên bất kỳ loại tệp nào. Ví dụ: sau đây là tất cả các mục đích sử dụng hợp lệ
của chương trình trong Liệt kê 4-1:
$ ./copy test test.old Sao chép một tập tin thông thường
$ ./copy a.txt / dev / tty Sao chép tệp thông thường vào thiết bị đầu cuối này
$ ./copy / dev / tty b.txt Sao chép đầu vào từ thiết bị đầu cuối này vào một tệp thông thường
$ ./copy / dev / pts / 16 / dev / tty Sao chép đầu vào từ một thiết bị đầu cuối khác
Tính phổ quát của I / O đạt được bằng cách đảm bảo rằng mỗi hệ thống tệp và trình điều khiển thiết bị
thực hiện cùng một bộ các cuộc gọi hệ thống I / O. Vì các chi tiết cụ thể cho hệ thống tệp hoặc thiết bị được xử lý trong kernel, chúng tôi thường có thể bỏ qua thiết bị cụ thể các yếu tố khi viết chương trình ứng dụng. Khi truy cập vào các tính năng cụ thể của hệ thống tệp hoặc thiết bị là bắt buộc, một chương trình có thể sử dụng lệnh gọi hệ thống catchall ioctl ()
(Phần 4.8), cung cấp giao diện cho các tính năng nằm ngoài phạm vi Mô hình I / O.

4.3 Opening a File: open() 

Lời gọi hệ thống open () mở ra một tệp hiện có hoặc tạo và mở tệp mới.

#include <sys/stat.h>
#include <fcntl.h>
int
open(const char *pathname, int flags, ... /* mode_t mode */);Returns file descriptor on success, or –1 on error  


Tệp được mở được xác định bằng đối số đường dẫn. Nếu path name là một liên kết tượng trưng, nó sẽ bị hủy bỏ. Khi thành công, open () trả về một bộ mô tả tập tin được sử dụng để tham khảo tệp trong các cuộc gọi hệ thống tiếp theo. Nếu xảy ra lỗi, mở () trả về –1 và errno được đặt phù hợp.
Đối số cờ là một mặt nạ bit chỉ định chế độ truy cập cho tệp, sử dụng một trong các hằng số được hiển thị trong Bảng 4-2.
Các triển khai UNIX ban đầu sử dụng các số 0, 1 và 2 thay vì tên được thể hiện trong Bảng 4-2. Hầu hết các triển khai UNIX hiện đại đều xác định những hằng số để có các giá trị đó. Do đó, chúng ta có thể thấy rằng O_RDWR không tương đương với O_RDONLY | O_WRONLY; sự kết hợp thứ hai là một lỗi logic.

Khi open () được sử dụng để tạo một tệp mới, đối số bit-mask chế độ chỉ định quyền được đặt trên tệp. (Kiểu dữ liệu mode_t được sử dụng để gõ chế độ là một loại số nguyên được chỉ định trong SUSv3.) Nếu lời gọi open () không chỉ định O_CREAT, chế độ có thể được bỏ qua.
Listing 4-2: Examples of the use of open()/* Open existing file for reading */
fd = open("startup", O_RDONLY);
if (fd == -1)
errExit("open");
/* Open new or existing file for reading and writing, truncating to zero
bytes; file permissions read+write for owner, nothing for all others */
fd = open("myfile", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
if (fd == -1)
errExit("open");
/* Open new or existing file for writing; writes should always
append to end of file */
fd = open("w.log", O_WRONLY | O_CREAT | O_TRUNC | O_APPEND,
S_IRUSR | S_IWUSR);
if (fd == -1)
errExit("open")
 ; 


Số mô tả tệp được trả về bằng open()

SUSv3 xác định rằng nếu open () thành công, nó được đảm bảo sử dụng số được đánh số thấp nhất
mô tả tập tin không sử dụng cho process. Chúng tôi có thể sử dụng tính năng này để đảm bảo rằng tệp
được mở bằng cách sử dụng một bộ mô tả tập tin cụ thể. Ví dụ, trình tự sau đảm bảo rằng một tập tin được mở bằng đầu vào tiêu chuẩn (bộ mô tả tập tin 0).
Bảng 4-2: Các chế độ truy cập tệp
Chế độ truy cập
O_RDONLY Mở tệp để chỉ đọc
O_WRONLY Mở tệp chỉ để ghi
O_RDWR Mở tập tin cho cả đọc và viết 74 Chương 4
if (close (STDIN_FILENO) == -1) / * Đóng bộ mô tả tập tin 0 * /
errExit ("đóng");
fd = open (tên đường dẫn, O_RDONLY);
if (fd == -1)
errExit ("mở");

Vì tệp mô tả 0 không được sử dụng, open() được đảm bảo để mở tệp bằng cách sử dụng bộ mô tả. Trong Phần 5.5, chúng ta xem xét việc sử dụng dup2 () và fcntl () để đạt được một kết quả tương tự
, nhưng với kiểm soát linh hoạt hơn bộ mô tả tập tin được sử dụng. Trong phần đó,chúng tôi cũng cho thấy một ví dụ về lý do tại sao nó có thể hữu ích để kiểm soát bộ mô tả tệp trên một tệp nào được mở.



Nhận xét

Bài đăng phổ biến