Làm thế nào có thể thực hiện cập nhật trực tiếp trong khi một chương trình đang chạy?


15

Tôi tự hỏi làm thế nào các ứng dụng sát thủ như Thunderbird hoặc Firefox có thể được cập nhật thông qua trình quản lý gói của hệ thống trong khi chúng vẫn đang chạy. Điều gì xảy ra với mã cũ trong khi chúng đang được cập nhật? Tôi phải làm gì khi tôi muốn viết chương trình a.out tự cập nhật trong khi nó đang chạy?



@derobert Không chính xác: chủ đề đó không đi vào đặc thù của các tệp thực thi. Nó cung cấp nền tảng có liên quan nhưng nó không phải là một bản sao.
Gilles 'SO- đừng trở nên xấu xa'

@Gilles Vâng, nếu bạn để trong phần còn lại của công cụ thì nó quá rộng. Có hai (ít nhất) câu hỏi ở đây. Và một câu hỏi khác khá gần, đó là hỏi tại sao thay thế các tệp để cập nhật hoạt động trên Unix.
derobert

Nếu bạn thực sự muốn làm điều này với mã của riêng bạn, bạn có thể muốn xem ngôn ngữ lập trình Erlang, trong đó các cập nhật mã "nóng" là một tính năng chính. learnyousomeerlang.com/relups
mattdm

1
@Gilles BTW: Đã thấy bài luận bạn đã thêm vào để phản hồi, tôi đã rút lại phiếu bầu gần gũi của mình. Bạn đã biến câu hỏi này thành một nơi tốt để chỉ ra bất cứ ai muốn biết cách cập nhật hoạt động.
derobert

Câu trả lời:


19

Thay thế các tập tin nói chung

Đầu tiên, có một số chiến lược để thay thế một tệp:

  1. Mở tệp hiện có để viết, cắt nó thành 0 độ dài và viết nội dung mới. (Một biến thể ít phổ biến hơn là mở tệp hiện có, ghi đè nội dung cũ bằng nội dung mới, cắt ngắn tệp thành độ dài mới nếu nó ngắn hơn.) Về mặt vỏ:

    echo 'new content' >somefile
    
  2. Xóa tệp cũ và tạo một tệp mới có cùng tên. Về mặt vỏ:

    rm somefile
    echo 'new content' >somefile
    
  3. Viết vào một tệp mới dưới một tên tạm thời, sau đó di chuyển tệp mới sang tên hiện có. Di chuyển xóa tập tin cũ. Về mặt vỏ:

    echo 'new content' >somefile.new
    mv somefile.new somefile
    

Tôi sẽ không liệt kê tất cả sự khác biệt giữa các chiến lược, tôi sẽ chỉ đề cập đến một số điều quan trọng ở đây. Với chiến lược 1, nếu bất kỳ quy trình nào hiện đang sử dụng tệp, quy trình sẽ thấy nội dung mới khi nó được cập nhật. Điều này có thể gây ra một số nhầm lẫn nếu quá trình mong muốn nội dung tệp vẫn giữ nguyên. Lưu ý rằng đây chỉ là về các quy trình mở tệp (như hiển thị trong lsofhoặc trong ; các ứng dụng tương tác có tài liệu mở (ví dụ: mở tệp trong trình chỉnh sửa) thường không giữ tệp mở, chúng tải nội dung tệp trong khi Tài liệu mở bằng tiếng Anh hoạt động và họ thay thế tệp (sử dụng một trong các chiến lược ở trên) trong quá trình lưu tài liệu lưu lại./proc/PID/fd/

Với chiến lược 2 và 3, nếu một số quy trình có tệp somefilemở, tệp cũ vẫn mở trong quá trình nâng cấp nội dung. Với chiến lược 2, bước thực hiện xóa tệp trong thực tế chỉ xóa mục nhập của tệp trong thư mục. Bản thân tệp chỉ bị xóa khi không có mục nhập thư mục dẫn đến nó (trên các hệ thống tệp Unix điển hình, có thể có nhiều mục nhập thư mục cho cùng một tệp ) không có quá trình nào mở được. Đây là một cách để quan sát điều này - tệp chỉ bị xóa khi sleepquá trình bị hủy ( rmchỉ xóa mục nhập thư mục của nó).

echo 'old content' >somefile
sleep 9999999 <somefile &
df .
rm somefile
df .
cat /proc/$!/fd/0
kill $!
df .

Với chiến lược 3, bước di chuyển tệp mới sang tên hiện có sẽ xóa mục nhập thư mục dẫn đến nội dung cũ và tạo mục nhập thư mục dẫn đến nội dung mới. Điều này được thực hiện trong một thao tác nguyên tử, vì vậy chiến lược này có một lợi thế lớn: nếu một quá trình mở tệp bất cứ lúc nào, nó sẽ thấy nội dung cũ hoặc nội dung mới - không có nguy cơ nhận được nội dung hỗn hợp hoặc không phải là tệp hiện có.

Thay thế thực thi

Nếu bạn thử chiến lược 1 với một chương trình thực thi đang chạy trên Linux, bạn sẽ gặp lỗi.

cp /bin/sleep .
./sleep 999999 &
echo oops >|sleep
bash: sleep: Text file busy

Một tập tin văn bản Tiếng Việt có nghĩa là một tập tin chứa mã thực thi vì lý do lịch sử tối nghĩa . Linux, giống như nhiều biến thể unix khác, từ chối ghi đè mã của chương trình đang chạy; một vài biến thể unix cho phép điều này, dẫn đến sự cố trừ khi mã mới là một sửa đổi rất tốt của mã cũ.

Trên Linux, bạn có thể ghi đè mã của thư viện được tải động. Nó có khả năng dẫn đến sự cố của chương trình sử dụng nó. (Bạn có thể không quan sát được điều này sleepvì nó tải tất cả mã thư viện cần khi khởi động. Hãy thử một chương trình phức tạp hơn, có ích gì đó sau khi ngủ, như thế perl -e 'sleep 9; print lc $ARGV[0]'.)

Nếu một trình thông dịch đang chạy một tập lệnh, thì tập tin tập lệnh được trình thông dịch mở theo cách thông thường, do đó không có sự bảo vệ nào đối với việc ghi đè tập lệnh. Một số phiên dịch viên đọc và phân tích toàn bộ tập lệnh trước khi họ bắt đầu thực thi dòng đầu tiên, những người khác đọc kịch bản khi cần thiết. Xem Điều gì xảy ra nếu bạn chỉnh sửa tập lệnh trong khi thực thi? Linux xử lý các kịch bản shell như thế nào? để biết thêm chi tiết.

Chiến lược 2 và 3 cũng an toàn cho các tệp thực thi: mặc dù đang chạy các tệp thực thi (và các thư viện được tải động) không mở các tệp theo nghĩa là có một bộ mô tả tệp, chúng hoạt động theo cách rất giống nhau. Miễn là một số chương trình đang chạy mã, tệp vẫn còn trên đĩa ngay cả khi không có mục nhập thư mục.

Nâng cấp ứng dụng

Hầu hết các nhà quản lý gói sử dụng chiến lược 3 để thay thế các tệp, vì lợi thế chính được đề cập ở trên - tại bất kỳ thời điểm nào, việc mở tệp dẫn đến một phiên bản hợp lệ của nó.

Trường hợp nâng cấp ứng dụng có thể bị phá vỡ là trong khi nâng cấp một tệp là nguyên tử, thì việc nâng cấp toàn bộ ứng dụng không phải là nếu ứng dụng bao gồm nhiều tệp (chương trình, thư viện, dữ liệu, chế độ). Hãy xem xét chuỗi các sự kiện sau đây:

  1. Một phiên bản của ứng dụng được bắt đầu.
  2. Ứng dụng được nâng cấp.
  3. Ứng dụng cá thể đang chạy sẽ mở một trong các tệp dữ liệu của nó.

Trong bước 3, phiên bản đang chạy của phiên bản cũ của ứng dụng đang mở tệp dữ liệu từ phiên bản mới. Việc này có hoạt động hay không phụ thuộc vào ứng dụng, đó là tập tin nào và tập tin đã được sửa đổi bao nhiêu.

Sau khi nâng cấp, bạn sẽ lưu ý rằng chương trình cũ vẫn đang chạy. Nếu bạn muốn chạy phiên bản mới, bạn sẽ phải thoát khỏi chương trình cũ và chạy phiên bản mới. Trình quản lý gói thường giết và khởi động lại daemon khi nâng cấp, nhưng chỉ để lại các ứng dụng của người dùng cuối.

Một vài daemon có các quy trình đặc biệt để xử lý các nâng cấp mà không phải giết daemon và đợi phiên bản mới khởi động lại (điều này gây ra sự gián đoạn dịch vụ). Điều này là cần thiết trong trường hợp init , không thể bị giết; hệ thống init cung cấp một cách để yêu cầu cuộc gọi cá thể đang chạy execvethay thế chính nó bằng phiên bản mới.


"sau đó di chuyển tệp mới sang tên hiện có. Di chuyển xóa tệp cũ." Điều đó hơi khó hiểu, vì nó thực sự chỉ là một unlink, như bạn sẽ nói sau. Có thể "thay thế tên hiện tại", nhưng điều đó vẫn hơi khó hiểu.
derobert

@derobert Tôi không muốn làm nặng về thuật ngữ của un unlink. Tôi đang sử dụng xóa xóa xóa trong tham chiếu đến mục nhập thư mục, một sự tinh tế sẽ được giải thích sau. Có khó hiểu ở giai đoạn đó không?
Gilles 'SO- ngừng trở nên xấu xa'

Có lẽ không đủ khó hiểu để đảm bảo một đoạn thêm hoặc hai lên trên giải thích hủy liên kết. Tôi hy vọng một số từ ngữ không gây nhầm lẫn, nhưng cũng đúng về mặt kỹ thuật. Có lẽ chỉ cần sử dụng "loại bỏ" một lần nữa, mà bạn đã đặt một liên kết để giải thích?
derobert

2

Bản nâng cấp có thể chạy trong khi chương trình chạy, nhưng chương trình đang chạy mà bạn thấy thực sự là phiên bản cũ của nó. Nhị phân cũ vẫn còn trên đĩa cho đến khi bạn đóng chương trình.

Giải thích: trên các hệ thống Linux, một tệp chỉ là một nút, có thể có một số liên kết đến nó. Ví dụ. những /bin/bashgì bạn thấy chỉ là một liên kết đến inode 3932163trên hệ thống của tôi. Bạn có thể tìm thấy inode nào liên kết với một cái gì đó bằng cách phát hành ls --inode /pathtrên liên kết. Một tệp (inode) chỉ bị xóa nếu không có liên kết nào trỏ đến nó và không được sử dụng bởi bất kỳ chương trình nào. Khi người quản lý gói nâng cấp, vd. /usr/bin/firefox, đầu tiên nó bỏ liên kết (loại bỏ liên kết cứng /usr/bin/firefox), sau đó tạo một tệp mới gọi là liên kết /usr/bin/firefoxcứng đến một inode khác ( tệp có chứa firefoxphiên bản mới ). Inode cũ hiện được đánh dấu là miễn phí và có thể được sử dụng lại để lưu trữ dữ liệu mới nhưng vẫn còn trên đĩa (inodes chỉ được tạo khi bạn xây dựng hệ thống tệp của mình và không bao giờ bị xóa). Vào ngày bắt đầu tiếp theo củafirefox, cái mới sẽ được sử dụng.

Nếu bạn muốn viết một chương trình "nâng cấp" chính nó trong khi chạy, giải pháp khả thi duy nhất tôi có thể nghĩ đến là kiểm tra định kỳ dấu thời gian của tệp nhị phân của chính nó và nếu nó mới hơn thời gian bắt đầu của chương trình, thì hãy tự tải lại.


1
Trên thực tế, đó là do cách xóa (hủy liên kết) các tệp hoạt động trên Unix; xem unix.stackexchange.com/questions/49299/ , Ngoài ra, ít nhất là trên Linux, bạn thực sự không thể ghi vào tệp nhị phân đang chạy, bạn sẽ gặp lỗi "tệp văn bản bận".
derobert

Lạ thật ... Thế thì sao. aptCông việc nâng cấp của Debian ? Tôi có thể nâng cấp bất kỳ chương trình đang chạy nào mà không gặp sự cố bao gồm Iceweasel( Firefox).
psimon

2
APT (hoặc, đúng hơn, dpkg) không ghi đè lên các tệp. Nó thay vào đó bỏ liên kết chúng và đặt một cái mới dưới cùng tên. Xem câu hỏi và câu trả lời tôi liên kết để giải thích.
derobert

2
Nó không chỉ bởi vì nó vẫn còn trong RAM, nó vẫn còn trên đĩa. Các tập tin không thực sự bị xóa cho đến khi phiên bản cuối cùng của chương trình thoát.
derobert

1
Trang web sẽ không cho phép tôi đề xuất chỉnh sửa (một khi đại diện của bạn đủ cao, bạn chỉ cần chỉnh sửa, bạn không còn có thể đề xuất chúng nữa). Vì vậy, như một nhận xét: một tệp (inode) trên hệ thống Unix thường có một tên. Nhưng nó có thể có nhiều hơn nếu bạn thêm tên bằng ln(liên kết cứng). Bạn có thể xóa tên bằng rm(bỏ liên kết). Bạn thực sự không thể trực tiếp xóa một tập tin, chỉ xóa tên của nó. Khi một tệp không có tên và ngoài ra không mở, kernel sẽ xóa nó. Một chương trình đang chạy có tệp mà nó đang chạy từ mở, vì vậy ngay cả sau khi xóa tất cả các tên, tệp vẫn ở xung quanh.
derobert

0

Tôi tự hỏi làm thế nào các ứng dụng sát thủ như Thunderbird hoặc Firefox có thể được cập nhật thông qua trình quản lý gói của hệ thống trong khi chúng vẫn đang chạy? Chà, tôi có thể nói với bạn rằng điều này không thực sự hoạt động tốt ... Tôi đã phá vỡ Firefox với tôi khá khủng khiếp nếu tôi để nó mở trong khi bản cập nhật gói đang chạy. Đôi khi tôi phải giết nó một cách mạnh mẽ và khởi động lại nó, vì nó bị hỏng đến mức tôi thậm chí không thể đóng nó đúng cách.

Điều gì xảy ra với mã cũ trong khi chúng đang được cập nhật? Thông thường trên Linux, một chương trình được tải vào bộ nhớ, do đó, tệp thực thi trên đĩa không cần thiết hoặc được sử dụng trong khi chương trình đang chạy. Trong thực tế, bạn thậm chí có thể xóa tệp thực thi và chương trình không cần quan tâm ... Tuy nhiên, một số chương trình có thể cần tệp thực thi và một số HĐH nhất định (như Windows) sẽ khóa tệp thực thi, ngăn chặn xóa hoặc thậm chí đổi tên / di chuyển, trong khi chương trình đang chạy. Firefox phá vỡ, vì nó thực sự khá phức tạp và sử dụng một loạt các tệp dữ liệu cho nó biết cách xây dựng GUI (giao diện người dùng). Trong quá trình cập nhật gói, các tệp này bị ghi đè (cập nhật), do đó, khi một tệp thực thi Firefox cũ hơn (trong bộ nhớ) cố gắng sử dụng các tệp GUI mới, những điều kỳ lạ có thể xảy ra ...

Tôi phải làm gì khi tôi muốn viết chương trình a.out tự cập nhật trong khi nó đang chạy? Có rất nhiều câu trả lời cho câu hỏi của bạn rồi. Hãy xem điều này: /programming/232347/how-should-i-im vây-an-auto-updater


1
Thực thi là thực sự nhu cầu phân trang (hoán đổi). Chúng không được tải hoàn toàn vào bộ nhớ và có thể bị xóa khỏi bộ nhớ bất cứ khi nào hệ thống muốn RAM cho thứ khác. Phiên bản cũ thực sự còn lại trên đĩa; xem unix.stackexchange.com/questions/49299/ . Ít nhất trên Linux, bạn thực sự không thể ghi vào một tệp thực thi đang chạy, bạn sẽ gặp lỗi "tệp văn bản bận". Ngay cả root cũng không thể làm được. (Mặc dù bạn khá chính xác về Firefox).
derobert
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.