Tại sao có thể di chuyển một chương trình đang chạy trong Ubuntu?


24

Tôi chỉ nhận ra rằng tôi có thể di chuyển một chương trình đang hoạt động sang một thư mục khác. Theo kinh nghiệm của tôi, điều đó là không thể trong MacOs hoặc Windows. Làm thế nào nó hoạt động trong Ubuntu?

Chỉnh sửa: Tôi nghĩ rằng điều đó là không thể trên Mac nhưng rõ ràng là có thể như các bình luận xác minh. Chỉ có thể là không thể trên Windows. Cảm ơn tất cả các câu trả lời.


2
Khá nhiều bản sao chéo trang: stackoverflow.com/a/196910/1394393 .
jpmc26

1
Bạn không thể rename(2)chạy thực thi trên OS X? Điều gì xảy ra, bạn có nhận được EBUSYhoặc một cái gì đó? Tại sao nó không hoạt động? Trang man đổi tên (2) không có tài liệu ETXTBUSYcho cuộc gọi hệ thống đó và chỉ nói về EBUSYviệc có thể đổi tên thư mục, vì vậy tôi không biết hệ thống POSIX thậm chí có thể không cho phép đổi tên thực thi.
Peter Cordes

3
Các ứng dụng macOS cũng có thể được di chuyển trong khi chúng chạy, nhưng không bị vứt đi. Tôi cho rằng một số ứng dụng có thể bị lỗi sau đó, ví dụ: nếu chúng lưu trữ URL tệp vào tài nguyên nhị phân hoặc được đóng gói ở đâu đó dưới dạng biến thay vì tạo URL đó thông qua NSBundle et al. Tôi nghi ngờ đó là sự tuân thủ POSIX của macOS.
Constantino Tsarouhas

1
Nó thực sự hoạt động như Linux dự định, bạn nên biết những gì bạn đang làm. : P
userDepth

2
Tôi đoán một cách khác để suy nghĩ về nó là, tại sao nó sẽ không thể? Chỉ vì Windows không cho phép bạn, không nhất thiết có nghĩa là về cơ bản là không thể vì cách các quy trình hoạt động hoặc một cái gì đó.
Thomas

Câu trả lời:


32

Hãy để tôi phá vỡ nó.

Khi bạn chạy một thực thi, một chuỗi các cuộc gọi hệ thống được thực hiện, đáng chú ý nhất fork()execve():

  • fork()tạo ra một quy trình con của quy trình gọi, phần lớn là bản sao chính xác của cha mẹ, cả hai vẫn chạy cùng một tệp thực thi (sử dụng các trang bộ nhớ sao chép, vì vậy nó hiệu quả). Nó trả về hai lần: Trong cha mẹ, nó trả về PID con. Trong đứa trẻ, nó trả về 0. Thông thường, tiến trình con gọi thực thi ngay lập tức:

  • execve()lấy một đường dẫn đầy đủ đến tệp thực thi làm đối số và thay thế quy trình gọi bằng tệp thực thi. Tại thời điểm này, quy trình mới được tạo sẽ có không gian địa chỉ ảo của riêng nó, tức là bộ nhớ ảo và việc thực thi bắt đầu tại điểm vào của nó (ở trạng thái được quy định bởi các quy tắc của nền tảng ABI cho các quy trình mới).

Tại thời điểm này, trình tải ELF của kernel đã ánh xạ các đoạn văn bản và dữ liệu của tệp thực thi vào bộ nhớ, như thể nó đã sử dụng lệnh mmap()gọi hệ thống (với ánh xạ chỉ đọc và ghi riêng được chia sẻ tương ứng). BSS cũng được ánh xạ như thể với MAP_ANONYMOUS. (BTW, tôi bỏ qua liên kết động ở đây để đơn giản: Trình liên kết động open()s và mmap()s tất cả các thư viện động trước khi chuyển đến điểm vào của tệp thực thi chính.)

Chỉ có một vài trang thực sự được tải vào bộ nhớ từ đĩa trước khi ed mới exec () bắt đầu chạy mã của chính nó. Các trang tiếp theo được yêu cầu phân trang khi cần, nếu / khi quá trình chạm vào các phần của không gian địa chỉ ảo của nó. (Tải trước bất kỳ trang mã hoặc dữ liệu nào trước khi bắt đầu thực thi mã vùng người dùng chỉ là tối ưu hóa hiệu suất.)


Các tập tin thực thi được xác định bởi các nút ở cấp độ thấp hơn. Sau khi tệp đã bắt đầu được thực thi, kernel giữ nguyên nội dung tệp bằng tham chiếu inode, không phải theo tên tệp, như đối với các mô tả tệp mở hoặc ánh xạ bộ nhớ được hỗ trợ tệp. Vì vậy, bạn có thể dễ dàng di chuyển tệp thực thi đến một vị trí khác của hệ thống tệp hoặc thậm chí trên một hệ thống tệp khác. Là một lưu ý phụ, để kiểm tra các chỉ số khác nhau của quy trình, bạn có thể xem qua thư mục /proc/PID(PID là ID quy trình của quy trình đã cho). Bạn thậm chí có thể mở tệp thực thi /proc/PID/exe, ngay cả khi nó không được liên kết khỏi đĩa.


Bây giờ chúng ta hãy đi xuống di chuyển:

Khi bạn di chuyển một tệp trong cùng một hệ thống tệp, lệnh gọi hệ thống được thực thi là rename(), chỉ cần đổi tên tệp thành tên khác, inode của tệp vẫn giữ nguyên.

Trong khi giữa hai hệ thống tập tin khác nhau, có hai điều xảy ra:

  • Nội dung của tệp được sao chép trước vào vị trí mới, bởi read()write()

  • Sau đó, tệp được hủy liên kết khỏi thư mục nguồn bằng cách sử dụng unlink()và rõ ràng tệp sẽ nhận được một nút mới trên hệ thống tệp mới.

rmthực ra chỉ là unlink()-ing tệp đã cho từ cây thư mục, do đó, có quyền ghi trên thư mục sẽ giúp bạn có đủ quyền để xóa bất kỳ tệp nào khỏi thư mục đó.

Bây giờ để giải trí, hãy tưởng tượng điều gì xảy ra khi bạn di chuyển tệp giữa hai tệp và bạn không có quyền đối unlink()với tệp từ nguồn?

Chà, tập tin sẽ được sao chép đến đích lúc đầu ( read(), write()) và sau đó unlink()sẽ thất bại do không đủ quyền. Vì vậy, tập tin sẽ vẫn còn trong cả hai hệ thống tập tin !!


5
Bộ nhớ ảo và vật lý của bạn hơi khó hiểu. Mô tả của bạn về cách chương trình được tải vào bộ nhớ vật lý là không chính xác. Cuộc gọi hệ thống exec hoàn toàn không sao chép các phần khác nhau của bộ thực thi vào bộ nhớ vật lý mà chỉ tải phần mà nó cần để bắt đầu quá trình. Sau đó, các trang yêu cầu được tải theo yêu cầu, có thể một thời gian dài sau đó. Các byte tệp thực thi là một phần của bộ nhớ ảo quá trình và có thể được đọc và có thể được đọc lại trong toàn bộ vòng đời của quá trình.
jlliagre

@jlliagre Đã chỉnh sửa, tôi hy vọng nó được làm rõ ngay bây giờ. Cảm ơn.
heemayl

6
Câu hỏi "Quá trình không sử dụng hệ thống tập tin nữa" vẫn còn nhiều nghi vấn.
jlliagre

2
Hiểu biết cơ bản rằng một tệp nhất định trong hệ thống tệp không được xác định trực tiếp bởi tên tệp sẽ rõ ràng hơn nhiều.
Thorbjørn Ravn Andersen

2
Vẫn còn thiếu chính xác trong bản cập nhật của bạn. Các cuộc gọi mmapunmaphệ thống không được sử dụng để tải và dỡ bỏ các trang theo yêu cầu, các trang được tải bởi kernel khi truy cập chúng tạo ra lỗi trang, các trang được tải khỏi bộ nhớ khi HĐH cảm thấy RAM sẽ được sử dụng tốt hơn cho mục đích khác. Không có cuộc gọi hệ thống nào liên quan đến các hoạt động tải / dỡ này.
jlliagre

14

Vâng, đó là khá căng thẳng. Chúng ta hãy thực hiện một tên thực thi / usr / local / bin / whoopdeedoo. Đó chỉ là một tham chiếu đến cái gọi là inode (cấu trúc cơ bản của các tệp trên Unix Filesystems). Đó là inode được đánh dấu "đang sử dụng".

Bây giờ khi bạn xóa hoặc di chuyển tệp / usr / local / whoopdeedoo, điều duy nhất được di chuyển (hoặc xóa sạch) là tham chiếu đến nút. Bản thân inode vẫn không thay đổi. Về cơ bản là vậy.

Tôi nên xác minh nó, nhưng tôi tin rằng bạn cũng có thể làm điều này trên các hệ thống tệp Mac OS X.

Windows có một cách tiếp cận khác. Tại sao? Ai biết...? Tôi không quen thuộc với các phần bên trong của NTFS. Về mặt lý thuyết, tất cả các hệ thống tệp sử dụng các tham chiếu đến cấu trúc bên trong cho tên tệp sẽ có thể thực hiện việc này.

Tôi thừa nhận, tôi đã đơn giản hóa quá mức, nhưng hãy đọc phần "Ý nghĩa" trên Wikipedia, công việc này tốt hơn tôi rất nhiều.


1
Chà, nếu bạn sử dụng một phím tắt trong Windows để bắt đầu thực thi, bạn cũng có thể xóa sạch các phím tắt đó, nếu bạn muốn so sánh nó như vậy, có thể? = 3
Ray

2
Không, điều đó sẽ giống như xóa sạch một liên kết tượng trưng. Ở đâu đó trong các bình luận khác, có tuyên bố rằng hành vi này là do hỗ trợ di sản với các hệ thống tệp FAT. Nghe có vẻ như một lý do có thể xảy ra.
jawtheshark

1
Điều này không có gì để làm cụ thể với inodes. NTFS sử dụng các bản ghi MFT để theo dõi trạng thái tệp và FAT sử dụng các mục nhập thư mục cho việc này, nhưng Linux vẫn hoạt động tương tự với các hệ thống tệp này - theo quan điểm của người dùng.
Ruslan

13

Một điều dường như còn thiếu trong tất cả các câu trả lời khác là: một khi tệp được mở và chương trình giữ mô tả tệp đang mở, tệp sẽ không bị xóa khỏi hệ thống cho đến khi mô tả tệp đó được đóng.

Nỗ lực xóa inode được tham chiếu sẽ bị trì hoãn cho đến khi tệp được đóng: đổi tên trong cùng một hệ thống tệp khác hoặc không thể ảnh hưởng đến tệp đang mở, độc lập với hành vi đổi tên, cũng không xóa hoặc ghi đè rõ ràng tệp bằng tệp mới. Cách duy nhất để bạn có thể làm rối một tập tin là bằng cách mở rõ ràng inode của nó và gây rối với nội dung, chứ không phải bằng các thao tác trên thư mục như đổi tên / xóa tập tin.

Ngoài ra, khi kernel thực thi một tệp, nó sẽ giữ một tham chiếu đến tệp thực thi và điều này sẽ một lần nữa ngăn chặn mọi sửa đổi của nó trong khi thực thi.

Vì vậy, cuối cùng ngay cả khi có vẻ như bạn có thể xóa / di chuyển các tệp tạo nên một chương trình đang chạy, thực tế nội dung của các tệp đó được giữ trong bộ nhớ cho đến khi chương trình kết thúc.


1
Điều này không chính xác. execve()không trả về bất kỳ FD nào, nó chỉ đơn giản thực hiện chương trình. Vì vậy, ví dụ, nếu bạn chạy tail -f /foo.logsau đó của họ là một FD ( /proc/PID/fd/<fd_num>) kết hợp với tailcho foo.lognhưng không cho thực thi chính nó, tailchứ không phải trên mẹ của nó là tốt. Điều này đúng cho các thực thi duy nhất quá.
heemayl

@heemayl Tôi không đề cập đến execvevì vậy tôi không thấy điều này có liên quan. Khi kernel bắt đầu thực thi một tệp, cố gắng thay thế tệp sẽ không sửa đổi chương trình mà kernel sẽ tải kết xuất điểm moot. Nếu bạn muốn "cập nhật" tệp thực thi trong khi nó đang chạy, bạn có thể gọi execvevào một lúc nào đó để làm cho kernel đọc lại tệp, nhưng tôi không thấy vấn đề này như thế nào. Vấn đề là: xóa một "chạy thực thi" không thực sự kích hoạt bất kỳ xóa dữ liệu cho đến khi dừng thực thi.
Bakuriu

Tôi đang nói về phần này nếu chương trình bao gồm một tệp thực thi duy nhất một khi bạn bắt đầu thực thi chương trình sẽ chạy tốt độc lập với bất kỳ thay đổi nào trong thư mục: đổi tên trong cùng một hoặc hệ thống tệp khác nhau có thể ảnh hưởng đến trình xử lý mở , bạn nhất thiết phải nói về execve()và một FD khi không có FD liên quan đến trường hợp này.
heemayl

2
Bạn không cần xử lý tệp để có tham chiếu đến tệp - có các trang được ánh xạ cũng là đủ.
Simon Richter

1
Unix không có "tập tin xử lý". open()trả về một mô tả tập tin , mà heemayl đang nói về ở đây với execve(). Có, một quy trình đang chạy có tham chiếu đến khả năng thực thi của nó, nhưng đó không phải là một mô tả tệp. Có lẽ ngay cả khi nó chỉnh sửa munmap()tất cả các ánh xạ có thể thực thi được của nó, nó vẫn sẽ có một tham chiếu (được phản ánh trong / Proc / self / exe) để ngăn chặn inode được giải phóng. (Điều này có thể xảy ra mà không gặp sự cố nếu nó thực hiện điều này từ chức năng thư viện không bao giờ quay trở lại.) BTW, cắt bớt hoặc sửa đổi một tệp thực thi đang sử dụng có thể cung cấp cho bạn ETXTBUSY, nhưng có thể hoạt động.
Peter Cordes

7

Trong hệ thống tệp Linux, khi bạn di chuyển tệp, miễn là nó không vượt qua ranh giới hệ thống tệp (đọc: nằm trên cùng một đĩa / phân vùng), tất cả những gì bạn đang thay đổi là inode của ..(thư mục mẹ) đến vị trí mới . Dữ liệu thực tế không di chuyển chút nào trên đĩa, chỉ là con trỏ để hệ thống tập tin biết nơi tìm nó.

Đây là lý do tại sao các hoạt động di chuyển rất nhanh và có khả năng tại sao không có vấn đề gì khi di chuyển một chương trình đang chạy vì bạn không thực sự di chuyển chương trình đó.


Câu trả lời của bạn dường như ngụ ý việc chuyển một tệp thực thi nhị phân sang một hệ thống tệp khác sẽ ảnh hưởng đến các quy trình đang chạy được khởi chạy từ tệp nhị phân đó.
jlliagre

6

Có thể bởi vì việc di chuyển một chương trình không ảnh hưởng đến các tiến trình đang chạy bằng cách khởi chạy nó.

Khi một chương trình được khởi chạy, các bit trên đĩa của nó được bảo vệ khỏi bị ghi đè nhưng không cần bảo vệ tệp được đổi tên, di chuyển đến một vị trí khác trên cùng một hệ thống tệp, tương đương với đổi tên tệp hoặc di chuyển đến một hệ thống tệp khác, tương đương với việc sao chép tệp ở nơi khác sau đó xóa tệp đó.

Xóa một tệp đang được sử dụng, bởi vì một quy trình có mô tả tệp mở trên đó hoặc do một quy trình đang thực thi nó, không xóa dữ liệu tệp, vẫn được tham chiếu bởi tệp inode mà chỉ xóa mục nhập thư mục, tức là một đường dẫn mà từ đó có thể đạt được.

Lưu ý rằng khởi chạy chương trình không tải mọi thứ cùng một lúc trong bộ nhớ (vật lý). Ngược lại, chỉ có mức tối thiểu nghiêm ngặt cần thiết cho quá trình bắt đầu được tải. Sau đó, các trang yêu cầu được tải theo yêu cầu trong suốt vòng đời của quy trình. điều này được gọi là phân trang nhu cầu. Nếu thiếu RAM, HĐH có thể tự do giải phóng RAM đang giữ các trang này, do đó, có thể quá trình tải nhiều lần cùng một trang từ inode thực thi.

Lý do tại sao điều đó không thể xảy ra với Windows ban đầu có khả năng là do thực tế hệ thống tệp cơ bản (FAT) không hỗ trợ khái niệm phân chia các mục nhập thư mục và inodes. Hạn chế này không còn nữa với NTFS nhưng thiết kế HĐH đã được giữ trong một thời gian dài, dẫn đến sự hạn chế đáng ghét phải khởi động lại khi cài đặt phiên bản nhị phân mới, không còn giống với các phiên bản Windows gần đây.


1
Tôi tin rằng các phiên bản Windows mới hơn có thể thay thế các tệp nhị phân đang sử dụng mà không cần khởi động lại.
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen Tôi tự hỏi tại sao tất cả các bản cập nhật vẫn yêu cầu khởi động lại :(
Braiam

1
@Braiam Họ không. Có một cái nhìn gần hơn. Mặc dù nhị phân có thể được cập nhật, kernel không thể (theo hiểu biết của tôi) và yêu cầu khởi động lại để được thay thế bằng phiên bản mới hơn. Điều này là hợp lệ cho hầu hết các nhân hệ điều hành. Những người thông minh hơn tôi đã viết kpatch cho Linux, có thể vá nhân Linux khi chạy - xem en.wikipedia.org/wiki/Kpatch
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen Ý tôi là "tất cả các bản cập nhật Windows"
Braiam

@Braiam có - tôi cũng vậy. Xin hãy xem kỹ hơn.
Thorbjørn Ravn Andersen

4

Về cơ bản, trong Unix và ilk của nó, một tên tệp (bao gồm cả đường dẫn thư mục dẫn đến nó) được sử dụng để liên kết / tìm tệp khi mở tệp (thực thi tệp là một cách để mở tệp theo cách). Sau khoảnh khắc đó, danh tính của tệp (thông qua "inode") được thiết lập và không còn bị nghi ngờ nữa. Bạn có thể xóa tệp, đổi tên nó, thay đổi quyền của nó. Miễn là bất kỳ quá trình hoặc đường dẫn tệp nào có xử lý trên tệp / inode đó, nó sẽ dính xung quanh, giống như một đường ống giữa các tiến trình (thực ra, trong UNIX lịch sử, một đường ống một nút không tên có kích thước vừa với "khối trực tiếp" tham chiếu lưu trữ đĩa trong inode, đại loại như 10 khối).

Nếu bạn có trình xem PDF mở trên tệp PDF, bạn có thể xóa tệp đó và mở tệp mới có cùng tên và miễn là trình xem cũ được mở, nó vẫn sẽ truy cập vào tệp cũ (trừ khi nó chủ động xem hệ thống tập tin để thông báo khi tập tin biến mất dưới tên ban đầu của nó).

Các chương trình cần các tệp tạm thời chỉ có thể mở một tệp như vậy dưới một số tên và sau đó loại bỏ ngay lập tức (hoặc đúng hơn là mục nhập thư mục của nó) trong khi nó vẫn đang mở. Sau đó, tệp không còn có thể truy cập được bằng tên, nhưng bất kỳ quy trình nào có mô tả tệp mở cho tệp vẫn có thể truy cập tệp và nếu có một lối thoát bất ngờ của chương trình sau đó, tệp sẽ tự động bị xóa và lưu trữ được lấy lại tự động.

Vì vậy, đường dẫn đến tệp không phải là thuộc tính của chính tệp đó (trên thực tế, các liên kết cứng có thể cung cấp một số đường dẫn khác nhau như vậy) và chỉ cần để mở tệp chứ không phải để tiếp tục truy cập bởi các quy trình đã mở.

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.