ตัวอธิบายไฟล์ใน Linux พร้อมตัวอย่าง

ครั้งหนึ่ง ในการสัมภาษณ์ ฉันถูกถามคุณจะทำอย่างไรหากคุณพบบริการที่เสียเนื่องจากดิสก์มีที่ว่างไม่เพียงพอ

แน่นอน ฉันตอบว่าฉันจะดูว่าสถานที่นี้ทำอะไร และถ้าเป็นไปได้ ฉันจะทำความสะอาดสถานที่นั้น
จากนั้นผู้สัมภาษณ์ถามว่าจะทำอย่างไรหากไม่มีพื้นที่ว่างบนพาร์ติชัน แต่คุณไม่เห็นไฟล์ที่จะใช้พื้นที่ทั้งหมดด้วย

สำหรับสิ่งนี้ ฉันบอกว่าคุณสามารถดูตัวอธิบายไฟล์แบบเปิดได้เสมอ เช่น ด้วยคำสั่ง lsof และทำความเข้าใจว่าแอปพลิเคชันใดใช้พื้นที่ที่มีอยู่ทั้งหมด จากนั้นคุณสามารถดำเนินการตามสถานการณ์ ขึ้นอยู่กับว่าข้อมูลนั้นจำเป็นหรือไม่ .

ผู้สัมภาษณ์ขัดจังหวะฉันในคำพูดสุดท้าย โดยเพิ่มคำถามของเขา: “สมมติว่าเราไม่ต้องการข้อมูล เป็นเพียงบันทึกการแก้ไขข้อบกพร่อง แต่แอปพลิเคชันหยุดทำงานเนื่องจากไม่สามารถเขียนการแก้ไขข้อบกพร่องได้”

“ตกลง” ฉันตอบ “เราสามารถปิดการดีบักในการกำหนดค่าแอปพลิเคชันและเริ่มต้นใหม่ได้”
ผู้สัมภาษณ์คัดค้าน: “ไม่ เราไม่สามารถเริ่มแอปพลิเคชันใหม่ได้ เรายังมีข้อมูลสำคัญอยู่ในหน่วยความจำ และไคลเอ็นต์สำคัญเชื่อมต่อกับบริการเอง ซึ่งเราไม่สามารถบังคับให้เชื่อมต่อใหม่ได้”

“โอเค” ฉันพูด “ถ้าเรารีสตาร์ทแอปพลิเคชันไม่ได้และเราไม่สนใจข้อมูล เราก็สามารถล้างไฟล์ที่เปิดอยู่นี้ผ่าน file descriptor แม้ว่าเราจะไม่เห็นไฟล์นั้นใน ls คำสั่งบนระบบไฟล์”

ผู้สัมภาษณ์พอใจแต่ฉันไม่

แล้วฉันก็คิดว่าทำไมคนที่ทดสอบความรู้ของฉันไม่เจาะลึกลงไปอีก แต่ถ้าข้อมูลนั้นมีความสำคัญล่ะ จะเกิดอะไรขึ้นหากเราไม่สามารถเริ่มกระบวนการใหม่ได้ และในขณะเดียวกันกระบวนการนี้ก็เขียนไปยังระบบไฟล์บนพาร์ติชันที่ไม่มีพื้นที่ว่าง จะเป็นอย่างไรหากเราไม่สามารถสูญเสียข้อมูลไม่เพียง แต่ข้อมูลที่เขียนไปแล้ว แต่ยังรวมถึงข้อมูลที่กระบวนการนี้กำลังเขียนหรือพยายามเขียนด้วย

เรือบดเล็ก

ในช่วงเริ่มต้นอาชีพของฉัน ฉันพยายามสร้างแอปพลิเคชันขนาดเล็กที่ต้องการเก็บข้อมูลเกี่ยวกับผู้ใช้ แล้วฉันก็คิดว่า ฉันจะจับคู่ผู้ใช้กับข้อมูลของเขาได้อย่างไร ตัวอย่างเช่น ฉันมี Ivanov Ivan Ivanovich และเขาก็มีข้อมูลอยู่บ้าง แต่จะเป็นเพื่อนกับพวกเขาได้อย่างไร ฉันสามารถชี้ให้เห็นโดยตรงว่าสุนัขชื่อ "ทูซิก" เป็นของอีวานคนเดียวกัน แต่ถ้าเขาเปลี่ยนชื่อและแทนที่จะเป็นอีวานจะกลายเป็นเช่น Olya? จากนั้นปรากฎว่า Olya Ivanovna Ivanova ของเราจะไม่มีสุนัขอีกต่อไปและ Tuzik ของเราจะยังคงเป็นของ Ivan ที่ไม่มีอยู่จริง ฐานข้อมูลช่วยแก้ปัญหานี้ซึ่งทำให้ผู้ใช้แต่ละคนมีตัวระบุที่ไม่ซ้ำกัน (ID) และ Tuzik ของฉันเชื่อมโยงกับ ID นี้ซึ่งในความเป็นจริงเป็นเพียงหมายเลขซีเรียล ดังนั้นเจ้าของ tuzik จึงใช้ ID หมายเลข 2 และในช่วงเวลาหนึ่ง Ivan ก็อยู่ภายใต้ ID นี้ จากนั้น Olya ก็กลายเป็น ID เดียวกัน ปัญหาของมนุษย์และการเลี้ยงสัตว์ได้รับการแก้ไขในทางปฏิบัติ

ตัวอธิบายไฟล์

ปัญหาของไฟล์และโปรแกรมที่ทำงานกับไฟล์นี้ก็เหมือนกับสุนัขและมนุษย์ของเรา สมมติว่าฉันเปิดไฟล์ชื่อ ivan.txt และเริ่มเขียนคำว่า tuzik ลงไป แต่สามารถเขียนเฉพาะตัวอักษรตัวแรก "t" ลงในไฟล์ได้ และไฟล์นี้ถูกเปลี่ยนชื่อโดยใครบางคน เช่น เป็น olya.txt แต่ไฟล์นั้นเหมือนกันและฉันยังต้องการเขียนเอซของฉัน ทุกครั้งที่คุณเปิดไฟล์ด้วยการเรียกระบบ เปิด ในภาษาโปรแกรมใดๆ ก็ตาม ฉันได้รับ ID เฉพาะที่ชี้ไปยังไฟล์ ID นี้คือตัวอธิบายไฟล์ และมันไม่สำคัญเลยว่าใครจะทำอะไรต่อไปกับไฟล์นี้ สามารถลบได้ เปลี่ยนชื่อได้ เปลี่ยนเจ้าของหรือตัดสิทธิ์ในการอ่านและเขียน ฉันจะยังเข้าถึงมันได้ เพราะในขณะที่เปิดไฟล์ฉันมีสิทธิ์อ่านและ / หรือเขียนมันและฉันก็เริ่มทำงานกับมันได้ซึ่งหมายความว่าฉันต้องทำต่อไป

บน Linux ไลบรารี libc จะเปิดไฟล์คำอธิบาย 3 ไฟล์สำหรับแต่ละแอ็พพลิเคชันที่กำลังรันอยู่ (กระบวนการ) โดยมีหมายเลข 0,1,2 ข้อมูลเพิ่มเติมสามารถพบได้ในลิงค์ แมน สตูดิโอ и ผู้ชายโง่เขลา

  • ตัวอธิบายไฟล์ 0 เรียกว่า STDIN และเชื่อมโยงกับอินพุตของแอปพลิเคชัน
  • ตัวอธิบายไฟล์ 1 เรียกว่า STDOUT และใช้งานโดยแอปพลิเคชันเอาต์พุต เช่น คำสั่งพิมพ์
  • ตัวอธิบายไฟล์ 2 มีชื่อว่า STDERR และใช้งานโดยแอปพลิเคชันเพื่อแสดงข้อความแสดงข้อผิดพลาด

หากคุณเปิดไฟล์ใด ๆ เพื่ออ่านหรือเขียนในโปรแกรมของคุณ เป็นไปได้มากว่าคุณจะได้รับรหัสฟรีตัวแรกและจะเป็นหมายเลข 3

คุณสามารถดูรายการตัวอธิบายไฟล์สำหรับกระบวนการใดๆ ได้ หากคุณทราบ PID ของกระบวนการนั้น

ตัวอย่างเช่น ลองเปิดคอนโซลด้วย bash และดู PID ของกระบวนการของเรา

[user@localhost ]$ echo $$
15771

ในคอนโซลที่สอง ให้เรียกใช้

[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

คุณสามารถเพิกเฉยต่อตัวอธิบายไฟล์ที่มีหมายเลข 255 ภายในเฟรมเวิร์กของบทความนี้ได้อย่างปลอดภัย มันถูกเปิดขึ้นตามความต้องการของคุณโดยทุบตีเอง ไม่ใช่โดยไลบรารีที่เชื่อมโยง

ตอนนี้ไฟล์คำอธิบายทั้ง 3 ไฟล์เชื่อมโยงกับอุปกรณ์ปลายทางเทียม /dev/ptsแต่เรายังสามารถจัดการมันได้ เช่น รันในคอนโซลที่สอง

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

และในคอนโซลแรกเราจะเห็น

[user@localhost ]$ hello world

เปลี่ยนเส้นทางและท่อ

คุณสามารถเขียนทับไฟล์ descriptor ทั้ง 3 ไฟล์ได้อย่างง่ายดายในทุกกระบวนการ รวมถึงใน bash ตัวอย่างเช่น ผ่านไพพ์ (pipe) ที่เชื่อมต่อสองกระบวนการ ดูที่

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

คุณสามารถรันคำสั่งนี้ได้ด้วยตัวเอง สเตรส -f และดูสิ่งที่เกิดขึ้นข้างใน แต่ฉันจะพูดสั้นๆ

กระบวนการ bash พาเรนต์ของเราด้วย PID 15771 จะแยกวิเคราะห์คำสั่งของเราและเข้าใจว่าเราต้องการเรียกใช้กี่คำสั่ง ในกรณีของเรามี 2 คำสั่ง ได้แก่ cat และ sleep Bash รู้ว่าต้องสร้างโปรเซสลูกสองโปรเซสและรวมไว้ในไพพ์เดียว โดยรวมแล้ว bash จะต้องมี XNUMX โปรเซสลูกและหนึ่งไพพ์

ก่อนที่จะสร้างกระบวนการลูก bash เรียกใช้การเรียกระบบ ท่อ และรับตัวอธิบายไฟล์ใหม่บนบัฟเฟอร์ไพพ์ชั่วคราว แต่บัฟเฟอร์นี้ยังไม่ได้เชื่อมต่อกระบวนการลูกทั้งสองของเราแต่อย่างใด

สำหรับกระบวนการหลัก ดูเหมือนว่าไพพ์มีอยู่แล้ว แต่ยังไม่มีกระบวนการย่อย:

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

จากนั้นใช้การเรียกระบบ โคลน bash สร้างกระบวนการลูกสองกระบวนการ และสามกระบวนการของเราจะมีลักษณะดังนี้:

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

อย่าลืมว่าโคลนจะโคลนกระบวนการพร้อมกับตัวอธิบายไฟล์ทั้งหมด ดังนั้นพวกเขาจะเหมือนกันในกระบวนการพาเรนต์และในไฟล์ลูก งานของกระบวนการพาเรนต์ที่มี PID 15771 คือการตรวจสอบกระบวนการลูก ดังนั้นจึงเพียงแค่รอการตอบกลับจากลูก

ดังนั้นเขาไม่ต้องการไพพ์และปิดตัวอธิบายไฟล์ด้วยหมายเลข 3 และ 4

ในกระบวนการ bash child ครั้งแรกด้วย PID 9004 ระบบจะเรียก สำเนา2เปลี่ยนตัวอธิบายไฟล์ STDOUT หมายเลข 1 เป็นตัวอธิบายไฟล์ที่ชี้ไปที่ไพพ์ ในกรณีของเราคือหมายเลข 3 ดังนั้น ทุกสิ่งที่กระบวนการลูกตัวแรกที่มี PID 9004 เขียนไปยัง STDOUT จะตกลงไปในบัฟเฟอร์ไพพ์โดยอัตโนมัติ

ในกระบวนการย่อยที่สองด้วย PID 9005 ให้ bash dup2 ส่งไฟล์ไปยัง STDIN descriptor หมายเลข 0 ตอนนี้ทุกอย่างที่ bash ที่สองของเราที่มี PID 9005 จะอ่านจะถูกอ่านจากไพพ์

หลังจากนั้น ตัวอธิบายไฟล์ที่มีหมายเลข 3 และ 4 จะถูกปิดในกระบวนการลูกด้วย เนื่องจากไม่ได้ใช้งานอีกต่อไป

ฉันจงใจละเว้น file descriptor 255 มันถูกใช้ภายในโดย bash เองและจะถูกปิดในกระบวนการย่อยด้วย

ถัดไป ในกระบวนการลูกคนแรกด้วย PID 9004 bash จะเริ่มต้นด้วยการเรียกระบบ exec ไฟล์เรียกทำงานที่เราระบุไว้ในบรรทัดคำสั่ง ในกรณีของเราคือ /usr/bin/cat

ในกระบวนการย่อยที่สองด้วย PID 9005 นั้น bash จะรันโปรแกรมเรียกทำงานตัวที่สองที่เราระบุไว้ ในกรณีของเรา /usr/bin/sleep

การเรียกระบบของ exec จะไม่ปิดตัวอธิบายไฟล์ เว้นแต่จะมีการเปิดด้วยแฟล็ก O_CLOEXEC ในขณะที่เรียกใช้การเรียกแบบเปิด ในกรณีของเรา หลังจากเรียกใช้ไฟล์ปฏิบัติการ ตัวอธิบายไฟล์ปัจจุบันทั้งหมดจะถูกบันทึก

ตรวจสอบในคอนโซล:

[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

อย่างที่คุณเห็น หมายเลขเฉพาะของไปป์ของเราจะเท่ากันในทั้งสองกระบวนการ ดังนั้นเราจึงมีการเชื่อมต่อระหว่างสองกระบวนการที่แตกต่างกันกับพาเรนต์เดียวกัน

สำหรับผู้ที่ไม่คุ้นเคยกับการเรียกระบบที่ bash ใช้ ผมขอแนะนำให้รันคำสั่งผ่าน strace และดูว่าเกิดอะไรขึ้นภายใน เช่น

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

กลับไปที่ปัญหาของเราเกี่ยวกับพื้นที่ดิสก์ไม่เพียงพอและพยายามบันทึกข้อมูลโดยไม่ต้องเริ่มกระบวนการใหม่ มาเขียนโปรแกรมขนาดเล็กที่จะเขียนลงดิสก์ประมาณ 1 เมกะไบต์ต่อวินาที ยิ่งไปกว่านั้น หากเราไม่สามารถเขียนข้อมูลลงดิสก์ได้ด้วยเหตุผลบางประการ เราจะเพิกเฉยต่อสิ่งนี้และพยายามเขียนข้อมูลอีกครั้งในไม่กี่วินาที ในตัวอย่าง ฉันใช้ Python คุณสามารถใช้ภาษาโปรแกรมอื่นก็ได้

[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

เรียกใช้โปรแกรมและดูที่ตัวอธิบายไฟล์

[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

อย่างที่คุณเห็น เรามีตัวอธิบายไฟล์มาตรฐาน 3 ตัวและอีกตัวที่เราเปิดไว้ ตรวจสอบขนาดไฟล์:

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

ข้อมูลถูกเขียน เราพยายามเปลี่ยนสิทธิ์ในไฟล์:

[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

เราเห็นว่าข้อมูลยังคงถูกเขียน แม้ว่าผู้ใช้ของเราไม่มีสิทธิ์เขียนลงในไฟล์ มาลองลบกัน:

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

ข้อมูลถูกเขียนที่ไหน? และพวกเขาเขียนเลยเหรอ? เราตรวจสอบ:

[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)

ใช่ ตัวอธิบายไฟล์ของเรายังคงมีอยู่ และเราสามารถทำงานกับตัวอธิบายไฟล์นี้เหมือนไฟล์เก่าของเรา เราสามารถอ่าน ล้างข้อมูล และคัดลอกได้

ดูขนาดไฟล์:

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

ขนาดไฟล์คือ 19923457 กำลังพยายามล้างไฟล์:

[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

อย่างที่คุณเห็น ขนาดไฟล์เพิ่มขึ้นเท่านั้น และลำตัวของเราไม่ทำงาน มาดูเอกสารเกี่ยวกับการเรียกระบบ เปิด. หากเราใช้แฟล็ก O_APPEND เมื่อเปิดไฟล์ จากนั้นในการเขียนแต่ละครั้ง ระบบปฏิบัติการจะตรวจสอบขนาดไฟล์และเขียนข้อมูลไปยังส่วนท้ายสุดของไฟล์ และทำแบบอะตอม สิ่งนี้ทำให้หลายเธรดหรือกระบวนการเขียนลงในไฟล์เดียวกัน แต่ในรหัสของเรา เราไม่ได้ใช้แฟล็กนี้ เราจะเห็นขนาดไฟล์ที่แตกต่างกันใน lsof หลัง trunk ก็ต่อเมื่อเราเปิดไฟล์เพื่อเขียน ซึ่งหมายความว่าในโค้ดของเรา แทนที่จะเป็น

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

เราต้องใส่

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

ตรวจสอบด้วยธง "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

และมีธง "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

การเขียนโปรแกรมกระบวนการที่กำลังทำงานอยู่

บ่อยครั้ง เมื่อสร้างและทดสอบโปรแกรม โปรแกรมเมอร์จะใช้ดีบักเกอร์ (เช่น GDB) หรือระดับต่างๆ ของการบันทึกในแอปพลิเคชัน ลีนุกซ์ให้ความสามารถในการเขียนและเปลี่ยนแปลงโปรแกรมที่รันอยู่แล้ว เช่น การเปลี่ยนค่าของตัวแปร การตั้งค่าเบรกพอยต์ และอื่นๆ เป็นต้น

กลับไปที่คำถามเดิมเกี่ยวกับการไม่มีพื้นที่ว่างในดิสก์ในการเขียนไฟล์ ลองจำลองปัญหากัน

มาสร้างไฟล์สำหรับพาร์ติชันของเรา ซึ่งเราจะเมานต์เป็นไดรฟ์แยกต่างหาก:

[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 ~]$

มาสร้างระบบไฟล์กันเถอะ:

[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 ~]$

มาติดตั้งระบบไฟล์กัน:

[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

สร้างไดเรกทอรีกับเจ้าของของเรา:

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

เปิดไฟล์เพื่อเขียนในโปรแกรมของเราเท่านั้น:

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

ปล่อย

[user@localhost ]$ python openforwrite.py 

รอไม่กี่วินาที

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

ดังนั้นเราจึงพบปัญหาที่อธิบายไว้ในตอนต้นของบทความนี้ พื้นที่ว่าง 0 ถูกครอบครอง 100%

เราจำได้ว่าตามเงื่อนไขของปัญหา เรากำลังพยายามบันทึกข้อมูลที่สำคัญมากที่ไม่สามารถสูญหายได้ และในการทำเช่นนั้น เราจำเป็นต้องแก้ไขบริการโดยไม่ต้องเริ่มกระบวนการใหม่

สมมติว่าเรายังมีพื้นที่ว่างในดิสก์ แต่ในพาร์ติชันอื่น เช่น ใน / โฮม

มาลอง "reprogram on the fly" รหัสของเรากัน

เราดูที่ PID ของกระบวนการของเรา ซึ่งกินพื้นที่ดิสก์ทั้งหมด:

[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

การเชื่อมต่อกับกระบวนการด้วย gdb

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

เราดูที่ตัวอธิบายไฟล์ที่เปิดอยู่:

(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

เราดูข้อมูลเกี่ยวกับตัวอธิบายไฟล์ด้วยหมายเลข 3 ที่เราสนใจ

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

โปรดจำไว้ว่าการเรียกระบบ Python ทำอะไร (ดูด้านบนที่เรารัน strace และพบการโทรที่เปิดอยู่) ในขณะที่ประมวลผลโค้ดของเราเพื่อเปิดไฟล์ เราทำเช่นเดียวกันในนามของกระบวนการของเรา แต่เราต้องการ O_WRONLY|O_CREAT| บิต O_TRUNC แทนที่ด้วยค่าตัวเลข ในการทำเช่นนี้ ให้เปิดเคอร์เนลซอร์ส ตัวอย่างเช่น ที่นี่ และดูว่าแฟล็กใดมีหน้าที่รับผิดชอบอะไรบ้าง

#กำหนด O_WRONLY 00000001
#กำหนด O_CREAT 00000100
#กำหนด O_TRUNC 00001000

เรารวมค่าทั้งหมดเข้าด้วยกันเราจะได้ 00001101

เรียกใช้การโทรของเราจาก gdb

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

ดังนั้นเราจึงมีตัวอธิบายไฟล์ใหม่ที่มีหมายเลข 4 และไฟล์เปิดใหม่ในพาร์ติชันอื่น ตรวจสอบ:

(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

เราจำตัวอย่างด้วยไพพ์ได้ - วิธีการที่ bash เปลี่ยนตัวอธิบายไฟล์และได้เรียนรู้การเรียกระบบ dup2 แล้ว

พยายามแทนที่ตัวอธิบายไฟล์หนึ่งไฟล์ด้วยไฟล์อื่น

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

เราตรวจสอบ:

(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

ปิดตัวอธิบายไฟล์ 4 เนื่องจากเราไม่ต้องการ:

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

และออกจาก 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

ตรวจสอบไฟล์ใหม่:

[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

อย่างที่คุณเห็น ข้อมูลถูกเขียนลงในไฟล์ใหม่ เราตรวจสอบไฟล์เก่า:

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

ข้อมูลไม่สูญหาย แอปพลิเคชันทำงาน บันทึกถูกเขียนไปยังตำแหน่งใหม่

มาทำให้สิ่งต่าง ๆ ยากขึ้นเล็กน้อย

ลองนึกภาพว่าข้อมูลมีความสำคัญสำหรับเรา แต่เราไม่มีพื้นที่ดิสก์ในพาร์ติชันใด ๆ และเราไม่สามารถเชื่อมต่อดิสก์ได้

สิ่งที่เราทำได้คือเปลี่ยนเส้นทางข้อมูลของเราไปที่ใดที่หนึ่ง เช่น ไปยังไพพ์ และเปลี่ยนเส้นทางข้อมูลจากไพพ์ไปยังเครือข่ายผ่านโปรแกรมบางอย่าง เช่น netcat
เราสามารถสร้างเนมไพพ์ได้ด้วยคำสั่ง mkfifo มันจะสร้างไฟล์หลอกบนระบบไฟล์แม้ว่าจะไม่มีพื้นที่ว่างก็ตาม

รีสตาร์ทแอปพลิเคชันและตรวจสอบ:

[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

ไม่มีพื้นที่ว่างในดิสก์ แต่เราสร้างไปป์ที่มีชื่อได้สำเร็จ:

[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

ตอนนี้เราจำเป็นต้องห่อข้อมูลทั้งหมดที่เข้ามาในไพพ์นี้ไปยังเซิร์ฟเวอร์อื่นผ่านเครือข่าย netcat เดียวกันนี้เหมาะสำหรับสิ่งนี้

บนเซิร์ฟเวอร์ remote-server.example.com ให้รัน

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

บนเซิร์ฟเวอร์ปัญหาของเรา ให้ทำงานในเทอร์มินัลแยกต่างหาก

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

ตอนนี้ข้อมูลทั้งหมดที่เข้าสู่ไพพ์จะไปที่ stdin ใน netcat โดยอัตโนมัติ ซึ่งจะส่งไปยังเครือข่ายที่พอร์ต 7777

สิ่งที่เราต้องทำคือเริ่มเขียนข้อมูลของเราไปยังไปป์ที่มีชื่อนี้

เรามีแอปพลิเคชันที่ทำงานอยู่:

[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

จากค่าสถานะทั้งหมด เราต้องการเพียง O_WRONLY เนื่องจากมีไฟล์อยู่แล้วและเราไม่จำเป็นต้องล้างข้อมูล

[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

ตรวจสอบเซิร์ฟเวอร์ระยะไกล remote-server.example.com

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

ข้อมูลกำลังมา เราตรวจสอบเซิร์ฟเวอร์ที่มีปัญหา

[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

ข้อมูลถูกบันทึก ปัญหาได้รับการแก้ไข

ฉันใช้โอกาสนี้ทักทายเพื่อนร่วมงานจาก Degiro
ฟังพอดคาสต์ Radio-T

ดีต่อทุกคน

ในการทำการบ้าน ฉันขอเสนอให้คิดถึงสิ่งที่จะอยู่ใน file descriptor ของ cat และกระบวนการ sleep หากคุณรันคำสั่งต่อไปนี้:

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

ที่มา: will.com

เพิ่มความคิดเห็น