Общая память между NDK и SDK ниже уровня API 26 - PullRequest
1 голос
/ 04 февраля 2020

Библиотека, написанная на c ++, генерирует непрерывный поток данных, и его нужно переносить на разные платформы. Теперь, интегрируя приложение lib в android, я пытаюсь создать общую память между NDK и SDK.

Ниже приведен рабочий фрагмент,

Собственный код:

#include <jni.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <linux/ashmem.h>
#include <android/log.h>
#include <string>

char  *buffer;
constexpr size_t BufferSize=100;
extern "C" JNIEXPORT jobject JNICALL
Java_test_com_myapplication_MainActivity_getSharedBufferJNI(
        JNIEnv* env,
        jobject /* this */) {

    int fd = open("/dev/ashmem", O_RDWR);

    ioctl(fd, ASHMEM_SET_NAME, "shared_memory");
    ioctl(fd, ASHMEM_SET_SIZE, BufferSize);

    buffer = (char*) mmap(NULL, BufferSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    return (env->NewDirectByteBuffer(buffer, BufferSize));
}

extern "C" JNIEXPORT void JNICALL
Java_test_com_myapplication_MainActivity_TestBufferCopy(
        JNIEnv* env,
        jobject /* this */) {

   for(size_t i=0;i<BufferSize;i = i+2) {
       __android_log_print(ANDROID_LOG_INFO, "native_log", "Count %d value:%d", i,buffer[i]);
   }

   //pass `buffer` to dynamically loaded library to update share memory
   //

}

Код SDK:

//MainActivity.java
public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.

    static {
        System.loadLibrary("native-lib");
    }

    final int BufferSize = 100;
    @RequiresApi(api = Build.VERSION_CODES.Q)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ByteBuffer byteBuffer = getSharedBufferJNI();

        //update the command to shared memory here
        //byteBuffer updated with commands
        //Call JNI to inform update and get the response
        TestBufferCopy();
    }


    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native ByteBuffer getSharedBufferJNI();
    public native int TestBufferCopy();
}

Вопрос:

  1. Доступ к примитивным массивам из Java в native является справочным, только если сборщик мусора поддерживает закрепление. Верно ли это с другой стороны?
  2. Гарантируется ли android платформой, что ссылка ВСЕГДА передается из NDK в SDK без избыточной копии?
  3. Является ли это правильный способ разделить память?

Ответы [ 2 ]

1 голос
/ 20 февраля 2020

Вам нужно только /dev/ashmem, чтобы разделить память между процессами. NDK и SDK (Java / Kotlin) работают в одном и том же Linux процессе и имеют полный доступ к одному и тому же пространству памяти.

Обычный способ определения памяти, который можно использовать как из C ++, так и Java, - это путем создания Direct ByteBuffer. Для этого вам не нужен JNI, Java API имеет ByteBuffer.allocateDirect (intacity) . Если для вашего логического потока более естественно выделить буфер на стороне C ++, JNI имеет функцию NewDirectByteBuffer (JNIEnv * env, void * address, jlong ​​емкость) , которую вы использовали в своем вопросе.

Работать с Direct ByteBuffer очень легко на стороне C ++, но не так эффективно на стороне JVM. Причина в том, что этот буфер не поддерживается массивом, и единственный API, который у вас есть, включает ByteBuffer.get () с типизированными вариациями (получение байтового массива, char, int,…). Вы управляете текущей позицией в буфере, но работа таким образом требует определенной дисциплины: каждая операция get () обновляет текущую позицию. Кроме того, произвольный доступ к этому буферу довольно медленный, потому что он включает в себя вызов API позиционирования и получения. Поэтому в некоторых случаях нетривиальных структур данных может быть проще написать свой код пользовательского доступа на C ++ и иметь «интеллектуальные» методы получения, вызываемые через JNI.

Важно не забыть установить ByteBuffer.order (ByteOrder.nativeOrder ()) . Порядок вновь созданного байтового буфера нелогично BIG_ENDIAN. Это относится как к буферу, созданному из Java, так и из C ++.

Если вы можете изолировать случаи, когда C ++ нужен доступ к такой общей памяти, и вам не нужно постоянно его закреплять, это Стоит рассмотреть работу с байтовым массивом. В Java у вас есть более эффективный произвольный доступ. На стороне NDK вы будете вызывать GetByteArrayElements () или GetPrimitiveArrayCritical () . Последний более эффективен, но его использование накладывает ограничения на то, какие функции Java можно вызывать до освобождения массива. На Android оба метода не включают выделение и копирование памяти (однако, без официальной гарантии). Даже если сторона C ++ использует ту же память, что и Java, ваш код JNI должен вызывать соответствующую функцию Release… (), и лучше сделать это как можно раньше. Это хорошая практика для обработки этого Get / Release через RAII.

0 голосов
/ 14 февраля 2020

Позвольте мне резюмировать мои выводы:

Доступ к примитивным массивам из Java в native является справочным, только если сборщик мусора поддерживает закрепление. Верно ли это с другой стороны?

Содержимое прямого буфера может потенциально находиться в собственной памяти вне обычной кучи, собираемой мусором. И, следовательно, сборщик мусора не может требовать памяти.

Гарантируется ли платформой android, что ссылка ВСЕГДА передается из NDK в SDK без избыточной копии?

Да, согласно документации NewDirectByteBuffer.

jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity);

Распределяет и возвращает прямое java.nio.ByteBuffer, ссылающееся на блок памяти, начиная с адреса памяти address и продолжая capacity байтов.

...