สคริปต์นี้ทำให้แน่ใจได้อย่างไรว่ามีเพียงตัวเดียวเท่านั้นที่กำลังทำงานอยู่


22

ในวันที่ 19 ส.ค. 2556 Randal L. Schwartzโพสต์สคริปต์เชลล์นี้ซึ่งมีจุดประสงค์เพื่อให้แน่ใจว่าบน Linux "ว่ามีเพียงหนึ่งตัวอย่างของสคริปต์ [the] ที่กำลังทำงานโดยไม่มีเงื่อนไขการแข่งขันหรือต้องล้างไฟล์ล็อค":

#!/bin/sh
# randal_l_schwartz_001.sh
(
    if ! flock -n -x 0
    then
        echo "$$ cannot get flock"
        exit 0
    fi
    echo "$$ start"
    sleep 10 # for testing.  put the real task here
    echo "$$ end"
) < $0

ดูเหมือนว่าจะทำงานตามที่โฆษณาไว้:

$ ./randal_l_schwartz_001.sh & ./randal_l_schwartz_001.sh
[1] 11863
11863 start
11864 cannot get flock
$ 11863 end

[1]+  Done                    ./randal_l_schwartz_001.sh
$

นี่คือสิ่งที่ฉันเข้าใจ:

  • สคริปต์เปลี่ยนเส้นทาง ( <) คัดลอกเนื้อหาของตัวเอง (เช่นจาก$0) ไปยัง STDIN (เช่น file descriptor 0) ของ subshell
  • ภายใน subshell ที่พยายามสคริปต์ที่จะได้รับไม่ปิดกั้น, ล็อคพิเศษ ( flock -n -x) 0บนไฟล์อธิบาย
    • หากความพยายามนั้นล้มเหลว subshell จะออก (และสคริปต์หลักจะทำเช่นนั้นเนื่องจากไม่มีสิ่งอื่นใดให้ทำ)
    • หากความพยายามแทนสำเร็จ subshell จะรันงานที่ต้องการ

นี่คือคำถามของฉัน:

  • ทำไมสคริปต์ต้องเปลี่ยนเส้นทางไปยังตัวอธิบายไฟล์ที่สืบทอดโดย subshell สำเนาของเนื้อหาของตัวเองแทนที่จะพูดเนื้อหาของไฟล์อื่น ๆ ? (ฉันพยายามเปลี่ยนเส้นทางจากไฟล์ที่แตกต่างกันและเรียกใช้อีกครั้งตามลำดับและการดำเนินการเปลี่ยนแปลง: งานที่ไม่ได้รับพื้นหลังได้รับการล็อคก่อนแบ็คกราวน์หนึ่งดังนั้นอาจใช้เนื้อหาของไฟล์เองเพื่อหลีกเลี่ยงสภาพการแข่งขัน
  • ทำไมสคริปต์ต้องเปลี่ยนเส้นทางไปยังตัวอธิบายไฟล์ที่สืบทอดโดย subshell ซึ่งเป็นสำเนาของเนื้อหาของไฟล์หรือไม่?
  • ทำไมถือล็อคพิเศษที่บ่งไฟล์0ในหนึ่งเปลือกป้องกันไม่ให้สำเนาของสคริปต์เดียวกันวิ่งในเปลือกที่แตกต่างกันจากการล็อคพิเศษในไฟล์อธิบาย0? อย่าเปลือกหอยมีของตัวเองสำเนาแยกของพวกเขาของอธิบายไฟล์มาตรฐาน ( 0, 1และ2เช่น STDIN, STDOUT และ STDERR)?

กระบวนการทดสอบที่แน่นอนของคุณคืออะไรเมื่อคุณลองการทดสอบเพื่อเปลี่ยนเส้นทางจากไฟล์อื่น
Freiheit

1
ฉันคิดว่าคุณสามารถอ้างอิงลิงค์นี้ stackoverflow.com/questions/185451/…
Deb Paikar

คำตอบ:


22

ทำไมสคริปต์ต้องเปลี่ยนเส้นทางไปยังตัวอธิบายไฟล์ที่สืบทอดโดย subshell สำเนาของเนื้อหาของตัวเองแทนที่จะพูดเนื้อหาของไฟล์อื่น ๆ ?

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

หากสคริปต์ถูกเรียกผ่าน symlink การล็อกจะอยู่ที่ไฟล์จริงไม่ใช่ลิงก์

(แน่นอนถ้าบางกระบวนการเรียกใช้สคริปต์และให้ค่าที่ทำขึ้นเป็นอาร์กิวเมนต์ zeroth แทนที่จะเป็นเส้นทางจริงการแบ่งนี้จะเกิดขึ้น แต่ก็ไม่ค่อยเสร็จ)

(ฉันลองใช้ไฟล์อื่นและเรียกใช้อีกครั้งตามที่กล่าวข้างต้นและลำดับการดำเนินการเปลี่ยนไป)

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

ทำไมสคริปต์ต้องเปลี่ยนเส้นทางไปยังตัวอธิบายไฟล์ที่สืบทอดโดย subshell ซึ่งเป็นสำเนาของเนื้อหาของไฟล์หรือไม่?

ดูเหมือนว่าเป็นเช่นนั้นเชลล์เองก็เก็บสำเนาของคำอธิบายไฟล์ที่ถือล็อคไว้แทนที่จะเป็นเพียงflockยูทิลิตี้ถือมัน การล็อคที่ทำด้วยflock(2)จะถูกปล่อยเมื่อตัวบ่งชี้ไฟล์ที่มีมันถูกปิด

flockมีสองโหมดไม่ว่าจะเป็นการล็อกตามชื่อไฟล์และรันคำสั่งภายนอก (ซึ่งในกรณีนี้flockจะต้องมีตัวอธิบายไฟล์แบบเปิดที่จำเป็น) หรือใช้ตัวอธิบายไฟล์จากภายนอกดังนั้นกระบวนการภายนอกจึงมีความรับผิดชอบในการเก็บ มัน.

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

เหตุใดการถือการล็อกแบบเอกสิทธิ์เฉพาะบุคคลใน file descriptor 0 ในเชลล์หนึ่งจึงป้องกันการคัดลอกสคริปต์เดียวกันทำงานในเชลล์ที่แตกต่างจากการล็อคแบบเอกสิทธิ์เฉพาะบุคคลใน file descriptor 0 เชลล์ไม่ได้มีตัวอธิบายไฟล์มาตรฐานแยกต่างหาก (0, 1 และ 2 ของตัวเองเช่น STDIN, STDOUT และ STDERR)

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


ฉันคิดว่าคุณควรจะสามารถทำได้โดยไม่ต้อง subshell โดยใช้execเพื่อเปิดหมายเลขอ้างอิงไปยังไฟล์ล็อค:

$ cat lock.sh
#!/bin/sh

exec 9< "$0"

if ! flock -n -x 9; then
    echo "$$/$1 cannot get flock" 
    exit 0
fi

echo "$$/$1 got the lock"
sleep 2
echo "$$/$1 exit"

$ ./lock.sh bg & ./lock.sh fg ; wait; echo
[1] 11362
11363/fg got the lock
11362/bg cannot get flock
11363/fg exit
[1]+  Done                    ./lock.sh bg

1
การใช้{ }แทนที่จะ( )ทำงานได้ดีและหลีกเลี่ยงการใช้ subshell
. ..

ลงต่อไปในการแสดงความคิดเห็นบน G + execโพสต์บางคนยังมีปัญหาประมาณวิธีการเดียวกันโดยใช้
David Z

@R .. โอ้วแน่ใจ แต่มันก็ยังน่าเกลียดด้วยเครื่องหมายปีกกาเสริมรอบสคริปต์จริง
ilkkachu

9

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

  1. เปิดไฟล์ที่ล็อคการเชื่อมต่อ ("ไฟล์ล็อค")
  2. ใช้การล็อคไฟล์ล็อค
  3. ทำสิ่งต่างๆ
  4. ปิดไฟล์ล็อค การทำเช่นนี้เป็นการปลดล็อคที่แนบมากับคำอธิบายไฟล์ที่สร้างขึ้นโดยการเปิดไฟล์

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

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

ที่จุดเริ่มต้นเมื่อเปิดไฟล์คำอธิบายไฟล์จะมีตัวอธิบายไฟล์เดียว แต่สามารถสร้าง descriptor ได้มากกว่าโดยสร้าง descriptor ตัวอื่น ( dupตระกูลของการเรียกระบบ) หรือโดยการฟอร์ก subprocess (หลังจากนั้นทั้งพาเรนต์และ เด็กสามารถเข้าถึงคำอธิบายไฟล์เดียวกัน) ตัวบ่งชี้ไฟล์สามารถปิดได้อย่างชัดเจนหรือเมื่อกระบวนการที่กำลังจะตาย เมื่อตัวอธิบายไฟล์ล่าสุดที่แนบมากับไฟล์ถูกปิดคำอธิบายไฟล์จะถูกปิด

ต่อไปนี้เป็นลำดับการดำเนินการด้านบนที่มีผลต่อคำอธิบายไฟล์

  1. การเปลี่ยนเส้นทาง<$0เปิดไฟล์สคริปต์ใน subshell สร้างคำอธิบายไฟล์ ณ จุดนี้จะมีไฟล์ descriptor ตัวเดียวแนบอยู่กับคำอธิบาย: descriptor number 0 ใน subshell
  2. subshell จะเรียกใช้flockและรอให้มันออก ในขณะที่ฝูงกำลังทำงานมี descriptor สองตัวที่แนบมากับคำอธิบาย: หมายเลข 0 ใน subshell และหมายเลข 0 ในกระบวนการ flock เมื่อฝูงใช้เวลาล็อคที่ตั้งค่าคุณสมบัติของคำอธิบายไฟล์ หากรายละเอียดไฟล์อื่นมีการล็อคไฟล์อยู่ฝูงจะไม่สามารถล็อคได้เนื่องจากเป็นการล็อคแบบเอกสิทธิ์
  3. ชั้นย่อยทำสิ่งต่างๆ เนื่องจากมันยังคงมี file descriptor ที่เปิดอยู่บนคำอธิบายด้วยการล็อคคำอธิบายนั้นจะคงอยู่และมันจะเก็บการล็อคไว้เพราะไม่มีใครเอาล็อคออกมา
  4. เชลล์ย่อยจะตายที่วงเล็บปิด นี่เป็นการปิดตัวอธิบายไฟล์สุดท้ายบนคำอธิบายไฟล์ที่มีการล็อคดังนั้นการล็อคจะหายไป ณ จุดนี้

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

fd = open($0)
flock(fd, LOCK_EX)
do stuff
close(fd)

คุณสามารถได้รับลำดับการดำเนินการที่เหมือนกันในเชลล์ถ้าคุณทำการเปลี่ยนเส้นทางด้วยexecbuiltin

exec <$0
flock -n -x 0
# do stuff
exec <&-

สคริปต์สามารถใช้ file descriptor อื่นหากต้องการเข้าถึงอินพุตมาตรฐานดั้งเดิมต่อไป

exec 3<$0
flock -n -x 0
# do stuff
exec 3<&-

หรือด้วย subshell:

(
  flock -n -x 3
  # do stuff
) 3<$0

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

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


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

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

1
เพียงแค่ FYI ลิงก์ใน "คำอธิบายไฟล์" ชี้ไปที่หน้าดัชนีสเปคของกลุ่มเปิดแทนที่จะเป็นคำอธิบายเฉพาะของแนวคิดซึ่งเป็นสิ่งที่ฉันคิดว่าคุณตั้งใจจะทำ หรือคุณสามารถเชื่อมโยงคำตอบเก่า ๆ ของคุณได้ที่นี่เช่นกันunix.stackexchange.com/a/195164/85039
Sergiy Kolodyazhnyy

5

ไฟล์ที่ใช้สำหรับล็อคไม่สำคัญสคริปต์ใช้$0เนื่องจากเป็นไฟล์ที่มีอยู่แล้ว

ลำดับที่จะได้รับล็อคจะสุ่มมากหรือน้อยขึ้นอยู่กับว่าเครื่องของคุณเร็วแค่ไหนที่จะสามารถเริ่มงานทั้งสองได้

คุณสามารถใช้ไฟล์ descriptor ใด ๆ ก็ได้โดยไม่จำเป็นต้องเป็น 0 ล็อคจะถูกเก็บไว้ในไฟล์ที่เปิดไปยังไฟล์ descriptor ไม่ใช่ descriptor นั้นเอง

( flock -x 9 || exit 1
  echo 'Locking for 5 secs'; sleep 5; echo 'Done' ) 9>/tmp/lock &
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.