ফাস্ট মেসেজ কিউ (FMQ)

HIDL-এর রিমোট প্রসিডিওর কল (RPC) পরিকাঠামো বাইন্ডার মেকানিজম ব্যবহার করে, অর্থাৎ কল করার সময় ওভারহেড হয়, কার্নেল অপারেশন করতে হয় এবং শিডিউলার অ্যাকশন ট্রিগার করতে পারে। তবে, এমন কিছু ক্ষেত্রে যেখানে কম ওভারহেড এবং কোনও কার্নেল ইনভলভমেন্ট ছাড়াই প্রসেসের মধ্যে ডেটা ট্রান্সফার করতে হবে, সেখানে ফাস্ট মেসেজ কিউ (FMQ) সিস্টেম ব্যবহার করা হয়।

FMQ পছন্দসই প্রপার্টি সহ মেসেজ কিউ তৈরি করে। আপনি HIDL RPC কলের মাধ্যমে MQDescriptorSync বা MQDescriptorUnsync অবজেক্ট পাঠাতে পারেন এবং গ্রহণকারী প্রসেসটি মেসেজ কিউ অ্যাক্সেস করার জন্য অবজেক্টটি ব্যবহার করে।

সারির ধরন

Android-এ দু'ধরনের কিউ (ফ্লেভার নামে পরিচিত) কাজ করে:

  • সিঙ্ক না করা কিউ ওভারফ্লো হতে পারে এবং এতে অনেক রিডার থাকতে পারে; প্রতিটি রিডারকে সময়মতো ডেটা রিড করতে হবে অথবা এটি মুছে যাবে।
  • সিঙ্ক্রোনাইজ করা কিউ ওভারফ্লো হতে পারে না এবং এতে শুধুমাত্র একটি রিডার থাকতে পারে।

দুটি কিউয়ের ক্ষেত্রেই আন্ডারফ্লো (খালি কিউ থেকে রিড করা ব্যর্থ হয়) হওয়া চলবে না এবং শুধুমাত্র একটি রাইটার থাকতে পারে।

সিঙ্ক্রোনাইজ করা হয়নি এমন সারি

আনসিঙ্ক্রোনাইজড সারিতে শুধুমাত্র একজন রাইটার থাকে, কিন্তু যেকোনও সংখ্যক রিডার থাকতে পারে। কিউয়ের জন্য একটি রাইট পজিশন আছে; তবে, প্রতিটি রিডার তার নিজস্ব স্বতন্ত্র রিড পজিশনের ট্র্যাক রাখে।

কনফিগার করা কিউ ক্যাপাসিটির থেকে ছোট হলে, কিউতে সবসময় লেখা যায় (ওভারফ্লো চেক করা হয় না)। কিউ ক্যাপাসিটির থেকে বড় হলে, লেখা যায় না। যেহেতু প্রতিটি রিডারের আলাদা আলাদা রিড পজিশন থাকতে পারে, তাই প্রতিটি রিডার যাতে ডেটার প্রতিটি অংশ পড়তে পারেন তার জন্য অপেক্ষা না করে, নতুন রাইট করার জন্য স্পেসের প্রয়োজন হলে, ডেটা কিউ থেকে বেরিয়ে যায়।

পাঠকদের দায়িত্ব হল, ডেটা সারির শেষ প্রান্তে পৌঁছানোর আগেই তা পুনরুদ্ধার করা। উপলভ্য ডেটার চেয়ে বেশি ডেটা রিড করার চেষ্টা করলে তা অবিলম্বে ব্যর্থ হয় (যদি ননব্লকিং হয়) অথবা পর্যাপ্ত ডেটা উপলভ্য হওয়ার জন্য অপেক্ষা করে (যদি ব্লকিং হয়)। কিউয়ের ধারণক্ষমতার চেয়ে বেশি ডেটা রিড করার চেষ্টা করলে তা সবসময়ই সঙ্গে সঙ্গে ব্যর্থ হয়ে যায়।

যদি কোনও রিডার লেখকের সাথে তাল মিলিয়ে চলতে না পারে, যেমন, লেখকের লেখা ডেটার পরিমাণ এবং সেই রিডারের এখনও না পড়া ডেটা যদি কিউয়ের ক্যাপাসিটির চেয়ে বেশি হয়ে যায়, তাহলে পরবর্তী রিড কোনও ডেটা ফেরত দেয় না; পরিবর্তে, এটি রিডারের রিড পজিশন রিসেট করে রাইট পজিশন প্লাস ক্যাপাসিটির অর্ধেক করে দেয় এবং তারপরে একটি ব্যর্থতা ফেরত দেয়। এর ফলে, বাফারের অর্ধেক অংশ পড়তে উপলভ্য থাকে এবং নতুন লেখার জন্য জায়গা রিজার্ভ করা হয় যাতে কোয়েরি আবার সঙ্গে সঙ্গে ওভারফ্লো না হয়ে যায়। ওভারফ্লো হওয়ার পরে কিন্তু পরবর্তী রিডের আগে, পড়ার জন্য উপলভ্য ডেটা চেক করা হলে, এটি কিউয়ের ক্ষমতার চেয়ে বেশি ডেটা পড়ার জন্য উপলভ্য আছে বলে দেখায়, এর থেকে বোঝা যায় যে ওভারফ্লো হয়েছে। (উপলভ্য ডেটা চেক করা এবং সেই ডেটা পড়ার চেষ্টার মধ্যে কিউ ওভারফ্লো হলে, ওভারফ্লোর একমাত্র ইঙ্গিত হল যে পড়া ব্যর্থ হয়।)

সিঙ্ক্রোনাইজ করা সারি

সিঙ্ক করা সারিতে একজন লেখক ও একজন পাঠক থাকেন এবং লেখার জন্য একটি ও পড়ার জন্য একটি পজিশন থাকে। কিউতে যত ডেটা রাখার জায়গা আছে তার থেকে বেশি ডেটা লেখা বা কিউতে বর্তমানে যত ডেটা আছে তার থেকে বেশি ডেটা পড়া সম্ভব নয়। ব্লকিং বা নন-ব্লকিং রাইট বা রিড ফাংশন কল করা হয়েছে কিনা তার উপর নির্ভর করে, উপলভ্য স্পেস বা ডেটা অতিক্রম করার চেষ্টা করলে, হয় সাথে সাথে ব্যর্থতার মেসেজ দেখায় অথবা কাঙ্ক্ষিত অপারেশন সম্পূর্ণ না হওয়া পর্যন্ত ব্লক করে রাখে। কিউয়ের ক্ষমতার চেয়ে বেশি ডেটা পড়া বা লেখার চেষ্টা করলে তা সবসময় সাথে সাথে ব্যর্থ হয়।

FMQ সেট-আপ করা

মেসেজ সারিতে একাধিক MessageQueue অবজেক্টের প্রয়োজন হয়: একটি লেখার জন্য এবং একটি বা একাধিক পড়ার জন্য। কোন অবজেক্ট লেখার বা পড়ার জন্য ব্যবহার করা হয় তার কোনও স্পষ্ট কনফিগারেশন নেই; ব্যবহারকারীকে নিশ্চিত করতে হবে যে কোনও অবজেক্ট পড়া এবং লেখা উভয়ের জন্যই ব্যবহার করা হচ্ছে না, সর্বাধিক একজন লেখক আছেন এবং সিঙ্ক করা সারির জন্য সর্বাধিক একজন পাঠক আছেন।

প্রথম MessageQueue অবজেক্ট তৈরি করা

একটি কল দিয়েই মেসেজ কিউ তৈরি ও কনফিগার করা হয়:

#include <fmq/MessageQueue.h>
using android::hardware::kSynchronizedReadWrite;
using android::hardware::kUnsynchronizedWrite;
using android::hardware::MQDescriptorSync;
using android::hardware::MQDescriptorUnsync;
using android::hardware::MessageQueue;
....
// For a synchronized nonblocking FMQ
mFmqSynchronized =
  new (std::nothrow) MessageQueue<uint16_t, kSynchronizedReadWrite>
      (kNumElementsInQueue);
// For an unsynchronized FMQ that supports blocking
mFmqUnsynchronizedBlocking =
  new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite>
      (kNumElementsInQueue, true /* enable blocking operations */);
  • MessageQueue<T, flavor>(numElements)ইনিশিয়ালাইজার এমন একটি অবজেক্ট তৈরি ও ইনিশিয়ালাইজ করে যা মেসেজ কিউ ফাংশনালিটিতে কাজ করে।
  • MessageQueue<T, flavor>(numElements, configureEventFlagWord) ইনিশিয়ালাইজার একটি অবজেক্ট তৈরি ও ইনিশিয়ালাইজ করে যা ব্লকিংয়ের মাধ্যমে মেসেজ কিউ ফাংশনালিটিকে সাপোর্ট করে।
  • flavor সিঙ্ক্রোনাইজ করা কিউয়ের জন্য kSynchronizedReadWrite অথবা সিঙ্ক্রোনাইজ করা নয় এমন কিউয়ের জন্য kUnsynchronizedWrite হতে পারে।
  • uint16_t (এই উদাহরণে) যেকোনও HIDL-সংজ্ঞায়িত প্রকার হতে পারে যেখানে নেস্টেড বাফার (string বা vec প্রকার নয়), হ্যান্ডেল বা ইন্টারফেস নেই।
  • kNumElementsInQueue এন্ট্রি সংখ্যায় সারির সাইজ নির্দেশ করে; এটি সারির জন্য বরাদ্দ করা শেয়ার করা মেমরি বাফারের সাইজ নির্ধারণ করে।

দ্বিতীয় MessageQueue অবজেক্ট তৈরি করুন

মেসেজ কিউয়ের দ্বিতীয় দিকটি প্রথম দিক থেকে পাওয়া MQDescriptor অবজেক্ট ব্যবহার করে তৈরি করা হয়। মেসেজ কিউয়ের দ্বিতীয় প্রান্তটি যে প্রসেসে থাকে, সেটিতে HIDL বা AIDL RPC কলের মাধ্যমে MQDescriptor অবজেক্ট পাঠানো হয়। MQDescriptor-এ সারির ব্যাপারে তথ্য থাকে, যার মধ্যে এগুলি অন্তর্ভুক্ত:

  • বাফার ও রাইট পয়েন্টার ম্যাপ করার তথ্য।
  • রিড পয়েন্টার ম্যাপ করার তথ্য (যদি সারি সিঙ্ক্রোনাইজ করা থাকে)।
  • ইভেন্ট ফ্ল্যাগ ওয়ার্ড ম্যাপ করার তথ্য (যদি সারি ব্লক করা থাকে)।
  • অবজেক্টের ধরন (<T, flavor>), যার মধ্যে কিউ এলিমেন্টের HIDL-এর মাধ্যমে নির্ধারিত ধরন এবং কিউয়ের ফ্লেভার (সিঙ্ক্রোনাইজ করা বা সিঙ্ক্রোনাইজ করা নয়) অন্তর্ভুক্ত।

আপনি MessageQueue অবজেক্ট তৈরি করতে MQDescriptor অবজেক্ট ব্যবহার করতে পারবেন:

MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)

resetPointers প্যারামিটার থেকে বোঝা যায় যে এই MessageQueue অবজেক্ট তৈরি করার সময় রিড ও রাইট পজিশন রিসেট করে ০ করা হবে কিনা। সিঙ্ক করা হয়নি এমন সারিতে, রিড পজিশন (যা সিঙ্ক করা হয়নি এমন সারিতে প্রতিটি MessageQueue অবজেক্টের জন্য লোকাল) সবসময় তৈরি করার সময় 0 হিসেবে সেট করা হয়। সাধারণত, প্রথম মেসেজ কোয়েরি অবজেক্ট তৈরি করার সময় MQDescriptor ইনশিয়ালাইজ করা হয়। শেয়ার করা মেমরির উপর অতিরিক্ত কন্ট্রোল পেতে, আপনি MQDescriptor ম্যানুয়ালি সেট-আপ করতে পারেন (MQDescriptor-এর সংজ্ঞা system/libhidl/base/include/hidl/MQDescriptor.h-এ দেওয়া আছে), তারপর এই বিভাগে বর্ণিত প্রতিটি MessageQueue অবজেক্ট তৈরি করুন।

ব্লক কিউ ও ইভেন্ট ফ্ল্যাগ

ডিফল্ট হিসেবে, সারিতে ব্লকিং রিড ও রাইট কাজ করে না। রিড ও রাইট কল ব্লক করার দুটি ধরন আছে:

  • তিনটি প্যারামিটার (ডেটা পয়েন্টার, আইটেমের সংখ্যা, টাইম-আউট) সহ সংক্ষিপ্ত ফর্ম, একটি কিউতে আলাদা আলাদা রিড ও রাইট অপারেশন ব্লক করার সুবিধা দেয়। এই ফর্ম ব্যবহার করার সময়, ইন্টার্নালি কিউ ইভেন্ট ফ্ল্যাগ ও বিটমাস্ক হ্যান্ডেল করে এবং প্রথম মেসেজ কিউ অবজেক্টকে অবশ্যই true-এর সেকেন্ড প্যারামিটার দিয়ে ইনিশিয়ালাইজ করতে হবে। যেমন:
    // For an unsynchronized FMQ that supports blocking
    mFmqUnsynchronizedBlocking =
      new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite>
          (kNumElementsInQueue, true /* enable blocking operations */);
    
  • ছয়টি প্যারামিটার সহ লং ফর্ম (এর মধ্যে ইভেন্ট ফ্ল্যাগ ও বিটমাস্ক অন্তর্ভুক্ত), একাধিক সারির মধ্যে শেয়ার করা EventFlag অবজেক্ট ব্যবহার করা এবং ব্যবহার করার জন্য বিজ্ঞপ্তি বিট মাস্ক নির্দিষ্ট করার অনুমতি দেয়। এই ক্ষেত্রে, প্রতিটি রিড ও রাইট কলের জন্য ইভেন্ট ফ্ল্যাগ ও বিটমাস্ক অবশ্যই দিতে হবে।

বড় দৈর্ঘ্যের উত্তরের ক্ষেত্রে, আপনি প্রতিটি readBlocking() ও writeBlocking() কলে EventFlag স্পষ্টভাবে উল্লেখ করতে পারেন। আপনি ইন্টার্নাল ইভেন্ট ফ্ল্যাগ সহ একটি কিউ ইনিশিয়ালাইজ করতে পারেন, যা অবশ্যই getEventFlagWord() ব্যবহার করে সেই কিউয়ের MessageQueue অবজেক্ট থেকে এক্সট্র্যাক্ট করতে হবে এবং অন্যান্য FMQ-এর সাথে ব্যবহারের জন্য প্রতিটি প্রসেসে EventFlag অবজেক্ট তৈরি করতে ব্যবহার করতে হবে। অথবা, আপনি যেকোনও উপযুক্ত শেয়ারড মেমরি দিয়ে EventFlag অবজেক্ট ইনিশিয়ালাইজ করতে পারেন।

সাধারণত, প্রতিটি সারিতে নন-ব্লকিং, শর্ট-ফর্ম ব্লকিং বা লং-ফর্ম ব্লকিংয়ের মধ্যে যেকোনও একটি ব্যবহার করা উচিত। এগুলি একসাথে ব্যবহার করলে কোনও সমস্যা হয় না, তবে কাঙ্ক্ষিত ফলাফল পেতে সাবধানে প্রোগ্রামিং করতে হবে।

মেমরিকে শুধু পড়ার জন্য হিসেবে চিহ্নিত করুন

ডিফল্ট হিসেবে, শেয়ার করা মেমরিতে ডেটা দেখা ও তৈরি করার অনুমতি থাকে। সিঙ্ক না করা কিউয়ের (kUnsynchronizedWrite) ক্ষেত্রে, লেখক MQDescriptorUnsync অবজেক্ট হ্যান্ড আউট করার আগে সব পাঠকের জন্য রাইট পার্মিশন সরিয়ে দিতে চাইতে পারেন। এটি নিশ্চিত করে যে অন্যান্য প্রসেস কিউতে লিখতে পারবে না, যা রিডার প্রসেসে বাগ বা খারাপ আচরণ থেকে রক্ষা করার জন্য সাজেস্ট করা হয়। রাইটার যদি চান যে পাঠকরা যখনই MQDescriptorUnsync ব্যবহার করে কিউয়ের রিড সাইড তৈরি করবেন, তখনই যেন কিউ রিসেট করতে পারেন, তাহলে মেমরিকে রিড-অনলি হিসেবে চিহ্নিত করা যাবে না। এটি হল MessageQueue কনস্ট্রাক্টরের ডিফল্ট অ্যাকশন। তাই, এই সারিতে আগে থেকেই ব্যবহারকারী থাকলে, তাদের কোড পরিবর্তন করে resetPointer=false দিয়ে সারি তৈরি করতে হবে।

  • রাইটার: MQDescriptor ফাইল ডেসক্রিপটর সহ ashmem_set_prot_region কল করুন এবং অঞ্চল শুধু-পড়ার জন্য সেট করা আছে (PROT_READ):
    int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
  • রিডার: resetPointer=false (ডিফল্ট হল true) সহ মেসেজ কিউ তৈরি করুন:
    mFmq = new (std::nothrow) MessageQueue(mqDesc, false);

MessageQueue ব্যবহার করা

MessageQueue অবজেক্টের পাবলিক API হল:

size_t availableToWrite() // Space available (number of elements).
size_t availableToRead() // Number of elements available.
size_t getQuantumSize() // Size of type T in bytes.
size_t getQuantumCount() // Number of items of type T that fit in the FMQ.
bool isValid() // Whether the FMQ is configured correctly.
const MQDescriptor<T, flavor>* getDesc() // Return info to send to other process.

bool write(const T* data)  // Write one T to FMQ; true if successful.
bool write(const T* data, size_t count) // Write count T's; no partial writes.

bool read(T* data); // read one T from FMQ; true if successful.
bool read(T* data, size_t count); // Read count T's; no partial reads.

bool writeBlocking(const T* data, size_t count, int64_t timeOutNanos = 0);
bool readBlocking(T* data, size_t count, int64_t timeOutNanos = 0);

// Allows multiple queues to share a single event flag word
std<::atomic>uint32_t* getEventFlagWord();

bool writeBlocking(const T* data, size_t count, uint32_t readNotification,
uint32_t writeNotification, int64_t timeOutNanos = 0,
android::hardware::EventFlag* evFlag = nullptr); // Blocking write operation for count Ts.

bool readBlocking(T* data, size_t count, uint32_t readNotification,
uint32_t writeNotification, int64_t timeOutNanos = 0,
android::hardware::EventFlag* evFlag = nullptr) // Blocking read operation for count Ts;

// APIs to allow zero copy read/write operations
bool beginWrite(size_t nMessages, MemTransaction* memTx) const;
bool commitWrite(size_t nMessages);
bool beginRead(size_t nMessages, MemTransaction* memTx) const;
bool commitRead(size_t nMessages);

একটি অপারেশনে কতটা ডেটা ট্রান্সফার করা যাবে তা নির্ধারণ করতে আপনি availableToWrite() এবং availableToRead() ব্যবহার করতে পারবেন। সিঙ্ক্রোনাইজ করা হয়নি এমন সারিতে:

  • availableToWrite() সবসময় সারির ক্যাপাসিটি রিটার্ন করে।
  • প্রতিটি রিডারের নিজস্ব রিড পজিশন আছে এবং availableToRead()-এর জন্য নিজস্ব গণনা করে।
  • ধীর গতির রিডারের দৃষ্টিকোণ থেকে, সারিতে ওভারফ্লো হওয়ার অনুমতি দেওয়া হয়; এর ফলে সারির সাইজের চেয়ে availableToRead()বড় ভ্যালু রিটার্ন করতে পারে। ওভারফ্লো ব্যর্থ হওয়ার পরে প্রথম রিড এবং এর ফলে সেই রিডারের রিড পজিশন বর্তমান রাইট পয়েন্টারের সমান হিসেবে সেট করা হয়, availableToRead()-এর মাধ্যমে ওভারফ্লো রিপোর্ট করা হোক বা না হোক।

read() এবং write() পদ্ধতি true রিটার্ন করে যদি অনুরোধ করা সব ডেটা কিউতে এবং কিউ থেকে ট্রান্সফার করা যেত (এবং করা হয়েছে)। এইসব পদ্ধতি ব্লক করে না; এগুলি হয় সফল হয় (এবং true রিটার্ন করে), অথবা অবিলম্বে ব্যর্থতা (false) রিটার্ন করে।

অনুরোধ করা অপারেশন সম্পূর্ণ না হওয়া পর্যন্ত অথবা টাইমআউট না হওয়া পর্যন্ত readBlocking() এবং writeBlocking() পদ্ধতি অপেক্ষা করে (timeOutNanos-এর ভ্যালু 0 হলে কখনও টাইমআউট হয় না)।

ইভেন্ট ফ্ল্যাগ ওয়ার্ড ব্যবহার করে ব্লকিং অপারেশন প্রয়োগ করা হয়। ডিফল্ট হিসেবে, প্রতিটি কিউ readBlocking() ও writeBlocking()-এর সংক্ষিপ্ত রূপকে সাপোর্ট করার জন্য নিজস্ব ফ্ল্যাগ শব্দ তৈরি ও ব্যবহার করে। একাধিক কিউ একটি শব্দ শেয়ার করতে পারে, যাতে কোনও প্রসেস যেকোনও কিউতে লেখা বা পড়ার জন্য অপেক্ষা করতে পারে। getEventFlagWord() কল করে, আপনি কোনও কিউয়ের ইভেন্ট ফ্ল্যাগ ওয়ার্ডের পয়েন্টার পেতে পারেন এবং সেই পয়েন্টার (অথবা উপযুক্ত শেয়ার করা মেমরি লোকেশনের যেকোনও পয়েন্টার) ব্যবহার করে, অন্য কিউয়ের জন্য readBlocking() ও writeBlocking()-এর বড় ফর্মের মধ্যে পাস করার জন্য EventFlag অবজেক্ট তৈরি করতে পারেন। readNotification এবং writeNotification প্যারামিটার থেকে জানা যায় যে ইভেন্ট ফ্ল্যাগের কোন বিট সেই সারিতে রিড ও রাইট সিগন্যাল দেওয়ার জন্য ব্যবহার করা উচিত। readNotification ও writeNotification হল ৩২-বিট বিটমাস্ক।

readBlocking() writeNotification বিটের জন্য অপেক্ষা করে; প্যারামিটারটি ০ হলে, কলটি সবসময়ই ব্যর্থ হয়। readNotification ভ্যালু ০ হলে, কলটি ফেল করে না, কিন্তু সফল রিড কোনও বিজ্ঞপ্তি বিট সেট করে না। সিঙ্ক্রোনাইজ করা সারিতে, এর অর্থ হল, সংশ্লিষ্ট writeBlocking() কলটি অন্য কোথাও বিট সেট না করা পর্যন্ত কখনওই জেগে ওঠে না। সিঙ্ক না করা সারিতে, writeBlocking() অপেক্ষা করে না (এটি এখনও রাইট নোটিফিকেশন বিট সেট করতে ব্যবহার করা উচিত), এবং এটি রিডের জন্য উপযুক্ত যাতে কোনও নোটিফিকেশন বিট সেট না করা হয়। একইভাবে, writeblocking() ব্যর্থ হয় যদি readNotification ০ হয় এবং সফলভাবে লেখা হলে নির্দিষ্ট করা writeNotification বিট সেট করে।

একসাথে একাধিক সারিতে অপেক্ষা করতে, বিজ্ঞপ্তির বিটম্যাপে অপেক্ষা করার জন্য EventFlag অবজেক্টের wait() মেথড ব্যবহার করুন। wait() পদ্ধতিটি বিট সহ একটি স্ট্যাটাস শব্দ রিটার্ন করে যা ওয়েক আপ সেট করার কারণ। এই তথ্য ব্যবহার করে যাচাই করা হয় যে সংশ্লিষ্ট সারিতে প্রত্যাশিত রাইট ও রিড অপারেশনের জন্য যথেষ্ট স্পেস বা ডেটা আছে কিনা এবং নন-ব্লকিং write() ও read() পারফর্ম করা হয়। পোস্ট অপারেশন বিজ্ঞপ্তি পেতে, EventFlag অবজেক্টের wake() পদ্ধতিতে আরেকটি কল ব্যবহার করুন। EventFlag অ্যাবস্ট্রাকশন-এর সংজ্ঞা জানতে, system/libfmq/include/fmq/EventFlag.h দেখুন।

জিরো কপি অপারেশন

read, write, readBlocking ও writeBlocking() মেথডগুলি আর্গুমেন্ট হিসেবে ইনপুট-আউটপুট বাফারের পয়েন্টার নেয় এবং একই ও FMQ রিং বাফারের মধ্যে ডেটা কপি করার জন্য ইন্টার্নালি memcpy() কল ব্যবহার করে। পারফর্ম্যান্স উন্নত করতে, Android 8.0 ও তার পরবর্তী যেকোনও ভার্সনে API-এর একটি সেট অন্তর্ভুক্ত থাকে যা রিং বাফারে সরাসরি পয়েন্টার অ্যাক্সেস প্রদান করে, এর ফলে memcpy কল ব্যবহার করার প্রয়োজন হয় না।

জিরো কপি FMQ অপারেশনের জন্য নিম্নলিখিত পাবলিক API ব্যবহার করুন:

bool beginWrite(size_t nMessages, MemTransaction* memTx) const;
bool commitWrite(size_t nMessages);

bool beginRead(size_t nMessages, MemTransaction* memTx) const;
bool commitRead(size_t nMessages);
  • beginWrite পদ্ধতিতে FMQ রিং বাফারে বেস পয়েন্টার প্রদান করা হয়। ডেটা লেখা হয়ে গেলে, commitWrite() ব্যবহার করে তা কমিট করুন। beginRead ও commitRead পদ্ধতি একই রকম কাজ করে।
  • beginRead ও Write পদ্ধতিতে ইনপুট হিসেবে পড়া ও লেখার জন্য মেসেজের সংখ্যা নেওয়া হয় এবং বুলিয়ান মান রিটার্ন করা হয় যা থেকে বোঝা যায় যে পড়া বা লেখা সম্ভব কিনা। রিড বা রাইট করা সম্ভব হলে, memTx struct বেস পয়েন্টার দিয়ে পপুলেট করা হয় যা শেয়ার করা মেমোরির রিং বাফারে সরাসরি পয়েন্টার অ্যাক্সেস করার জন্য ব্যবহার করা যেতে পারে।
  • MemRegion স্ট্রাক্টে মেমরি ব্লকের বিবরণ থাকে, যার মধ্যে বেস পয়েন্টার (মেমরি ব্লকের বেস অ্যাড্রেস) এবং T-এর পরিভাষায় দৈর্ঘ্য (মেসেজ কিউয়ের HIDL-সংজ্ঞায়িত প্রকারের পরিভাষায় মেমরি ব্লকের দৈর্ঘ্য) অন্তর্ভুক্ত।
  • MemTransaction struct-এ দুটি MemRegion struct, first ও second আছে। রিড বা রাইট করার জন্য রিং বাফারে র‍্যাপ-অ্যারাউন্ডের প্রয়োজন হতে পারে। এর অর্থ হল FMQ রিং বাফারে ডেটা পড়তে ও লিখতে দুটি বেস পয়েন্টার প্রয়োজন।

MemRegion স্ট্রাকচার থেকে বেস অ্যাড্রেস ও দৈর্ঘ্য পেতে:

T* getAddress(); // gets the base address
size_t getLength(); // gets the length of the memory region in terms of T
size_t getLengthInBytes(); // gets the length of the memory region in bytes

MemTransaction অবজেক্টের মধ্যে প্রথম ও দ্বিতীয় MemRegion struct-এর রেফারেন্স পেতে:

const MemRegion& getFirstRegion(); // get a reference to the first MemRegion
const MemRegion& getSecondRegion(); // get a reference to the second MemRegion

জিরো কপি API ব্যবহার করে FMQ-তে লেখার উদাহরণ:

MessageQueueSync::MemTransaction tx;
if (mQueue->beginRead(dataLe&n, tx)) {
    auto first = tx.getFirstRegion();
    auto second = tx.getSecondRegion();

    foo(first.getAddress(), first.getLength()); // method that performs the data write
    foo(second.getAddress(), second.getLength()); // method that performs the data write

    if(commitWrite(dataLen) == false) {
       // report error
    }
} else {
   // report error
}

নিম্নলিখিত হেল্পার পদ্ধতিগুলিও MemTransaction-এর অংশ:

  • T* getSlot(size_t idx);, MemRegions-এর মধ্যে স্লট idx-এ একটি পয়েন্টার রিটার্ন করে যা এই MemTransaction অবজেক্টের অংশ। MemTransaction অবজেক্ট যদি T ধরনের N আইটেম রিড ও রাইট করার জন্য মেমরি অঞ্চলকে রিপ্রেজেন্ট করে, তাহলে idx-এর সঠিক রেঞ্জ ০ থেকে N-১-এর মধ্যে হবে।
  • bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);, অবজেক্টের বর্ণনা করা মেমরি রিজনে T ধরনের nMessages আইটেম লেখে, যা startIdx ইনডেক্স থেকে শুরু হয়। এই পদ্ধতি memcpy() ব্যবহার করে এবং এটি জিরো কপি অপারেশনের জন্য ব্যবহার করার কথা নয়। MemTransaction অবজেক্ট যদি T ধরনের N আইটেম রিড ও রাইট করার মেমরিকে রিপ্রেজেন্ট করে, তাহলে idx-এর বৈধ রেঞ্জ হল ০ থেকে N-১-এর মধ্যে।
  • bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1); হল startIdx থেকে শুরু করে অবজেক্টের বর্ণনা করা মেমরি অঞ্চল থেকে T টাইপের nMessages আইটেম পড়ার সহায়ক পদ্ধতি। এই পদ্ধতি memcpy() ব্যবহার করে এবং জিরো কপি অপারেশনের জন্য ব্যবহার করা হয় না।

HIDL-এর মাধ্যমে সারি পাঠান

তৈরি করার ক্ষেত্রে:

  1. উপরে যেভাবে বর্ণনা করা হয়েছে সেইভাবে একটি মেসেজ কিউ অবজেক্ট তৈরি করুন।
  2. isValid()-এর মাধ্যমে অবজেক্টটি সঠিক কিনা তা যাচাই করুন।
  3. আপনি যদি EventFlag পাস করে একাধিক সারিতে অপেক্ষা করেন readBlocking() বা writeBlocking()-এর দীর্ঘ ফর্মে, তাহলে আপনি ইভেন্ট ফ্ল্যাগ পয়েন্টার (getEventFlagWord() ব্যবহার করে) এক্সট্র্যাক্ট করতে পারেন MessageQueue অবজেক্ট থেকে যা ফ্ল্যাগ তৈরি করার জন্য ইনিশিয়ালাইজ করা হয়েছে এবং প্রয়োজনীয় EventFlag অবজেক্ট তৈরি করতে সেই ফ্ল্যাগ ব্যবহার করুন।
  4. ডেসক্রিপ্টর অবজেক্ট পেতে MessageQueue পদ্ধতি getDesc() ব্যবহার করুন।
  5. HAL ফাইলে, পদ্ধতিটিকে fmq_sync বা fmq_unsync টাইপের প্যারামিটার দিন, যেখানে T হল উপযুক্ত HIDL-এর মাধ্যমে সংজ্ঞায়িত টাইপ। getDesc()-এর মাধ্যমে রিটার্ন করা অবজেক্ট রিসিভিং প্রসেসে পাঠানোর জন্য এটি ব্যবহার করুন।

প্রাপকের ডিভাইসে:

  1. MessageQueue অবজেক্ট তৈরি করতে ডেসক্রিপ্টর অবজেক্ট ব্যবহার করুন। একই কিউ ফ্লেভার ও ডেটা টাইপ ব্যবহার করুন, তা না হলে টেমপ্লেট কম্পাইল করা যাবে না।
  2. আপনি যদি কোনও ইভেন্ট ফ্ল্যাগ এক্সট্র্যাক্ট করে থাকেন, তাহলে গ্রহণ করার প্রসেসে সংশ্লিষ্ট MessageQueue অবজেক্ট থেকে ফ্ল্যাগ এক্সট্র্যাক্ট করুন।
  3. ডেটা ট্রান্সফার করতে MessageQueue অবজেক্ট ব্যবহার করুন।