Thứ Bảy, 6 tháng 1, 2018

[Vụn vặt Linux] File và FileSystem trong Linux (P1)

Linux tuân theo nguyên lí:
Everything is a file
Trong hệ thống Linux, mỗi file sẽ được đặc trưng bởi một đặc tả tệp (file descriptor), đây là một số nguyên được quản lý và cung cấp bởi Linux kernel và được share với user-space. Khi ta đã có file descriptor, ta có thể thực hiện các thao tác trên file như đọc, ghi v.v...

Regular files
Hay còn gọi là tệp thông thường, các file này chính là các file mà ta thường thao tác nhất như các file có dạng text, file object v.v... Trong các file này chứa các dữ liệu được sắp xếp theo byte-stream, nghĩa là chúng ta có thể truy cập với đơn vị nhỏ nhất cho việc đọc và ghi trên 1 file.

Việc ta trỏ đến byte nào của 1 file sẽ phụ thuộc vào vị trí hiện tại của ta ở trên file đó.
Ví dụ, khi ta mở 1 file lên, ta sẽ ở vị trí đầu tiên của nó với offset là 0. Tiếp theo, mỗi khi ta thao tác, vị trí offset sẽ được tăng tuyến tính với byte mà ta đã thao tác cùng.
Ngoài ra ta cũng có thể thay đổi vị trí offset này bằng 1 số hàm. Vậy bạn cnó thể đặt câu hỏi là giá trị offset này giá trị nằm trong dải bao nhiêu. Tôi xin trả lời luôn đó là 0 - 2^64-1.
Ta cũng có thể nhận thấy giá trị offset hợp lệ của 1 file sẽ quyết định kích thước của file đó, tôi tạm gọi là length.

Vậy ta có thể cắt 1 file hoặc nối vào 1 file không?
Câu trả lời là CÓ!
Thư viện POSIX có hỗ trợ các hàm:
#include <unistd.h>
int ftruncate(int fildes, off_t length);
int truncate(const char *path, off_t length);
Việc giảm size của 1 file sẽ đồng nghĩa với việc ta cắt phần cuối của file.
Việc tăng size 1 file sẽ đồng nghĩa ta chèn các giá trị zero vào khoảng được thêm mới của file.
Một file có thể rỗng nghĩa là length của file đó là 0 đồng nghĩa với việc bất cứ việc truy nhập vào byte nào của file này sẽ vô nghĩa (sẽ ra vùng nhớ khác).

Một file có thể được đồng thời mở ở nhiều chương trình, do đó việc đọc ghi tới file đó sẽ được cho phép đối với tất cả các chương trình mở nó.
Một chương trình có thể mở file nhiều lần, mỗi lần mở file, 1 file descriptor sẽ được trả về tương ứng cho phép truy nhập vào file đó.
Vậy các chương trình chia sẻ 1 file descriptor thì sao? Câu trả lời là được. Bạn có thể tự hỏi rằng việc đọc và ghi tùy tiện vào 1 file bởi nhiều chương trình khác nhau có thể gây ra lỗi, việc này xử lý bằng cơ chế đồng bộ (sẽ được trình bày sau).

Mặc dù ta thường truy nhập file dựa trên filename, nhưng thực tế các file là các đối tượng gọi là i-node trong hệ thống Linux (information node), i-node là một số nguyên được gán với vùng dữ liệu vật lý trong bộ nhớ. Một i-node sẽ chứa các thông tin như:
+ Thời gian sửa đổi
+ Owner
+ Loại file
+ Kích thước
+ Ví trí dữ liệu.

Directories and Links
Tạm gọi la thư mục và liên kết
Thông thường, khi ta truy nhập đến một file, ta sẽ sử dụng bằng tên của file chứ không phải là inode-number mặc dù chính số này mới thể hiện một cách vật lý dữ liệu ta muốn truy nhập. Tất nhiên, để truy cập đến một file ta cần biết đường dẫn của nó, và đối tượng gần với file đó nhất chính là thư mục (directory). Một thư mực hoạt động giống như một ánh xạ các inode-number với tên của các file tương ứng. Một cặp tên và số inode sẽ cho ta một liên kết (link).

Một thư mục cũng là một file thông thường ta đã đề cập ở trên, chỉ khác là file này chỉ bao gồm thông tin ánh xạ (mapping) giữa inode và tên file chứ không chứa thông tin giống như file thông thường. Việc ánh xạ giữa inode và tên file được thực hiện bởi 1 bảng băm (hash table) dưới kernel, khi ta sử dụng một đường dẫn, kernel cần sử dụng bảng này để phân giải ra giá trị node cho phép ta truy cập file.

Vậy flow khi ta mở 1 file thông thường sẽ là như sau:
File name -> inode_number -> inode -> metadata (data).

Vậy thư mục cũng là một file thông thường, do đó nó phải có một inode tương ứng. Các link ở trong inode mà thư mục trỏ tới cũng có thể trỏ đến các thư mục khác, điều này tạo ra directory hierarchy (phân tầng thư mục) như hình dưới đây:

Ban đầu, ta chỉ có 1 thư mục root, được kí hiệu bởi /, nhưng có rất nhiều thư mục trong một hệ thống như ta biết. Vậy việc truy cập chúng sẽ có hai trường hợp.
1. Path name bắt đầu từ root (gọi là đường dẫn tuyệt đối).
2. Path name bắt đầu từ một thư mục khác root (đường dẫn tương đối).

Trong trường hợp 1, ta thấy kernel có thể rõ ràng truy vấn đến đường dẫn đó vì inode của root là đã biết trước.

Nhưng trong trường hợp 2, việc phân giải đường dẫn được bắt đầu từ current working directory - PWD (thư mục hiện tại) , việc truy vấn cũng tương tự đến file nhưng lúc này đường dẫn sẽ là việc ghép giữa PWD và đường dẫn tương đối.
Ví dụ:
1. root/usr/dexter/hobbies/luring.txt (đường dẫn tuyệt đối).
2. pwd = root/user/dexter
    hobbies/luring

Mặc dù thư mục cũng giống 1 file nhưng kernel không cho phép ta thực hiện các hành động giống như một file thông thường, mà ta chỉ có thể thêm link hoặc xóa link của 1 thư mục(tương ứng với việc thêm file hoặc xóa file) trong thư mục đó.

Hard Links
Là khi ta có nhiều link trỏ tới cùng 1 inode.
Hard-link cho phép nhiều pathname khác nhau trỏ tới cùng 1 inode. Ví dụ:
 /home/bluebeard/treasure.txt và /home/blackbeard/to_steal.txt.
Ví dụ trong 1 thư mục ta gõ:
ls -la
Mỗi file sẽ có 1 biến count, với mỗi link trỏ tới inode của file, biến count sẽ tăng lên 1, tương tự khi ta sử dụng unlinking, biến count sẽ giảm đi 1. Do đó, khi ta thực hiện thao tác xóa bỏ 1 file, dữ liệu thực tế (inode) sẽ chỉ thực sự biến mất trên filesystem khi mà biến count của inode đó là 0.

Symbolic Links
Khác với hard-link, symlink là một file (nghĩa là có inode) và dữ liệu của nó chính là đường dẫn đến inode mà nó thực sự trỏ tới. Một symlink có thể trỏ tới bất kỳ đâu, hoặc đến một file không tồn tại, khi này sẽ gọi là broken link.
Một ví dụ rõ ràng của Symlink đó là short-cut. Khi ta xóa bỏ dữ liệu gốc của short-cut, short-cut sẽ không thể thực thi do dữ liệu thực tế đã bị xóa bỏ hoàn toàn trên filesystem.


Không có nhận xét nào:

Đăng nhận xét