Cách triển khai các phần quan trọng trên ARM Cortex A9


15

Tôi đang chuyển một số mã kế thừa từ lõi ARM926 sang CortexA9. Mã này là từ xa và không bao gồm hệ điều hành hoặc thư viện tiêu chuẩn, tất cả đều tùy chỉnh. Tôi đang gặp một lỗi dường như có liên quan đến tình trạng chủng tộc cần được ngăn chặn bằng cách phân đoạn mã quan trọng.

Tôi muốn có một số phản hồi về cách tiếp cận của tôi để xem các phần quan trọng của tôi có thể không được thực hiện chính xác cho CPU này hay không. Tôi đang sử dụng GCC. Tôi nghi ngờ có một số lỗi tinh tế.

Ngoài ra, có một thư viện mã nguồn mở có các loại nguyên thủy cho ARM (hoặc thậm chí là một thư viện spinlock / semephore nhẹ tốt) không?

#define ARM_INT_KEY_TYPE            unsigned int
#define ARM_INT_LOCK(key_)   \
asm volatile(\
    "mrs %[key], cpsr\n\t"\
    "orr r1, %[key], #0xC0\n\t"\
    "msr cpsr_c, r1\n\t" : [key]"=r"(key_) :: "r1", "cc" );

#define ARM_INT_UNLOCK(key_) asm volatile ("MSR cpsr_c,%0" : : "r" (key_))

Mã được sử dụng như sau:

/* lock interrupts */
ARM_INT_KEY_TYPE key;
ARM_INT_LOCK(key);

<access registers, shared globals, etc...>

ARM_INT_UNLOCK(key);

Ý tưởng của "khóa" là cho phép các phần quan trọng lồng nhau và chúng được sử dụng ở đầu và cuối của các hàm để tạo các hàm reentrant.

Cảm ơn!


1
vui lòng tham khảo infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/ không làm điều đó trong nhúng asm btw. làm cho nó một chức năng như bài viết nào.
Jason Hu

Tôi không biết gì về ARM, nhưng tôi hy vọng rằng đối với mutex (hoặc bất kỳ chức năng đồng bộ hóa chéo hoặc xử lý chéo nào), bạn nên sử dụng trình ghi chú "bộ nhớ" để đảm bảo rằng a) tất cả các giá trị bộ nhớ hiện được lưu trong bộ đăng ký sẽ bị xóa trở lại bộ nhớ trước khi thực hiện mã asm và b) mọi giá trị trong bộ nhớ được truy cập sau khi mã asm được tải lại. Lưu ý rằng việc thực hiện một cuộc gọi (như HuStmpHrrr khuyến nghị) nên ngầm thực hiện thao tác ghi đè này cho bạn.

Ngoài ra, trong khi tôi vẫn không nói ARM, các ràng buộc của bạn đối với 'key_' có vẻ không chính xác. Vì bạn nói rằng mục đích này được sử dụng để nhập lại, việc khai báo nó là "= r" trong khóa có vẻ đáng ngờ. '=' Có nghĩa là bạn có ý định ghi đè lên nó và giá trị hiện tại là không quan trọng. Có vẻ như bạn dự định sử dụng '+' để biểu thị ý định cập nhật giá trị hiện có. Và một lần nữa để mở khóa, liệt kê nó làm đầu vào cho gcc bạn không có ý định thay đổi nó, nhưng nếu tôi không nhầm, bạn sẽ làm (thay đổi nó). Tôi đoán điều này cũng nên được liệt kê dưới dạng đầu ra '+'.

1
+1 để mã hóa trong lắp ráp cho lõi spec cao như vậy. Dù sao, điều này có thể liên quan đến chế độ đặc quyền?
Dzarda

Tôi khá chắc chắn rằng bạn sẽ cần phải sử dụng ldrexstrexlàm điều đó đúng cách. Đây là một trang web chỉ cho bạn cách sử dụng ldrexstrexthực hiện một spinlock.

Câu trả lời:


14

Phần khó nhất khi xử lý một phần quan trọng mà không có HĐH là không thực sự tạo ra mutex, mà là tìm hiểu điều gì sẽ xảy ra nếu mã muốn sử dụng tài nguyên hiện không có sẵn. Các hướng dẫn độc quyền tải và không có cửa hàng có điều kiện giúp tạo ra một hàm "hoán đổi" khá dễ dàng, được cung cấp một con trỏ cho một số nguyên, về cơ bản sẽ lưu trữ một giá trị mới nhưng trả về những gì mà số nguyên nhọn đã chứa:

int32_t atomic_swap(int32_t *dest, int32_t new_value)
{
  int32_t old_value;
  do
  {
    old_value = __LDREXW(&dest);
  } while(__STREXW(new_value,&dest);
  return old_value;
}

Với một chức năng như trên, người ta có thể dễ dàng nhập một mutex thông qua một cái gì đó như

if (atomic_swap(&mutex, 1)==0)
{
   ... do stuff in mutex ... ;
   mutex = 0; // Leave mutex
}
else
{ 
  ... couldn't get mutex...
}

Trong trường hợp không có HĐH, khó khăn chính thường nằm ở mã "không thể có được mutex". Nếu ngắt xảy ra khi tài nguyên được bảo vệ bằng mutex đang bận, có thể cần phải có mã xử lý ngắt đặt cờ và lưu một số thông tin để cho biết những gì nó muốn làm, và sau đó có bất kỳ mã giống như chính nào có được kiểm tra mutex bất cứ khi nào nó sẽ phát hành mutex để xem liệu một ngắt có muốn làm gì không trong khi mutex được giữ và nếu vậy, thực hiện hành động thay mặt cho ngắt.

Mặc dù có thể tránh các sự cố với các ngắt muốn sử dụng các tài nguyên được bảo vệ bằng cách đơn giản bằng cách vô hiệu hóa các ngắt (và thực tế, vô hiệu hóa các ngắt có thể loại bỏ sự cần thiết của bất kỳ loại đột biến nào khác), nói chung, mong muốn tránh vô hiệu hóa các ngắt lâu hơn cần thiết.

Một thỏa hiệp hữu ích có thể là sử dụng một cờ như được mô tả ở trên, nhưng có mã dòng chính sẽ giải phóng các ngắt vô hiệu hóa mutex và kiểm tra cờ đã nói ở trên trước khi thực hiện (kích hoạt lại các ngắt sau khi phát hành mutex). Cách tiếp cận như vậy không yêu cầu để các ngắt bị vô hiệu hóa rất lâu, nhưng sẽ bảo vệ khả năng rằng nếu mã dòng chính kiểm tra cờ của ngắt sau khi phát hành mutex, có một mối nguy hiểm là giữa thời gian nó nhìn thấy cờ và thời gian nó hành động theo nó, nó có thể bị đánh cắp bởi mã khác mua lại và giải phóng mutex và hành động theo cờ ngắt; nếu mã dòng chính không kiểm tra cờ của ngắt sau khi phát hành mutex,

Trong mọi trường hợp, điều quan trọng nhất sẽ là có một phương tiện mà mã cố gắng sử dụng tài nguyên được bảo vệ bằng mutex khi không có sẵn sẽ có phương tiện lặp lại nỗ lực của nó sau khi tài nguyên được phát hành.


7

Đây là một cách nặng tay để làm các phần quan trọng; vô hiệu hóa ngắt. Nó có thể không hoạt động nếu hệ thống của bạn có / xử lý lỗi dữ liệu. Nó cũng sẽ làm tăng độ trễ ngắt. Các irqflags.h Linux có một số macro có thể xử lý này. Các hướng dẫn cpsiecpsidcó thể hữu ích; Tuy nhiên, chúng không lưu trạng thái và sẽ không cho phép lồng nhau. cpskhông sử dụng một đăng ký.

Đối với sê-ri Cortex-A , ldrex/strexhiệu quả hơn và có thể hoạt động để tạo ra một mutex cho phần quan trọng hoặc chúng có thể được sử dụng với các thuật toán không khóa để thoát khỏi phần quan trọng.

Ở một khía cạnh nào đó, ldrex/strexcó vẻ như ARMv5 swp. Tuy nhiên, chúng phức tạp hơn nhiều để thực hiện trong thực tế. Bạn cần một bộ đệm làm việc và bộ nhớ đích của các ldrex/strexnhu cầu cần có trong bộ đệm. Tài liệu ARM về điều ldrex/strexnày khá mơ hồ vì họ muốn các cơ chế hoạt động trên các CPU không phải Cortex-A. Tuy nhiên, đối với Cortex-A, cơ chế giữ đồng bộ bộ đệm CPU cục bộ với các CPU khác là cùng một cơ chế được sử dụng để thực hiện các ldrex/strexhướng dẫn. Đối với sê-ri Cortex-A, phần hạt dự trữ (kích thước của ldrex/strexbộ nhớ dự trữ) giống như dòng bộ đệm; bạn cũng cần căn chỉnh bộ nhớ với dòng bộ đệm nếu bạn có ý định sửa đổi nhiều giá trị, như với danh sách liên kết đôi.

Tôi nghi ngờ có một số lỗi tinh tế.

mrs %[key], cpsr
orr r1, %[key], #0xC0  ; context switch here?
msr cpsr_c, r1

Bạn cần đảm bảo rằng chuỗi không bao giờ có thể bị xóa trước . Nếu không, bạn có thể nhận được hai biến chính với các ngắt được kích hoạt và việc phát hành khóa sẽ không chính xác. Bạn có thể sử dụng swphướng dẫn với bộ nhớ chính để đảm bảo tính nhất quán trên ARMv5, nhưng hướng dẫn này không được chấp nhận trên Cortex-A ldrex/strexvì nó hoạt động tốt hơn cho các hệ thống nhiều CPU.

Tất cả điều này phụ thuộc vào loại lịch trình mà hệ thống của bạn có. Có vẻ như bạn chỉ có đường chính và ngắt. Bạn thường cần các nguyên thủy của phần quan trọng để có một số móc nối với bộ lập lịch tùy thuộc vào mức độ nào (hệ thống / không gian người dùng / v.v.) mà bạn muốn phần quan trọng hoạt động.

Ngoài ra, có một thư viện mã nguồn mở có các loại nguyên thủy cho ARM (hoặc thậm chí là một thư viện spinlock / semephore nhẹ tốt) không?

Điều này rất khó để viết theo cách di động. Tức là, các thư viện như vậy có thể tồn tại cho các phiên bản nhất định của CPU ARM và cho các HĐH cụ thể.


2

Tôi thấy một số vấn đề tiềm năng với những phần quan trọng. Có những cảnh báo và giải pháp cho tất cả những điều này, nhưng như một bản tóm tắt:

  • Không có gì ngăn trình biên dịch di chuyển mã trên các macro này, để tối ưu hóa hoặc ngẫu nhiên các lý do khác.
  • Họ lưu và khôi phục một số phần của bộ xử lý trạng thái trình biên dịch dự kiến ​​lắp ráp nội tuyến để riêng (trừ khi nó được nói khác).
  • Không có gì ngăn cản một sự gián đoạn xảy ra ở giữa chuỗi và thay đổi trạng thái giữa khi nó được đọc và khi nó được viết.

Trước hết, bạn chắc chắn cần một số rào cản bộ nhớ trình biên dịch . GCC thực hiện những điều này như là clobbers . Về cơ bản, đây là một cách để nói với trình biên dịch "Không, bạn không thể di chuyển các truy cập bộ nhớ qua phần lắp ráp nội tuyến này vì nó có thể ảnh hưởng đến kết quả của việc truy cập bộ nhớ." Cụ thể, bạn cần cả hai "memory"và trình "cc"chặn, trên cả macro bắt đầu và kết thúc. Những điều này sẽ ngăn những thứ khác (như các lệnh gọi hàm) được sắp xếp lại so với cụm nội tuyến, bởi vì trình biên dịch biết rằng chúng có thể có quyền truy cập bộ nhớ. Tôi đã thấy trạng thái giữ GCC cho ARM trong các thanh ghi mã điều kiện trong quá trình lắp ráp nội tuyến với "memory"các trình "cc"thu thập dữ liệu , vì vậy bạn chắc chắn cần trình ghi đè.

Thứ hai, các phần quan trọng này đang tiết kiệm và khôi phục rất nhiều hơn là chỉ cho phép ngắt. Cụ thể, họ đang lưu và khôi phục hầu hết CPSR (Đăng ký trạng thái chương trình hiện tại) (liên kết dành cho Cortex-R4 vì tôi không thể tìm thấy một sơ đồ đẹp cho A9, nhưng nó phải giống hệt nhau). Có những hạn chế tinh tế xung quanh những phần trạng thái thực sự có thể được sửa đổi, nhưng nó cần thiết hơn ở đây.

Trong số những thứ khác, điều này bao gồm các mã điều kiện (trong đó kết quả của các hướng dẫn như cmpđược lưu trữ để các hướng dẫn có điều kiện tiếp theo có thể tác động đến kết quả). Trình biên dịch chắc chắn sẽ bị nhầm lẫn bởi điều này. Điều này có thể dễ dàng giải quyết bằng cách sử dụng "cc"clobber như đã đề cập ở trên. Tuy nhiên, điều này sẽ khiến mã bị lỗi mỗi lần, vì vậy nó không giống như những gì bạn đang gặp vấn đề. Tuy nhiên, phần nào đó của một quả bom hẹn giờ, trong đó việc sửa đổi mã ngẫu nhiên khác có thể khiến trình biên dịch làm một cái gì đó hơi khác một chút sẽ bị phá vỡ bởi điều này.

Điều này cũng sẽ cố gắng lưu / khôi phục các bit IT, được sử dụng để thực hiện thực thi có điều kiện Thumb . Lưu ý rằng nếu bạn không bao giờ thực thi mã Thumb, điều này không thành vấn đề. Tôi chưa bao giờ tìm ra cách lắp ráp nội tuyến của GCC đối phó với các bit CNTT, ngoài việc kết luận là không, có nghĩa là trình biên dịch không bao giờ đặt lắp ráp nội tuyến trong một khối CNTT và luôn mong muốn việc lắp ráp kết thúc bên ngoài khối CNTT. Tôi chưa bao giờ thấy GCC tạo mã vi phạm các giả định này và tôi đã thực hiện một số lắp ráp nội tuyến khá phức tạp với tối ưu hóa nặng, vì vậy tôi chắc chắn rằng họ nắm giữ một cách hợp lý. Điều này có nghĩa là nó có thể sẽ không thực sự cố gắng thay đổi các bit IT, trong trường hợp đó mọi thứ đều ổn. Cố gắng sửa đổi các bit này được phân loại là "không thể đoán trước về mặt kiến ​​trúc", vì vậy nó có thể làm tất cả những điều tồi tệ, nhưng có lẽ sẽ không làm gì cả.

Loại bit cuối cùng sẽ được lưu / khôi phục (bên cạnh các bit thực sự vô hiệu hóa ngắt) là các bit chế độ. Chúng có thể sẽ không thay đổi, vì vậy nó có thể không thành vấn đề, nhưng nếu bạn có bất kỳ mã nào cố tình thay đổi chế độ thì các phần ngắt này có thể gây ra sự cố. Thay đổi giữa chế độ đặc quyền và người dùng là trường hợp duy nhất để làm điều này tôi mong đợi.

Thứ ba, không có gì ngăn cản sự thay đổi các phần khác của CPSR giữa MRSMSRtrong ARM_INT_LOCK. Bất kỳ thay đổi như vậy có thể được ghi đè. Trong hầu hết các hệ thống hợp lý, các ngắt không đồng bộ không làm thay đổi trạng thái của mã mà chúng bị gián đoạn (bao gồm cả CPSR). Nếu họ làm như vậy, sẽ rất khó để suy luận về những gì mã sẽ làm. Tuy nhiên, điều đó là có thể (thay đổi bit vô hiệu hóa FIQ dường như rất có thể với tôi), vì vậy bạn nên xem xét nếu hệ thống của bạn thực hiện việc này.

Đây là cách tôi sẽ thực hiện những điều này theo cách giải quyết tất cả các vấn đề tiềm ẩn mà tôi đã chỉ ra:

#define ARM_INT_KEY_TYPE            unsigned int
#define ARM_INT_LOCK(key_)   \
asm volatile(\
    "mrs %[key], cpsr\n\t"\
    "ands %[key], %[key], #0xC0\n\t"\
    "cpsid if\n\t" : [key]"=r"(key_) :: "memory", "cc" );
#define ARM_INT_UNLOCK(key_) asm volatile (\
    "tst %[key], #0x40\n\t"\
    "beq 0f\n\t"\
    "cpsie f\n\t"\
    "0: tst %[key], #0x80\n\t"\
    "beq 1f\n\t"\
    "cpsie i\n\t"
    "1:\n\t" :: [key]"r" (key_) : "memory", "cc")

Đảm bảo biên dịch với -mcpu=cortex-a9vì ít nhất một số phiên bản GCC (như của tôi) mặc định là CPU ARM cũ không hỗ trợ cpsiecpsid.

Tôi đã sử dụng andsthay vì chỉ andtrong ARM_INT_LOCKmột hướng dẫn 16 bit nếu điều này được sử dụng trong mã Thumb. Các "cc"clobber là cần thiết dù sao, do đó, nó hoàn toàn là một lợi ích kích thước hiệu suất / code.

01nhãn địa phương , để tham khảo.

Chúng nên được sử dụng theo tất cả các cách giống như các phiên bản của bạn. Nó ARM_INT_LOCKchỉ nhanh / nhỏ như bản gốc của bạn. Thật không may, tôi không thể đưa ra một cách để làm ARM_INT_UNLOCKmột cách an toàn ở bất cứ nơi nào gần như một vài hướng dẫn.

Nếu hệ thống của bạn có các ràng buộc về thời điểm IRQ và FIQ bị vô hiệu hóa, điều này có thể được đơn giản hóa. Ví dụ: nếu chúng luôn bị vô hiệu hóa cùng nhau, bạn có thể kết hợp thành một cbz+ cpsie ifnhư thế này:

#define ARM_INT_UNLOCK(key_) asm volatile (\
    "cbz %[key], 0f\n\t"\
    "cpsie if\n\t"\
    "0:\n\t" :: [key]"r" (key_) : "memory", "cc")

Ngoài ra, nếu bạn không quan tâm đến FIQ thì nó cũng tương tự như việc tắt bật / tắt hoàn toàn chúng.

Nếu bạn biết rằng không có gì khác thay đổi bất kỳ bit trạng thái nào khác trong CPSR giữa khóa và mở khóa, thì bạn cũng có thể sử dụng tiếp tục với một cái gì đó rất giống với mã gốc của bạn, ngoại trừ cả hai "memory"và mã bị "cc"chặn trong cả hai ARM_INT_LOCKARM_INT_UNLOCK


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.