Giao Tiếp Giữa Các Tiến Trình Và Chia Sẻ Dữ Liệu Trên Windows
Trên hệ điều hành Windows, có nhiều phương pháp để thực hiện giao tiếp giữa các tiến trình (IPC) như sử dụng socket, ống dẫn (Pipe), hộp thư (Mailslot)… Tuy nhiên, cách cơ bản và hiệu quả nhất vẫn là sử dụng cơ chế chia sẻ bộ nhớ (Shared Memory). Các phương pháp khác về bản chất đều dựa trên nền tảng của kỹ thuật này.
Có thể dễ dàng hình dung rằng nếu cùng truy cập một vùng bộ nhớ vật lý duy nhất, sau đó ánh xạ vùng nhớ này vào không gian địa chỉ ảo của từng tiến trình khác nhau, thì mọi tiến trình đều có thể đọc/ghi dữ liệu chung một cách trực tiếp. Đây chính là phương pháp trao đổi dữ liệu hiệu quả nhất về mặt hiệu suất. Chúng ta sẽ cùng tìm hiểu cách triển khai cụ thể.
Trên Windows, cơ chế chia sẻ bộ nhớ được thực hiện thông qua File Mapping. Hàm API CreateFileMapping cho phép tạo một đối tượng ánh xạ tệp tin vào bộ nhớ. Đặc biệt, trong trường hợp này chúng ta không cần một tệp tin vật lý thực sự, do đó tham số hFile có thể được thiết lập là INVALID_HANDLE_VALUE. Tuy nhiên, chúng ta vẫn cần chỉ định kích thước vùng nhớ cần ánh xạ. Vì hầu hết các ứng dụng hiện tại không yêu cầu vượt quá 4GB bộ nhớ, nên tham số dwMaximumSizeHigh luôn đặt là 0, chỉ cần điền giá trị dwMaximumSizeLow.
Quy trình cơ bản bao gồm:
- Tạo đối tượng FileMapping bằng CreateFileMapping
- Ánh xạ vùng nhớ vào không gian địa chỉ tiến trình bằng MapViewOfFile
- Giải phóng tài nguyên bằng UnmapViewOfFile khi không sử dụng
Việc tách biệt CreateFileMapping và MapViewOfFile là có lý do kỹ thuật. Điều này cho phép ánh xạ các tệp tin lớn hơn 4GB (vì Windows hỗ trợ tệp tin 64-bit) trong khi không gian địa chỉ ảo của tiến trình chỉ giới hạn ở 4GB (thực tế thường chỉ 2GB cho ứng dụng người dùng). MapViewOfFile cho phép chỉ định offset bắt đầu và kích thước vùng nhớ cần ánh xạ.
Để chia sẻ vùng nhớ giữa các tiến trình không quan hệ cha-con, có thể sử dụng hai cách:
- Đặt tên cho FileMapping object và dùng OpenFileMapping ở tiến trình đích
- Dùng DuplicateHandle để nhân bản handle và truyền qua kênh thông điệp
Trong trường hợp cha-con, phương pháp tối ưu là thiết lập thuộc tính kế thừa của handle. Cụ thể:
|
|
Khi gọi CreateProcess, cần đặt bInheritHandles = TRUE. Hệ điều hành sẽ tự động sao chép các handle có thể kế thừa sang tiến trình con trước khi thread chính của tiến trình con hoạt động. Lưu ý rằng cả hai tiến trình đều phải gọi CloseHandle khi hoàn tất để giảm bộ đếm tham chiếu.
Một số điểm quan trọng khi sử dụng CreateProcess:
- Tham số dòng lệnh phải là chuỗi có thể sửa đổi:
|
|
- Sao chép cấu trúc STARTUPINFO từ tiến trình cha:
|
|
Cơ chế kế thừa handle đảm bảo cả hai tiến trình cha-con đều truy cập cùng một đối tượng nội hạt nhân (kernel object) thông qua cùng một giá trị handle. Điều này được thực hiện hiệu quả thông qua việc tăng bộ đếm tham chiếu (reference count) của đối tượng nội hạt nhân, thay vì sao chép toàn bộ dữ liệu.