Trong hệ điều hành máy tính Unix và kiểu Unix, file descriptor (tạm dịch: mô tả tập tin), thường được viết tắt là fd hay fildes, là định danh (handler) độc nhất cho tập tin hoặc tài nguyên đầu vào/đầu ra khác, chẳng hạn như pipe hoặc socket mạng.
File descriptor thường có giá trị nguyên không âm, còn giá trị âm thì được dành để biểu thị trạng thái "không có giá trị" hoặc tình trạng bị lỗi.
File descriptor là một phần của POSIX API. Mỗi tiến trình Unix (ngoại trừ daemon) phải có 3 file descriptor POSIX tiêu chuẩn, tương ứng với 3 luồng tiêu chuẩn:[a]
Giá trị số nguyên | Tên | Hằng biểu trưng <unistd.h>[1] | Luồng tập tin <stdio.h>[2] |
---|---|---|---|
0 | Đầu vào tiêu chuẩn[b] | STDIN_FILENO | stdin |
1 | Đầu ra tiêu chuẩn[c] | STDOUT_FILENO | stdout |
2 | Đầu lỗi tiêu chuẩn[d] | STDERR_FILENO | stderr |
Trong bản thực hiện truyền thống của Unix, file descriptor lập chỉ mục vào trong bảng file descriptor dành riêng cho mỗi tiến trình, kernel có vai trò bảo quản bảng này, kế đến lập chỉ mục vào trong một bảng liệt kê các file đang được mở từ tất cả tiến trình, bảng đó gọi là bảng file và dùng chung cho toàn hệ thống. Bảng này ghi lại chế độ mà file (hoặc tài nguyên khác) được mở lên: để đọc, để ghi, để phụ chú,[e] và có thể là chế độ nào đó khác. Nó cũng lập chỉ mục vào một bảng thứ ba được gọi là bảng inode mô tả tập tin thực tế nằm bên dưới.[3] Để thi hành lên đầu vào hoặc đầu ra thì tiến trình sẽ truyền file descriptor vào kernel thông qua lệnh gọi hệ thống[f] và kernel sẽ truy cập file giùm cho quá trình. Tiến trình không có cách nào truy cập trực tiếp vào file hoặc bảng inode.
Trên Linux, tập hợp các file descriptor được mở trong tiến trình thì có thể được truy cập theo đường dẫn /proc/PID/fd/
, trong đó PID là định danh tiến trình. File descriptor /proc/PID/fd/0
là stdin
, /proc/PID/fd/1
là stdout
và /proc/PID/fd/2
là stderr
. Tiến trình đang chạy còn có thể truy cập các file descriptor của chính mình thông qua các thư mục lối tắt là /proc/self/fd
và /dev/fd
thay vì dùng đường dẫn cụ thể như thế kia.[4]
Trong các hệ thống kiểu Unix, file descriptor có thể tham chiếu đến bất kỳ kiểu file Unix nào có mang tên trong hệ thống file. Không chỉ các file thông thường mà còn bao gồm cả thư mục, thiết bị khối[g] và thiết bị kí tự[h] (còn được gọi là "file đặc biệt"), Unix domain socket[i] và named pipe. File descriptor cũng có thể tham chiếu đến những đối tượng khác mà bình thường không tồn tại trong hệ thống file, chẳng hạn như anonymous pipe và socket mạng.
Cấu trúc dữ liệu FILE trong thư viện I/O tiêu chuẩn C thường bao gồm file descriptor cấp thấp cho các loại đối tượng như trên trong các hệ thống kiểu Unix. Cấu trúc dữ liệu có tính tổng thể đấy mang lại thêm sự trừu tượng và nó được gọi với cái tên khác là file handle.
Dưới đây liệt kê các thao tác (hàm) điển hình lên file descriptor trên các hệ thống kiểu Unix hiện đại. Hầu hết các hàm này đều được khai báo trong header <unistd.h>
, nhưng một số thì lại nằm trong header <fcntl.h>
.
Hàm fcntl() được dùng để làm các thao tác khác nhau trên file descriptor, tùy vào 'đối số lệnh' được truyền cho nó. Có các lệnh để truy xuất và thiết đặt những thuộc tính liên đới với file descriptor, bao gồm F_GETFD, F_SETFD, F_GETFL và F_SETFL.
Một loạt các thao tác mới trên file descriptor đã được bổ sung vào nhiều hệ thống kiểu Unix hiện đại và cũng đã được bổ sung vào khá nhiều thư viện C, nhằm để được chuẩn hóa trong một phiên bản tương lai nào đó của POSIX.[7] Hậu tố at
trong tên của hàm biểu thị rằng hàm đấy nhận vào thêm một đối số ở vị trí thứ nhất, đối số đó cung cấp file descriptor ứng với thư mục cơ sở để từ đó phân giải đường dẫn tương đối, còn nếu dùng hàm mà tên không có hậu tố at
thì tương đương với việc gọi hàm tương ứng có hậu tố at
và truyền file descriptor ứng với thư mục làm việc[j] hiện hành làm tham số thứ nhất. Mục đích của các thao tác mới này là để phòng ngừa một số loại tấn công TOCTOU nhất định.
Theo nhiều cách thì file descriptor của Unix vận hành như một dạng công năng. Công năng như vậy có thể được truyền giữa các tiến trình thông qua Unix domain socket bằng cách sử dụng lệnh gọi hệ thống sendmsg()
. Tuy nhiên, lưu ý rằng cái thực sự được truyền đi chính là tham chiếu đến "open file description" có trạng thái khả biến đổi (offset của file, tình trạng của file, và cờ truy cập của file). Điều này gây rắc rối cho việc sử dụng file descriptor làm công năng sao cho an toàn, vì khi các chương trình chia sẻ quyền truy cập vào cùng "open file description" thì chúng có thể can thiệp vào việc sử dụng file của nhau như bằng cách thay đổi offset của file hoặc thay đổi file thành blocking hoặc là non-blocking, chẳng hạn vậy.[8][9] Trong các hệ điều hành được đặc biệt thiết kế để làm hệ thống công năng, rất hiếm khi có bất kỳ trạng thái khả biến đổi nào mà bản thân nó có liên đới với công năng nào đó.
Bảng file descriptor của tiến trình trong Unix là một ví dụ về C-list (danh sách công năng).