ครั้งหนึ่ง ในการสัมภาษณ์ ฉันถูกถามคุณจะทำอย่างไรหากคุณพบบริการที่เสียเนื่องจากดิสก์มีที่ว่างไม่เพียงพอ
แน่นอน ฉันตอบว่าฉันจะดูว่าสถานที่นี้ทำอะไร และถ้าเป็นไปได้ ฉันจะทำความสะอาดสถานที่นั้น
จากนั้นผู้สัมภาษณ์ถามว่าจะทำอย่างไรหากไม่มีพื้นที่ว่างบนพาร์ติชัน แต่คุณไม่เห็นไฟล์ที่จะใช้พื้นที่ทั้งหมดด้วย
สำหรับสิ่งนี้ ฉันบอกว่าคุณสามารถดูตัวอธิบายไฟล์แบบเปิดได้เสมอ เช่น ด้วยคำสั่ง 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 แต่ไฟล์นั้นเหมือนกันและฉันยังต้องการเขียนเอซของฉัน ทุกครั้งที่คุณเปิดไฟล์ด้วยการเรียกระบบ
บน 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 ไฟล์เชื่อมโยงกับอุปกรณ์ปลายทางเทียม
[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
จากนั้นใช้การเรียกระบบ
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 ระบบจะเรียก
ในกระบวนการย่อยที่สองด้วย PID 9005 ให้ bash dup2 ส่งไฟล์ไปยัง STDIN descriptor หมายเลข 0 ตอนนี้ทุกอย่างที่ bash ที่สองของเราที่มี PID 9005 จะอ่านจะถูกอ่านจากไพพ์
หลังจากนั้น ตัวอธิบายไฟล์ที่มีหมายเลข 3 และ 4 จะถูกปิดในกระบวนการลูกด้วย เนื่องจากไม่ได้ใช้งานอีกต่อไป
ฉันจงใจละเว้น file descriptor 255 มันถูกใช้ภายในโดย bash เองและจะถูกปิดในกระบวนการย่อยด้วย
ถัดไป ในกระบวนการลูกคนแรกด้วย PID 9004 bash จะเริ่มต้นด้วยการเรียกระบบ
ในกระบวนการย่อยที่สองด้วย 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
อย่างที่คุณเห็น ขนาดไฟล์เพิ่มขึ้นเท่านั้น และลำตัวของเราไม่ทำงาน มาดูเอกสารเกี่ยวกับการเรียกระบบ
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