/ / Cách phân bổ bộ nhớ hoạt động trên Linux

Cách phân bổ bộ nhớ hoạt động trên Linux

Trong máy tính, để một tiến trình có thể thực thi được, nó cần phải được đặt trong bộ nhớ. Đối với điều này, một trường phải được gán cho một tiến trình trong bộ nhớ. Cấp phát bộ nhớ là một vấn đề quan trọng cần lưu ý, đặc biệt là trong các kiến ​​trúc hệ thống và nhân.

Chúng ta hãy xem xét phân bổ bộ nhớ Linux một cách chi tiết và hiểu những gì diễn ra đằng sau hậu trường.

Phân bổ bộ nhớ được thực hiện như thế nào?

Hầu hết các kỹ sư phần mềm không biết chi tiết của quá trình này. Nhưng nếu bạn là một ứng cử viên lập trình hệ thống, bạn nên biết thêm về nó. Khi xem xét quy trình cấp phát, cần phải đi vào một số chi tiết nhỏ về Linux và glibc thư viện.

Khi các ứng dụng cần bộ nhớ, chúng phải yêu cầu bộ nhớ đó từ hệ điều hành. Yêu cầu này từ hạt nhân đương nhiên sẽ yêu cầu một cuộc gọi hệ thống. Bạn không thể tự mình phân bổ bộ nhớ trong chế độ người dùng.

Các malloc () họ hàm chịu trách nhiệm cấp phát bộ nhớ trong ngôn ngữ C. Câu hỏi cần đặt ra ở đây là liệu malloc (), với tư cách là một hàm glibc, có thực hiện một lệnh gọi hệ thống trực tiếp hay không.

Không có lệnh gọi hệ thống nào được gọi là malloc trong nhân Linux. Tuy nhiên, có hai lệnh gọi hệ thống cho nhu cầu bộ nhớ ứng dụng, đó là brkmmap.

Vì bạn sẽ yêu cầu bộ nhớ trong ứng dụng của mình thông qua các hàm glibc, nên bạn có thể tự hỏi rằng glibc đang sử dụng lệnh gọi hệ thống nào trong số này tại thời điểm này. Câu trả lời là cả hai.

LÀM VIDEO TRONG NGÀY

Cuộc gọi hệ thống đầu tiên: brk

Mỗi quá trình có một trường dữ liệu liền kề. Với lệnh gọi hệ thống brk, giá trị ngắt chương trình, xác định giới hạn của trường dữ liệu, được tăng lên và quá trình cấp phát được thực hiện.

Mặc dù cấp phát bộ nhớ bằng phương pháp này rất nhanh, nhưng không phải lúc nào cũng có thể trả lại dung lượng chưa sử dụng cho hệ thống.

Ví dụ: hãy xem xét rằng bạn phân bổ năm trường, mỗi trường có kích thước 16KB, với lệnh gọi hệ thống brk thông qua hàm malloc (). Khi bạn thực hiện xong với số hai trong số các trường này, không thể trả về tài nguyên có liên quan (phân bổ) để hệ thống có thể sử dụng nó. Bởi vì nếu bạn giảm giá trị địa chỉ để hiển thị nơi bắt đầu trường số hai của bạn, với một lệnh gọi tới brk, bạn sẽ thực hiện việc phân bổ cho các trường số ba, bốn và năm.


Để tránh mất bộ nhớ trong trường hợp này, việc triển khai malloc trong glibc giám sát các vị trí được cấp phát trong trường dữ liệu quy trình và sau đó chỉ định trả lại nó cho hệ thống bằng hàm free (), để hệ thống có thể sử dụng không gian trống cho bộ nhớ tiếp theo sự phân bổ.

Nói cách khác, sau khi năm vùng 16KB được cấp phát, nếu vùng thứ hai được trả lại bằng hàm free () và một vùng 16KB khác được yêu cầu lại sau một thời gian, thay vì mở rộng vùng dữ liệu thông qua lệnh gọi hệ thống brk, địa chỉ trước đó là trả lại.

Tuy nhiên, nếu vùng mới được yêu cầu lớn hơn 16KB, thì vùng dữ liệu sẽ được mở rộng bằng cách phân bổ vùng mới với lệnh gọi hệ thống brk vì vùng hai không thể được sử dụng. Mặc dù khu vực số hai không được sử dụng, ứng dụng không thể sử dụng nó vì sự khác biệt về kích thước. Do các tình huống như thế này, có một tình huống được gọi là phân mảnh nội bộ, và trên thực tế, bạn hiếm khi có thể sử dụng tối đa tất cả các phần của bộ nhớ.


Để hiểu rõ hơn, hãy thử biên dịch và chạy ứng dụng mẫu sau:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
char *ptr[7];
int n;
printf("Pid of %s: %d", argv[0], getpid());
printf("Initial program break : %p", sbrk(0));
for(n=0; n<5; n++) ptr[n] = malloc(16 * 1024);
printf("After 5 x 16kB malloc : %p", sbrk(0));
free(ptr[1]);
printf("After free of second 16kB : %p", sbrk(0));
ptr[5] = malloc(16 * 1024);
printf("After allocating 6th of 16kB : %p", sbrk(0));
free(ptr[5]);
printf("After freeing last block : %p", sbrk(0));
ptr[6] = malloc(18 * 1024);
printf("After allocating a new 18kB : %p", sbrk(0));
getchar();
return 0;
}


Khi bạn chạy ứng dụng, bạn sẽ nhận được một kết quả tương tự như đầu ra sau:

Pid of ./a.out: 31990
Initial program break : 0x55ebcadf4000
After 5 x 16kB malloc : 0x55ebcadf4000
After free of second 16kB : 0x55ebcadf4000
After allocating 6th of 16kB : 0x55ebcadf4000
After freeing last block : 0x55ebcadf4000
After allocating a new 18kB : 0x55ebcadf4000

Đầu ra cho brk với strace sẽ như sau:

brk(NULL)                               = 0x5608595b6000
brk(0x5608595d7000) = 0x5608595d7000

Bạn có thể thấy, 0x21000 đã được thêm vào địa chỉ kết thúc của trường dữ liệu. Bạn có thể hiểu điều này từ giá trị 0x5608595d7000. Vì vậy, khoảng 0x21000hoặc 132KB bộ nhớ đã được cấp phát.

Có hai điểm quan trọng cần xem xét ở đây. Đầu tiên là phân bổ nhiều hơn số lượng được chỉ định trong mã mẫu. Một dòng khác là dòng mã nào gây ra lệnh gọi brk đã cung cấp phân bổ.


Ngẫu nhiên bố cục không gian địa chỉ: ASLR

Khi bạn chạy ứng dụng ví dụ trên lần lượt, bạn sẽ thấy các giá trị địa chỉ khác nhau mỗi lần. Làm cho không gian địa chỉ thay đổi ngẫu nhiên theo cách này làm phức tạp đáng kể công việc của các cuộc tấn công bảo mật và tăng tính bảo mật của phần mềm.

Tuy nhiên, trong kiến ​​trúc 32 bit, tám bit thường được sử dụng để ngẫu nhiên hóa không gian địa chỉ. Việc tăng số lượng bit sẽ không thích hợp vì vùng có thể định địa chỉ trên các bit còn lại sẽ rất thấp. Ngoài ra, việc chỉ sử dụng kết hợp 8-bit không gây khó khăn cho kẻ tấn công.

Mặt khác, trong kiến ​​trúc 64 bit, vì có quá nhiều bit có thể được cấp phát cho hoạt động ASLR, độ ngẫu nhiên lớn hơn nhiều được cung cấp và mức độ bảo mật tăng lên.

Nhân Linux cũng cung cấp năng lượng cho các thiết bị dựa trên Android và tính năng ASLR được kích hoạt hoàn toàn trên Android 4.0.3 trở lên. Thậm chí chỉ vì lý do này, sẽ không sai khi nói rằng điện thoại thông minh 64 bit cung cấp lợi thế bảo mật đáng kể so với các phiên bản 32 bit.

Bằng cách tạm thời vô hiệu hóa tính năng ASLR bằng lệnh sau, có vẻ như ứng dụng thử nghiệm trước đó sẽ trả về các giá trị địa chỉ giống nhau mỗi khi nó được chạy:

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

Để khôi phục nó về trạng thái trước đó, chỉ cần ghi 2 thay vì 0 trong cùng một tệp.

Lệnh gọi hệ thống thứ hai: mmap

mmap là lệnh gọi hệ thống thứ hai được sử dụng để cấp phát bộ nhớ trên Linux. Với cuộc gọi mmap, không gian trống trong bất kỳ vùng nào của bộ nhớ được ánh xạ tới không gian địa chỉ của quá trình gọi.

Trong phân bổ bộ nhớ được thực hiện theo cách này, khi bạn muốn trả lại phân vùng 16KB thứ hai bằng hàm free () trong ví dụ brk trước đó, không có cơ chế nào ngăn hoạt động này. Đoạn bộ nhớ có liên quan bị xóa khỏi không gian địa chỉ của tiến trình. Nó được đánh dấu là không còn được sử dụng và trả lại hệ thống.

Vì cấp phát bộ nhớ với mmap rất chậm so với cấp phát brk, nên cần cấp phát brk.

Với mmap, bất kỳ vùng trống nào của bộ nhớ được ánh xạ tới không gian địa chỉ của tiến trình, vì vậy nội dung của không gian được cấp phát sẽ được đặt lại trước khi quá trình này hoàn tất. Nếu quá trình đặt lại không được thực hiện theo cách này, thì dữ liệu thuộc về quá trình trước đó đã sử dụng vùng bộ nhớ liên quan cũng có thể được truy cập bởi quá trình không liên quan tiếp theo. Điều này sẽ khiến chúng ta không thể nói về bảo mật trong hệ thống.

Tầm quan trọng của phân bổ bộ nhớ trong Linux

Cấp phát bộ nhớ là rất quan trọng, đặc biệt là trong vấn đề tối ưu hóa và bảo mật. Như đã thấy trong các ví dụ trên, việc không hiểu đầy đủ về vấn đề này có thể đồng nghĩa với việc phá hủy bảo mật hệ thống của bạn.

Ngay cả các khái niệm tương tự như push và pop tồn tại trong nhiều ngôn ngữ lập trình cũng dựa trên các hoạt động cấp phát bộ nhớ. Khả năng sử dụng và làm chủ tốt bộ nhớ hệ thống là điều cần thiết cả trong lập trình hệ thống nhúng và phát triển kiến ​​trúc hệ thống an toàn và tối ưu hóa.

Nếu bạn cũng muốn nhúng tay vào phát triển nhân Linux, trước tiên hãy cân nhắc việc thành thạo ngôn ngữ lập trình C.


ngôn ngữ lập trình c

Giới thiệu ngắn gọn về ngôn ngữ lập trình C

Đọc tiếp


Similar Posts

Leave a Reply

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