2009-03-25 36 views
15

Đối với các ứng dụng nhúng, nó thường là cần thiết để truy cập các vị trí bộ nhớ cố định cho các thanh ghi ngoại vi. Cách chuẩn mà tôi đã tìm thấy để làm điều này là một cái gì đó như sau:Biến địa chỉ cố định trong C

// access register 'foo_reg', which is located at address 0x100 
#define foo_reg *(int *)0x100 

foo_reg = 1;  // write to foo_reg 
int x = foo_reg; // read from foo_reg 

tôi hiểu làm thế nào mà làm việc, nhưng những gì tôi không hiểu là như thế nào không gian cho foo_reg được phân bổ (tức là những gì giữ cho mối liên kết từ đặt một biến khác ở 0x100?). Không gian có thể được đặt trước ở cấp độ C hay không phải có tùy chọn trình liên kết chỉ định rằng không có gì được đặt ở 0x100. Tôi đang sử dụng các công cụ GNU (gcc, ld, v.v.), do đó, tôi chủ yếu quan tâm đến các chi tiết cụ thể của bộ công cụ đó vào lúc này.

Một số thông tin thêm về kiến ​​trúc của tôi để làm rõ câu hỏi:

giao diện của tôi xử lý để một FPGA thông qua một tập các thanh ghi ánh xạ vào không gian dữ liệu thường xuyên (nơi biến sống) của bộ xử lý. Vì vậy, tôi cần phải trỏ đến những thanh ghi và chặn không gian địa chỉ liên quan. Trong quá khứ, tôi đã sử dụng một trình biên dịch có phần mở rộng để định vị các biến từ mã C. Tôi sẽ nhóm các thanh ghi vào một cấu trúc, sau đó đặt cấu trúc tại vị trí thích hợp:

typedef struct 
{ 
    BYTE reg1; 
    BYTE reg2; 
    ... 
} Registers; 

Registers regs _at_ 0x100; 

regs.reg1 = 0; 

Thực tế việc tạo cấu trúc 'Đăng ký' bảo toàn không gian trong trình biên dịch/mắt của người liên kết.

Bây giờ, bằng cách sử dụng các công cụ GNU, tôi rõ ràng không có số ở số. Sử dụng phương pháp con trỏ:

#define reg1 *(BYTE*)0x100; 
#define reg2 *(BYTE*)0x101; 
reg1 = 0 

// or 
#define regs *(Registers*)0x100 
regs->reg1 = 0; 

Đây là ứng dụng đơn giản không có hệ điều hành và không có quản lý bộ nhớ nâng cao. Về cơ bản:

void main() 
{ 
    while(1){ 
     do_stuff(); 
    } 
} 

Trả lời

12

Trình liên kết và trình biên dịch của bạn không biết về điều đó (dĩ nhiên bạn không nói gì cả). Tùy thuộc vào nhà thiết kế của ABI của nền tảng của bạn để xác định họ không phân bổ các đối tượng tại các địa chỉ đó. Vì vậy, đôi khi có một phạm vi trong không gian địa chỉ ảo được ánh xạ trực tiếp đến các địa chỉ vật lý và một dải khác có thể được sử dụng bởi các quy trình không gian người dùng để phát triển ngăn xếp hoặc phân bổ bộ nhớ heap.

Bạn có thể sử dụng tùy chọn defsym với GNU ld để phân bổ một số biểu tượng ở một địa chỉ cố định:

--defsym symbol=expression 

Hoặc nếu biểu thức là phức tạp hơn số học đơn giản, sử dụng một kịch bản tùy chỉnh mối liên kết. Đó là nơi bạn có thể xác định vùng bộ nhớ và cho người liên kết biết khu vực nào nên được trao cho phần/đối tượng nào. Xem here để được giải thích. Mặc dù đó thường là công việc chính xác của nhà văn của chuỗi công cụ bạn sử dụng. Họ lấy thông số kỹ thuật của ABI và sau đó viết các kịch bản liên kết và trình biên dịch/trình biên dịch kết thúc đáp ứng các yêu cầu của nền tảng của bạn.

Ngẫu nhiên, GCC có attribute section mà bạn có thể sử dụng để đặt cấu trúc của mình vào một phần cụ thể. Sau đó, bạn có thể yêu cầu trình liên kết đặt phần đó vào khu vực nơi đăng ký của bạn hoạt động.

Registers regs __attribute__((section("REGS"))); 
+0

Trên thực tế, trình liên kết thực hiện, thông qua tập lệnh trình liên kết xác định vùng bộ nhớ có sẵn để sử dụng. Nó định nghĩa (ví dụ) các vùng .text và .bss cho dữ liệu không đổi và dữ liệu biến động (RAM), tương ứng. – strager

+0

thực sự :) có nghĩa là để nói mà không làm bất cứ điều gì, không có cơ hội cho nó để biết về điều đó :) –

+0

Liệu 'defsym' thực sự phân bổ bất cứ điều gì ở tất cả tại một địa chỉ nhất định? AFAIK về cơ bản nó là một trình liên kết tương đương '# define', bạn thậm chí không thể xác định kích thước của đối tượng với nó, và nó sẽ không ngăn chặn mối liên kết đặt một biến khác tại cùng một địa chỉ. –

1

Thông thường các địa chỉ này nằm ngoài tầm với của quá trình của bạn. Vì vậy, mối liên kết của bạn sẽ không dám đặt công cụ ở đó.

+0

Bộ xử lý của tôi giao tiếp với một FPGA thông qua một bộ thanh ghi được ánh xạ vào không gian dữ liệu thông thường của bộ xử lý. Vì vậy, tôi cần phải trỏ đến những thanh ghi và chặn không gian địa chỉ liên quan. –

+0

Bạn có thực sự gặp phải tình huống có chồng chéo không? Mã đối tượng chứa các địa chỉ relocatable chủ yếu, và chúng được ánh xạ vào không gian tiến trình của người dùng khi chạy. – dirkgently

1

Nếu vị trí bộ nhớ có ý nghĩa đặc biệt trên kiến ​​trúc của bạn, trình biên dịch nên biết và không đặt bất kỳ biến nào ở đó. Điều đó sẽ tương tự như không gian bản đồ IO trên hầu hết các kiến ​​trúc. Nó không có kiến ​​thức rằng bạn đang sử dụng nó để lưu trữ các giá trị, nó chỉ biết rằng các biến bình thường không nên đến đó. Nhiều trình biên dịch nhúng hỗ trợ các phần mở rộng ngôn ngữ cho phép bạn khai báo các biến và hàm tại các vị trí cụ thể, thường sử dụng #pragma. Ngoài ra, nói chung cách tôi đã nhìn thấy mọi người thực hiện các loại ánh xạ bộ nhớ bạn đang cố gắng làm là để khai báo một int tại vị trí bộ nhớ mong muốn, sau đó chỉ cần coi nó như là một biến toàn cầu. Cách khác, bạn có thể khai báo một con trỏ tới một int và khởi tạo nó đến địa chỉ đó. Cả hai đều cung cấp nhiều loại an toàn hơn so với macro.

3

Khi hệ điều hành nhúng tải ứng dụng vào bộ nhớ, nó sẽ tải ứng dụng đó thường ở một số vị trí được chỉ định, cho phép nói 0x5000. Tất cả bộ nhớ cục bộ bạn đang sử dụng sẽ liên quan đến địa chỉ đó, tức là int x sẽ ở một nơi nào đó giống như kích thước 0x5000 + mã + 4 ... giả sử đây là một biến toàn cục. Nếu nó là một biến địa phương, nó nằm trên ngăn xếp. Khi bạn tham chiếu 0x100, bạn đang tham chiếu không gian bộ nhớ hệ thống, cùng một không gian mà hệ điều hành chịu trách nhiệm quản lý và có thể là một nơi rất cụ thể mà nó giám sát.

Trình liên kết sẽ không đặt mã tại các vị trí bộ nhớ cụ thể, nó hoạt động trong 'liên quan đến vị trí mã chương trình của tôi là' không gian bộ nhớ.

Điều này sẽ phân tích một chút khi bạn vào bộ nhớ ảo, nhưng đối với các hệ thống nhúng, điều này có xu hướng đúng.

Chúc mừng!

+0

Điều này chỉ đúng đối với [mã độc lập vị trí] (https://en.wikipedia.org/wiki/Position-independent_code) có thể hoặc không được sử dụng trong một hệ thống nhúng cụ thể. Hầu hết các hệ thống như vậy thậm chí không có hệ điều hành, mã chỉ nằm trong ROM và không có lý do để làm cho nó độc lập vị trí. –

9

Trình liên kết thường sẽ sử dụng tập lệnh trình liên kết để xác định nơi các biến được phân bổ. Điều này được gọi là phần "dữ liệu" và tất nhiên nên trỏ đến vị trí RAM. Do đó không thể cho một biến được phân bổ tại một địa chỉ không có trong RAM.

Bạn có thể đọc thêm về tập lệnh liên kết trong GCC here.

5

Trình liên kết của bạn xử lý vị trí của dữ liệu và các biến. Nó biết về hệ thống đích của bạn thông qua một kịch bản liên kết. Kịch bản liên kết xác định các vùng trong một số memory layout như .text (đối với dữ liệu và mã không đổi) và .bss (đối với biến toàn cục và vùng heap), và cũng tạo ra mối tương quan giữa địa chỉ ảo và vật lý (nếu cần). Đây là công việc của người duy trì kịch bản liên kết để đảm bảo rằng các phần có thể sử dụng bởi trình liên kết không ghi đè địa chỉ IO của bạn.

0

Điều này phụ thuộc một chút vào hệ điều hành bạn đang sử dụng. Tôi đoán bạn đang sử dụng một cái gì đó như DOS hoặc vxWorks. Nói chung, hệ thống sẽ có các vùng xác thực của không gian bộ nhớ dành riêng cho phần cứng và các trình biên dịch cho nền tảng đó sẽ luôn là đủ thông minh để tránh các khu vực đó cho phân bổ của riêng chúng. Nếu không, bạn sẽ liên tục viết rác ngẫu nhiên vào máy in đĩa hoặc đường khi bạn muốn truy cập vào các biến.

Trong trường hợp có điều gì khác khiến bạn khó hiểu, tôi cũng nên chỉ ra rằng #define là một chỉ thị tiền xử lý. Không có mã được tạo ra cho điều đó. Nó chỉ yêu cầu trình biên dịch thay thế bất kỳ văn bản foo_reg nào mà nó thấy trong tệp nguồn của bạn với *(int *)0x100. Nó không khác gì so với việc chỉ gõ *(int *)0x100 vào chính bạn ở mọi nơi bạn có foo_reg, ngoại trừ việc nó có thể trông sạch hơn.

gì tôi có lẽ sẽ làm thay (trong một trình biên dịch C hiện đại) là:

// access register 'foo_reg', which is located at address 0x100 
const int* foo_reg = (int *)0x100; 
*foo_reg = 1; // write to foo_regint 
x = *foo_reg; // read from foo_reg 
1

Để mở rộng về câu trả lời litb, bạn cũng có thể sử dụng {symbolfile} tùy chọn --just-symbols= để xác định một vài biểu tượng, trong trường hợp bạn có nhiều hơn một vài thiết bị được ánh xạ trên bộ nhớ. Các tập tin biểu tượng cần phải được theo định dạng

symbolname1 = address; 
symbolname2 = address; 
... 

(các không gian xung quanh dấu bằng dường như được yêu cầu.)

3

Lấy toolchain GCC cung cấp cho bạn một hình ảnh phù hợp để sử dụng trực tiếp trên phần cứng mà không cần một hệ điều hành để tải nó là có thể, nhưng liên quan đến một vài bước mà không bình thường cần thiết cho các chương trình bình thường.

  1. Bạn gần như chắc chắn sẽ cần tùy chỉnh mô-đun khởi động thời gian chạy C. Đây là mô-đun lắp ráp (thường được đặt tên là crt0.s) có trách nhiệm khởi tạo dữ liệu khởi tạo, xóa BSS, gọi hàm tạo cho các đối tượng chung nếu mô-đun C++ với các đối tượng chung được bao gồm, vv thực sự giải quyết RAM (có thể bao gồm thiết lập bộ điều khiển DRAM) để có một nơi để đặt dữ liệu và ngăn xếp. Một số CPU cần phải thực hiện những việc này trong một chuỗi cụ thể: ví dụ: Các ColdFire MCF5307 có một chip chọn đáp ứng mọi địa chỉ sau khi khởi động mà cuối cùng phải được cấu hình để chỉ bao gồm các khu vực của bản đồ bộ nhớ lên kế hoạch cho chip đính kèm.

  2. Đội phần cứng của bạn (hoặc bạn có mũ khác, có thể) nên có bản đồ bộ nhớ ghi lại những gì ở các địa chỉ khác nhau. ROM ở 0x00000000, RAM ở 0x10000000, thiết bị đăng ký tại 0xD0000000, v.v. Trong một số bộ vi xử lý, nhóm phần cứng chỉ có thể kết nối một chip chọn từ CPU với thiết bị và để nó quyết định địa chỉ nào kích hoạt .

  3. GNU ld hỗ trợ ngôn ngữ kịch bản trình liên kết rất linh hoạt cho phép các phần khác nhau của hình ảnh thực thi được đặt trong không gian địa chỉ cụ thể. Đối với lập trình thông thường, bạn không bao giờ thấy tập lệnh trình liên kết vì tập lệnh được cung cấp bởi gcc được điều chỉnh theo các giả định của hệ điều hành cho một ứng dụng thông thường.

  4. Đầu ra của trình liên kết ở định dạng có thể định vị lại được dự định được tải vào RAM bởi hệ điều hành. Nó có thể có sửa chữa di dời cần phải được hoàn thành, và thậm chí có thể tự động tải một số thư viện. Trong một hệ thống ROM, tải động là (thường) không được hỗ trợ, do đó bạn sẽ không làm điều đó. Nhưng bạn vẫn cần một hình ảnh nhị phân thô (thường ở định dạng HEX phù hợp cho một lập trình viên PROM của một số biểu mẫu), vì vậy bạn sẽ cần phải sử dụng tiện ích objcopy từ binutil để chuyển đổi đầu ra của trình liên kết sang định dạng phù hợp.

Vì vậy, để trả lời câu hỏi thực tế mà bạn hỏi ...

Bạn sử dụng một kịch bản mối liên kết để xác định địa chỉ mục tiêu của từng bộ phận của hình ảnh của chương trình của bạn. Trong kịch bản đó, bạn có một số tùy chọn để xử lý các thanh ghi thiết bị, nhưng tất cả chúng đều liên quan đến việc đặt các đoạn văn bản, dữ liệu, bss và phân đoạn heap trong các dải địa chỉ tránh các thanh ghi phần cứng. Ngoài ra còn có các cơ chế có sẵn mà có thể đảm bảo rằng ld ném một lỗi nếu bạn overfill ROM hoặc RAM của bạn, và bạn nên sử dụng những người là tốt.

Thực tế việc nhập địa chỉ thiết bị vào mã C có thể được thực hiện với ví dụ của bạn hoặc bằng cách khai báo một biểu tượng trực tiếp trong tập lệnh liên kết được giải quyết tới địa chỉ cơ sở của thanh ghi, với tuyên bố extern phù hợp một tệp tiêu đề C.

Mặc dù có thể sử dụng thuộc tính section của GCC để xác định trường hợp struct chưa được khởi tạo như được đặt trong một phần cụ thể (chẳng hạn như FPGA_REGS), tôi thấy rằng không hoạt động tốt trong các hệ thống thực. Nó có thể tạo ra các vấn đề bảo trì, và nó trở thành một cách đắt tiền để mô tả bản đồ đăng ký đầy đủ của các thiết bị trên chip. Nếu bạn sử dụng kỹ thuật đó, kịch bản trình liên kết sau đó sẽ chịu trách nhiệm ánh xạ FPGA_REGS đến đúng địa chỉ của nó.

Trong mọi trường hợp, bạn sẽ cần hiểu rõ khái niệm tệp đối tượng như "phần" (cụ thể là phần văn bản, dữ liệu và bss tối thiểu) và có thể cần phải theo dõi chi tiết khoảng cách giữa phần cứng và phần mềm như bảng vectơ ngắt, các ưu tiên ngắt, chế độ giám sát so với chế độ người dùng (hoặc đổ chuông 0 đến 3 trên các biến thể x86) và tương tự.

1

Thông thường, đối với phần mềm nhúng, bạn có thể xác định trong tệp liên kết một vùng RAM cho biến được liên kết chỉ định và khu vực riêng biệt cho các biến tại vị trí tuyệt đối mà trình liên kết sẽ không chạm.

Không thực hiện được điều này sẽ gây ra lỗi liên kết, vì nó sẽ phát hiện ra rằng nó đang cố gắng đặt một biến tại một vị trí đã được sử dụng bởi một biến có địa chỉ tuyệt đối.

Các vấn đề liên quan