Việc triển khai C ++ AtomicInt này có đúng không?


9

Tiền đề: Tôi đang làm việc với môi trường nhúng ARM (gần như kim loại trần), nơi tôi thậm chí không có sẵn C ++ 11 (có std::atomic<int>), vì vậy vui lòng tránh các câu trả lời như " chỉ sử dụng C ++ tiêu chuẩnstd::atomic<int> ": Tôi không thể .

Việc ARM triển khai AtomicInt này có đúng không? (giả sử kiến ​​trúc ARM là ARMv7-A )

Bạn có thấy một số vấn đề đồng bộ hóa? Có volatilecần thiết / hữu ích không?

// File: atomic_int.h

#ifndef ATOMIC_INT_H_
#define ATOMIC_INT_H_

#include <stdint.h>

class AtomicInt
{
public:
    AtomicInt(int32_t init = 0) : atom(init) { }
    ~AtomicInt() {}

    int32_t add(int32_t value); // Implement 'add' method in platform-specific file

    int32_t sub(int32_t value) { return add(-value); }
    int32_t inc(void)          { return add(1);      }
    int32_t dec(void)          { return add(-1);     }

private:
    volatile int32_t atom;
};

#endif
// File: arm/atomic_int.cpp

#include "atomic_int.h"

int32_t AtomicInt::add(int32_t value)
{
    int32_t res, prev, tmp;

    asm volatile(

    "try:    ldrex   %1, [%3]\n"     // prev = atom;
    "        add     %0, %1, %4\n"   // res = prev + value;
    "        strex   %2, %0, [%3]\n" // tmp = outcome(atom = res); // may fail
    "        teq     %2, #0\n"       // if (tmp)
    "        bne     try"            //     goto try; /* add failed: someone else modified atom -> retry */

    : "=&r" (res), "=&r" (prev), "=&r" (tmp), "+mo" (atom)  // output (atom is both in-out)
    : "r" (value)                                           // input
    : "cc");                                                // clobbers (condition code register [CPSR] changed)

    return prev; // safe return (local variable cannot be changed by other execution contexts)
}

Ngoài ra, tôi đang cố gắng để đạt được một số tái sử dụng mã, đó là lý do tại sao tôi chỉ tách biệt một chức năng cơ bản để triển khai trong mã dành riêng cho nền tảng ( add()phương thức bên trong arm/atomic_int.cpp).

atomic_int.hthực sự di động vì nó là trên các nền tảng / kiến ​​trúc / trình biên dịch khác nhau? Cách tiếp cận này có khả thi không? (Với tính khả thi tôi có nghĩa là khả thi cho mọi nền tảng để đảm bảo tính nguyên tử bằng cách thực hiện chỉ add()phương pháp ).

đây là triển khai ARM GCC 8.3.1 tương ứng có cùng chức năng. Rõ ràng, sự khác biệt thực sự duy nhất là sự hiện diện của dmbtrước và sau. Họ có thực sự cần thiết trong trường hợp của tôi? Tại sao? Bạn có một ví dụ mà tôi AtomicInt(không dmb) không thành công?

CẬP NHẬT: thực hiện cố định, get()phương pháp loại bỏ để giải quyết các vấn đề nguyên tử và căn chỉnh. Bây giờ các add()hành vi như một tiêu chuẩn fetchAndAdd().


volatiletừ khóa trong C ++ có nghĩa là không tối ưu hóa thông qua biến. Vì vậy, get()phương pháp được hưởng lợi từ nó. Mặc dù, nói chung, dễ bay hơi sắp cạn kiệt trong C ++. Nếu hệ thống của bạn không thể tích hợp đồng bộ hóa dữ liệu 32 bit, thì bạn có rất ít sự lựa chọn ngoài việc sử dụng mutexes - spinlock trong ít nhất.
ALX23z

Phiên bản nào của kiến ​​trúc cánh tay bạn đang sử dụng? armv-7?
Mike van Dyke

1
Điều này không giải quyết được câu hỏi, nhưng các tên có chứa hai dấu gạch dưới liên tiếp ( __ATOMIC_INT_H_) và các tên bắt đầu bằng dấu gạch dưới theo sau là chữ in hoa được dành riêng cho việc triển khai. Đừng sử dụng chúng trong mã của bạn.
Pete Becker

Tên thành viên atomiccó lẽ tốt nhất không được sử dụng để tránh nhầm lẫn std::atomic, mặc dù nó đặt ra câu hỏi tại sao bạn sẽ không sử dụng nó trong mọi trường hợp.
Clifford

Đã thêm kiến ​​trúc ARM, đổi tên thành __ATOMIC_INT_H_định danh.
gentooise

Câu trả lời:


2

Nếu bạn sử dụng gcccó thể bạn có thể sử dụng Legacy __syncBuilt-in chức năng cho Atomic Memory Access :

void add(int volatile& a, int value) {
    __sync_fetch_and_add(&a, value);
}

Tạo :

add(int volatile&, int):
.L2:
        ldxr    w2, [x0]
        add     w2, w2, w1
        stlxr   w3, w2, [x0]
        cbnz    w3, .L2
        dmb     ish
        ret

Thật không may, tôi không sử dụng gccvà trong mọi trường hợp tôi không muốn ràng buộc việc thực hiện với bất kỳ trình biên dịch cụ thể nào. Dù sao cũng cảm ơn bạn vì gợi ý của bạn, ít nhất nó cho tôi biết rằng add()phần ARM của tôi phải chính xác. Sự khác biệt giữa ldxrvà là ldrexgì?
gentooise

Đây là ARM8 (eg64bit) chứ không phải là một trong các phiên bản 32 bit.
marko

Tôi quản lý để có được mã tương ứng bằng cách chỉ định kiến ​​trúc đích: liên kết . Có vẻ như GCC thực sự đặt dmbtrước và sau vòng lặp ldrex/ strex.
gentooise

2
Tôi nghĩ rằng đây là cách tiếp cận tốt nhưng để làm cho trình biên dịch độc lập, chỉ cần truy cập kiểu godbolt.org/z/WB8rxw trong hàm bạn muốn sử dụng nội dung gcc và sao chép đầu ra lắp ráp tương ứng. Đảm bảo khớp thông số -march với phiên bản cụ thể của ARM.
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.