Làm thế nào để kịch bản này đảm bảo rằng chỉ có một phiên bản của chính nó đang chạy?


22

Vào ngày 19 tháng 8 năm 2013, Randal L. Schwartz đã đăng tập lệnh shell này , nhằm đảm bảo, trên Linux, "chỉ có một phiên bản của tập lệnh [đang chạy], không có điều kiện chạy đua hoặc phải dọn sạch các tệp khóa":

#!/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

Nó dường như hoạt động như quảng cáo:

$ ./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
$

Đây là những gì tôi hiểu:

  • Kịch bản chuyển hướng ( <) một bản sao nội dung của chính nó (tức là từ $0) sang STDIN (tức là mô tả tệp 0) của một khung con.
  • Trong lớp con, tập lệnh cố gắng để có được một khóa độc quyền, không khóa ( flock -n -x) trên bộ mô tả tệp 0.
    • Nếu lần thử đó thất bại, lớp con thoát ra (và kịch bản chính cũng vậy, vì nó không có gì khác để làm).
    • Nếu nỗ lực thay vào đó thành công, lớp con sẽ chạy tác vụ mong muốn.

Đây là câu hỏi của tôi:

  • Tại sao tập lệnh cần chuyển hướng, đến một bộ mô tả tệp được kế thừa bởi lớp con, một bản sao nội dung của chính nó chứ không phải là nội dung của một số tệp khác? .
  • Tại sao tập lệnh cần phải chuyển hướng, đến một bộ mô tả tệp được kế thừa bởi lớp con, một bản sao nội dung của tệp, dù sao?
  • Tại sao việc giữ một khóa độc quyền trên bộ mô tả tệp 0trong một trình bao ngăn chặn một bản sao của cùng một tập lệnh, chạy trong một trình bao khác, để có được một khóa độc quyền trên bộ mô tả tệp 0? Đừng vỏ có riêng, bản riêng của họ về file descriptor chuẩn ( 0, 1, và 2, tức là STDIN, STDOUT, và stderr)?

Quá trình kiểm tra chính xác của bạn là gì khi bạn thử trải nghiệm để chuyển hướng từ một tệp khác?
Freiheit

1
Tôi nghĩ rằng bạn có thể tham khảo liên kết này. stackoverflow.com/questions/185451/
Deb Paikar

Câu trả lời:


22

Tại sao tập lệnh cần phải chuyển hướng, đến một bộ mô tả tệp được kế thừa bởi lớp con, một bản sao nội dung của chính nó chứ không phải là nội dung của một số tệp khác?

Bạn có thể sử dụng bất kỳ tệp nào, miễn là tất cả các bản sao của tập lệnh sử dụng cùng một tệp. $0Chỉ sử dụng liên kết khóa với chính tập lệnh: Nếu bạn sao chép tập lệnh và sửa đổi tập lệnh này cho một số mục đích sử dụng khác, bạn không cần phải đưa ra một tên mới cho tập tin khóa. Điều này là thuận tiện.

Nếu tập lệnh được gọi thông qua một liên kết tượng trưng, ​​khóa nằm trên tệp thực tế chứ không phải liên kết.

(Tất nhiên, nếu một số quá trình chạy tập lệnh và cung cấp cho nó một giá trị tạo thành như đối số zeroth thay vì đường dẫn thực tế, thì điều này sẽ phá vỡ. Nhưng điều đó hiếm khi được thực hiện.)

(Tôi đã thử sử dụng một tệp khác và chạy lại như trên và thứ tự thực hiện đã thay đổi)

Bạn có chắc chắn đó là do tập tin được sử dụng, và không chỉ là biến thể ngẫu nhiên? Như với một đường ống dẫn, thực sự không có cách nào để chắc chắn các lệnh được chạy theo thứ tự nào cmd1 & cmd. Nó chủ yếu phụ thuộc vào bộ lập lịch hệ điều hành. Tôi nhận được biến thể ngẫu nhiên trên hệ thống của tôi.

Tại sao tập lệnh cần phải chuyển hướng, đến một bộ mô tả tệp được kế thừa bởi lớp con, một bản sao nội dung của tệp, dù sao?

Có vẻ như đó là do chính vỏ giữ một bản sao của mô tả tệp giữ khóa, thay vì chỉ flocktiện ích giữ nó. Một khóa được thực hiện với flock(2)được phát hành khi các mô tả tập tin có nó được đóng lại.

flockcó hai chế độ, để lấy khóa dựa trên tên tệp và chạy lệnh bên ngoài (trong trường hợp đó flockgiữ bộ mô tả tệp mở cần thiết) hoặc để mô tả tệp từ bên ngoài, do đó, một quy trình bên ngoài có trách nhiệm giữ nó

Lưu ý rằng nội dung của tệp không liên quan ở đây và không có bản sao nào được thực hiện. Việc chuyển hướng đến lớp con không sao chép bất kỳ dữ liệu nào xung quanh, nó chỉ mở một tay cầm cho tệp.

Tại sao việc giữ một khóa độc quyền trên bộ mô tả tệp 0 trong một trình bao ngăn chặn một bản sao của cùng một tập lệnh, chạy trong một trình bao khác, để có được một khóa độc quyền trên bộ mô tả tệp 0? Các shell không có bản sao riêng của các bộ mô tả tệp tiêu chuẩn (0, 1 và 2, ví dụ STDIN, STDOUT và STDERR)?

Có, nhưng khóa nằm trên tệp chứ không phải mô tả tệp. Mỗi lần chỉ có một tệp được mở có thể giữ khóa.


Tôi nghĩ bạn sẽ có thể làm điều tương tự mà không cần subshell, bằng cách sử dụng execđể mở một tay cầm cho tệp khóa:

$ 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
Sử dụng { }thay vì ( )cũng sẽ làm việc và tránh subshell.
R ..

Hơn nữa trong các bình luận trên bài đăng G +, một người nào đó cũng đề xuất phương pháp tương tự sử dụng exec.
David Z

@R .., ồ, chắc chắn rồi. Nhưng nó vẫn xấu với các dấu ngoặc nhọn xung quanh kịch bản thực tế.
ilkkachu

9

Một khóa tập tin được gắn vào một tập tin, thông qua một mô tả tập tin . Ở mức cao, chuỗi các hoạt động trong một phiên bản của tập lệnh là:

  1. Mở tập tin mà khóa được đính kèm (tập tin khóa tập tin).
  2. Lấy một khóa trên tập tin khóa.
  3. Làm công cụ.
  4. Đóng tệp khóa. Thao tác này sẽ giải phóng khóa được đính kèm với mô tả tệp được tạo bằng cách mở tệp.

Giữ khóa ngăn chặn một bản sao khác của cùng một tập lệnh để chạy vì đó là những gì khóa làm. Miễn là một khóa độc quyền trên một tệp tồn tại ở đâu đó trên hệ thống, không thể tạo phiên bản thứ hai của cùng một khóa, thậm chí thông qua một mô tả tệp khác.

Mở một tập tin tạo ra một mô tả tập tin . Đây là một đối tượng kernel không có nhiều khả năng hiển thị trực tiếp trong các giao diện lập trình. Bạn truy cập một mô tả tệp gián tiếp thông qua các mô tả tệp, nhưng thông thường bạn nghĩ về nó như là truy cập tệp (đọc hoặc viết nội dung hoặc siêu dữ liệu của nó). Khóa là một trong những thuộc tính là một thuộc tính cho mô tả tệp chứ không phải là tệp hoặc mô tả.

Lúc đầu, khi một tệp được mở, mô tả tệp có một mô tả tệp duy nhất, nhưng có thể tạo nhiều mô tả hơn bằng cách tạo một mô tả khác ( duphọ gọi hệ thống) hoặc bằng cách tạo một quy trình con (sau đó cả cha và mẹ trẻ em có quyền truy cập vào cùng một mô tả tập tin). Một bộ mô tả tập tin có thể được đóng lại một cách rõ ràng hoặc khi quá trình nó chết. Khi bộ mô tả tệp cuối cùng được đính kèm vào một tệp được đóng, mô tả tệp được đóng lại.

Đây là cách trình tự các hoạt động ở trên ảnh hưởng đến mô tả tập tin.

  1. Chuyển hướng <$0mở tệp tập lệnh trong lớp con, tạo mô tả tệp. Tại thời điểm này, có một mô tả tập tin duy nhất được đính kèm với mô tả: mô tả số 0 trong khung con.
  2. Subshell gọi flockvà chờ cho nó thoát. Trong khi đàn đang chạy, có hai mô tả được đính kèm với mô tả: số 0 trong lớp con và số 0 trong quy trình đàn. Khi đàn có khóa, nó đặt thuộc tính của mô tả tệp. Nếu một mô tả tệp khác đã có khóa trên tệp, đàn không thể lấy khóa, vì đó là khóa độc quyền.
  3. Subshell làm công cụ. Vì nó vẫn có một bộ mô tả tệp mở trên mô tả với khóa, mô tả đó vẫn tồn tại và nó giữ khóa vì không ai tháo khóa.
  4. Subshell chết ở dấu ngoặc đơn đóng. Điều này sẽ đóng bộ mô tả tệp cuối cùng trên mô tả tệp có khóa, vì vậy khóa sẽ biến mất vào thời điểm này.

Lý do tập lệnh sử dụng chuyển hướng từ $0đó là chuyển hướng là cách duy nhất để mở tệp trong trình bao và giữ cho chuyển hướng hoạt động là cách duy nhất để giữ mô tả tệp mở. Subshell không bao giờ đọc từ đầu vào tiêu chuẩn của nó, nó chỉ cần giữ cho nó mở. Trong ngôn ngữ cho phép truy cập trực tiếp vào cuộc gọi mở và đóng, bạn có thể sử dụng

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

Bạn thực sự có thể có được chuỗi hoạt động tương tự trong trình bao nếu bạn thực hiện chuyển hướng với execnội trang.

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

Kịch bản có thể sử dụng một bộ mô tả tệp khác nếu nó muốn tiếp tục truy cập vào đầu vào tiêu chuẩn ban đầu.

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

hoặc với một nhánh con:

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

Khóa không phải là trên tập tin kịch bản. Nó có thể nằm trên bất kỳ tệp nào có thể được mở để đọc (vì vậy nó phải tồn tại, nó phải là một loại tệp có thể đọc được như tệp thông thường hoặc ống có tên nhưng không phải là thư mục và quy trình tập lệnh phải có sự cho phép để đọc nó). Tệp tập lệnh có lợi thế là nó được đảm bảo có mặt và có thể đọc được (ngoại trừ trong trường hợp cạnh bị xóa bên ngoài giữa thời gian tập lệnh được gọi và thời gian tập lệnh được <$0chuyển hướng).

Miễn là flockthành công và tập lệnh nằm trên một hệ thống tệp mà khóa không bị lỗi (một số hệ thống tệp mạng như NFS có thể bị lỗi), tôi không thấy cách sử dụng tệp khóa khác có thể cho phép điều kiện cuộc đua. Tôi nghi ngờ một lỗi thao tác trên phần của bạn.


Có một tình trạng chủng tộc: bạn không thể kiểm soát thể hiện của kịch bản được khóa. May mắn thay, cho hầu hết các mục đích, nó không thành vấn đề.
Đánh dấu

4
@Mark Có một cuộc đua đến khóa, nhưng đó không phải là một điều kiện cuộc đua. Một điều kiện cuộc đua là khi thời gian có thể cho phép điều gì đó xấu xảy ra, chẳng hạn như hai quá trình nằm trong cùng một phần quan trọng cùng một lúc. Không biết quá trình nào sẽ đi vào phần quan trọng được mong đợi là không điều kiện, đây không phải là một điều kiện chủng tộc.
Gilles 'SO- ngừng trở nên xấu xa'

1
Chỉ cần FYI, liên kết trong "mô tả tệp" trỏ đến trang chỉ mục thông số kỹ thuật của nhóm mở thay vì mô tả cụ thể về khái niệm này, đó là điều tôi nghĩ bạn dự định làm. Hoặc bạn cũng có thể liên kết câu trả lời cũ hơn của mình tại đây cũng như unix.stackexchange.com/a/195164/85039
Sergiy Kolodyazhnyy

5

Các tập tin được sử dụng để khóa là không quan trọng, tập lệnh sử dụng $0bởi vì đó là một tập tin được biết là tồn tại.

Thứ tự thu được các khóa sẽ ít nhiều ngẫu nhiên, tùy thuộc vào tốc độ máy của bạn có thể bắt đầu hai tác vụ.

Bạn có thể sử dụng bất kỳ bộ mô tả tệp nào, không nhất thiết phải là 0. Khóa được giữ trên tệp được mở cho bộ mô tả tệp chứ không phải chính bộ mô tả.

( flock -x 9 || exit 1
  echo 'Locking for 5 secs'; sleep 5; echo 'Done' ) 9>/tmp/lock &
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.