Shared Memory

1. Giới thiệu chung

Shared memory ( bộ nhớ được chia sẻ ) là một cơ chế giao tiếp giữa các tiến trình (IPC) trong Linux và các hệ thống Unix khác. Shared memory là bộ nhớ được chia sẻ giữa hai hay nhiều process khác nhau.

Ở đây, các process chia sẻ một vùng nhớ vật lý thông qua trung gian không gian địa chỉ của chúng. Một vùng nhớ chia sẻ tồn tại độc lập với các process, và khi một process muốn truy xuất đến vùng nhớ này, process phải kết gắn vùng nhớ chung đó vào không gian địa chỉ riêng của từng process, và thao tác trên đó như một vùng nhớ riêng của mình.

Ngoài ra, các process cần đồng bộ hóa việc sử dụng shared memory bằng cách sử dụng semaphores để các process không ghi dữ liệu tại một vị trí cùng một lúc.

Shared memory là cơ chế giao tiếp ( IPC ) nhanh nhất giữa các process

Nếu chúng ta xem xét các cơ chế IPC khác như message queues hoặc các cơ chế cũ hơn như pipe hoặc FIFO, các kĩ thuật này cần sao chép nội dung dữ liệu cần gửi ít nhất hai lần. Đầu tiên, process thứ nhất thực hiện gửi dữ liệu từ địa chỉ trên user space tới kernel space. Sau đó, process thứ hai thực lệnh nhận và dữ liệu tiếp tục được sao chép từ kernel space vào không gian bộ nhớ của process đó.

Đối với Shared memory, bộ nhớ được chia sẻ được ánh xạ vào không gian bộ nhớ của cả hai process. Ngay sau khi process đầu tiên ghi dữ liệu vào bộ nhớ được chia sẻ, nó sẽ có sẵn cho process thứ hai truy cập mà không cần thực hiện bất cứ thao tác sao chép nào.

Có 2 phương pháp để tạo shared memory:

  • Sử dụng API của shm (System V)
  • Sử dụng API của mmap (POSIX)

System V shared memory là kỹ thuật sử dụng khóa và mã định danh cho bộ nhớ được chia sẻ, điều này không đồng nhất với mô hình tiêu chuẩn UNIX I/O (sử dụng tên tệp). Sự khác biệt này yêu cầu một bộ system call hoàn toàn mới để làm việc với vùng nhớ dùng chung (shared memory segment).

Để khắc phục hạn chế trên, POSIX shared memory đã được ra đời với các API mới tương tác với shared memory (shared memory object).

2. Tổng quan POSIX shared memory

POSIX shared memory cho phép chia sẻ vùng được ánh xạ giữa các process không liên quan mà không cần tạo tệp ánh xạ tương ứng. POSIX shared memory  được hỗ trợ trên Linux kể từ kernel 2.4

Linux tạo cho shared memory dưới dạng tệp thông qua hệ thống tệp tmpfs được nằm trong thư mục /dev/shm. Hệ thống tệp này sẽ tồn tại ngay cả khi không có process nào đang mở chúng, chúng chỉ bị mất đi khi hệ thống bị tắt.

Tổng dung lượng bộ nhớ trong tất cả các vùng POSIX shared memory trên hệ thống bị giới hạn bởi kích thước của hệ thống tệp tmpfs cơ bản. Hệ thống tệp này thường được khởi tạo vào lúc khởi động với một số kích thước mặc định (ví dụ: 256 MB). Nếu cần thiết, người dùng có thể thay đổi kích thước của hệ thống tệp bằng cách kết nối lại nó bằng lệnh mount –o remount,size=<numbytes>

Để sử dụng POSIX shared memory, có 5 bước sau:

  1. Tạo file discriptor: Vùng nhớ được chia sẻ được tạo ra và được định danh bởi một chuỗi string mang tính global trong OS với hàm shm_open()
  2. Khởi tạo kích thước cho vùng nhớ vật lý: ftruncate()
  3. Cấp một vùng virtual address để mapping vào vùng nhớ vật lý đó: mmap()
  4. Giải phóng địa chỉ virtual address khi không sử dụng nữa: munmap()
  5. Giải phòng vùng nhớ vật lý: shm_unlink()

3. Khởi tạo Shared Memory

Hàm shm_open() tạo và mở một shared memory object mới hoặc mở một đối tượng hiện có. Các đối số của shm_open() tương tự như đối số của open().

Các đối số của hàm:

  • name: Tên của POSIX shared memory object. Trong thực tế, tên này không phải là một đường dẫn trên hệ thống tệp, mà chỉ là một chuỗi ký tự duy nhất để định danh shared memory object.
  • oflag: Cờ mở (open flags). Các cờ này có thể là O_RDONLY (chỉ đọc), O_RDWR (đọc và ghi), O_CREAT (tạo nếu không tồn tại), và nhiều cờ khác.Có thể kết hợp chúng bằng cách sử dụng toán tử |
  • mode: Quyền truy cập khi tạo mới một POSIX shared memory object (O_CREAT được sử dụng). Quyền này thường được thiết lập bằng các hằng số như S_IRUSR (quyền đọc cho người sở hữu), S_IWUSR (quyền ghi cho người sở hữu) hoặc thiết lập bằng mặt nạ bit ( 0666 ).

Giá trị trả về:

  • Trong trường hợp thành công, hàm trả về một file descriptor của POSIX shared memory object (kiểu int).
  • Trong trường hợp lỗi, hàm trả về -1 và người dùng có thể thiết lập biến errno để chỉ ra lý do lỗi.

4. Hàm ftruncate()

Khi một shared memory object mới được tạo, ban đầu nó có độ dài bằng 0. Điều này có nghĩa là sau khi tạo một shared memory object mới, chúng ta thường gọi ftruncate() để khởi tạo kích thước của bộ nhớ đó trước khi gọi hàm mmap().

  • fd: File descriptor của shared memory object cần thay đổi kích thước.
  • length: Kích thước mới mà người dùng muốn thiết lập cho shared memory object.

Giá trị trả về của ftruncate():

  • Trong trường hợp thành công, hàm ftruncate() trả về 0.
  • Trong trường hợp lỗi, nó trả về -1 và người dùng có thể thiết lập biến errno để chỉ ra lý do lỗi.

Ngoài ra, sau khi gọi lệnh mmap(), người dùng vẫn có thể sử dụng hàm ftruncate() để mở rộng hoặc thu nhỏ kích thước của shared memory theo ý muốn. Khi kích thước của nó được mở rộng, các byte mới được thêm vào sẽ tự động khởi tạo giá trị 0.

Kích thước khởi tạo cho shared memory nên để là bội số của một page memory do hệ điều hành quản lý vùng nhớ tại RAM theo từng page. Nên hệ điều hành sẽ luôn cấp phát kích thước theo page.

5. Hàm mmap() và munmap()

Hàm mmap() được sử dụng để ánh xạ một phần hoặc toàn bộ nội dung hoặc một phần POSIX shared memory vào không gian bộ nhớ của một process gọi hàm.

  • addr: Địa chỉ bắt đầu của phần bộ nhớ được ánh xạ. Thông thường, bạn sẽ để giá trị này là NULL, để hệ thống tự động chọn địa chỉ thích hợp.
  • length: Độ dài của phần bộ nhớ cần ánh xạ, tính bằng byte.
  • prot: Quyền truy cập cho phần bộ nhớ được ánh xạ. Các giá trị thường sử dụng là PROT_READ, PROT_WRITE, và/hoặc PROT_EXEC.
  • flags: Các cờ kiểm soát hành vi của ánh xạ, như MAP_SHARED để ánh xạ cho shared memory hoặc MAP_PRIVATE để tạo bản sao riêng tư.
  • fd: File descriptor của POSIX shared memory.
  • offset: Vị trí bắt đầu ánh xạ trong tệp tin, tính bằng byte.

Giá trị trả về:

  • Trong trường hợp thành công, hàm mmap() trả về địa chỉ bắt đầu của phần bộ nhớ được ánh xạ.
  • Trong trường hợp lỗi, nó trả về MAP_FAILED ((void *)-1) và người dùng có thể thiết lập biến errno để chỉ ra lý do lỗi.

Hàm munmap() được sử dụng để giải phóng bộ nhớ đã được ánh xạ bằng hàm mmap() khỏi không gian bộ nhớ của process. Điều này thường được thực hiện sau khi không còn cần truy cập đến dữ liệu tại vùng nhớ được ánh xạ hoặc khi kết thúc việc làm việc với một shared memory.

  • addr: Địa chỉ bắt đầu của phần bộ nhớ đã ánh xạ cần được giải phóng.
  • length: Độ dài của phần bộ nhớ cần giải phóng, tính bằng byte.

Giá trị trả về:

  • Trong trường hợp thành công, hàm munmap() trả về 0.
  • Trong trường hợp lỗi, nó trả về -1 và người dùng có thể thiết lập biến errno để chỉ ra lý do lỗi.

6. Xóa Shared Memory Objects

Shared memory tồn tại cho đến khi chúng bị loại bỏ hoặc hệ thống được khởi động lại. Khi một shared memory object không còn cần thiết nữa, nó sẽ bị xóa bằng cách sử dụng shm_unlink().

Hàm shm_unlink() loại bỏ shared memory object được chỉ định theo name.

Việc xóa shared memory object không ảnh hưởng đến các ánh xạ hiện có của nó(điều này sẽ vẫn có hiệu lực cho đến khi các process tương ứng gọi munmap), nhưng hàm shm_unlink ngăn các lệnh gọi shm_open() tiếp theo mở đối tượng. Khi tất cả các process đã hủy ánh xạ (unmapped) shared memory object, nó sẽ bị xóa và nội dung của nó sẽ bị mất.

7. Ví dụ POSIX Shared Memory

Chương trình thực hiện việc ghi dữ liệu vào shared memory:

Trong ví dụ trên, process thực hiện khởi tạo shared memory object với tên định danh “OS” với kích thước 4096 byte. Shared memory có vùng virtual memory (mapped) bắt đầu từ địa chỉ chứa trong con trỏ ptr và process ghi chuỗi “Hello World!” vào vùng địa chỉ vật lý của shared memory.

Chương trình thực hiện việc đọc dữ liệu:

Kết quả sau khi biên dịch và thực hiện chương trình:

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top