Bộ mô tả tệp trong Linux với các ví dụ

Một lần, trong một cuộc phỏng vấn, tôi được hỏi, bạn sẽ làm gì nếu thấy một dịch vụ không hoạt động do đĩa đã hết dung lượng?

Tất nhiên, tôi trả lời rằng tôi sẽ xem nơi này có gì và nếu có thể, tôi sẽ dọn dẹp nơi này.
Sau đó, người phỏng vấn hỏi, nếu không có dung lượng trống trên phân vùng nhưng bạn cũng không thấy bất kỳ tệp nào chiếm hết dung lượng thì sao?

Về điều này, tôi đã nói rằng bạn luôn có thể xem các bộ mô tả tệp đang mở, chẳng hạn như bằng lệnh lsof và hiểu ứng dụng nào đã chiếm hết dung lượng có sẵn, sau đó bạn có thể hành động tùy theo trường hợp, tùy thuộc vào việc dữ liệu có cần thiết hay không .

Người phỏng vấn ngắt lời tôi ở lời cuối cùng, thêm vào câu hỏi của anh ta: “Giả sử chúng ta không cần dữ liệu, nó chỉ là nhật ký gỡ lỗi, nhưng ứng dụng không hoạt động vì không thể viết bản gỡ lỗi”?

“Được rồi,” tôi trả lời, “chúng ta có thể tắt tính năng gỡ lỗi trong cấu hình ứng dụng và khởi động lại nó.”
Người phỏng vấn phản đối: “Không, chúng tôi không thể khởi động lại ứng dụng, chúng tôi vẫn còn dữ liệu quan trọng được lưu trong bộ nhớ và các máy khách quan trọng đã được kết nối với chính dịch vụ mà chúng tôi không thể buộc kết nối lại”.

“Được rồi,” tôi nói, “nếu chúng tôi không thể khởi động lại ứng dụng và dữ liệu không quan trọng đối với chúng tôi thì chúng tôi có thể chỉ cần xóa tệp đang mở này thông qua bộ mô tả tệp, ngay cả khi chúng tôi không thấy nó trong lệnh ls trên hệ thống tập tin.”

Người phỏng vấn hài lòng, còn tôi thì không.

Sau đó tôi nghĩ, tại sao người kiểm tra kiến ​​thức của tôi không đào sâu hơn? Nhưng nếu dữ liệu đó quan trọng thì sao? Điều gì sẽ xảy ra nếu chúng ta không thể khởi động lại một quy trình và quy trình đó ghi vào hệ thống tệp trên một phân vùng không còn dung lượng trống? Điều gì sẽ xảy ra nếu chúng ta không thể mất không chỉ dữ liệu đã được ghi mà còn cả dữ liệu mà quá trình này ghi hoặc cố gắng ghi?

Tuzik

Khi mới bắt đầu sự nghiệp, tôi đã cố gắng tạo một ứng dụng nhỏ cần lưu trữ thông tin người dùng. Và sau đó tôi nghĩ, làm cách nào để khớp người dùng với dữ liệu của anh ấy. Ví dụ, tôi có Ivanov Ivan Ivanovich, anh ấy có một số thông tin, nhưng làm sao tôi có thể kết bạn với họ? Tôi có thể chỉ ra trực tiếp rằng con chó tên “Tuzik” thuộc về chính Ivan này. Nhưng điều gì sẽ xảy ra nếu anh ta đổi tên và thay vì Ivan lại trở thành Olya chẳng hạn? Sau đó, hóa ra Olya Ivanovna Ivanova của chúng ta sẽ không còn con chó nữa, và Tuzik của chúng ta vẫn sẽ thuộc về Ivan không tồn tại. Cơ sở dữ liệu cung cấp cho mỗi người dùng một mã định danh (ID) duy nhất đã giúp giải quyết vấn đề này và Tuzik của tôi được gắn với ID này, trên thực tế, chỉ là một số sê-ri. Do đó, chủ nhân của quân át có ID số 2, và tại một thời điểm nào đó Ivan có ID này, và sau đó Olya trở thành có cùng ID. Vấn đề nhân loại và chăn nuôi đã được giải quyết trên thực tế.

Bộ mô tả tập tin

Vấn đề của tệp và chương trình hoạt động với tệp này gần giống như vấn đề của con chó và con người của chúng ta. Giả sử tôi mở một tệp có tên ivan.txt và bắt đầu viết từ tuzik vào đó, nhưng chỉ viết được chữ cái đầu tiên “t” trong tệp và tệp này đã được ai đó đổi tên, chẳng hạn như thành olya.txt. Nhưng tập tin vẫn giữ nguyên và tôi vẫn muốn ghi con át chủ bài của mình vào đó. Mỗi lần một tập tin được mở bằng lệnh gọi hệ thống mở trong bất kỳ ngôn ngữ lập trình nào, tôi nhận được một ID duy nhất trỏ tôi đến một tệp, ID này là bộ mô tả tệp. Và không quan trọng ai và ai làm gì với tập tin này tiếp theo, nó có thể bị xóa, có thể đổi tên, có thể thay đổi chủ sở hữu hoặc có thể bị tước quyền đọc và ghi, tôi vẫn có quyền truy cập đối với nó, bởi vì tại thời điểm mở tệp, tôi có quyền đọc và/hoặc ghi nó và tôi đã cố gắng bắt đầu làm việc với nó, điều đó có nghĩa là tôi phải tiếp tục làm như vậy.

Trong Linux, thư viện libc mở 3 tệp mô tả cho mỗi ứng dụng (tiến trình) đang chạy, được đánh số 0,1,2. Thông tin thêm có thể được tìm thấy trên các liên kết phòng thu người đàn ông и người đàn ông xuất sắc

  • Bộ mô tả tệp 0 được gọi là STDIN và được liên kết với đầu vào ứng dụng
  • Bộ mô tả tệp 1 được gọi là STDOUT và được các ứng dụng sử dụng để xuất dữ liệu, chẳng hạn như lệnh in
  • Bộ mô tả tệp 2 được gọi là STDERR và được các ứng dụng sử dụng để xuất thông báo lỗi.

Nếu trong chương trình của bạn, bạn mở bất kỳ tệp nào để đọc hoặc ghi, thì rất có thể bạn sẽ nhận được ID miễn phí đầu tiên và nó sẽ là số 3.

Danh sách các bộ mô tả tệp có thể được xem cho bất kỳ quy trình nào nếu bạn biết PID của nó.

Ví dụ: hãy mở bảng điều khiển bash và xem PID của quy trình của chúng tôi

[user@localhost ]$ echo $$
15771

Trong bảng điều khiển thứ hai hãy chạy

[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user  0 Oct  7 15:42 .
dr-xr-xr-x 9 user user  0 Oct  7 15:42 ..
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21

Bạn có thể yên tâm bỏ qua phần mô tả tệp số 255 vì mục đích của bài viết này; nó được mở theo nhu cầu của chính nó bằng chính bash chứ không phải bởi thư viện được liên kết.

Bây giờ cả 3 tệp mô tả đều được liên kết với thiết bị đầu cuối giả / dev / pts, nhưng chúng ta vẫn có thể thao tác với chúng, chẳng hạn như chạy chúng trong bảng điều khiển thứ hai

[user@localhost ]$ echo "hello world" > /proc/15771/fd/0

Và trong bảng điều khiển đầu tiên chúng ta sẽ thấy

[user@localhost ]$ hello world

Chuyển hướng và ống

Bạn có thể dễ dàng ghi đè 3 tệp mô tả này trong bất kỳ quy trình nào, kể cả trong bash, chẳng hạn như thông qua một đường ống kết nối hai quy trình, xem

[user@localhost ]$ cat /dev/zero | sleep 10000

Bạn có thể tự chạy lệnh này với dấu vết -f và xem chuyện gì đang xảy ra bên trong, nhưng tôi sẽ kể cho bạn nghe ngắn gọn.

Quá trình bash gốc của chúng tôi với PID 15771 phân tích lệnh của chúng tôi và hiểu chính xác có bao nhiêu lệnh chúng tôi muốn chạy, trong trường hợp của chúng tôi có hai trong số đó: cat và sleep. Bash biết rằng nó cần tạo hai tiến trình con và hợp nhất chúng thành một ống. Tổng cộng, bash sẽ cần 2 tiến trình con và một đường ống.

Bash chạy lệnh gọi hệ thống trước khi tạo tiến trình con đường ống và nhận các bộ mô tả tệp mới trên bộ đệm ống tạm thời, nhưng bộ đệm này chưa kết nối hai quy trình con của chúng tôi.

Đối với quy trình gốc, có vẻ như đã có một đường ống, nhưng chưa có quy trình con nào:

PID    command
15771  bash
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21

Sau đó sử dụng lệnh gọi hệ thống nhân bản bash tạo hai tiến trình con và ba tiến trình của chúng ta sẽ trông như thế này:

PID    command
15771  bash
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21
PID    command
9004  bash
lrwx------ 1 user user 64 Oct  7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 255 -> /dev/pts/21
PID    command
9005  bash
lrwx------ 1 user user 64 Oct  7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 255 -> /dev/pts/21

Đừng quên rằng bản sao sẽ sao chép quy trình cùng với tất cả các bộ mô tả tệp, vì vậy chúng sẽ giống nhau trong quy trình cha và quy trình con. Công việc của tiến trình cha với PID 15771 là giám sát các tiến trình con nên nó chỉ chờ phản hồi từ tiến trình con.

Do đó, nó không cần pipe và nó đóng các bộ mô tả tệp được đánh số 3 và 4.

Trong quy trình bash con đầu tiên với PID 9004, lệnh gọi hệ thống Dup2, thay đổi bộ mô tả tệp STDOUT số 1 của chúng tôi thành bộ mô tả tệp trỏ đến đường ống, trong trường hợp của chúng tôi là số 3. Do đó, mọi thứ mà tiến trình con đầu tiên với PID 9004 ghi vào STDOUT sẽ tự động kết thúc trong bộ đệm ống.

Trong tiến trình con thứ hai với PID 9005, bash sử dụng dup2 để thay đổi bộ mô tả tệp STDIN số 0. Bây giờ mọi thứ mà bash thứ hai của chúng ta với PID 9005 sẽ đọc sẽ được đọc từ đường ống.

Sau đó, các bộ mô tả tệp được đánh số 3 và 4 cũng bị đóng trong các tiến trình con vì chúng không còn được sử dụng nữa.

Tôi cố tình bỏ qua bộ mô tả tập tin 255; nó được sử dụng cho mục đích nội bộ bởi chính bash và cũng sẽ bị đóng trong các tiến trình con.

Tiếp theo, trong tiến trình con đầu tiên với PID 9004, bash bắt đầu sử dụng lệnh gọi hệ thống giám đốc điều hành tệp thực thi mà chúng tôi đã chỉ định trên dòng lệnh, trong trường hợp của chúng tôi là /usr/bin/cat.

Trong quy trình con thứ hai với PID 9005, bash chạy tệp thực thi thứ hai mà chúng tôi đã chỉ định, trong trường hợp của chúng tôi là /usr/bin/sleep.

Lệnh gọi hệ thống exec không đóng các thẻ điều khiển tệp trừ khi chúng được mở bằng cờ O_CLOEXEC tại thời điểm lệnh gọi mở được thực hiện. Trong trường hợp của chúng tôi, sau khi khởi chạy các tệp thực thi, tất cả các bộ mô tả tệp hiện tại sẽ được lưu.

Kiểm tra trong bảng điều khiển:

[user@localhost ]$ pgrep -P 15771
9004
9005
[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user  0 Oct  7 15:42 .
dr-xr-xr-x 9 user user  0 Oct  7 15:42 ..
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21
[user@localhost ]$ ls -lah /proc/9004/fd
total 0
dr-x------ 2 user user  0 Oct  7 15:57 .
dr-xr-xr-x 9 user user  0 Oct  7 15:57 ..
lrwx------ 1 user user 64 Oct  7 15:57 0 -> /dev/pts/21
l-wx------ 1 user user 64 Oct  7 15:57 1 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
lr-x------ 1 user user 64 Oct  7 15:57 3 -> /dev/zero
[user@localhost ]$ ls -lah /proc/9005/fd
total 0
dr-x------ 2 user user  0 Oct  7 15:57 .
dr-xr-xr-x 9 user user  0 Oct  7 15:57 ..
lr-x------ 1 user user 64 Oct  7 15:57 0 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
[user@localhost ]$ ps -up 9004
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
user  9004  0.0  0.0 107972   620 pts/21   S+   15:57   0:00 cat /dev/zero
[user@localhost ]$ ps -up 9005
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
user  9005  0.0  0.0 107952   360 pts/21   S+   15:57   0:00 sleep 10000

Như bạn có thể thấy, số lượng ống duy nhất của chúng tôi là như nhau trong cả hai quy trình. Do đó, chúng ta có một kết nối giữa hai tiến trình khác nhau có cùng một tiến trình gốc.

Đối với những người không quen với các lệnh gọi hệ thống mà bash sử dụng, tôi thực sự khuyên bạn nên chạy các lệnh thông qua strace và xem điều gì đang xảy ra bên trong, ví dụ như thế này:

strace -s 1024 -f bash -c "ls | grep hello"

Hãy quay lại vấn đề dung lượng ổ đĩa thấp và cố gắng lưu dữ liệu mà không cần khởi động lại quá trình. Hãy viết một chương trình nhỏ có tốc độ ghi khoảng 1 megabyte mỗi giây vào đĩa. Hơn nữa, nếu vì lý do nào đó mà chúng tôi không thể ghi dữ liệu vào đĩa, chúng tôi sẽ đơn giản bỏ qua điều này và cố gắng ghi lại dữ liệu sau một giây. Trong ví dụ tôi đang sử dụng Python, bạn có thể sử dụng bất kỳ ngôn ngữ lập trình nào khác.

[user@localhost ]$ cat openforwrite.py 
import datetime
import time

mystr="a"*1024*1024+"n"
with open("123.txt", "w") as f:
    while True:
        try:
            f.write(str(datetime.datetime.now()))
            f.write(mystr)
            f.flush()
            time.sleep(1)
        except:
            pass

Hãy chạy chương trình và xem phần mô tả tập tin

[user@localhost ]$ python openforwrite.py &
[1] 3762
[user@localhost ]$ ps axuf | grep [o]penforwrite
user  3762  0.0  0.0 128600  5744 pts/22   S+   16:28   0:00  |   _ python openforwrite.py
[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user  0 Oct  7 16:29 .
dr-xr-xr-x 9 user user  0 Oct  7 16:29 ..
lrwx------ 1 user user 64 Oct  7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  7 16:29 3 -> /home/user/123.txt

Như bạn có thể thấy, chúng tôi có 3 bộ mô tả tệp tiêu chuẩn và một bộ mô tả tệp khác mà chúng tôi đã mở. Hãy kiểm tra kích thước tập tin:

[user@localhost ]$ ls -lah 123.txt 
-rw-rw-r-- 1 user user 117M Oct  7 16:30 123.txt

Dữ liệu đang được ghi, chúng tôi cố gắng thay đổi quyền trên tệp:

[user@localhost ]$ sudo chown root: 123.txt
[user@localhost ]$ ls -lah 123.txt 
-rw-rw-r-- 1 root root 168M Oct  7 16:31 123.txt
[user@localhost ]$ ls -lah 123.txt 
-rw-rw-r-- 1 root root 172M Oct  7 16:31 123.txt

Chúng tôi thấy rằng dữ liệu vẫn đang được ghi, mặc dù người dùng của chúng tôi không có quyền ghi vào tệp. Hãy thử loại bỏ nó:

[user@localhost ]$ sudo rm 123.txt 
[user@localhost ]$ ls 123.txt
ls: cannot access 123.txt: No such file or directory

Dữ liệu được viết ở đâu? Và chúng có được viết không? Chung ta kiểm tra:

[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user  0 Oct  7 16:29 .
dr-xr-xr-x 9 user user  0 Oct  7 16:29 ..
lrwx------ 1 user user 64 Oct  7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  7 16:29 3 -> /home/user/123.txt (deleted)

Có, bộ mô tả tệp của chúng tôi vẫn tồn tại và chúng tôi có thể coi bộ mô tả tệp này giống như tệp cũ của mình, chúng tôi có thể đọc, xóa và sao chép nó.

Hãy nhìn vào kích thước tập tin:

[user@localhost ]$ lsof | grep 123.txt
python    31083             user    3w      REG                8,5   19923457   2621522 /home/user/123.txt

Kích thước tệp là 19923457. Hãy thử xóa tệp:

[user@localhost ]$ truncate -s 0 /proc/31083/fd/3
[user@localhost ]$ lsof | grep 123.txt
python    31083             user    3w      REG                8,5  136318390   2621522 /home/user/123.txt

Như bạn có thể thấy, kích thước tệp ngày càng tăng và đường trung kế của chúng tôi không hoạt động. Hãy xem tài liệu cuộc gọi hệ thống mở. Nếu chúng ta sử dụng cờ O_APPEND khi mở tệp thì với mỗi lần ghi, hệ điều hành sẽ kiểm tra kích thước tệp và ghi dữ liệu vào cuối tệp và thực hiện việc này một cách nguyên tử. Điều này cho phép nhiều luồng hoặc tiến trình ghi vào cùng một tệp. Nhưng trong mã của chúng tôi, chúng tôi không sử dụng cờ này. Chúng tôi chỉ có thể thấy kích thước tệp khác trong lsof sau trung kế nếu chúng tôi mở tệp để ghi bổ sung, điều đó có nghĩa là trong mã của chúng tôi thay vào đó

with open("123.txt", "w") as f:

chúng ta phải đặt

with open("123.txt", "a") as f:

Kiểm tra bằng cờ “w”

[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1| grep 123.txt
open("123.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

và với cờ "a"

[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1| grep 123.txt
open("123.txt", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3

Lập trình một tiến trình đang chạy

Thông thường, các lập trình viên, khi tạo và thử nghiệm chương trình, sử dụng trình gỡ lỗi (ví dụ GDB) hoặc nhiều cấp độ đăng nhập khác nhau vào ứng dụng. Linux cung cấp khả năng thực sự viết và thay đổi một chương trình đang chạy, ví dụ: thay đổi giá trị của các biến, đặt điểm dừng, v.v., v.v.

Quay trở lại câu hỏi ban đầu về việc không đủ dung lượng ổ đĩa để ghi một tập tin, chúng ta hãy thử mô phỏng vấn đề.

Hãy tạo một tệp cho phân vùng của chúng tôi, chúng tôi sẽ gắn tệp này dưới dạng một đĩa riêng:

[user@localhost ~]$ dd if=/dev/zero of=~/tempfile_for_article.dd bs=1M count=10
10+0 records in
10+0 records out
10485760 bytes (10 MB) copied, 0.00525929 s, 2.0 GB/s
[user@localhost ~]$

Hãy tạo một hệ thống tập tin:

[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-Dec-2013)
/home/user/tempfile_for_article.dd is not a block special device.
Proceed anyway? (y,n) y
...
Writing superblocks and filesystem accounting information: done
[user@localhost ~]$

Gắn kết hệ thống tập tin:

[user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/
[sudo] password for user: 
[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  172K  7.9M   3% /mnt

Chúng tôi tạo một thư mục với chủ sở hữu của chúng tôi:

[user@localhost ~]$ sudo mkdir /mnt/logs
[user@localhost ~]$ sudo chown user: /mnt/logs

Hãy mở tệp chỉ để viết trong chương trình của chúng tôi:

with open("/mnt/logs/123.txt", "w") as f:

Phóng

[user@localhost ]$ python openforwrite.py 

Chúng tôi đợi vài giây

[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  8.0M     0 100% /mnt

Vì vậy, chúng tôi có vấn đề được mô tả ở đầu bài viết này. Không gian trống 0, đã chiếm 100%.

Chúng tôi nhớ rằng tùy theo điều kiện của nhiệm vụ, chúng tôi đang cố gắng ghi lại những dữ liệu rất quan trọng không thể bị mất. Và đồng thời, chúng ta cần sửa dịch vụ mà không cần khởi động lại quy trình.

Giả sử chúng ta vẫn còn dung lượng ổ đĩa, nhưng ở một phân vùng khác, chẳng hạn như trong /home.

Hãy thử “lập trình lại nhanh chóng” mã của chúng ta.

Hãy xem xét PID của quy trình của chúng tôi, nó đã chiếm hết dung lượng ổ đĩa:

[user@localhost ~]$ ps axuf | grep [o]penfor
user 10078 27.2  0.0 128600  5744 pts/22   R+   11:06   0:02  |   _ python openforwrite.py

Kết nối với quá trình thông qua gdb

[user@localhost ~]$ gdb -p 10078
...
(gdb) 

Hãy xem các mô tả tệp đang mở:

(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user  0 Oct  8 11:06 .
dr-xr-xr-x 9 user user  0 Oct  8 11:06 ..
lrwx------ 1 user user 64 Oct  8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:09 3 -> /mnt/logs/123.txt

Chúng tôi xem thông tin về bộ mô tả tập tin số 3 mà chúng tôi quan tâm

(gdb) shell cat /proc/10078/fdinfo/3
pos:    8189952
flags:  0100001
mnt_id: 482

Hãy ghi nhớ lệnh gọi hệ thống mà Python thực hiện (xem ở trên nơi chúng tôi chạy strace và tìm thấy lệnh gọi mở), khi xử lý mã của chúng tôi để mở một tệp, chúng tôi tự thực hiện điều tương tự thay mặt cho quy trình của mình, nhưng chúng tôi cần O_WRONLY|O_CREAT| Các bit O_TRUNC thay thế bằng một giá trị số. Để thực hiện việc này, hãy mở nguồn kernel, ví dụ: đây và xem lá cờ nào chịu trách nhiệm về việc gì

#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000

Chúng tôi kết hợp tất cả các giá trị thành một, chúng tôi nhận được 00001101

Chúng tôi thực hiện cuộc gọi của mình từ gdb

(gdb) call open("/home/user/123.txt", 00001101,0666)
$1 = 4

Vì vậy, chúng tôi có một bộ mô tả tệp mới với số 4 và một tệp đang mở mới trên một phân vùng khác, chúng tôi kiểm tra:

(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user  0 Oct  8 11:06 .
dr-xr-xr-x 9 user user  0 Oct  8 11:06 ..
lrwx------ 1 user user 64 Oct  8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:09 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct  8 11:15 4 -> /home/user/123.txt

Chúng ta nhớ lại ví dụ về pipe - cách bash thay đổi bộ mô tả tệp và chúng ta đã học được lệnh gọi hệ thống dup2.

Chúng tôi cố gắng thay thế một bộ mô tả tập tin bằng một bộ mô tả tập tin khác

(gdb) call dup2(4,3)
$2 = 3

Chúng tôi kiểm tra:

(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user  0 Oct  8 11:06 .
dr-xr-xr-x 9 user user  0 Oct  8 11:06 ..
lrwx------ 1 user user 64 Oct  8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:09 3 -> /home/user/123.txt
l-wx------ 1 user user 64 Oct  8 11:15 4 -> /home/user/123.txt

Chúng tôi đóng bộ mô tả tệp 4 vì chúng tôi không cần nó:

(gdb) call close (4)
$1 = 0

Và thoát gdb

(gdb) quit
A debugging session is active.

    Inferior 1 [process 10078] will be detached.

Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2.7, process 10078

Kiểm tra tập tin mới:

[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 5.1M Oct  8 11:18 /home/user/123.txt
[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 7.1M Oct  8 11:18 /home/user/123.txt

Như bạn có thể thấy, dữ liệu được ghi vào một tệp mới, hãy kiểm tra tệp cũ:

[user@localhost ~]$ ls -lah /mnt/logs/123.txt 
-rw-rw-r-- 1 user user 7.9M Oct  8 11:08 /mnt/logs/123.txt

Không có dữ liệu nào bị mất, ứng dụng hoạt động, nhật ký được ghi vào vị trí mới.

Hãy phức tạp hóa nhiệm vụ một chút

Hãy tưởng tượng rằng dữ liệu quan trọng đối với chúng ta nhưng chúng ta không có dung lượng ổ đĩa trong bất kỳ phân vùng nào và chúng ta không thể kết nối ổ đĩa.

Những gì chúng ta có thể làm là chuyển hướng dữ liệu của mình đến một nơi nào đó, chẳng hạn như đến đường ống, và lần lượt chuyển hướng dữ liệu từ đường ống sang mạng thông qua một số chương trình, chẳng hạn như netcat.
Chúng ta có thể tạo một đường ống được đặt tên bằng lệnh mkfifo. Nó sẽ tạo một tệp giả trên hệ thống tệp ngay cả khi không có dung lượng trống trên đó.

Khởi động lại ứng dụng và kiểm tra:

[user@localhost ]$ python openforwrite.py 
[user@localhost ~]$ ps axuf | grep [o]pen
user  5946 72.9  0.0 128600  5744 pts/22   R+   11:27   0:20  |   _ python openforwrite.py
[user@localhost ~]$ ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/123.txt
[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  8.0M     0 100% /mnt

Không có dung lượng ổ đĩa, nhưng chúng tôi đã tạo thành công một đường ống có tên ở đó:

[user@localhost ~]$ mkfifo /mnt/logs/megapipe
[user@localhost ~]$ ls -lah /mnt/logs/megapipe 
prw-rw-r-- 1 user user 0 Oct  8 11:28 /mnt/logs/megapipe

Bây giờ chúng ta cần bằng cách nào đó bọc tất cả dữ liệu đi vào đường ống này đến một máy chủ khác qua mạng; cùng một netcat phù hợp cho việc này.

Trên máy chủ remote-server.example.com chúng tôi chạy

[user@localhost ~]$ nc -l 7777 > 123.txt 

Trên máy chủ có vấn đề của chúng tôi, chúng tôi khởi chạy trong một thiết bị đầu cuối riêng biệt

[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe 

Bây giờ tất cả dữ liệu kết thúc trong đường ống sẽ tự động chuyển đến stdin trong netcat, dữ liệu này sẽ gửi đến mạng trên cổng 7777.

Tất cả những gì chúng ta phải làm là bắt đầu ghi dữ liệu của mình vào đường dẫn có tên này.

Chúng tôi đã có ứng dụng đang chạy:

[user@localhost ~]$ ps axuf | grep [o]pen
user  5946 99.8  0.0 128600  5744 pts/22   R+   11:27 169:27  |   _ python openforwrite.py
[user@localhost ~]$ ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/123.txt

Trong tất cả các cờ, chúng tôi chỉ cần O_WRONLY vì tệp đã tồn tại và chúng tôi không cần xóa nó

[user@localhost ~]$ gdb -p 5946
...
(gdb) call open("/mnt/logs/megapipe", 00000001,0666)
$1 = 4
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct  8 14:20 4 -> /mnt/logs/megapipe
(gdb) call dup2(4,3)
$2 = 3
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/megapipe
l-wx------ 1 user user 64 Oct  8 14:20 4 -> /mnt/logs/megapipe
(gdb) call close(4)
$3 = 0
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/megapipe
(gdb) quit
A debugging session is active.

    Inferior 1 [process 5946] will be detached.

Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2.7, process 5946

Kiểm tra máy chủ từ xa remote-server.example.com

[user@localhost ~]$ ls -lah 123.txt 
-rw-rw-r-- 1 user user 38M Oct  8 14:21 123.txt

Dữ liệu đang đến, chúng tôi kiểm tra máy chủ có vấn đề

[user@localhost ~]$ ls -lah /mnt/logs/
total 7.9M
drwxr-xr-x 2 user user 1.0K Oct  8 11:28 .
drwxr-xr-x 4 root     root     1.0K Oct  8 10:55 ..
-rw-rw-r-- 1 user user 7.9M Oct  8 14:17 123.txt
prw-rw-r-- 1 user user    0 Oct  8 14:22 megapipe

Dữ liệu được lưu, vấn đề được giải quyết.

Tôi nhân cơ hội này để gửi lời chào đến các đồng nghiệp của tôi từ Degiro.
Nghe podcast Radio-T.

Tốt cho tất cả.

Để làm bài tập về nhà, tôi khuyên bạn nên suy nghĩ về những gì sẽ có trong bộ mô tả tệp tiến trình cat và sleep nếu bạn chạy lệnh sau:

[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000

Nguồn: www.habr.com

Thêm một lời nhận xét