2014-05-23 14 views
16

Tôi đã nhìn thấy mã cho Arduino và phần cứng khác có lắp ráp nội tuyến với C, một cái gì đó dọc theo dòng:Làm thế nào để bao gồm lắp ráp nội tuyến với mã C làm việc?

asm("movl %ecx %eax"); /* moves the contents of ecx to eax */ 
__asm__("movb %bh (%eax)"); /*moves the byte from bh to the memory pointed by eax */ 

Làm thế nào thực hiện điều này thực sự làm việc ? Tôi nhận ra mỗi trình biên dịch là khác nhau, nhưng những lý do phổ biến này được thực hiện là gì, và làm thế nào ai đó có thể tận dụng điều này?

Trả lời

9

Mã lắp ráp nội tuyến đi ngay vào mã được lắp ráp hoàn chỉnh không bị ảnh hưởng và trong một phần. Bạn làm điều này khi bạn thực sự cần kiểm soát hoàn toàn toàn bộ chuỗi lệnh của bạn, hoặc có thể khi bạn không đủ khả năng để một người tối ưu hóa có cách của nó với mã của bạn. Có lẽ bạn cần đánh dấu đồng hồ. Có lẽ bạn cần mọi nhánh mã của bạn để lấy chính xác số lượng đồng hồ của đồng hồ, và bạn dùng phím NOP để thực hiện điều này.

Trong mọi trường hợp, rất nhiều lý do khiến ai đó có thể muốn thực hiện việc này, nhưng bạn thực sự cần phải biết mình đang làm gì. Những đoạn mã sẽ khá mờ đục đối với trình biên dịch của bạn, và có khả năng bạn sẽ không nhận được bất kỳ cảnh báo nào nếu bạn đang làm điều gì đó xấu.

+2

Để theo dõi "bạn thực sự cần biết mình đang làm gì". Lời cảnh cáo rằng các dòng chính xác của lắp ráp mà bạn có ý định nội tuyến, không phải luôn luôn được vinh danh chính xác bởi trình biên dịch. Tôi đã trải qua các trình biên dịch tạo ra những thay đổi tinh tế. Luôn luôn có một cái nhìn tốt về tháo gỡ sau khi biên dịch để đảm bảo nó làm chính xác những gì được dự định. – BabaBooey

+1

Tôi tin rằng chúng được vinh danh chính xác nếu bạn cũng thêm công cụ chỉ định 'biến động ', ví dụ: '__asm__ volatile (" instrs ");' – slugonamission

+0

Bạn cũng cần phải biết bộ xử lý nào (gia đình) mã của bạn sẽ chạy trên và, trong một số trường hợp, mô hình bộ nhớ nào. ANSI-C có thể được biên dịch cho hầu như mọi bộ xử lý và hệ điều hành nhưng hướng dẫn asm là * không * xách tay. –

4

Thông thường trình biên dịch sẽ chỉ chèn các lệnh lắp ráp ngay vào đầu ra của bộ tạo được tạo ra của nó. Và nó sẽ làm điều này mà không quan tâm đến hậu quả.

Ví dụ, trong mã này, trình tối ưu hóa đang thực hiện sao chép bản sao, nhờ đó nó thấy rằng y = x, sau đó z = y. Vì vậy, nó thay thế z = y bằng z = x, hy vọng rằng điều này sẽ cho phép nó thực hiện các tối ưu hóa hơn nữa. Howver, nó không phát hiện ra rằng tôi đã sai lầm với giá trị của x trong thời gian có nghĩa là.

char x=6; 
char y,z; 

y=x;     // y becomes 6 

_asm      
    rrncf x, 1  // x becomes 3. Optimiser doesn't see this happen! 
_endasm 

z=y;     // z should become 6, but actually gets 
        // the value of x, which is 3 

Để làm được việc này, bạn về cơ bản có thể nói optimizer không để thực hiện tối ưu hóa này cho biến này.

volatile char x=6; // Tell the compiler that this variable could change 
        // all by itself, and any time, and therefore don't 
        // optimise with it. 
char y,z; 

y=x;     // y becomes 6 

_asm      
    rrncf x, 1  // x becomes 3. Optimiser doesn't see this happen! 
_endasm 

z=y;     // z correctly gets the value of y, which is 6 
+4

Sẽ "biến động char y, z;" ngăn chặn lỗi tối ưu hóa đó? –

+0

@ScottSeidman: Có, cụ thể là 'bay hơi char y'. Trình biên dịch sẽ đảm bảo gán giá trị 'y' thực tế chứ không phải giá trị được tối ưu hóa gần đây được ghi vào x, và sau đó là y '. Chỉ làm cho 'z' dễ bay hơi sẽ không giúp được gì. –

3

asm ("")__asm__ đều sử dụng hợp lệ. Về cơ bản, bạn có thể sử dụng __asm__ nếu từ khóa asm xung đột với nội dung nào đó trong chương trình của bạn. Nếu bạn có nhiều hướng dẫn, bạn có thể viết một dòng trên mỗi dòng trong dấu ngoặc kép và cũng có thể thêm một số ’\ n’’\ t’ vào hướng dẫn. Điều này là do gcc gửi mỗi lệnh dưới dạng chuỗi thành (GAS) và bằng cách sử dụng dòng/tab mới, bạn có thể gửi các dòng được định dạng chính xác đến trình biên dịch. Đoạn mã trong câu hỏi của bạn là nội tuyến cơ bản.

Trong cơ bản lắp ráp nội tuyến, chỉ có hướng dẫn. Trong lắp ráp mở rộng, bạn cũng có thể chỉ định các toán hạng . Nó cho phép bạn chỉ định các thanh ghi đầu vào, thanh ghi đầu ra và danh sách các thanh ghi được ghi đè. Không bắt buộc phải chỉ định sổ đăng ký sử dụng, bạn có thể để điều đó cho GCC và có thể phù hợp với lược đồ tối ưu hóa của GCC tốt hơn. Một ví dụ cho asm mở rộng là:

__asm__ ("movl %eax, %ebx\n\t" 
      "movl $56, %esi\n\t" 
      "movl %ecx, $label(%edx,%ebx,$4)\n\t" 
      "movb %ah, (%ebx)"); 

Chú ý rằng '\ n \ t' vào cuối mỗi dòng trừ người cuối cùng, và mỗi dòng được kèm theo trong dấu ngoặc kép.Điều này là do gcc gửi mỗi lệnh dưới dạng một chuỗi như tôi đã đề cập trước đây. Sự kết hợp dòng mới/tab là bắt buộc để các dòng được cho ăn theo đúng định dạng.

+0

Mức độ cụ thể này đối với gcc như thế nào? –

+0

Đây là một ví dụ ** 'as' ** assembler mà GNU C Compiler sử dụng như một backend. Và assmbler này sử dụng ** cú pháp AT & T **. – gbudan

+0

Một vài sửa đổi: 1) __asm__ thay vì asm cũng được yêu cầu nếu bạn đang biên dịch theo tiêu chuẩn ISO. 2) Trong khi các '\ t' có thể được yêu cầu bởi một số lắp ráp, cho phần còn lại nó chỉ làm cho sản lượng asm của bạn khá. 3) Mô tả của bạn về cơ bản vs mở rộng là chính xác, tuy nhiên mẫu bạn đăng cho gcc là "cơ bản" không "mở rộng". Xem tài liệu về cơ bản và mở rộng tại https://gcc.gnu.org/onlinedocs/gcc/Using-Assembly-Language-with-C.html –

4

Trong lịch sử, các trình biên dịch C tạo ra mã assembly, sau đó sẽ được một bộ lắp ráp dịch sang mã máy. Việc lắp ráp nội tuyến phát sinh như một tính năng đơn giản - trong mã lắp ráp trung gian, tại thời điểm đó, tiêm một số mã do người dùng chọn. Một số trình biên dịch trực tiếp tạo ra mã máy, trong trường hợp này chúng chứa một bộ lắp ráp hoặc gọi một bộ ghép bên ngoài để tạo mã máy cho các đoạn mã lắp ráp nội tuyến.

Cách sử dụng phổ biến nhất cho mã lắp ráp là sử dụng các hướng dẫn xử lý chuyên biệt mà trình biên dịch không thể tạo. Ví dụ, vô hiệu hóa các ngắt cho một phần quan trọng, kiểm soát các tính năng của bộ xử lý (cache, MMU, MPU, quản lý điện năng, truy vấn CPU,…), truy cập các bộ xử lý và thiết bị ngoại vi phần cứng (ví dụ: inb/outb hướng dẫn trên x86), v.v. hiếm khi tìm thấy asm("movl %ecx %eax"), bởi vì điều đó ảnh hưởng đến các thanh ghi đa năng mà mã C xung quanh nó cũng đang sử dụng, nhưng một cái gì đó như asm("mcr p15, 0, 0, c7, c10, 5") đã sử dụng nó (hàng rào bộ nhớ dữ liệu trên ARM). OSDev wiki có một số ví dụ với đoạn mã.

Mã lắp ráp cũng hữu ích để triển khai các tính năng phá vỡ mô hình điều khiển luồng của C. Một ví dụ phổ biến là bối cảnh chuyển đổi giữa các chủ đề (cho dù hợp tác xã hoặc preemptive, cho dù trong cùng một không gian địa chỉ hay không) đòi hỏi mã lắp ráp để lưu và khôi phục lại các giá trị đăng ký.

Mã lắp ráp cũng hữu ích để tối ưu hóa thủ công các bit mã nhỏ cho bộ nhớ hoặc tốc độ. Khi các trình biên dịch ngày càng trở nên thông minh hơn, điều này hiếm khi liên quan ở cấp ứng dụng hiện nay, nhưng nó vẫn có liên quan trong nhiều thế giới nhúng.

Có hai cách để kết hợp lắp ráp với C: với lắp ráp nội tuyến hoặc bằng cách nối các mô-đun lắp ráp với các mô-đun C. Liên kết được cho là gọn gàng hơn nhưng không phải lúc nào cũng áp dụng: đôi khi bạn cần một lệnh ở giữa chức năng (ví dụ để tiết kiệm đăng ký trên một chuyển ngữ cảnh, một cuộc gọi hàm sẽ ghi đè thanh ghi) hoặc bạn không muốn trả chi phí của một cuộc gọi hàm.

Hầu hết các trình biên dịch C đều hỗ trợ lắp ráp nội tuyến, nhưng cú pháp thay đổi. Nó thường được giới thiệu bởi từ khóa asm, _asm, __asm hoặc __asm__. Ngoài mã assembly, cấu trúc assembly nội tuyến có thể chứa mã bổ sung cho phép bạn truyền các giá trị giữa assembly và C (ví dụ, yêu cầu giá trị của biến cục bộ được sao chép vào thanh ghi trên mục nhập), hoặc khai báo mã lắp ráp ghi đè hoặc bảo tồn một số thanh ghi nhất định.

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