Sửa đổi nhị phân trong khi thực hiện


10

Tôi thường gặp tình huống khi phát triển, nơi tôi đang chạy một tệp nhị phân, nói a.outtrong nền vì nó thực hiện một số công việc dài. Trong khi thực hiện điều đó, tôi thực hiện các thay đổi đối với mã C được sản xuất a.outvà biên dịch a.outlại. Cho đến nay, tôi không có bất kỳ vấn đề với điều này. Quá trình đang chạy a.outtiếp tục như bình thường, không bao giờ gặp sự cố và luôn chạy mã cũ mà từ đó nó bắt đầu.

Tuy nhiên, nói a.outlà một tập tin lớn, có thể tương đương với kích thước của RAM. Điều gì sẽ xảy ra trong trường hợp này? Và nói rằng nó được liên kết với một tệp đối tượng chia sẻ libblas.so, nếu tôi sửa đổi libblas.sotrong thời gian chạy thì sao? Chuyện gì sẽ xảy ra?

Câu hỏi chính của tôi là - hệ điều hành có đảm bảo rằng khi tôi chạy a.out, thì mã gốc sẽ luôn chạy bình thường, theo nhị phân gốc , bất kể kích thước của tệp nhị phân hoặc .sotệp mà nó liên kết đến, ngay cả khi các tệp .o.sotệp đó được sửa đổi trong thời gian chạy?

Tôi biết có những câu hỏi giải quyết các vấn đề tương tự: /programming/8506865/when-a-binary-file-runs-does-it-copy-its-entire-binary-data-into-memory -at-once Điều gì xảy ra nếu bạn chỉnh sửa tập lệnh trong khi thực thi? 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?

Điều này đã giúp tôi hiểu thêm một chút về điều này nhưng tôi không nghĩ rằng họ đang hỏi chính xác những gì tôi muốn, đó là một quy tắc chung cho hậu quả của việc sửa đổi nhị phân trong khi thực hiện


Đối với tôi, các câu hỏi bạn liên kết (đặc biệt là Câu hỏi về Stack Overflow) đã cung cấp trợ giúp đáng kể trong việc hiểu các hậu quả này (hoặc không có). Vì kernel tải chương trình của bạn vào các vùng / phân đoạn văn bản bộ nhớ , nên nó không bị ảnh hưởng bởi những thay đổi được thực hiện thông qua hệ thống con tập tin.
John WH Smith

@JohnWHSmith On Stackoverflow, câu trả lời đầu nói if they are read-only copies of something already on disc (like an executable, or a shared object file), they just get de-allocated and are reloaded from their source, vì vậy tôi có cảm giác rằng nếu nhị phân của bạn là rất lớn, sau đó nếu một phần của nhị phân của bạn đi ra khỏi RAM, nhưng sau đó sẽ cần một lần nữa nó được "nạp lại từ nguồn" - vì vậy bất kỳ thay đổi trong các .(s)otập tin sẽ được phản ánh trong khi thực hiện. Nhưng tất nhiên tôi có thể đã hiểu lầm - đó là lý do tại sao tôi hỏi câu hỏi cụ thể hơn này
texasflood 3/03/2015

@JohnWHSmith Ngoài ra câu trả lời thứ hai cho biết No, it only loads the necessary pages into memory. This is demand paging.Vì vậy, tôi thực sự cảm thấy rằng những gì tôi yêu cầu không thể được đảm bảo.
texasflood 3/03/2015

Câu trả lời:


11

Mặc dù câu hỏi Stack Overflow ban đầu dường như là đủ, nhưng tôi hiểu, từ nhận xét của bạn, tại sao bạn vẫn có thể nghi ngờ về điều này. Đối với tôi, đây chính xác là loại tình huống quan trọng liên quan khi hai hệ thống con UNIX (quy trình và tệp) giao tiếp với nhau.

Như bạn có thể biết, các hệ thống UNIX thường được chia thành hai hệ thống con: hệ thống con tệp và hệ thống con quy trình. Bây giờ, trừ khi nó được hướng dẫn khác thông qua một cuộc gọi hệ thống, kernel không nên có hai hệ thống con này tương tác với nhau. Tuy nhiên, có một ngoại lệ: việc tải tệp thực thi vào các vùng văn bản của quy trình . Tất nhiên, người ta có thể tranh luận rằng hoạt động này cũng được kích hoạt bởi một cuộc gọi hệ thống ( execve), nhưng điều này thường được biết đến là những một trong trường hợp hệ thống phụ quá trình làm cho một yêu cầu tiềm ẩn tới hệ thống tập tin.

Bởi vì hệ thống con quá trình tự nhiên không có cách xử lý tệp (nếu không sẽ không có điểm nào trong việc chia toàn bộ thành hai phần), nên nó phải sử dụng bất cứ thứ gì mà hệ thống con tệp cung cấp để truy cập tệp. Điều này cũng có nghĩa là hệ thống con quy trình được gửi tới bất kỳ biện pháp nào mà hệ thống con tập tin thực hiện liên quan đến phiên bản / xóa tập tin. Về điểm này, tôi sẽ khuyên bạn nên đọc Gilles' câu trả lời để U & L câu hỏi này . Phần còn lại của câu trả lời của tôi dựa trên câu trả lời tổng quát hơn này từ Gilles.

Điều đầu tiên cần lưu ý là trong nội bộ, các tệp chỉ có thể truy cập thông qua các nút . Nếu kernel được cung cấp một đường dẫn, bước đầu tiên của nó sẽ là dịch nó thành một inode được sử dụng cho tất cả các hoạt động khác. Khi một quá trình tải một tệp thực thi vào bộ nhớ, nó sẽ thực hiện nó thông qua inode của nó, được hệ thống con tệp cung cấp sau khi dịch một đường dẫn. Các nút có thể được liên kết với một số đường dẫn (liên kết) và các chương trình chỉ có thể xóa các liên kết. Để xóa một tập tin và inode của nó, userland phải xóa tất cả các liên kết hiện có đến inode đó và đảm bảo rằng nó hoàn toàn không được sử dụng. Khi các điều kiện này được đáp ứng, kernel sẽ tự động xóa tệp khỏi đĩa.

Nếu bạn xem phần thực thi thay thế trong câu trả lời của Gilles, bạn sẽ thấy rằng tùy thuộc vào cách bạn chỉnh sửa / xóa tệp, hạt nhân sẽ phản ứng / điều chỉnh khác nhau, luôn thông qua cơ chế được triển khai trong hệ thống con tệp.

  • Nếu bạn thử chiến lược một ( mở / cắt ngắn thành 0 / ghi hoặc mở / ghi / cắt ngắn sang kích thước mới ), bạn sẽ thấy hạt nhân sẽ không xử lý yêu cầu của bạn. Bạn sẽ gặp lỗi 26: Tệp văn bản bận ( ETXTBSY). Không có hậu quả gì.
  • Nếu bạn thử chiến lược hai, bước đầu tiên là xóa tệp thực thi của bạn. Tuy nhiên, vì nó đang được sử dụng bởi một quy trình, hệ thống con tệp sẽ khởi động và ngăn không cho tệp (và inode của nó) thực sự bị xóa khỏi đĩa. Từ thời điểm này, cách duy nhất để truy cập nội dung của tệp cũ là thực hiện thông qua inode của nó, đó là điều mà hệ thống con quy trình thực hiện bất cứ khi nào nó cần tải dữ liệu mới vào các phần văn bản (bên trong, không có điểm nào trong việc sử dụng các đường dẫn, ngoại trừ khi dịch chúng thành inodes). Mặc dù bạn đã bỏ liên kếttệp (đã xóa tất cả các đường dẫn của nó), quá trình vẫn có thể sử dụng nó như thể bạn không làm gì cả. Tạo một tệp mới với đường dẫn cũ sẽ không thay đổi bất cứ điều gì: tệp mới sẽ được cung cấp một nút hoàn toàn mới, mà quá trình đang chạy không có kiến ​​thức.

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.

  • Chiến lược ba khá giống nhau vì mvhoạt động là một nguyên tử. Điều này có thể sẽ yêu cầu sử dụng renamelệnh gọi hệ thống và vì các quy trình không thể bị gián đoạn trong khi ở chế độ kernel, không có gì có thể can thiệp vào thao tác này cho đến khi hoàn thành (thành công hay không). Một lần nữa, không có sự thay đổi nào đối với inode của tệp cũ: một cái mới được tạo và các quy trình đã chạy sẽ không có kiến ​​thức về nó, ngay cả khi nó được liên kết với một trong các liên kết của inode cũ.

Với chiến lược 3, bước di chuyển tệp mới sang tên hiện có sẽ loại bỏ mục nhập thư mục dẫn đến nội dung cũ và tạo một 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ó.

Biên dịch lại một tệp : khi sử dụng gcc(và hành vi có thể tương tự đối với nhiều trình biên dịch khác), bạn đang sử dụng chiến lược 2. Bạn có thể thấy điều đó bằng cách chạy một stracequy trình của trình biên dịch:

stat("a.out", {st_mode=S_IFREG|0750, st_size=8511, ...}) = 0
unlink("a.out") = 0
open("a.out", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
chmod("a.out", 0750) = 0
  • Trình biên dịch phát hiện ra rằng tệp đã tồn tại thông qua các cuộc gọi statlstathệ thống.
  • Các tập tin được liên kết . Ở đây, mặc dù nó không còn có thể truy cập thông qua tên a.out, inode và nội dung của nó vẫn còn trên đĩa, miễn là chúng đang được sử dụng bởi các quy trình đã chạy.
  • Một tập tin mới được tạo và thực hiện dưới tên a.out. Đây là một inode hoàn toàn mới và các nội dung hoàn toàn mới, mà các quy trình đã chạy không quan tâm.

Bây giờ, khi nói đến các thư viện chia sẻ, hành vi tương tự sẽ được áp dụng. Miễn là một đối tượng thư viện được sử dụng bởi một tiến trình, nó sẽ không bị xóa khỏi đĩa, bất kể bạn thay đổi liên kết của nó như thế nào. Bất cứ khi nào một cái gì đó phải được tải vào bộ nhớ, kernel sẽ thực hiện nó thông qua inode của tệp và do đó sẽ bỏ qua những thay đổi bạn đã thực hiện với các liên kết của nó (chẳng hạn như liên kết chúng với các tệp mới).


Tuyệt vời, câu trả lời chi tiết. Điều đó giải thích cho sự nhầm lẫn của tôi. Vì vậy, tôi đã đúng khi giả sử rằng vì inode vẫn có sẵn, dữ liệu từ tệp nhị phân gốc vẫn còn trên đĩa và do đó sử dụng dfđể xử lý số byte miễn phí trên đĩa là sai vì nó không lấy được các nút có tất cả các liên kết hệ thống tập tin được gỡ bỏ đưa vào tài khoản? Vậy tôi có nên sử dụng df -i? (Đây chỉ là một sự tò mò về kỹ thuật, tôi thực sự không cần biết cách sử dụng đĩa chính xác!)
texasflood

1
Chỉ cần làm rõ cho các độc giả trong tương lai - sự nhầm lẫn của tôi là tôi đã nghĩ rằng khi thực thi, toàn bộ nhị phân sẽ được tải vào RAM, vì vậy nếu RAM nhỏ, thì một phần của nhị phân sẽ rời khỏi RAM và phải được tải lại từ đĩa - sẽ gây ra vấn đề nếu bạn thay đổi tập tin Nhưng câu trả lời đã làm rõ rằng nhị phân không bao giờ thực sự bị xóa khỏi đĩa ngay cả khi bạn rmhoặc mvnó như là nút inode của tệp gốc không bị xóa cho đến khi tất cả các quá trình loại bỏ liên kết của chúng đến nút đó.
texasflood 04/03/2015

@texasflood Chính xác. Khi tất cả các đường dẫn đã bị xóa, không có quy trình mới nào ( dfbao gồm) có thể nhận thông tin về inode. Bất cứ thông tin mới nào bạn tìm thấy đều liên quan đến tệp mới và inode mới. Điểm chính ở đây là hệ thống con quy trình không quan tâm đến vấn đề này, vì vậy các khái niệm về quản lý bộ nhớ (phân trang theo yêu cầu, hoán đổi quy trình, lỗi trang, ...) là hoàn toàn không liên quan. Đây là một vấn đề hệ thống con tập tin và nó được chăm sóc bởi hệ thống con tập tin. Hệ thống con quy trình không bận tâm với điều đó, đó không phải là những gì nó ở đây.
John WH Smith

@texasflood Một lưu ý về df -i: công cụ này có thể lấy thông tin từ siêu khối của fs hoặc bộ đệm của nó, có nghĩa là nó có thể bao gồm inode của nhị phân cũ (mà tất cả các liên kết đã bị xóa). Tuy nhiên, điều này không có nghĩa là các quy trình mới được sử dụng miễn phí dữ liệu cũ đó.
John WH Smith

2

Tôi hiểu rằng do ánh xạ bộ nhớ của một tiến trình đang chạy, kernel sẽ không cho phép cập nhật một phần dành riêng của tệp được ánh xạ. Tôi đoán trong trường hợp một tiến trình đang chạy thì tất cả các tệp của nó được bảo lưu do đó cập nhật nó vì bạn đã biên dịch một phiên bản mới của nguồn thực sự dẫn đến việc tạo ra một tập hợp các nút mới. Nói tóm lại, (các) phiên bản cũ hơn của (các) thực thi của bạn vẫn có thể truy cập được trên đĩa thông qua các sự kiện lỗi trang. Vì vậy, ngay cả khi bạn cập nhật một tập tin rất lớn, nó nên vẫn truy cập được và hạt nhân nên vẫn thấy phiên bản bị ảnh hưởng càng lâu càng tiến trình đang chạy. Các tập tin gốc không nên được sử dụng lại miễn là quá trình đang chạy.

Điều này tất nhiên phải được xác nhận.


2

Điều này không phải lúc nào cũng đúng khi thay thế tệp .jar. Tài nguyên Jar và một số trình tải lớp phản ánh thời gian chạy không được đọc từ đĩa cho đến khi chương trình yêu cầu thông tin rõ ràng.

Đây chỉ là một vấn đề vì một jar chỉ đơn giản là một kho lưu trữ chứ không phải là một tệp thực thi duy nhất được ánh xạ vào bộ nhớ. Điều này hơi khó hiểu nhưng vẫn không đúng với câu hỏi của bạn và điều gì đó tôi đã tự bắn vào chân mình.

Vì vậy, đối với thực thi: có. Đối với các tệp jar: có thể (tùy thuộc vào việc thực hiện).

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.