RAM MEMORY: Cấu trúc, chức năng và thực hành debug trên VĐK STM32 (Phần 1)

Tại sao cần nắm vững kiến thức về RAM?

Trong lĩnh vực Lập trình nhúng, đặc biệt đối với các hệ thống sử dụng Vi điều khiển, việc quản lý và sử dụng hiệu quả tài nguyên bộ nhớ là một vấn đề mà người lập trình cần quan tâm. Việc hiểu về cấu trúc và cơ chế hoạt động của bộ nhớ RAM sẽ giúp cho chúng ta:

  • Tối ưu việc sử dụng bộ nhớ một cách hiệu quả, giảm thiểu tình trạng thiếu hụt tài nguyên, từ đó nâng cao hiệu suất và độ ổn định của hệ thống. Ngoài ra, việc tối ưu còn giảm tải việc sinh ra bug trong quá trình phát triển sản phẩm.
  • Debug một cách hiệu quả khi gặp các lỗi thường gặp liên quan đến RAM như xung đột dữ liệu (data corruption), tràn bộ đệm (buffer overflow), hoặc rò rỉ bộ nhớ (memory leak). 

Chuỗi bài viết này sẽ cùng bạn khám phá bộ nhớ RAM từ cấu trúc, chức năng, nguyên lý hoạt động đến các kỹ thuật thực hành và gỡ lỗi trên nền tảng vi điều khiển STM32.

–o–

Phần 1. Tổng quan về RAM. Cấu trúc và chức năng của phân vùng Data và BSS

 

1. Ý nghĩa của từ khoá “RAM”

RAM – Random-access memory (Bộ nhớ truy cập ngẫu nhiên). Dựa theo tên đầy đủ của RAM, chúng ta sẽ phân tích với 2 đặc tính “access” và “random”:

  • Access” – “truy cập”: đề cập đến các thao tác cơ bản của CPU trên bộ nhớ, đó là hành động đọc, ghi và hiệu chỉnh dữ liệu.
  • Random” – “ngẫu nhiên”: CPU có thể truy cập trực tiếp vào bất kỳ địa chỉ cụ thể nào trên bộ nhớ. Khác với những loại bộ nhớ truy cập tuần tự như HDD (Hard Disk Drive – Ổ cứng), đĩa CD, DVD, dữ liệu được đọc và ghi theo thứ tự, để đến được một địa chỉ cụ thể, phải truy cập lần lượt qua các địa chỉ trước đó.

2. Phân loại RAM

Bộ nhớ RAM bao gồm 2 loại kiến trúc cơ bản: DRAM (Dynamic RAM – RAM động) và SRAM (Static RAM – RAM tĩnh). Sự khác nhau giữa 2 kiến trúc này nằm ở cấu trúc tế bào bộ nhớ (memory cell) và cơ chế duy trì dữ liệu.

  • DRAM: Cấu trúc của một cell DRAM gồm 1 transister (vai trò như công tắc điều khiển) và 1 tụ điện (vai trò lưu trữ giá trị bit dưới dạng điện tích). Một vấn đề trong thực tế là các tụ điện bị rò rỉ điện tích theo thời gian. Do đó, để duy trì dữ liệu được lưu trữ trong bộ nhớ, các tụ điện phải được nạp lại định kỳ (Refresh). Chính lí do này mà người ta gọi là RAM động.
  • SRAM: Cấu trúc của một cell SRAM là một mạch flip-flop gồm các transistor, cần có nguồn điện để duy trì trạng thái của mạch flip-flop. Vì vậy SRAM không cần refresh như DRAM và được gọi là RAM tĩnh.

Điểm chung của DRAM và SRAM là đều phụ thuộc vào nguồn điện cung cấp thì mới duy trì được dữ liệu trên bộ nhớ. Toàn bộ dữ liệu lưu trữ trong RAM sẽ mất khi nguồn cung cấp bị ngắt. Đó chính là đặc điểm của Volatile memory.

3. Đặc tính của RAM

Một số đặc tính của RAM:

  • Volatile – tính bay hơi: các tín hiệu logic lưu trữ trong cell cần phải có nguồn cung cấp. Nếu thiết bị mất nguồn, dữ liệu trong RAM sẽ bị mất hoàn toàn.
  • Tốc độ truy cập cao: giúp cho việc thực hiện các tác vụ nhanh chóng.
  • Dung lượng RAM trên vi điều khiển càng lớn, càng hỗ trợ tối ưu việc xử lý dữ liệu lớn như hình ảnh, âm thanh, video, truy xuất dữ liệu nhanh do không cần lưu trữ trên bộ nhớ ngoài, thực hiện các tác vụ đồng thời.  
  • Giá thành cao hơn so với các bộ nhớ khác như như ổ cứng (HDD), ổ cứng thể rắn (SSD) hay bộ nhớ flash.

Với những đặc tính trên, RAM đóng vai trò là nơi lưu trữ dữ liệu tạm thời trong quá trình chương trình thực thi.

Tiếp theo, chúng ta cùng tìm hiểu về cơ chế lưu trữ dữ liệu trên kiến trúc bộ nhớ RAM.

4. Kiến trúc chung của RAM

Bộ nhớ RAM bao gồm 4 phân vùng cơ bản (phân bổ từ địa chỉ thấp lên cao): Data, BSS, Heap, Stack. 

Hình 1. Cấu trúc các phân vùng của bộ nhớ RAM

4.1. Phân vùng Initialized Data (Data) – Phân vùng dữ liệu khởi tạo 

  • Địa chỉ của phân vùng Initialized Data chính là địa chỉ bắt đầu của bộ nhớ RAM. Initialized data chỉ đơn giản là dữ liệu của biến được khởi tạo với giá trị khác 0.
  • Phân vùng Initialized Data chứa biến toàn cục, biến static được khởi tạo với giá trị khác 0 : các biến được lưu trữ giá trị tại khu vực này có quyền đọc và ghi, nghĩa là các biến có thể được thay đổi giá trị trong quá trình chương trình thực thi.Để làm rõ hơn, chúng ta sẽ tiến hành phân tích chi tiết thông qua một số chương trình minh họa được cung cấp.

Trước khi đi vào các ví dụ cụ thể, chúng ta sẽ cùng nhau thiết lập môi trường phát triển để hỗ trợ quá trình gỡ lỗi (debug).

+ Công cụ: Chúng ta sẽ sử dụng STM32CubeIDE để biên dịch các chương trình ví dụ. Để thực hành, bạn đọc cần chuẩn bị một kit phát triển STM32. Trong chuỗi bài viết này, mình sẽ sử dụng kit STM32F103C6T6. Tuy nhiên, bạn hoàn toàn có thể sử dụng bất kỳ kit STM32 nào khác, vì quy trình triển khai là tương tự.

+ File map: Trong thư mục Debug sẽ sinh ra file map – file cung cấp chi tiết về dữ liệu của chương trình được sắp xếp trong bộ nhớ (Flash và RAM) của vi điều khiển.

Hình 2. Vị trí file map (*.map) trong cây thư mục dự án

+ Cửa sổ Memory: công cụ gỡ lỗi vô cùng quan trọng, cho phép bạn quan sát trực tiếp nội dung của bộ nhớ (RAM và Flash) của vi điều khiển STM32 trong quá trình debug. 

Hình 3. Các bước để truy cập cửa sổ Memory trong debug

Ví dụ 1: Chương trình với biến toàn cục và biến static mang giá trị khởi tạo khác 0.

Phân tích file map, chúng ta biết được địa chỉ bắt đầu của phân vùng data và địa chỉ lưu trữ giá trị cho các biến toàn cục và biến static được khai báo trong chương trình.

Hình 4. Địa chỉ và kích thước của biến toàn cục và biến static trong phân vùng Data tại file map 

Sau khi biết được địa chỉ bắt đầu của phân vùng Data (0x20000000) và địa chỉ lưu trữ giá trị các biến, mình tiếp tục Debug và kiểm tra các giá trị được lưu trữ tại cửa sổ Memory của STM32 CubeIDE.

Hình 5. Debug giá trị khởi tạo và giá trị sau hiệu chỉnh của các biến tại phân vùng Data

Ví dụ 2: Chương trình với biến chuỗi chứa dữ liệu string literal

Hình 6. Địa chỉ và kích thước của các biến trong phân vùng Data tại file map

Biến toàn cục month2rd và biến static tapitslogan sẽ được lưu trữ tại phân vùng Data. Điểm khác biệt ở đây là các giá trị literal. Giá trị literal sẽ được phân bổ tại phân vùng Read Only Data (rodata) thuộc bộ nhớ ROM. 

Biến con trỏ tapitslogan lưu trữ địa chỉ bắt đầu của chuỗi literal “Learning-Research-Sharing” là 0x08001210.

Hình 7. Debug dữ liệu biến tại phân vùng ROData (ROM) và Data (RAM)

4.2. Phân vùng Uninitialized Data (BSS) – Phân vùng dữ liệu không khởi tạo

  • Uninitialized Data là dữ liệu của biến không được khởi tạo giá trị hoặc được khởi tạo với giá trị 0.
  • Phân vùng này thường được gọi là BSS (viết tắt của Block Started by Symbol), chứa biến toàn cục và biến static. Các biến này sẽ được thiết lập về giá trị 0 khi chương trình bắt đầu khởi chạy. “Block Started by Symbol” nghĩa là khối bộ nhớ được xác định bởi ký hiệu, ký hiệu ở đâu được hiểu là biến không chứa dữ liệu.

Ví dụ 3: Chương trình với biến toàn cục và biến static mang giá trị khởi tạo bằng 0.

Dựa vào file map, chúng ta biết được địa chỉ bắt đầu của phân vùng BSS (0x20000060) và địa chỉ lưu trữ giá trị cho các biến toàn cục và biến static được khởi tạo bằng 0.

Hình 8. Địa chỉ và kích thước của các biến trong phân vùng BSS tại file map

Hình 9. Debug giá trị khởi tạo và giá trị sau hiệu chỉnh của các biến tại phân vùng BSS

Qua các chương trình ví dụ, chúng ta đã tiến hành phân tích chi tiết địa chỉ của các biến trong các phân vùng bộ nhớ. Đồng thời, việc debug đã giúp chúng ta quan sát giá trị khởi tạo và sự cập nhật giá trị của từng biến. Để hiểu rõ hơn cách dữ liệu được lưu trữ trên các phân vùng Data và BSS, bạn nên thực hành viết các chương trình đơn giản và thực hiện gỡ lỗi.

Qua bài viết này, mình hy vọng mang đến cái nhìn tổng quan về cấu trúc bộ nhớ RAM; cũng như làm rõ đặc điểm chức năng và hoạt động của hai phân vùng Data và BSS.

Cùng đón chờ phần 2 của chuỗi bài viết này nhé, chúng ta sẽ tiếp tục phân tích hai phân vùng còn lại là Heap và Stack. Đây là hai phân vùng quan trọng có ảnh hưởng trực tiếp đến hiệu suất và tính ổn định của chương trình.

Chúc các bạn thành công!

N.T.Nhien

Tìm hiểu thêm:
Fanpage TAPIT: TAPIT – AIoT Learning