File descriptor
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 |
Tổng quan
[sửa | sửa mã nguồn]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.
Các thao tác trên file descriptor
[sửa | sửa mã nguồn]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>
.
Tạo ra file descriptor
[sửa | sửa mã nguồn]- open()
- creat()[5]
- socket()
- accept()
- socketpair()
- pipe()
- epoll_create() (Linux)
- signalfd() (Linux)
- eventfd() (Linux)
- timerfd_create() (Linux)
- memfd_create() (Linux)
- userfaultfd() (Linux)
- fanotify_init() (Linux)
- inotify_init() (Linux)
- clone() (khi kèm cờ CLONE_PIDFD, Linux)
- pidfd_open() (Linux)
- open_by_handle_at() (Linux)
Phái sinh ra file descriptor
[sửa | sửa mã nguồn]- dirfd()
- fileno()
Thao tác trên file descriptor đơn
[sửa | sửa mã nguồn]- read(), write()
- readv(), writev()
- pread(), pwrite()
- recv(), send()
- recvfrom(), sendto()
- recvmsg(), sendmsg() (cũng được dùng để gửi FD sang tiến trình khác thông qua Unix domain socket)
- recvmmsg(), sendmmsg()
- lseek(), llseek()
- fstat()
- fstatvfs()
- fchmod()
- fchown()
- ftruncate()
- fsync()
- fdatasync()
- fdopendir()
- fgetxattr(), fsetxattr() (Linux)
- flistxattr(), fremovexattr() (Linux)
- statx (Linux)
- setns (Linux)
- vmsplice() (Linux)
- pidfd_send_signal() (Linux)
- waitid() (khi kèm kiểu ID là P_PIDFD, Linux)
- fdopen() (hàm stdio: chuyển đổi file descriptor thành FILE*)
- dprintf() (hàm stdio: in ra file descriptor)
Thao tác trên nhiều file descriptor
[sửa | sửa mã nguồn]- select(), pselect()
- select(), pselect()
- poll(), ppoll()
- epoll_wait(), epoll_pwait(), epoll_pwait2() (Linux, nhận vào một epoll filedescriptor đơn để chờ nhiều file descriptor khác)
- epoll_ctl() (dành cho Linux)
- kqueue() (dành cho hệ thống dựa trên BSD).
- sendfile()
- splice(), tee() (cho Linux)
- copy_file_range() (cho Linux)
- close_range() (cho Linux)[6]
Thao tác trên bảng file descriptor
[sửa | sửa mã nguồn]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.
- close()
- closefrom() (chỉ có ở BSD và Solaris; xóa hết những file descriptor lớn hơn hoặc bằng trị số chỉ định nào đó)
- dup() (nhân bản file descriptor có sẵn nào đó, đảm bảo tạo ra file descriptor có trị số nhỏ nhất sẵn có)
- dup2(), dup3() (đóng fd1 nếu cần thiết, và khiến file descriptor fd1 trỏ đến file đang mở của fd2)
- fcntl (F_DUPFD)
Thao tác sửa đổi trạng thái tiến trình
[sửa | sửa mã nguồn]- fchdir() (thiết đặt thư mục hiện hành của tiến trình dựa trên file descriptor thư mục nào đó)
- mmap() (ánh xạ một khoảng của file vào trong không gian địa chỉ của tiến trình)
Khóa file
[sửa | sửa mã nguồn]- flock()
- fcntl() (F_GETLK, F_SETLK và F_SETLKW)
- lockf()
Socket
[sửa | sửa mã nguồn]- connect()
- bind()
- listen()
- accept() (tạo file descriptor mới cho kết nối truyền tới nào đó)
- getsockname()
- getpeername()
- getsockopt()
- setsockopt()
- shutdown() (shut down một hoặc cả hai đầu của kết nối song công toàn phần nào đó)
Tạp vụ
[sửa | sửa mã nguồn]- ioctl() (làm được nhiều thao tác lặt vặt trên một file descriptor đơn, thường hay được liên đới với một thiết bị nào đó)
Các thao tác sẽ có
[sửa | sửa mã nguồn]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.
- openat()
- faccessat()
- fchmodat()
- fchownat()
- fstatat()
- futimesat()
- linkat()
- mkdirat()
- mknodat()
- readlinkat()
- renameat()
- symlinkat()
- unlinkat()
- mkfifoat()
- fdopendir()
File descriptor làm công năng
[sửa | sửa mã nguồn]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).
Ghi chú thuật ngữ
[sửa | sửa mã nguồn]Tham khảo
[sửa | sửa mã nguồn]- ^ The Open Group. “The Open Group Base Specifications Issue 7, IEEE Std 1003.1-2008, 2016 Edition”. Truy cập ngày 21 tháng 9 năm 2017.
- ^ The Open Group. “The Open Group Base Specifications Issue 7, IEEE Std 1003.1-2008, 2016 Edition”. <stdio.h>. Truy cập ngày 21 tháng 9 năm 2017.
- ^ a b Bach, Maurice J. (1986). The Design of the UNIX Operating System (ấn bản thứ 8). Prentice-Hall. tr. 92–96. ISBN 9780132017992.
- ^ “Devices - What does the output of 'll /Proc/Self/Fd/' (From 'll /Dev/Fd') mean?”.
- ^ The Open Group. “The Open Group Base Specifications Issue 7, IEEE Std 1003.1-2008, 2018 Edition – creat”. Truy cập ngày 11 tháng 4 năm 2019.
- ^ Stephen Kitt, Michael Kerrisk. “close_range(2) — Linux manual page”. Truy cập ngày 22 tháng 3 năm 2021.
- ^ Extended API Set, Part 2. The Open Group. tháng 10 năm 2006. ISBN 1931624674.
- ^ Brinkmann, Marcus (4 tháng 2 năm 2009). “Building a bridge: library API's and file descriptors?”. cap-talk. Bản gốc lưu trữ ngày 30 tháng 7 năm 2012. Truy cập ngày 21 tháng 9 năm 2017.
- ^ de Boyne Pollard, Jonathan (2007). “Don't set shared file descriptors to non-blocking I/O mode”. Truy cập ngày 21 tháng 9 năm 2017.