QRT: موبائل فون پروسیسر کے اندر ایک حقیقی وقت کا OS [Full Handbook]

Qualcomm پر مبنی تمام فونز میں Hexagon DSP ویک اپ کریکٹر ڈیٹیکشن، سینسر پروسیسنگ، شور کینسلیشن، اور بلوٹوتھ آڈیو اسٹریمنگ کو ہینڈل کرتا ہے، جبکہ بنیادی ARM CPU اینڈرائیڈ چلاتا ہے۔

DSP جس آپریٹنگ سسٹم کو آپریٹ کرنے کے لیے کوآرڈینیٹ کرتا ہے وہ Qualcomm ریئل ٹائم آپریٹنگ سسٹم (QRT) ہے، ایک POSIX جیسا ترجیحی بنیاد پر پہلے سے تیار کردہ RTOS خاص طور پر Qualcomm کے Hexagon ڈیجیٹل سگنل پروسیسرز کے لیے بنایا گیا ہے۔

یہ دستاویز Qualcomm کے ریئل ٹائم آپریٹنگ سسٹم کے لیے ایک عملی گائیڈ ہے۔ ہم QRT کو گراؤنڈ اپ سے کور کرتے ہیں، بشمول فن تعمیر، دھاگے کی تخلیق، مطابقت پذیری پرائمیٹوز، میموری مینجمنٹ، انٹرپٹ ہینڈلنگ، ٹائمرز، فاسٹ آر پی سی کے ذریعے انٹر پروسیسر کمیونیکیشن، اور مکمل سینسر فیوژن پائپ لائن۔ ہر تصور میں ورکنگ کوڈ اور ہڈ کے نیچے کیا ہو رہا ہے اس کی تفصیل شامل ہوتی ہے۔

انڈیکس

QRT کیوں اہم ہے۔

اس بارے میں سوچیں کہ فون کال کے دوران کیا ہوتا ہے۔ یہ آلہ بیک وقت مائیکروفون آڈیو پر شور منسوخی انجام دیتا ہے، ویک ورڈ کا پتہ لگانے کے لیے ایک نیورل نیٹ ورک چلاتا ہے، ایکسلرومیٹر ڈیٹا کو فی سیکنڈ 400 بار پڑھتا ہے، اور بلوٹوتھ آڈیو اسٹریمنگ کا انتظام کرتا ہے۔

ان میں سے کوئی بھی مقامی ARM CPU پر نہیں چلے گا۔ یہ سب Qualcomm میں ہوتا ہے۔ مسدس ڈی ایس پیآپریٹنگ سسٹم جو اس کو ایڈجسٹ کرتے ہیں ان میں شامل ہیں: QRT.

Qualcomm ریئل ٹائم آپریٹنگ سسٹم (QRT) ایک POSIX جیسا ترجیحی بنیاد پر پہلے سے تیار کردہ RTOS ہے جو Qualcomm کے Hexagon ڈیجیٹل سگنل پروسیسر پر چلتا ہے۔ جب کہ لینکس ایک عام مقصد کا آپریٹنگ سسٹم ہے جو لچک کے لیے ڈیزائن کیا گیا ہے، کیو آر ٹی ایک درست آلہ ہے جو مائیکرو سیکنڈ لیول کے نظام الاوقات کے لیے ڈیزائن کیا گیا ہے۔

جہاں QRT آپ کے سسٹم میں فٹ بیٹھتا ہے۔

یہ خاکہ Qualcomm SoC کے اندر دو پروسیسر کے فن تعمیر کو دکھاتا ہے۔ بائیں طرف کا ARM CPU Android یا Linux چلاتا ہے اور عام ایپلیکیشن منطق کو ہینڈل کرتا ہے۔ دائیں طرف Hexagon DSP QuRT چلاتا ہے اور آڈیو پروسیسنگ، سینسر فیوژن، ML انفرنس، اور کمپیوٹ آف لوڈ سمیت تاخیر سے متعلق حساس کام کے بوجھ کو ہینڈل کرتا ہے۔

دو پروسیسرز درج ذیل فریم ورک کے ذریعے بات چیت کرتے ہیں: تیز آر پی سی. آپ اپنا DSP-سائیڈ کوڈ لکھنے کے لیے Hexagon SDK استعمال کرتے ہیں، اور QRT وہ OS ہے جو آپ کے کوڈ کو ہیکساگون پروسیسر پر چلاتا ہے۔

ترقیاتی ماحول کی ترتیبات

QRT کوڈ لکھنے سے پہلے، آپ کو ایک ٹول چین اور ایک سمیلیٹر یا فزیکل ہارڈویئر کی ضرورت ہے۔

شرطیں

Hexagon SDK (ورژن 3.5+ یا 4.x) کی ضرورت ہے، جو Qualcomm کا آفیشل SDK ہے اور اس میں Hexagon Tools کمپائلر ٹول چین شامل ہے۔

کوڈ کو چلانے کے لیے، آپ Qualcomm ڈیولپمنٹ بورڈ (جیسے Robotics RB5 یا SM8250 HDK) یا SDK میں بنایا ہوا سمیلیٹر استعمال کر سکتے ہیں۔ اوبنٹو 18.04 یا 20.04 پر چلنے والا لینکس ہوسٹ سسٹم ترقی کے لیے بہترین ہے۔

ہیکساگون SDK انسٹال کریں۔

# Download the Hexagon SDK from Qualcomm's developer portal
# https://developer.qualcomm.com/software/hexagon-dsp-sdk

# Extract and run the installer
chmod +x qualcomm_hexagon_sdk_4_x_x_x.bin
./qualcomm_hexagon_sdk_4_x_x_x.bin

# Set up environment variables
export HEXAGON_SDK_ROOT=~/Qualcomm/Hexagon_SDK/4.x.x.x
export HEXAGON_TOOLS_ROOT=~/Qualcomm/Hexagon_SDK/4.x.x.x/tools
source $HEXAGON_SDK_ROOT/setup_sdk_env.source

یہ آپ کی ہوم ڈائرکٹری میں SDK کو انسٹال کرے گا اور آپ کے بلڈ سسٹم اور سمیلیٹر کے لیے ضروری ماحولیاتی متغیرات کو سیٹ کرے گا۔ کہ setup_sdk_env.source اسکرپٹ شیل کو کمپائلر، سمیلیٹر اور لائبریریوں کے راستوں کے ساتھ تشکیل دیتا ہے۔

ترتیبات چیک کریں۔

# Check the Hexagon compiler
hexagon-clang --version

# You should see something like:
# Qualcomm Hexagon Clang version 8.x.xx

# Run the QuRT simulator to make sure it works
$HEXAGON_SDK_ROOT/tools/HEXAGON_Tools/8.x.xx/Tools/bin/hexagon-sim 
    --simulated_returnval --cosim_file 
    $HEXAGON_SDK_ROOT/libs/common/qurt/computev66/sdksim_bin/osam.cfg 
    -- $HEXAGON_SDK_ROOT/libs/common/qurt/computev66/sdksim_bin/bootimg.pbn

پہلی کمانڈ اس بات کی تصدیق کرتی ہے کہ ہیکساگون کلینگ کمپائلر انسٹال اور قابل رسائی ہے۔ دوسری کمانڈ QRT سمیلیٹر شروع کرتی ہے، جو کہ اینڈرائیڈ ایمولیٹر کی طرح ہے۔ یہ آپ کو اپنے QRT پروگرام کو فزیکل ہارڈ ویئر کے بغیر جانچنے کی اجازت دیتا ہے۔ اگرچہ ٹائمنگ اصلی ہارڈ ویئر سے میل نہیں کھاتی، لیکن سمیلیٹر ترقی کے دوران درستگی کی تصدیق کے لیے مفید ہیں۔

منصوبے کی ساخت

مسدس SDK استعمال کرتا ہے: SCons ڈیفالٹ بلڈ سسٹم کے طور پر استعمال کیا جاتا ہے۔ پراجیکٹس SDK درخت کے اندر واقع ہیں اور ان کے ذریعے منظم ہیں: .min ایک فائل جو ایک اعلاناتی تعمیراتی وضاحت کنندہ ہے جو SDK کے SCons انفراسٹرکچر کے ذریعے پارس کی گئی ہے۔

کم از کم پروجیکٹ ہے:

$HEXAGON_SDK_ROOT/examples/my_qurt_project/
├── src/
│   └── main.c              # Your QuRT application code
├── inc/
│   └── my_module.h         # Header files
├── hexagon.min              # SCons build config for Hexagon DSP side
└── android.min              # SCons build config for ARM side (if using FastRPC)

کہ hexagon.min فائلیں ڈی ایس پی سائیڈ بلڈ کو ترتیب دیتی ہیں، android.min کراس پروسیسر کمیونیکیشن کے لیے FastRPC استعمال کرتے وقت ARM سائیڈ کو ہینڈل کرتا ہے۔ دونوں SDK کے اوپری سطح پر پڑھتے ہیں۔ SConstruct فائلیں یہاں موجود ہیں: $HEXAGON_SDK_ROOT/SConstruct. آپ کو الگ سے اس کی ضرورت نہیں ہے۔ Makefile یا SConscript SDK درخت کے اندر منصوبوں کے لیے۔

SCons کا استعمال کرتے ہوئے کنفیگریشن بنائیں

کم سے کم hexagon.min تعمیر فائل ہے:

# hexagon.min - SCons build descriptor for the DSP side

BUILD_LIBS = libmy_qurt_app

# Source files
libmy_qurt_app_C_SRCS = src/main.c

# QuRT OS library
libmy_qurt_app_LIBS = atomic rpcmem

# Compiler flags
libmy_qurt_app_HEXAGON_CFLAGS = -O2 -Wall

# Link against QuRT
libmy_qurt_app_DLLS = libmy_qurt_app_skel

کہ .min فائل کی شکل ہیکساگون SDK کے SCons بلڈ سسٹم پر منحصر ہے۔ BUILD_LIBS لائبریری ہدف کا نام بتاتا ہے۔ C_SRCS ماخذ فائلوں کی فہرست بنائیں۔ LIBS اس کے خلاف لنک کرنے کے لیے لائبریری کی وضاحت کرتا ہے۔ HEXAGON_CFLAGS مرتب کرنے والے جھنڈے مرتب کریں۔ DLLS مشترکہ لائبریری آؤٹ پٹ نام کی وضاحت کرتا ہے۔ _skel لاحقے DSP کی طرف سے نفاذ کے لیے FastRPC کے اصول ہیں۔

اندرونی طور پر، SDK SConstruct پروجیکٹ ٹری پر جائیں اور ہر مواد کو پڑھیں۔ .min ایک فائل بنائیں اور اس کے ڈیکلریشن کو SCons بلڈ اہداف میں تبدیل کریں۔ کہ V (تبدیلی) جو پیرامیٹرز آپ تعمیر کے وقت پاس کرتے ہیں وہ ٹارگٹ فن تعمیر، تعمیر کی قسم، اور ٹول چین ورژن کا انتخاب کرتے ہیں۔ مثال کے طور پر، V=hexagon_Release_dynamic_toolv84_v66 اس کا کیا مطلب ہے: v66 DSP فن تعمیر کو ہدف بناتے ہوئے v84 ٹول چین کا استعمال کرتے ہوئے مسدس، ریلیز موڈ، ڈائنامک لنکنگ کے لیے تعمیر کریں۔

ان منصوبوں کے لیے جن کے لیے زیادہ کنٹرول کی ضرورت ہوتی ہے۔ .min آپ فارمیٹ فراہم کر کے اسٹینڈ لون لکھ سکتے ہیں۔ SConscript فائل:

# SConscript - Standalone SCons build for a QuRT project

Import('env')

env = env.Clone()

# Add include paths
env.Append(CPPPATH = ['inc'])

# Compiler flags
env.Append(CCFLAGS = ['-O2', '-Wall'])

# Build the shared library
sources = ['src/main.c']
libs = ['atomic', 'rpcmem']

env.SharedLibrary(
    target="libmy_qurt_app_skel",
    source = sources,
    LIBS = libs
)

کہ SConscript نقطہ نظر آپ کو SCons خصوصیات تک مکمل رسائی فراہم کرتا ہے، بشمول مشروط تالیف، اپنی مرضی کے مطابق تعمیراتی اقدامات، انحصار کا پتہ لگانے، اور مختلف قسم کی تعمیر۔ کہ Import('env') کال کو SDK کے اوپری سطح پر تشکیل شدہ ماحول مل جاتا ہے۔ SConstructیہ پہلے ہی ہیکساگون کمپائلر پاتھ، کیو آر ٹی ہیڈرز، اور سسٹم لائبریریوں کے بارے میں جانتا ہے۔ env.Clone() ایک کاپی بنائیں تاکہ آپ کی ترمیم درخت کے دوسرے منصوبوں کو متاثر نہ کرے۔

QRT پروگرامنگ ماڈل

QRT پروگرامنگ کا بنیادی ذہنی ماڈل سادہ ہے۔

کیو آر ٹی ترجیحی بنیاد پر پیشگی آر ٹی او ایس ہے۔ اس کا مطلب ہے کہ ہر چیز دھاگوں میں چلتی ہے (کوئی ننگی دھاتی مین لوپ نہیں)۔ ایک اعلی ترجیحی دھاگہ ہمیشہ بغیر کسی گفت و شنید کے کم ترجیحی تھریڈ کو فوری طور پر پیش کرتا ہے۔ اسی ترجیحی سطح کے دھاگوں کو راؤنڈ رابن میں شیڈول کیا گیا ہے۔

شیڈولر کے پاس کوئی ٹک نہیں ہے۔ اس کا مطلب ہے کہ یہ وقتاً فوقتاً نہیں اٹھتا۔ یہ صرف اس وقت چلتا ہے جب چیزیں تبدیل ہوتی ہیں، جیسے تھریڈ کو بلاک کرنا، سگنل سیٹ کرنا، یا اعلی ترجیحی دھاگہ تیار کرنا۔

Priority Levels (0-255, lower number = higher priority)

 000  ┃ ████ Interrupt handlers (do not touch this)
 001  ┃ ████ Critical system tasks
 ...  ┃
 064  ┃ ████ Your high-priority audio processing
 ...  ┃
 128  ┃ ████ Your medium-priority sensor fusion
 ...  ┃
 192  ┃ ████ Your low-priority logging/reporting
 ...  ┃
 255  ┃ ████ Idle thread (QuRT's built-in background)

یہ ترجیحی نقشہ دکھاتا ہے کہ کس طرح QRT کی 256 ترجیحی سطحیں عام طور پر تفویض کی جاتی ہیں۔ ترجیح 0 ہے۔ سب سے زیادہ ترجیح 255 ہے۔ سب سے کم. یہ FreeRTOS کے برعکس ہے، جہاں اعلی نمبروں کو ترجیح دی جاتی ہے۔

انٹرپٹ ہینڈلرز سب سے زیادہ ترجیحی سطح پر قابض ہیں، سسٹم کے کام بالکل نیچے ہیں، اور صارف کے تھریڈز درمیانی رینج پر قابض ہیں۔ ترجیح 255 کے ساتھ ایک بیکار تھریڈ صرف اس وقت چلے گا جب کچھ بھی تیار نہ ہو۔

اپنا پہلا QRT تھریڈ بنائیں

سب سے آسان QRT پروگرام ایک ہی تھریڈ کو جنم دیتا ہے جو ایک پیغام پرنٹ کرتا ہے اور باہر نکلتا ہے۔

/* main.c - First QuRT program */

#include 
#include 
#include 

#define STACK_SIZE 4096

/* Thread stack must be 8-byte aligned */
static char thread_stack[STACK_SIZE] __attribute__((aligned(8)));

void my_thread_func(void *arg)
{
    int thread_id = (int)(uintptr_t)arg;

    printf("Hello from QuRT thread %d!n", thread_id);
    printf("My thread ID: %lun", qurt_thread_get_id());

    /* Thread must explicitly exit */
    qurt_thread_exit(QURT_EOK);
}

int main(void)
{
    qurt_thread_t      thread_id;
    qurt_thread_attr_t attr;

    printf("Main thread starting on QuRT!n");

    /* Initialize thread attributes */
    qurt_thread_attr_init(&attr);

    /* Configure the thread */
    qurt_thread_attr_set_name(&attr, "my_first_thread");
    qurt_thread_attr_set_stack_addr(&attr, thread_stack);
    qurt_thread_attr_set_stack_size(&attr, STACK_SIZE);
    qurt_thread_attr_set_priority(&attr, 128);  /* Medium priority */

    /* Create and start the thread */
    int result = qurt_thread_create(&thread_id, &attr,
                                     my_thread_func,
                                     (void *)42);

    if (result != QURT_EOK) {
        printf("Thread creation failed with error: %dn", result);
        return -1;
    }

    printf("Thread created successfully! ID: %lun", thread_id);

    /* Wait for the thread to finish */
    int status;
    qurt_thread_join(thread_id, &status);

    printf("Thread finished with status: %dn", status);
    return 0;
}

یہ پروگرام QRT کے چار مراحل پر مشتمل تھریڈ بنانے کے عمل کو ظاہر کرتا ہے۔ سب سے پہلے qurt_thread_attr_init() دھاگے کی خصوصیات کی ساخت کو شروع کرتا ہے۔ دوسرا، پروگرام تھریڈز کو ڈیبگ نام (کریش ڈمپ میں دکھایا گیا ہے)، اسٹیک ایڈریس، اسٹیک سائز، اور ترجیح کے ساتھ ترتیب دیتا ہے۔ تیسرا qurt_thread_create() فنکشن پوائنٹر اور آرگیومینٹس پاس کرکے تھریڈ بنائیں اور اسے فوراً شروع کریں۔ چوتھا، qurt_thread_join() کالنگ تھریڈ کو بلاک کرتا ہے جب تک کہ کوئی نیا تھریڈ کال نہ کرے۔ qurt_thread_exit().

دو تفصیلات اہم ہیں۔ QRT اسٹیک میموری مختص نہیں کرتا ہے۔ اس کا مطلب ہے کہ آپ کو ایک مستحکم طور پر مختص کردہ 8 بائٹ الائنمنٹ بفر فراہم کرنا ہوگا۔ اور ہر تھریڈ کو کال کرنا چاہیے: qurt_thread_exit() واپسی سے پہلے۔ اگر کوئی تھریڈ فنکشن صرف ٹرمینیشن کو کال کیے بغیر واپس آجاتا ہے تو رویے کی وضاحت نہیں کی جاتی ہے۔

دھاگے کی تخلیق کا بہاؤ

     qurt_thread_attr_init()
              │
              ▼
    ┌─────────────────────┐
    │  Set name           │
    │  Set stack address  │
    │  Set stack size     │
    │  Set priority       │
    └─────────────────────┘
              │
              ▼
     qurt_thread_create()
              │
              ▼
    Thread starts running ──► my_thread_func()
              │                      │
              ▼                      ▼
     qurt_thread_join()       qurt_thread_exit()
     (waits for exit)         (signals "I'm done")

یہ بہاؤ ایک ہی دھاگے کا لائف سائیکل دکھاتا ہے۔ پراپرٹی کا ڈھانچہ کنفیگریشن آبجیکٹ کے طور پر کام کرتا ہے۔ تمام تھریڈ پیرامیٹرز کو سیٹ کریں اور پھر ان کو پاس کریں۔ qurt_thread_create(). جب کوئی تھریڈ بنتا ہے تو وہ تھریڈ انٹری فنکشن کو انجام دیتا ہے۔ جب انٹری فنکشن کو بلایا جاتا ہے۔ qurt_thread_exit()تھریڈ ختم ہو جاتا ہے اور تمام تھریڈز بلاک ہو جاتے ہیں۔ qurt_thread_join() بلاک غیر مسدود ہے اور ایگزٹ اسٹیٹس کوڈ موصول ہوتا ہے۔

دھاگے کی تخلیق اندرونی طور پر کیسے کام کرتی ہے۔

زیادہ تر ٹیوٹوریلز اس بات کو چھوڑ دیتے ہیں کہ کیا ہو رہا ہے۔ qurt_thread_create(). انٹرنل کو سمجھنا ڈیبگنگ اور ترجیحی ڈیزائن کے فیصلوں کو زیادہ واضح کرتا ہے۔

دھاگے کی تخلیق کے دوران دانا کیا کرتا ہے۔

جب آپ کال کرتے ہیں۔ qurt_thread_create()تم ہو سسٹم کال QRT دانا میں۔ دانا ترتیب وار پانچ مراحل انجام دیتا ہے۔

  Your code calls qurt_thread_create()
         │
         ▼
  ┌──────────────────────────────────────────────────────────┐
  │  1. VALIDATE                                             │
  │     • Is the stack pointer non-NULL and aligned?         │
  │     • Is the stack size >= minimum (typ. 2KB)?           │
  │     • Is the priority in range 0-255?                    │
  │     • Is the entry function pointer non-NULL?            │
  │     (If any check fails → return QURT_EINVALID)          │
  ├──────────────────────────────────────────────────────────┤
  │  2. ALLOCATE THREAD CONTROL BLOCK (TCB)                  │
  │     • QuRT allocates a kernel-side data structure        │
  │     • This holds: thread ID, priority, state, saved      │
  │       registers, signal masks, mutex wait list, etc.     │
  ├──────────────────────────────────────────────────────────┤
  │  3. INITIALIZE THE STACK FRAME                           │
  │     • The kernel sets up a synthetic stack frame at the  │
  │       top of YOUR stack memory                           │
  │     • It writes the initial register values:             │
  │       ┌──────────────────────────────────────┐           │
  │       │  Stack Top (high address)            │           │
  │       │  ┌──────────────────────────────────┐│           │
  │       │  │ PC  = my_thread_func (entry)     ││           │
  │       │  │ SP  = stack_addr + stack_size    ││           │
  │       │  │ R0  = arg (your void* argument)  ││           │
  │       │  │ LR  = qurt_thread_exit           ││           │
  │       │  │ SR  = default status register    ││           │
  │       │  │ R1-R31 = 0                       ││           │
  │       │ └──────────────────────────────────┘│            │
  │       │  ... (rest of stack is untouched) ...│           │
  │       │  Stack Bottom (low address)          │           │
  │       └──────────────────────────────────────┘           │
  ├──────────────────────────────────────────────────────────┤
  │  4. INSERT INTO READY QUEUE                              │
  │     • The TCB is added to the scheduler's ready queue    │
  │       at the appropriate priority level                  │
  │     • The thread's state is set to READY                 │
  ├──────────────────────────────────────────────────────────┤
  │  5. TRIGGER A RESCHEDULE                                 │
  │     • The scheduler checks: "Is this new thread's        │
  │       priority higher than the currently running         │
  │       thread?"                                           │
  │     • If YES: context switch happens RIGHT NOW           │
  │       (the calling thread is preempted)                  │
  │     • If NO: the new thread waits in the ready queue     │
  │       until it's the highest priority runnable thread    │
  └──────────────────────────────────────────────────────────┘
         │
         ▼
  qurt_thread_create() returns to the caller
  (but the new thread may already be running!)

اس بہاؤ کے بارے میں سب سے حیران کن چیز مرحلہ 5 ہے۔ اگر نئے تھریڈ کی ترجیح اس تھریڈ سے زیادہ ہے جس نے اسے بنایا ہے، اس سے پہلے ایک نیا تھریڈ شروع ہوتا ہے۔ qurt_thread_create() کالر پر واپس جائیں. تخلیق کا دھاگہ کال کے دوران پہلے سے تیار کیا جاتا ہے۔ یہ وہی ہے جو "قبل از وقت” کا اصل مطلب ہے۔ شیڈولر کسی آسان لمحے کا انتظار نہیں کرتا ہے۔ فوری طور پر ترجیحات کا اطلاق کریں۔

اسٹیک فریم ایک فنکشن کو کیسے شروع کرتا ہے۔

جب شیڈیولر سیاق و سباق پہلی بار کسی نئے تھریڈ پر سوئچ کرتا ہے، تو یہ بالکل وہی کرتا ہے جو تمام سیاق و سباق کے سوئچز کے لیے کرتا ہے۔ یعنی، یہ TCB میں محفوظ شدہ رجسٹروں کو بحال کرتا ہے اور محفوظ شدہ پروگرام کاؤنٹر پر چھلانگ لگا دیتا ہے۔

ایک نئے دھاگے کے لیے، اس کے رجسٹر کو دانا کے ذریعے مرحلہ 3 میں جامع طور پر ترتیب دیا گیا ہے۔ پی سی (پروگرام کاؤنٹر) پر مقرر ہے my_thread_funcپروسیسر اس فنکشن میں چھلانگ لگاتا ہے۔ R0 آپ کے لئے مقرر arg چونکہ یہ ایک پیرامیٹر ہے، اس لیے فنکشن اسے پہلی دلیل کے طور پر لیتا ہے (ہیکساگون کالنگ کنونشن کے مطابق)۔ کہ ایس پی (اسٹیک پوائنٹر) یہ اسٹیک کے اوپری حصے پر سیٹ ہے، اس لیے فنکشن میں کام کرنے کے لیے ایک اسٹیک ہے۔ اور LR (لنک رجسٹر) پر مقرر ہے qurt_thread_exitلہذا اگر فنکشن عام طور پر واپس آتا ہے (جس پر آپ کو بھروسہ نہیں کرنا چاہئے) تو آگے بڑھیں۔ qurt_thread_exit.

The illusion:
──────────────
To your thread function, it looks like someone
"called" it normally with the argument you passed.

The reality:
──────────────
The scheduler restored a set of synthetic registers
that make the processor THINK it is returning from
a function call into your entry point.

It's like waking up in a room you have never been in,
but someone arranged everything so perfectly that
you do not realize you did not walk in through the door.

یہ خاکہ پروگرامر کے دماغی ماڈل (ایک عام فنکشن کال) سے متصادم ہے جو اصل میں ہارڈ ویئر کی سطح پر ہوتا ہے (رجسٹر ریسٹوریشن ایک فنکشن کال کی نقل کرتے ہوئے)۔ تھریڈ فنکشنز کے پاس ان دو منظرناموں میں فرق کرنے کا کوئی طریقہ نہیں ہے۔ یہ بالکل نقطہ ہے. دانا کمال کا وہم پیدا کرتا ہے۔

سیاق و سباق کو تبدیل کرنے کی مشق

ایک مخصوص مثال پر غور کریں۔ تھریڈ A (ترجیح 128) تھریڈ B (ترجیح 64، جو ایک اعلی ترجیح ہے) کو جنم دیتا ہے۔ درج ذیل ٹائم لائن دکھاتی ہے کہ ہر قدم میں کیا ہوتا ہے۔

Time ──────────────────────────────────────────────►

Thread A (pri 128)          Kernel/Scheduler         Thread B (pri 64)
────────────────           ────────────────           ────────────────
Calls                      
qurt_thread_create()       
   │                       
   ├─► System call ──────►  Validates params
                            Allocates TCB
                            Sets up stack frame
                            Inserts B into ready queue
                            
                            "B (64) > A (128)?  YES."
                            
                            SAVE A's registers   ──┐
                            to A's TCB             │
                                                   │
                            LOAD B's registers   ◄─┘
                            from B's TCB (the
                            synthetic ones)
                            
                            Jump to PC ─────────► my_thread_func(arg)
                                                   │
                                                   │ does work...
                                                   │ calls qurt_thread_exit()
                                                   │
                            B is removed ◄─────── Exit system call
                            from ready queue
                            
                            "Who's next? A."
                            
                            LOAD A's registers
   │                        Jump to A's PC
   │◄──────────────────────
   │
   ├─► qurt_thread_create()
   │   returns QURT_EOK
   │
   ▼ continues...

تھریڈ اے کے نقطہ نظر سے: qurt_thread_create() یہ صرف ایک فنکشن کال ہے جسے واپس آنے میں وقت لگتا ہے۔ تھریڈ اے کو کوئی اندازہ نہیں ہے کہ یہ معطل ہے۔ اس وقفے کے دوران، آپ کو معلوم نہیں ہوگا کہ تھریڈ بی پہلے ہی چل چکا ہے اور مکمل ہو چکا ہے۔

شیڈیولر پہلے سے تیار کردہ دھاگوں کو غیر مرئی بنا دیتا ہے۔ یہ قبل از وقت شیڈولنگ کی ایک بنیادی خاصیت ہے۔ دھاگوں کو ایک دوسرے کے ساتھ تعاون کرنے یا ایک دوسرے کے وجود سے آگاہ ہونے کی ضرورت نہیں ہے۔

تھریڈ کنٹرول بلاک مشمولات

TCB ہر تھریڈ کو ٹریک کرنے کے لیے دانا کا اندرونی ڈیٹا ڈھانچہ ہے۔ اگرچہ یہ براہ راست قابل رسائی نہیں ہے، لیکن اس کے مواد کو سمجھنا QRT کے زیادہ تر رویے کی وضاحت کر سکتا ہے۔

/* Conceptual TCB layout (simplified, not actual QuRT source) */
struct qurt_tcb {
    /* Identity */
    qurt_thread_t   thread_id;
    char            name[16];
    
    /* Scheduling */
    uint8_t         base_priority;
    uint8_t         effective_priority; /* May differ due to priority inheritance */
    uint8_t         state;             /* READY, RUNNING, BLOCKED, SUSPENDED */
    
    /* Saved CPU context (filled during context switch) */
    uint32_t        saved_regs[32];
    uint32_t        saved_pc;
    uint32_t        saved_sp;
    uint32_t        saved_sr;
    
    /* Stack info (for debugging and overflow detection) */
    void           *stack_base;
    size_t          stack_size;
    
    /* Blocking info */
    void           *wait_object;  /* Mutex/signal/pipe being waited on */
    uint32_t        wait_mask;    /* Signal bits being waited for */
    
    /* Linked list pointers */
    struct qurt_tcb *next_ready;
    struct qurt_tcb *next_waiting;
    
    /* Join support */
    int             exit_status;  /* Value passed to qurt_thread_exit() */
    qurt_thread_t   joiner;      /* Thread waiting in qurt_thread_join() */
};

TCB شیڈیولر کے لیے درکار ہر چیز کو اسٹور کرتا ہے: شناخت کی معلومات (تھریڈ آئی ڈی اور ڈیبگ کا نام)، نظام الاوقات (بنیاد اور موثر ترجیحات، موجودہ حالت)، محفوظ کردہ سی پی یو سیاق و سباق (تمام 32 عام مقصد کے رجسٹر پلس پی سی، ایس پی، اور اسٹیٹس رجسٹر)، اسٹیک باؤنڈریز، بلاکنگ انفارمیشن (کیا ہے)، تھریڈ میں شامل ہونے کے لیے تیار پوائنٹس اور ویٹنگ پوائنٹس کا انتظار کرنا کھیتوں

کہ effective_priority فیلڈز مختلف ہو سکتے ہیں۔ base_priority اگر ترجیحی وراثت فعال ہے، تو یہ مطابقت پذیری سیکشن میں شامل ہے۔

تھریڈ سٹیٹ مشین

ایک QRT دھاگہ ہمیشہ چار حالتوں میں سے ایک میں ہوتا ہے:

                    qurt_thread_create()
                           │
                           ▼
                    ┌──────────┐
          ┌─────────│  READY   │◄──────────────────────────┐
          │         └──────────┘                           │
          │              │ ▲                               │
          │  Scheduler   │ │ Preempted by                  │
          │  picks this  │ │ higher-priority               │
          │  thread      │ │ thread                        │
          │              ▼ │                               │
          │         ┌──────────┐     Signal/mutex/         │
          │         │ RUNNING  │     timer event           │
          │         └──────────┘     unblocks thread       │
          │              │                                 │
          │  Thread calls│                                 │
          │  blocking    │                                 │
          │  API:        │                                 │
          │  - mutex_lock│                                 │
          │  - signal_   │                                 │
          │    wait      │                                 │
          │  - pipe_     │                                 │
          │    receive   ▼                                 │
          │         ┌──────────┐                           │
          │         │ BLOCKED  │───────────────────────────┘
          │         └──────────┘
          │
          │  qurt_thread_exit()
          │         │
          │         ▼
          │    ┌──────────┐
          └───►│  DEAD    │
               └──────────┘
  • تیار اس کا مطلب ہے کہ تھریڈ چل سکتا ہے اور ہارڈ ویئر تھریڈ سلاٹ کا انتظار کر رہا ہے۔

  • چل رہا ہے اس کا مطلب ہے کہ دھاگہ فی الحال ہارڈویئر تھریڈ پر چل رہا ہے (ایک وقت میں صرف ایک دھاگہ فی ہارڈ ویئر تھریڈ سلاٹ اس حالت میں ہے)۔

  • مسدود اس کا مطلب ہے کہ تھریڈ کسی بیرونی ایونٹ کا انتظار کر رہا ہے (میوٹیکس جاری کریں، سگنل سیٹ کریں، ٹائمر ختم کریں)۔

  • مردہ اس سے مراد وہ دھاگہ ہے جس سے اسے بلایا گیا تھا۔ qurt_thread_exit(). جب کسی اور تھریڈ کو بلایا جاتا ہے۔ qurt_thread_join() اس تھریڈ کو پھر ایگزٹ اسٹیٹس ملتا ہے۔

ہارڈ ویئر دھاگے کی سلاٹ

مسدس ڈی ایس پی ہارڈ ویئر ملٹی تھریڈ پروسیسر ہر کور میں ایک سے زیادہ ہارڈ ویئر تھریڈ سلاٹ ہیں (عام طور پر 2 سے 4)۔ اس کا مطلب یہ ہے کہ کیو آر ٹی صرف ٹائم سلائسنگ کے بجائے ایک ہی کور پر متعدد تھریڈز کو بیک وقت چلا سکتا ہے۔

┌─────────────────────────────────────────┐
│          Hexagon DSP Core               │
│                                         │
│  ┌───────────┐  ┌───────────┐           │
│  │ HW Thread │  │ HW Thread │           │
│  │ Slot 0    │  │ Slot 1    │  ...      │
│  │           │  │           │           │
│  │ Thread A  │  │ Thread B  │           │
│  │ (running) │  │ (running) │           │
│  └───────────┘  └───────────┘           │
│                                         │
│  Ready Queue: [C, D, E, F, ...]         │
│  The scheduler fills HW slots with      │
│  the highest-priority READY threads     │
└─────────────────────────────────────────┘

یہ خاکہ ایک ہیکساگونل کور کو دو ہارڈویئر تھریڈ سلاٹس کے ساتھ دکھاتا ہے۔ ہر سلاٹ تھریڈز کو آزادانہ اور بیک وقت چلا سکتا ہے۔ شیڈیولر ہارڈویئر سلاٹس کو سب سے زیادہ ترجیحی تیار تھریڈز سے بھرتا ہے۔ اگر ہارڈویئر سلاٹس سے زیادہ سافٹ ویئر تھریڈز ہیں، تو شیڈیولر ٹائم کم ترجیحی تھریڈز کو سلائس کرتا ہے۔ تاہم، سب سے زیادہ ترجیحی دھاگے میں ہارڈویئر کا ایک وقف شدہ سلاٹ ہوتا ہے اور سیاق و سباق کے سوئچنگ کے بغیر چلتا ہے۔

ایک عام ہیکساگون v66 میں چار ہارڈویئر تھریڈز کے ساتھ، سب سے اوپر چار ترجیحی تھریڈز میں سے ہر ایک کی اپنی ایکیکیوشن پائپ لائن ہوتی ہے۔ سیاق و سباق کا سوئچ صرف اس وقت ہوتا ہے جب تھریڈ کو بلاک کیا جاتا ہے یا ایک اعلی ترجیحی دھاگہ جاگتا ہے اور دھاگے کو ہارڈویئر سلاٹ میں بدل دیتا ہے۔ یہی وجہ ہے کہ QRT اتنی کم ریزرویشن میں تاخیر حاصل کرتا ہے۔

مکمل تھریڈ لائف سائیکل

مندرجہ ذیل کوڈ مکمل تھریڈ لائف سائیکل دکھاتا ہے، اس بارے میں تبصرے کے ساتھ کہ QRT ہر قدم پر کیا کرتا ہے۔

static char stack[8192] __attribute__((aligned(8)));

void my_func(void *arg)
{
    /* State: RUNNING. Stack is fresh, R0 contains arg. */
    int val = *(int *)arg;

    qurt_mutex_lock(&some_mutex);
    /* If mutex is held: state becomes BLOCKED until holder unlocks */

    shared_data = val;
    qurt_mutex_unlock(&some_mutex);

    qurt_thread_exit(QURT_EOK);
    /* State becomes DEAD. Joiner (if any) is unblocked. */
}

int main(void)
{
    qurt_thread_t tid;
    qurt_thread_attr_t attr;
    int my_arg = 42;

    qurt_thread_attr_init(&attr);
    qurt_thread_attr_set_stack_addr(&attr, stack);
    qurt_thread_attr_set_stack_size(&attr, sizeof(stack));
    qurt_thread_attr_set_priority(&attr, 100);

    qurt_thread_create(&tid, &attr, my_func, &my_arg);
    /* If my_func's priority (100) > main's: main is preempted here */

    int status;
    qurt_thread_join(tid, &status);
    /* Blocks until my_func exits; returns immediately if already exited */

    return 0;
}

جب my_func جب عمل درآمد شروع ہوتا ہے، دانا نے پہلے ہی رجسٹر سیٹ کر لیے ہیں۔ arg کے لیے ایک پوائنٹر پر مشتمل ہے۔ my_arg. تھریڈ کی حالت چل رہی ہے۔

جب یہ کال کرتا ہے qurt_mutex_lock()دو چیزوں میں سے ایک ہو گی: یعنی اگر mutex دستیاب ہو تو دھاگہ اسے حاصل کر لیتا ہے اور جاری رہتا ہے۔ اگر mutex کسی دوسرے دھاگے کے پاس ہو تو کالنگ تھریڈ کی حالت کو BLOCKED میں تبدیل کر دیا جاتا ہے، اس کے رجسٹر TCB میں محفوظ ہوتے ہیں، اور شیڈولر اگلی سب سے زیادہ ترجیحی تیار دھاگے کا انتخاب کرتا ہے۔

جب mutex ہولڈر کال کرتا ہے۔ qurt_mutex_unlock()مسدود دھاگے کو واپس READY پر منتقل کر دیا گیا ہے اور شیڈولر اپنی ترجیح کا دوبارہ جائزہ لیتا ہے۔

کو main طرف qurt_thread_create() یہ پہلے واپس آ سکتا ہے یا نہیں۔ my_func ختم ہو جاتا ہے۔ اگر my_func اسے اعلیٰ ترجیح حاصل ہے۔ mainشیڈولر preempts. main فوری طور پر اور qurt_thread_create() جب تک واپس نہیں آئے گا۔ my_func ہو گیا (یا بلاک)۔ qurt_thread_join() دونوں بلاکس main کو my_func اسے فوری طور پر ختم ہونے پر واپس کر دیا جائے گا یا اگر: my_func یہ پہلے ہی ختم ہو چکا ہے۔

اسٹیک سائزنگ کے بارے میں اہم نوٹ: STACK_SIZE اسے بہت چھوٹی چیز (جیسے 256 بائٹس) میں تبدیل کرنے سے تھریڈ بلایا جائے گا۔ printfنتیجہ یہ ہے۔ اسٹیک اوور فلو. QRT اسٹیک اوور فلو کا پتہ نہیں لگاتا ہے۔ حادثے پرسکون اور تشخیص کرنا مشکل ہیں۔ اپنے تھریڈز کو ہمیشہ کم از کم 8192 بائٹس کا اسٹیک دیں اور پروفائلنگ کے بعد بعد میں آپٹیمائز کریں۔

سمیلیٹر میں بنائیں اور چلائیں۔

مسدس SDK فراہم کرتا ہے: make ذیل میں ایک ریپر ہے جسے SCons کہتے ہیں۔ مندرجہ ذیل کمانڈ سب ایک جیسے نتائج پیدا کرتے ہیں:

# Option 1: Use the make wrapper (invokes SCons internally)
cd $HEXAGON_SDK_ROOT
make V=hexagon_Release_dynamic_toolv84_v66 
     tree=my_qurt_project

# Option 2: Invoke SCons directly
cd $HEXAGON_SDK_ROOT
python tools/build/scons/scons.py 
    V=hexagon_Release_dynamic_toolv84_v66 
    my_qurt_project

دونوں کمانڈز ریلیز موڈ میں v84 ٹول چین کا استعمال کرتے ہوئے ہیکساگون v66 فن تعمیر کے منصوبے بناتے ہیں۔ کہ make ریپر ایک سہولت کی پرت ہے۔ V= اور tree= اپنا دعویٰ کریں اور اسے SCons کو بھیجیں۔ SCons مندرجہ ذیل اضافی جھنڈوں تک براہ راست رسائی کی اجازت دیتا ہے: --jobs=N متوازی تعمیرات کے لیے --verbose مکمل کمپائلر کمانڈ آؤٹ پٹ کے لیے۔

# Run on the simulator
hexagon-sim --simulated_returnval 
    --cosim_file osam.cfg 
    -- bootimg.pbn 
    -- my_qurt_app.so

کہ hexagon-sim کمانڈ ایک مرتب کردہ ایپلیکیشن کے ساتھ QRT سمیلیٹر شروع کرتی ہے۔ کہ --simulated_returnval پرچم آپ کی واپسی کی قیمت کو پکڑتا ہے۔ main فنکشن اور --cosim_file QRT OS کنفیگریشن کا حوالہ دیتا ہے۔

ملٹی تھریڈڈ آپریشن

ایک حقیقی QRT ایپلیکیشن میں ایک سے زیادہ تھریڈز بیک وقت چلتے ہیں۔ ڈی ایس پی پروگرامنگ میں پروڈیوسر-صارف پیٹرن سب سے زیادہ عام پیٹرن میں سے ایک ہے۔ ایک تھریڈ ہارڈ ویئر سے پڑھتا ہے اور دوسرا تھریڈ ڈیٹا پر کارروائی کرتا ہے۔

#include 
#include 

#define STACK_SIZE    8192
#define BUFFER_SIZE   16
#define NUM_ITEMS     100

/* Thread stacks */
static char producer_stack[STACK_SIZE] __attribute__((aligned(8)));
static char consumer_stack[STACK_SIZE] __attribute__((aligned(8)));

/* Shared buffer */
static int buffer[BUFFER_SIZE];
static int head = 0;
static int tail = 0;
static int count = 0;

/* Synchronization primitives */
qurt_mutex_t buffer_mutex;
qurt_cond_t  not_full;
qurt_cond_t  not_empty;

void producer_thread(void *arg)
{
    for (int i = 0; i < NUM_ITEMS; i++) {
        qurt_mutex_lock(&buffer_mutex);

        /* Wait until there is space in the buffer */
        while (count == BUFFER_SIZE) {
            qurt_cond_wait(&not_full, &buffer_mutex);
        }

        /* Produce an item */
        buffer[head] = i;
        head = (head + 1) % BUFFER_SIZE;
        count++;

        printf("[Producer] Put item %d (buffer count: %d)n", i, count);

        /* Signal the consumer that data is available */
        qurt_cond_signal(&not_empty);
        qurt_mutex_unlock(&buffer_mutex);
    }

    qurt_thread_exit(QURT_EOK);
}

void consumer_thread(void *arg)
{
    for (int i = 0; i < NUM_ITEMS; i++) {
        qurt_mutex_lock(&buffer_mutex);

        /* Wait until there is data in the buffer */
        while (count == 0) {
            qurt_cond_wait(&not_empty, &buffer_mutex);
        }

        /* Consume an item */
        int item = buffer[tail];
        tail = (tail + 1) % BUFFER_SIZE;
        count--;

        printf("[Consumer] Got item %d (buffer count: %d)n", item, count);

        /* Signal the producer that space is available */
        qurt_cond_signal(&not_full);
        qurt_mutex_unlock(&buffer_mutex);
    }

    qurt_thread_exit(QURT_EOK);
}

int main(void)
{
    qurt_thread_t producer, consumer;
    qurt_thread_attr_t attr;

    /* Initialize sync primitives BEFORE creating threads */
    qurt_mutex_init(&buffer_mutex);
    qurt_cond_init(&not_full);
    qurt_cond_init(&not_empty);

    /* Create producer (higher priority) */
    qurt_thread_attr_init(&attr);
    qurt_thread_attr_set_name(&attr, "producer");
    qurt_thread_attr_set_stack_addr(&attr, producer_stack);
    qurt_thread_attr_set_stack_size(&attr, STACK_SIZE);
    qurt_thread_attr_set_priority(&attr, 100);
    qurt_thread_create(&producer, &attr, producer_thread, NULL);

    /* Create consumer (lower priority) */
    qurt_thread_attr_init(&attr);
    qurt_thread_attr_set_name(&attr, "consumer");
    qurt_thread_attr_set_stack_addr(&attr, consumer_stack);
    qurt_thread_attr_set_stack_size(&attr, STACK_SIZE);
    qurt_thread_attr_set_priority(&attr, 110);
    qurt_thread_create(&consumer, &attr, consumer_thread, NULL);

    /* Wait for both threads to finish */
    int status;
    qurt_thread_join(producer, &status);
    qurt_thread_join(consumer, &status);

    /* Clean up */
    qurt_mutex_destroy(&buffer_mutex);
    qurt_cond_destroy(&not_full);
    qurt_cond_destroy(&not_empty);

    printf("All done! Produced and consumed %d items.n", NUM_ITEMS);
    return 0;
}

یہ کوڈ روایتی پابند بفر پروڈیوسر-صارف پیٹرن کو نافذ کرتا ہے۔ مشترکہ بفر 16 عدد کا ایک سرکلر سرنی ہے جو ایک mutex کے ذریعے محفوظ ہے۔ پروڈیوسر بفر کو اشیاء لکھتے ہیں اور صارفین اشیاء پڑھتے ہیں۔

جب بفر بھر جاتا ہے، تو پروڈیوسر بلاک کرتا ہے: not_full حالت متغیر۔ اگر بفر خالی ہے تو، صارف بلاک کرتا ہے: not_empty. ہر طرف اپنے بفرز کو تبدیل کرتا ہے اور پھر دوسرے کو سگنل بھیجتا ہے۔

ڈیزائن کے لحاظ سے، پروڈیوسر کو صارفین (110) سے زیادہ ترجیح (100) حاصل ہوتی ہے۔ ایک حقیقی DSP منظر نامے میں، پروڈیوسر عام طور پر ہارڈ ویئر (مائیکروفون، سینسر) سے معلومات پڑھتے ہیں۔ اگر کوئی پروڈیوسر ہارڈویئر کا نمونہ کھو دیتا ہے، تو وہ ڈیٹا ہمیشہ کے لیے ضائع ہو جاتا ہے۔ صارفین بعد میں کسی بھی وقت اپنے ڈیٹا پر کارروائی کر سکتے ہیں۔ یہ RTOS ڈیزائن کا ایک عمومی اصول ہے۔ ہارڈ ویئر سے متعلقہ دھاگوں کو بھوکا نہ رکھیں۔

ہم وقت سازی کے ابتدائی

QRT پانچ اہم ہم آہنگی میکانزم فراہم کرتا ہے: mutexes، حالت متغیر، سگنل، رکاوٹیں، اور سیمفورس۔

┌──────────────┬────────────────────────────────────────────────────┐
│ Primitive    │ When to Use                                        │
├──────────────┼────────────────────────────────────────────────────┤
│ Mutex        │ Protecting shared data from concurrent access      │
│ Condition Var│ "Wait until X is true" (always paired with mutex)  │
│ Signal       │ One thread notifying another (like poking someone) │
│ Barrier      │ "Everyone wait here until all threads arrive"      │
├──────────────┼────────────────────────────────────────────────────┤
│ Semaphore    │ Controlling access to a limited resource pool      │
│              │ (for example, 4 DMA channels shared by 10 threads)        │
└──────────────┴────────────────────────────────────────────────────┘

یہ جدول ہر قدیم اور اس کے بنیادی استعمال کے معاملے کا خلاصہ کرتا ہے۔ Mutex مشترکہ ڈیٹا تک خصوصی رسائی کو نافذ کرتا ہے۔ کنڈیشن متغیرات دھاگے کو اس وقت تک سوتے رہتے ہیں جب تک کہ ڈیٹا کی ایک مخصوص حالت درست نہ ہو جائے اور ہمیشہ mutexes کے ساتھ استعمال ہوتے ہیں۔ سگنل تھریڈز کے درمیان ہلکے پھلکے ون ٹو ون اطلاع فراہم کرتے ہیں۔ ایک رکاوٹ ایک مشترکہ نقطہ پر دھاگوں کے ایک گروپ کو ہم آہنگ کرتی ہے۔ سیمفورس N ایک جیسے وسائل کے تالاب تک رسائی کو کنٹرول کرتے ہیں۔

mutex

ایک mutex اس بات کو یقینی بناتا ہے کہ ایک وقت میں صرف ایک تھریڈ ایک اہم حصے تک رسائی حاصل کرتا ہے۔ QRT mutexes غیر مسدود حصول کی بھی حمایت کرتے ہیں: qurt_mutex_try_lock().

qurt_mutex_t my_mutex;

void init_example(void)
{
    /* Always initialize before use */
    qurt_mutex_init(&my_mutex);
}

void critical_section_example(void)
{
    qurt_mutex_lock(&my_mutex);

    /* Only one thread can be here at a time */
    shared_counter++;
    shared_buffer[index] = new_value;

    qurt_mutex_unlock(&my_mutex);
}

/* Non-blocking version */
void try_lock_example(void)
{
    int result = qurt_mutex_try_lock(&my_mutex);

    if (result == QURT_EOK) {
        shared_counter++;
        qurt_mutex_unlock(&my_mutex);
    } else {
        printf("Busy, will try latern");
    }
}

void cleanup_example(void)
{
    qurt_mutex_destroy(&my_mutex);
}

کہ qurt_mutex_lock() کال کالنگ تھریڈ کو بلاک کرتی ہے جب تک کہ mutex دستیاب نہ ہو اور پھر اسے حاصل کر لے۔ qurt_mutex_try_lock() یہ mutex حاصل کرنے کی کوشش کرتا ہے اور فوری طور پر واپس آتا ہے۔ QURT_EOK کامیابی پر غلطی کا کوڈ یا اگر mutex برقرار ہے۔ ہمیشہ مجھے کال کریں۔ qurt_mutex_destroy() mutex آپریشن مکمل ہونے کے بعد.

QRT mutex کا نفاذ ترجیحی وراثت. اگر ایک اعلی ترجیحی دھاگہ کم ترجیحی دھاگے کے پاس موجود mutex پر انتظار کر رہا ہے، تو کم ترجیح والے دھاگے کو عارضی طور پر اعلی ترجیحی سطح پر ترقی دی جاتی ہے۔ یہ روکتا ہے۔ ترجیحی الٹایہ ایک کلاسک بگ ہے جس کی وجہ سے مارس پاتھ فائنڈر خلائی جہاز اپنے مشن کے دوران خود کو بار بار دوبارہ ترتیب دیتا ہے۔

QRT ترجیحی وراثت کو خود بخود ہینڈل کرتا ہے، لیکن آپ کو اس کے ہونے سے آگاہ ہونے کی ضرورت ہے تاکہ ڈیبگ کرتے وقت آپ غیر متوقع ترجیحی رویے سے الجھن میں نہ پڑیں۔

سگنل

QRT کے سگنل ہلکا پھلکا نوٹیفکیشن میکانزم ہیں۔ ایک تھریڈ ایک مخصوص سگنل بٹ کا انتظار کرتا ہے، اور دوسرے تھریڈز (یا ISRs) اس بٹ کو بیدار کرنے کے لیے سیٹ کرتے ہیں۔

#include 

#define SIGNAL_DATA_READY   0x01
#define SIGNAL_STOP         0x02
#define SIGNAL_ERROR        0x04

qurt_signal_t my_signal;

void signal_init(void)
{
    qurt_signal_init(&my_signal);
}

/* Waiting thread */
void waiter_thread(void *arg)
{
    unsigned int received_signals;

    while (1) {
        /* Wait for ANY of these signals */
        received_signals = qurt_signal_wait(
            &my_signal,
            SIGNAL_DATA_READY | SIGNAL_STOP | SIGNAL_ERROR,
            QURT_SIGNAL_ATTR_WAIT_ANY
        );

        if (received_signals & SIGNAL_STOP) {
            printf("Received stop signal. Exiting.n");
            break;
        }

        if (received_signals & SIGNAL_DATA_READY) {
            printf("Data is ready! Processing...n");
            process_data();
            /* Clear the signal after handling it */
            qurt_signal_clear(&my_signal, SIGNAL_DATA_READY);
        }

        if (received_signals & SIGNAL_ERROR) {
            printf("Error occurred! Handling...n");
            handle_error();
            qurt_signal_clear(&my_signal, SIGNAL_ERROR);
        }
    }

    qurt_signal_destroy(&my_signal);
    qurt_thread_exit(QURT_EOK);
}

/* Signaling thread (or ISR) */
void sender_thread(void *arg)
{
    prepare_data();
    qurt_signal_set(&my_signal, SIGNAL_DATA_READY);

    /* Later, tell it to stop */
    qurt_signal_set(&my_signal, SIGNAL_STOP);

    qurt_thread_exit(QURT_EOK);
}

کال انتظار کا دھاگہ qurt_signal_wait() دلچسپی کے سگنل کا بٹ ماسک استعمال کریں۔ QURT_SIGNAL_ATTR_WAIT_ANY جب مخصوص بٹس میں سے ایک سیٹ ہوجاتا ہے، تو اس کا مطلب ہے کہ تھریڈ جاگ جائے گا۔ تھریڈ کال بھیجی جا رہی ہے۔ qurt_signal_set() ایک یا زیادہ بٹس سیٹ کرتا ہے۔ سگنل پر کارروائی کرنے کے بعد، ویٹر کو ایک فون کال کرنا چاہیے۔ qurt_signal_clear() بٹ کو دوبارہ ترتیب دیں۔ اگر آپ سگنل صاف کرنا بھول جاتے ہیں تو اگلی کال ہے۔ qurt_signal_wait() یہ فوری طور پر واپس آجاتا ہے اور دھاگہ دوبارہ اسی واقعہ پر کارروائی کرتا ہے۔

سگنلز اور کنڈیشن متغیر کے درمیان انتخاب آپ کے استعمال کے معاملے پر منحصر ہے۔ سگنلز آسان اور ہلکے ہوتے ہیں، اس لیے وہ غیر متعلقہ دھاگوں یا ISRs کے درمیان اطلاعات کے لیے بہترین موزوں ہیں۔ کنڈیشن متغیرات اس وقت بہتر ہوتے ہیں جب نوٹیفکیشن کو ڈیٹا کی مخصوص حالت سے منسلک کیا جاتا ہے (بفر مکمل، قطار خالی) اور ڈیٹا کی تصدیق کے لیے mutex تحفظ درکار ہوتا ہے۔

رکاوٹ

ایک رکاوٹ تمام حصہ لینے والے تھریڈز کو اس وقت تک روکتی ہے جب تک کہ تمام حصہ لینے والے تھریڈز رکاوٹ کے مقام تک نہ پہنچ جائیں۔ یہ اس وقت کارآمد ہوتا ہے جب ایک حساب کو متعدد مراحل میں تقسیم کیا جاتا ہے، ہر قدم کا انحصار پچھلے مرحلے کے نتائج پر ہوتا ہے۔

#define NUM_WORKER_THREADS  4

qurt_barrier_t sync_barrier;

void worker_thread(void *arg)
{
    int thread_num = (int)(uintptr_t)arg;

    /* Phase 1: Each thread computes its portion */
    printf("Thread %d: Computing phase 1...n", thread_num);
    compute_partial_result(thread_num);

    /* All threads wait here until everyone finishes phase 1 */
    qurt_barrier_wait(&sync_barrier);

    /* Phase 2: All partial results are ready, combine them */
    printf("Thread %d: Computing phase 2...n", thread_num);
    combine_results(thread_num);

    qurt_thread_exit(QURT_EOK);
}

int main(void)
{
    qurt_barrier_init(&sync_barrier, NUM_WORKER_THREADS);

    /* Create worker threads */
    for (int i = 0; i < NUM_WORKER_THREADS; i++) {
        create_worker(i);
    }

    join_all_workers();

    qurt_barrier_destroy(&sync_barrier);
    return 0;
}

رکاوٹ کو حصہ لینے والے دھاگوں کی تعداد کے ساتھ شروع کیا جاتا ہے۔ ہر تھریڈ کال کرتا ہے۔ qurt_barrier_wait() جب سنکرونائزیشن پوائنٹ پر پہنچ جاتا ہے، تمام تھریڈز آنے تک کال بلاک ہو جاتی ہے۔ جب آخری تھریڈ بلایا جاتا ہے۔ qurt_barrier_wait()تمام تھریڈز ایک ساتھ جاری کیے جاتے ہیں اور مرحلہ 2 کے ساتھ جاری رہتے ہیں۔

سیمفور

سیمفورس N ایک جیسے وسائل کے تالاب تک رسائی کو کنٹرول کرتے ہیں۔ ایک mutex (N=1 کے ساتھ ایک سیمفور) کے برعکس، ایک سیمفور کو N دھاگوں تک ایک ساتھ رکھا جا سکتا ہے۔

#define MAX_DMA_CHANNELS 4

qurt_sem_t dma_semaphore;

void init_dma_pool(void)
{
    /* 4 DMA channels available */
    qurt_sem_init_val(&dma_semaphore, MAX_DMA_CHANNELS);
}

void thread_needing_dma(void *arg)
{
    /* Acquire a DMA channel (blocks if all 4 are in use) */
    qurt_sem_down(&dma_semaphore);

    int channel = allocate_dma_channel();
    perform_dma_transfer(channel);
    release_dma_channel(channel);

    /* Release the semaphore slot */
    qurt_sem_up(&dma_semaphore);

    qurt_thread_exit(QURT_EOK);
}

سیمفورس 4 سے شروع ہوتے ہیں، جو DMA چینلز کی تعداد سے مماثل ہوتے ہیں۔ ہر ایک qurt_sem_down() جب گنتی 0 تک پہنچ جائے تو گنتی کو کم کریں اور بلاک کریں۔ qurt_sem_up() گنتی میں اضافہ کریں اور اگر انتظار کرنے والے تھریڈز ہیں تو ایک ویٹنگ تھریڈ کو غیر مسدود کریں۔ یہ یقینی بناتا ہے کہ چار سے زیادہ تھریڈز بیک وقت DMA چینل کا استعمال نہ کریں۔

میموری کا انتظام

ڈی ایس پی کی یادداشت محدود ہے۔ ایک عام ہیکساگون DSP 256KB اور 2MB کے درمیان مضبوطی سے جوڑے ہوئے میموری (TCM) اور DDR تک رسائی فراہم کرتا ہے۔ QRT دونوں کو مؤثر طریقے سے منظم کرنے کے لیے ٹولز فراہم کرتا ہے۔

میموری نقشہ

┌───────────────────────────────────┐  High Address
│         DDR (Shared with ARM)     │
│   - Large buffers                 │
│   - Neural network weights        │
│   - Audio/video frames            │
├───────────────────────────────────┤
│         QuRT Virtual Memory       │
│   - User heap                     │
│   - Thread stacks                 │
├───────────────────────────────────┤
│         L2 Cache (TCM Mode)       │
│   - Frequently accessed buffers   │
│   - Lookup tables                 │
├───────────────────────────────────┤
│         QuRT Kernel               │
│   - Scheduler, ISR handlers       │
│   - System data structures        │
└───────────────────────────────────┘  Low Address

یہ خاکہ ہیکساگون ڈی ایس پی میموری لے آؤٹ کو کم سے اعلی پتوں تک دکھاتا ہے۔ QuRT کرنل سب سے کم پتہ پر قبضہ کرتا ہے اور صارف کوڈ ممنوع ہے۔ اس کے اوپری حصے میں، TCM موڈ میں کنفیگر کردہ L2 کیش گرم ڈیٹا کے لیے تیز اسٹوریج فراہم کرتا ہے۔ ورچوئل میموری ایریا صارف کے ہیپ اور تھریڈ اسٹیک کو رکھتا ہے۔ سب سے اوپر، DDR کو ARM CPU کے ساتھ اشتراک کیا جاتا ہے اور بڑے ڈیٹا بفرز، ML ماڈل کے وزن، اور میڈیا فریموں کے لیے استعمال کیا جاتا ہے۔ ڈی ڈی آر میں ٹی سی ایم سے زیادہ تاخیر ہے لیکن اس کی گنجائش بہت زیادہ ہے۔

متحرک میموری مختص کرنا

#include 
#include 

void memory_examples(void)
{
    /* Standard malloc/free works (QuRT provides a heap) */
    int *data = (int *)malloc(1024 * sizeof(int));
    if (!data) {
        printf("malloc failed! Out of heap memory.n");
        return;
    }

    for (int i = 0; i < 1024; i++) {
        data[i] = i * 2;
    }

    free(data);
}

QRT ایک معیاری C ہیپ فراہم کرتا ہے، لہذا malloc اور free یہ توقع کے مطابق کام کرتا ہے۔ لیکن malloc عمل درآمد کا وقت غیر متوقع ہے کیونکہ اسے مفت فہرست تلاش کرنے، بلاکس تقسیم کرنے، یا ملحقہ آزاد علاقوں کو ضم کرنے کی ضرورت پڑ سکتی ہے۔ یہ ریئل ٹائم ہاٹ پاتھز کے لیے نامناسب بناتا ہے جہاں پر عمل درآمد کا وقت مقررہ ہونا چاہیے۔ استعمال کریں malloc یہ آن اور آف کے لیے ہے، فی فریم یا فی نمونہ مختص کرنے کے لیے نہیں۔

کیش مینجمنٹ

Hexagon DSP میں، ARM CPU کے ساتھ میموری کا اشتراک کرتے وقت واضح کیشے کا انتظام ضروری ہے۔

#include 

void cache_management_example(void)
{
    void *buffer;
    size_t buffer_size = 4096;

    /* Allocate physically contiguous, cache-aligned memory */
    int result = qurt_mem_region_create(
        &buffer,
        buffer_size,
        qurt_mem_default_pool,
        QURT_MEM_REGION_SHARED
    );

    if (result != QURT_EOK) {
        printf("Memory region creation failedn");
        return;
    }

    /* BEFORE reading data written by another processor (e.g., ARM): */
    qurt_mem_cache_clean(buffer, buffer_size,
                          QURT_MEM_CACHE_INVALIDATE);

    /* Read data from the buffer... */

    /* AFTER writing data that another processor will read: */
    fill_buffer_with_results(buffer, buffer_size);
    qurt_mem_cache_clean(buffer, buffer_size,
                          QURT_MEM_CACHE_FLUSH);
}

کہ qurt_mem_region_create() کال دوسرے پروسیسرز کے ساتھ اشتراک کے لیے موزوں میموری کا ایک جسمانی طور پر متصل علاقہ مختص کرتی ہے۔ کہ QURT_MEM_REGION_SHARED کراس پروسیسر کے استعمال کے لیے ایک جھنڈا اس کو نشان زد کرتا ہے۔

مشترکہ میموری کے لیے کیشے کے اصول سادہ لیکن اہم ہیں۔

  1. بدگمانی آپ کے سامنے پڑھیںیہ اس بات کو یقینی بناتا ہے کہ آپ پرانے کیش اندراجات کے بجائے ARM CPU کے ذریعے لکھا ہوا تازہ ترین ڈیٹا دیکھیں۔

  2. فلش آپ کے بعد لکھنالہذا ARM CPU تبدیلیوں کو دیکھتا ہے، نہ کہ مین میموری کے پچھلے مواد کو۔

ایسا کرنا بھول جانے سے آپ کے کوڈ میں کیڑے پیدا ہو سکتے ہیں جو منطقی طور پر درست ہیں لیکن پرانے ڈیٹا پر کام کرتے ہیں۔

پیش قیاسی مختص کے لیے میموری پول

میموری پولز O(1) مختص کرنے کا وقت فراہم کرتے ہیں، جو انہیں حقیقی وقت کے گرم راستوں کے لیے موزوں بناتے ہیں۔

#include 

#define BLOCK_SIZE    256
#define NUM_BLOCKS    32

/* Pool memory is statically allocated for determinism */
static char pool_memory[BLOCK_SIZE * NUM_BLOCKS] __attribute__((aligned(8)));
static qurt_mem_pool_t my_pool;

void pool_init(void)
{
    qurt_mem_pool_create(&my_pool, pool_memory,
                          BLOCK_SIZE * NUM_BLOCKS,
                          BLOCK_SIZE);
}

void *pool_alloc(void)
{
    void *block = qurt_mem_pool_alloc(&my_pool);
    if (!block) {
        printf("Pool exhausted!n");
    }
    return block;
}

void pool_free(void *block)
{
    qurt_mem_pool_free(&my_pool, block);
}

یہ کوڈ 32 بلاکس کا ایک پول بناتا ہے، ہر بلاک 256 بائٹس کا ہوتا ہے۔ پول میموری کو مستحکم طور پر مختص کیا جاتا ہے تاکہ انحصار سے بچ سکیں: malloc رن ٹائم پر۔

qurt_mem_pool_alloc() ایک خاص وقت پر ایک بلاک واپس کریں، qurt_mem_pool_free() ایک خاص وقت پر واپسی ہوتی ہے۔ جب پول ختم ہو جاتا ہے، مختصات کسی اور جگہ میموری کو بلاک کرنے یا بازیافت کرنے کے بجائے NULL لوٹاتا ہے۔

یہ عزمیت میموری پولز کو آڈیو پروسیسنگ لوپس، سینسر ڈیٹا ہینڈلرز، اور دوسرے کوڈ کے لیے ایک اچھا انتخاب بناتا ہے جو سخت ڈیڈ لائن کے تحت چلتا ہے۔

ٹائمر اور ٹائمنگ

QRT درست وقت کے لیے ہارڈ ویئر سے معاون ٹائمر فراہم کرتا ہے۔ ڈی ایس پی آپریشنز کے لیے یہ بہت ضروری ہے۔ 48 kHz پر آڈیو پروسیسنگ کے لیے ہر 10.67 ملی سیکنڈ بعد ایک نیا بفر درکار ہوتا ہے، بغیر کسی استثنا کے۔

ایک شاٹ ٹائمر

#include 
#include 

qurt_timer_t my_timer;
qurt_signal_t timer_signal;

#define TIMER_EXPIRED_SIGNAL  0x01

void timer_example(void)
{
    qurt_signal_init(&timer_signal);

    qurt_timer_attr_t attr;
    qurt_timer_attr_init(&attr);

    /* Set timer duration: 10 milliseconds */
    qurt_timer_attr_set_duration(&attr,
        qurt_timer_convert_time_to_ticks(10000,  /* microseconds */
                                          QURT_TIME_USEC));

    /* Set the signal to fire when timer expires */
    qurt_timer_attr_set_signal(&attr, &timer_signal);
    qurt_timer_attr_set_signal_mask(&attr, TIMER_EXPIRED_SIGNAL);

    /* One-shot: fires once */
    qurt_timer_attr_set_type(&attr, QURT_TIMER_ONESHOT);

    /* Create and start the timer */
    qurt_timer_create(&my_timer, &attr);

    /* Wait for the timer to expire */
    qurt_signal_wait(&timer_signal,
                      TIMER_EXPIRED_SIGNAL,
                      QURT_SIGNAL_ATTR_WAIT_ANY);

    printf("Timer expired! 10ms have passed.n");
    qurt_signal_clear(&timer_signal, TIMER_EXPIRED_SIGNAL);

    /* Clean up */
    qurt_timer_delete(my_timer);
    qurt_signal_destroy(&timer_signal);
}

یہ ایک شاٹ ٹائمر بناتا ہے جو 10 ملی سیکنڈ کے بعد چلتا ہے۔ ٹائمر مدت، سگنل آبجیکٹ جس کو مطلع کیا جائے، سیٹ کرنے کے لیے سگنل بٹ ماسک، اور ٹائمر کی قسم (QURT_TIMER_ONESHOT)۔ ٹائمر کی میعاد ختم ہونے پر، مخصوص سگنل بٹ سیٹ کرکے مسدود دھاگے کو جگا دیں۔ qurt_signal_wait(). ایونٹ پر کارروائی کرنے کے بعد، دھاگہ سگنل کو صاف کرتا ہے اور ٹائمر کو صاف کرتا ہے۔

متواتر ٹائمر

void periodic_timer_thread(void *arg)
{
    qurt_timer_t periodic_timer;
    qurt_signal_t periodic_signal;
    qurt_timer_attr_t attr;

    qurt_signal_init(&periodic_signal);
    qurt_timer_attr_init(&attr);

    /* Fire every 1 millisecond */
    qurt_timer_attr_set_duration(&attr,
        qurt_timer_convert_time_to_ticks(1000, QURT_TIME_USEC));
    qurt_timer_attr_set_signal(&attr, &periodic_signal);
    qurt_timer_attr_set_signal_mask(&attr, 0x01);
    qurt_timer_attr_set_type(&attr, QURT_TIMER_PERIODIC);

    qurt_timer_create(&periodic_timer, &attr);

    int iteration = 0;
    while (iteration < 1000) {
        qurt_signal_wait(&periodic_signal, 0x01,
                          QURT_SIGNAL_ATTR_WAIT_ANY);
        qurt_signal_clear(&periodic_signal, 0x01);

        /* This runs every 1ms */
        process_audio_frame(iteration);
        iteration++;
    }

    qurt_timer_delete(periodic_timer);
    qurt_signal_destroy(&periodic_signal);
    qurt_thread_exit(QURT_EOK);
}

ایک متواتر ٹائمر استعمال کرتا ہے: QURT_TIMER_PERIODIC اس کے بجائے QURT_TIMER_ONESHOT. مخصوص وقفوں پر بار بار آگ لگتی ہے۔ یہ مثال 1ms کے وقفوں کے ساتھ 1000 تکرار کو انجام دے کر فی ٹک ایک آڈیو فریم پر کارروائی کرتی ہے۔ ہر تکرار کے بعد یا اگلی تکرار کے بعد سگنل کو صاف کرنا ضروری ہے۔ qurt_signal_wait() فوراً واپس آجائے گا۔

موجودہ وقت پڑھیں

void timing_example(void)
{
    unsigned long long start_ticks = qurt_sysclock_get_hw_ticks();

    heavy_computation();

    unsigned long long end_ticks = qurt_sysclock_get_hw_ticks();
    unsigned long long elapsed_ticks = end_ticks - start_ticks;

    unsigned long long elapsed_us =
        qurt_timer_convert_ticks_to_time(elapsed_ticks, QURT_TIME_USEC);

    printf("Computation took %llu microsecondsn", elapsed_us);
}

qurt_sysclock_get_hw_ticks() ہارڈویئر سائیکل کاؤنٹر کو پڑھتا ہے، جو DSP میں دستیاب اعلی ترین ریزولوشن ٹائمنگ فراہم کرتا ہے۔ qurt_timer_convert_ticks_to_time() خام ٹک کو انسانی پڑھنے کے قابل اکائیوں میں تبدیل کریں، اس صورت میں مائیکرو سیکنڈز۔ انفرادی خصوصیات کو پروفائل کرنے اور کارکردگی کی رکاوٹوں کی نشاندہی کرنے کے لیے اس پیٹرن کا استعمال کریں۔

ہینڈلنگ میں خلل ڈالنا

ڈی ایس پی میں، رکاوٹ ہارڈ ویئر کا اشارہ دینے کا طریقہ ہے کہ اسے توجہ دینے کی ضرورت ہے۔ QRT ننگے دھاتی ISR ہینڈلرز کے مقابلے میں دھاگے پر مبنی ایک زیادہ ساختی مداخلت کا ماڈل فراہم کرتا ہے۔

#include 
#include 

#define MY_SENSOR_IRQ      42
#define IRQ_SIGNAL         0x01

static qurt_signal_t irq_signal;

void sensor_isr_thread(void *arg)
{
    int irq = MY_SENSOR_IRQ;

    /* Register this thread as the handler for IRQ 42 */
    qurt_interrupt_register(irq, &irq_signal, IRQ_SIGNAL);

    printf("Sensor ISR thread ready, waiting for interrupts...n");

    while (1) {
        /* Block until the hardware interrupt fires */
        unsigned int sigs = qurt_signal_wait(
            &irq_signal, IRQ_SIGNAL, QURT_SIGNAL_ATTR_WAIT_ANY);

        if (sigs & IRQ_SIGNAL) {
            qurt_signal_clear(&irq_signal, IRQ_SIGNAL);

            /* Read sensor data quickly */
            int sensor_value = read_sensor_register();

            /* Put data in a queue for the processing thread */
            enqueue_sensor_data(sensor_value);

            /* Signal the processing thread */
            qurt_signal_set(&processing_signal, DATA_READY);

            /* Re-enable the interrupt */
            qurt_interrupt_acknowledge(irq);
        }
    }
}

QRT ISR ننگی دھاتی ISR سے مختلف ہے۔ یہ ایک سرشار دھاگے کے سیاق و سباق میں چلتا ہے۔ اس کا مطلب ہے کہ آپ اندرونی طور پر mutexes اور سگنلز استعمال کر سکتے ہیں۔ تاہم، ISR تھریڈ کو ابھی بھی کم سے کم کام کرنے کی ضرورت ہے۔ یعنی، اسے ہارڈ ویئر کے رجسٹر پڑھنا، قطار میں ڈیٹا شامل کرنا، سگنل پروسیسنگ تھریڈز، اور رکاوٹوں کو تسلیم کرنا چاہیے۔ کوئی بھی مہنگا حساب الگ، کم ترجیحی پروسیسنگ تھریڈ میں کیا جانا چاہیے۔

Hardware IRQ
     │
     ▼
ISR Thread (high priority)     Processing Thread (medium priority)
┌──────────────────┐          ┌──────────────────────────┐
│ Read HW register │          │ Wait for DATA_READY      │
│ Enqueue data     │ ──────►  │ Dequeue data             │
│ Signal "ready"   │          │ Run FFT / filter / etc.  │
│ ACK interrupt    │          │ Write results            │
└──────────────────┘          └──────────────────────────┘

یہ خاکہ ISR آف لوڈنگ پیٹرن کو دکھاتا ہے۔ بائیں طرف کا ISR دھاگہ کم سے کم تاخیر کے ساتھ ہارڈویئر مداخلت کو ہینڈل کرتا ہے۔ یہ سینسر کے رجسٹروں کو پڑھتا ہے، قطار میں خام ڈیٹا شامل کرتا ہے، دھاگوں کی پروسیسنگ کو سگنل دیتا ہے، اور رکاوٹوں کو تسلیم کرتا ہے تاکہ وہ دوبارہ کام کر سکیں۔ دائیں طرف پروسیسنگ تھریڈ مہنگے آپریشنز (FFT، فلٹرنگ، ML inference) کو کم ترجیح پر انجام دیتا ہے۔

یہ ڈیزائن اس بات کو یقینی بناتا ہے کہ ISR تھریڈ اگلے ہارڈ ویئر میں رکاوٹ کی خدمت کے لیے ہمیشہ دستیاب ہے، چاہے پروسیسنگ تھریڈز پچھلے نمونے سے کام کر رہے ہوں۔

پائپ اور پیغام کی قطار

QRT محفوظ، سٹرکچرڈ انٹر تھریڈ کمیونیکیشن کے لیے بلٹ ان پائپ سپورٹ فراہم کرتا ہے۔ پائپ ایک مقررہ سائز کے پیغام کی قطار ہے جو بھیجنے اور وصول کرنے کی کارروائیوں کو روکتی ہے۔

#include 
#include 

#define PIPE_ELEMENTS   16
#define ELEMENT_SIZE    sizeof(sensor_msg_t)

typedef struct {
    int sensor_id;
    int value;
    unsigned long long timestamp;
} sensor_msg_t;

/* Pipe buffer must be allocated by you */
static char pipe_buffer[PIPE_ELEMENTS * ELEMENT_SIZE]
    __attribute__((aligned(8)));

qurt_pipe_t sensor_pipe;

void pipe_init(void)
{
    qurt_pipe_attr_t attr;
    qurt_pipe_attr_init(&attr);
    qurt_pipe_attr_set_buffer(&attr, pipe_buffer);
    qurt_pipe_attr_set_buffer_partition(&attr, PIPE_ELEMENTS);
    qurt_pipe_attr_set_elements(&attr, PIPE_ELEMENTS);
    qurt_pipe_attr_set_element_size(&attr, ELEMENT_SIZE);

    qurt_pipe_create(&sensor_pipe, &attr);
}

/* Producer: send sensor data into the pipe */
void sensor_reader_thread(void *arg)
{
    while (1) {
        sensor_msg_t msg;
        msg.sensor_id = 1;
        msg.value = read_accelerometer();
        msg.timestamp = qurt_sysclock_get_hw_ticks();

        /* Blocking send: waits if pipe is full */
        qurt_pipe_send(&sensor_pipe, (char *)&msg, ELEMENT_SIZE);
    }
}

/* Consumer: receive sensor data from the pipe */
void data_processor_thread(void *arg)
{
    sensor_msg_t msg;

    while (1) {
        /* Blocking receive: waits if pipe is empty */
        qurt_pipe_receive(&sensor_pipe, (char *)&msg, ELEMENT_SIZE);

        printf("Sensor %d: value=%d at tick=%llun",
               msg.sensor_id, msg.value, msg.timestamp);

        process_sensor_reading(&msg);
    }
}

ایک QRT پائپ جامد طور پر مختص کردہ بفر، عناصر کی تعداد، اور عنصر کے سائز پر مشتمل ہوتا ہے۔ اسٹیک کی طرح، بفر میموری بھی آپ کی ذمہ داری ہے۔ qurt_pipe_send() پیغامات کو پائپ میں کاپی کرتا ہے اور پائپ بھر جانے پر بلاک کرتا ہے۔ qurt_pipe_receive() پیغام کو کاپی کرتا ہے اور اگر پائپ خالی ہے تو بلاک کرتا ہے۔ پائپ تمام اندرونی مطابقت پذیری کو سنبھالتے ہیں، لہذا علیحدہ mutex کی ضرورت نہیں ہے.

پائپ قدرتی طور پر یہاں دکھائے گئے سینسر ڈیٹا پیٹرن میں فٹ ہوجاتے ہیں۔ ریڈر تھریڈ ایک مقررہ شرح پر ہارڈ ویئر کا نمونہ لیتا ہے اور پیغامات کو پائپ میں دھکیلتا ہے، جبکہ پروسیسر تھریڈ پیغامات کو باہر نکالتا ہے اور ان پر کارروائی کرتا ہے۔ پائپ خود بخود بفرنگ اور بیک پریشر فراہم کرتا ہے۔

کیو آر ٹی اور فاسٹ آر پی سی

اصل Qualcomm آلات میں، QuRT شاذ و نادر ہی اکیلے استعمال ہوتا ہے۔ اے آر ایم سی پی یوز پر اینڈرائیڈ یا لینکس ایپلیکیشنز ڈی ایس پی کو کمپیوٹیشنل طور پر انتہائی کاموں کو آف لوڈ کرتے ہیں: تیز آر پی سی (فاسٹ ریموٹ طریقہ کار کال). مندرجہ ذیل خاکہ پوری پائپ لائن کو دکھاتا ہے۔

┌───────────────────────────────────────────────────────────────┐
│                         ARM CPU Side                          │
│                                                               │
│   your_app.c                                                  │
│   ┌───────────────────────────────────────────────────┐       │
│   │  #include "my_dsp_module.h"  // auto-generated    │       │
│   │                                                   │       │
│   │  // This looks like a normal function call,       │       │
│   │  // but it actually executes on the DSP!          │       │
│   │  result = my_dsp_module_process_audio(            │       │
│   │      input_buffer, output_buffer, num_samples);   │       │
│   └───────────────────┬───────────────────────────────┘       │
│                       │ FastRPC                               │
└───────────────────────┼───────────────────────────────────────┘
            (crosses processor boundary)          
┌───────────────────────┼───────────────────────────────────────┐
│                       ▼                                       │
│                  DSP Side (QuRT)                              │
│   my_dsp_module_skel.c  // auto-generated skeleton            │
│   ┌───────────────────────────────────────────────────┐       │
│   │  int my_dsp_module_process_audio(                 │       │
│   │      const int16_t *input,                        │       │
│   │      int16_t *output,                             │       │
│   │      int num_samples)                             │       │
│   │  {                                                │       │
│   │      // This runs on the Hexagon DSP under QuRT   │       │
│   │      apply_noise_reduction(input, output,         │       │
│   │                             num_samples);         │       │
│   │      return 0;                                    │       │
│   │  }                                                │       │
│   └───────────────────────────────────────────────────┘       │
└───────────────────────────────────────────────────────────────┘

یہ خاکہ FastRPC فن تعمیر کو ظاہر کرتا ہے۔ ARM CPU کی طرف، ایپلیکیشن ایسے فنکشنز کو کال کرتی ہے جو کہ ریگولر C فنکشنز کی طرح نظر آتے ہیں۔ اندرونی طور پر، FastRPC دلائل کو سیریلائز کرتا ہے، انہیں پروسیسر کی حدود کے پار ہیکساگون DSP کو بھیجتا ہے، QRT پر فنکشن کو انجام دیتا ہے، اور نتائج واپس کرتا ہے۔ پروگرامر کا تجربہ شفاف ریموٹ پروسیجر کالز ہے۔

مرحلہ 1: انٹرفیس کی وضاحت کریں (IDL فائل)

بنانا .idl فائل کے افعال کو بیان کرنے والی فائل جسے ARM DSP سے کال کر سکتا ہے:

/* my_dsp_module.idl */
#include "remote.idl"
#include "AEEStdDef.idl"

interface my_dsp_module {

    /* Simple computation */
    long process_audio(
        in sequence input,
        rout sequence output,
        in long num_samples
    );

    /* Matrix multiply offload */
    long matrix_multiply(
        in sequence mat_a,
        in sequence mat_b,
        rout sequence result,
        in long rows_a,
        in long cols_a,
        in long cols_b
    );
};

انٹرفیس ڈیفینیشن لینگویج (IDL) فائلیں کراس پروسیسر APIs کی وضاحت کرتی ہیں۔ ہر فنکشن سمت کوالیفائر کے ساتھ اپنے پیرامیٹرز کی وضاحت کرتا ہے۔ in اے آر ایم سے ڈی ایس پی تک بہنے والے ڈیٹا کے لیے rout ڈی ایس پی سے واپس اے آر ایم میں بہنے والے ڈیٹا کے لیے۔ کہ sequence نحو ایک متغیر لمبائی کی صف کی وضاحت کرتا ہے۔ Hexagon SDK کا IDL کمپائلر اس تعریف سے ARM سائیڈ کے لیے سٹب کوڈ اور DSP سائیڈ کے لیے سکیلیٹن کوڈ تیار کرتا ہے۔

مرحلہ 2: ڈی ایس پی سائیڈ پر عمل درآمد

/* my_dsp_module_imp.c - DSP implementation */

#include "my_dsp_module.h"
#include 
#include 

int my_dsp_module_process_audio(
    const int16_t *input, int input_len,
    int16_t *output, int output_len,
    int num_samples)
{
    if (!input || !output || num_samples <= 0) {
        return -1;
    }

    /* Invalidate cache: ARM wrote this data */
    qurt_mem_cache_clean((void *)input,
                          num_samples * sizeof(int16_t),
                          QURT_MEM_CACHE_INVALIDATE);

    /* Process on the DSP */
    for (int i = 0; i < num_samples; i++) {
        /* Simple noise gate */
        if (abs(input[i]) < 100) {
            output[i] = 0;
        } else {
            output[i] = input[i];
        }
    }

    /* Flush cache: ARM will read this data */
    qurt_mem_cache_clean(output,
                          num_samples * sizeof(int16_t),
                          QURT_MEM_CACHE_FLUSH);

    return 0;
}

DSP کے نفاذ کو ARM CPU کے لکھے ہوئے ان پٹ بفرز موصول ہوتے ہیں۔ پڑھنے سے پہلے، کوڈ کیشے کو باطل کر دیتا ہے لہذا ڈی ایس پی باسی کیش اندراجات کے بجائے مین میموری میں تازہ ترین ڈیٹا دیکھتا ہے۔ آؤٹ پٹ لکھنے کے بعد، کوڈ کیشے کو فلش کرتا ہے تاکہ ARM CPU DSP سے نتائج دیکھ سکے۔ اصل پروسیسنگ (اس مثال میں ایک سادہ شور گیٹ) کیش آپریشنز کے درمیان چلتا ہے۔

مرحلہ 3: اے آر ایم سائیڈ کا نفاذ

/* main_arm.c - ARM/Android application */

#include 
#include 
#include 
#include "my_dsp_module.h"

int main(void)
{
    int num_samples = 1024;

    /* Use ION memory for zero-copy sharing with DSP */
    rpcmem_init();

    int16_t *input = (int16_t *)rpcmem_alloc(
        RPCMEM_HEAP_ID_SYSTEM,
        RPCMEM_DEFAULT_FLAGS,
        num_samples * sizeof(int16_t));

    int16_t *output = (int16_t *)rpcmem_alloc(
        RPCMEM_HEAP_ID_SYSTEM,
        RPCMEM_DEFAULT_FLAGS,
        num_samples * sizeof(int16_t));

    if (!input || !output) {
        printf("rpcmem_alloc failed!n");
        return -1;
    }

    /* Fill input with audio data */
    for (int i = 0; i < num_samples; i++) {
        input[i] = (int16_t)(i % 256);
    }

    /* This call goes to the DSP via FastRPC */
    int result = my_dsp_module_process_audio(
        input, num_samples,
        output, num_samples,
        num_samples);

    if (result != 0) {
        printf("DSP processing failed: %dn", result);
    } else {
        printf("DSP processing succeeded!n");
        printf("First 10 output samples: ");
        for (int i = 0; i < 10; i++) {
            printf("%d ", output[i]);
        }
        printf("n");
    }

    rpcmem_free(input);
    rpcmem_free(output);
    rpcmem_deinit();

    return 0;
}

ARM سائیڈ استعمال کرتا ہے: rpcmem_alloc() ARM CPU اور Hexagon DSP دونوں ION میموری کو مختص کرتے ہیں، ایک مشترکہ میموری ایریا جس تک بغیر کاپی کیے رسائی حاصل کی جا سکتی ہے۔ کال کریں my_dsp_module_process_audio() اگرچہ یہ ایک عام فنکشن کال کی طرح لگتا ہے، فاسٹ آر پی سی اسے شفاف طریقے سے ڈی ایس پی تک پہنچاتا ہے۔ جب کال واپس آتی ہے، آؤٹ پٹ بفر میں DSP کے نتائج ہوتے ہیں۔

ایک مکمل پروجیکٹ کی تعمیر

FastRPC پروجیکٹ کے لیے دو SCons کی تعمیر کی ضرورت ہے۔ ایک ARM CPU سائیڈ کے لیے ہے اور دوسرا Hexagon DSP سائیڈ کے لیے ہے۔ ہر پہلو کا اپنا مواد ہے۔ .min فائل(android.min اور hexagon.min)، دونوں کو SDK کے ذریعے ہینڈل کیا جاتا ہے۔ SConstruct.

cd $HEXAGON_SDK_ROOT

# Build for ARM target (Android) via make wrapper
make V=android_Release tree=my_dsp_module

# Build for Hexagon DSP via make wrapper
make V=hexagon_Release_dynamic_toolv84_v66 tree=my_dsp_module

# Or invoke SCons directly for both variants
python tools/build/scons/scons.py 
    V=android_Release 
    V=hexagon_Release_dynamic_toolv84_v66 
    my_dsp_module

# Push to device
adb push android_Release/ship/my_dsp_module /data/local/tmp/
adb push hexagon_Release_dynamic_toolv84_v66/ship/libmy_dsp_module_skel.so 
    /data/local/tmp/

# Run it
adb shell "cd /data/local/tmp && ./my_dsp_module"

تعمیر دو آؤٹ پٹ تیار کرتی ہے: ARM قابل عمل (ایک اسٹب سے مرتب کیا گیا) اور main_arm.c) اور مسدس مشترکہ لائبریریاں ( _skel.so ڈی ایس پی کے نفاذ سے مرتب کردہ فائلیں)۔ SCons خود بخود IDL تالیف کے مرحلے کو ہینڈل کرتا ہے۔ .idl سٹب اور سکیلیٹن سی سورس فائلیں بنانے کے لیے فائلیں بنائیں اور انہیں مناسب ویرینٹ بلڈ میں شامل کریں۔ دونوں آؤٹ پٹ کو ڈیوائس پر دھکیل دیا جاتا ہے۔

جب ARM ایگزیکیوٹیبل چلتا ہے اور FastRPC فنکشن کو کال کرتا ہے، تو سسٹم اسکیلیٹن لائبریری کو DSP میں لوڈ کرتا ہے اور کال کو روٹ کرتا ہے۔

سینسر فیوژن پائپ لائن کی تعمیر

اس سیکشن میں، ہم تھریڈز، سنکرونائزیشن، ٹائمرز، اور سگنلز کو ایک مکمل، حقیقت پسندانہ QRT ایپلی کیشن میں ضم کرتے ہیں۔ پائپ لائن تین مصنوعی سینسرز (ایکسلرومیٹر، گائروسکوپ، اور میگنیٹومیٹر) سے پڑھتی ہے، تکمیلی فلٹرز کا استعمال کرتے ہوئے ڈیٹا کو فیوز کرتی ہے، اور 100 ہرٹج پر واقفیت کی اطلاع دیتی ہے۔

/*
 * sensor_fusion.c - Multi-sensor fusion pipeline on QuRT
 *
 * Architecture:
 *   [Accel ISR] ──► [Fusion Thread] ──► [Report Thread]
 *   [Gyro ISR]  ──►       ▲
 *   [Mag ISR]   ──►       │
 *                    [Timer Thread]
 *                    (triggers fusion every 10ms)
 */

#include 
#include 
#include 
#include 
#include 

/* Configuration */
#define STACK_SIZE          8192
#define FUSION_PERIOD_US    10000   /* 10ms = 100Hz fusion rate */
#define QUEUE_DEPTH         32

/* Data types */
typedef struct {
    float x, y, z;
    unsigned long long timestamp;
} vec3_sample_t;

typedef struct {
    vec3_sample_t accel;
    vec3_sample_t gyro;
    vec3_sample_t mag;
    float roll, pitch, yaw;
} fused_state_t;

/* Thread stacks */
static char accel_stack[STACK_SIZE]  __attribute__((aligned(8)));
static char gyro_stack[STACK_SIZE]   __attribute__((aligned(8)));
static char mag_stack[STACK_SIZE]    __attribute__((aligned(8)));
static char fusion_stack[STACK_SIZE] __attribute__((aligned(8)));
static char report_stack[STACK_SIZE] __attribute__((aligned(8)));

/* Shared state */
static vec3_sample_t latest_accel;
static vec3_sample_t latest_gyro;
static vec3_sample_t latest_mag;
static fused_state_t latest_fused;

static qurt_mutex_t sensor_mutex;
static qurt_mutex_t fused_mutex;
static qurt_signal_t fusion_signal;
static qurt_signal_t report_signal;

#define SIG_FUSION_TICK    0x01
#define SIG_NEW_FUSED_DATA 0x01
#define SIG_SHUTDOWN       0x80

static volatile int running = 1;

/* Simulated sensor reads */
static void read_accelerometer(vec3_sample_t *sample)
{
    sample->x = 0.01f;
    sample->y = 0.02f;
    sample->z = 9.81f;
    sample->timestamp = qurt_sysclock_get_hw_ticks();
}

static void read_gyroscope(vec3_sample_t *sample)
{
    sample->x = 0.001f;
    sample->y = -0.002f;
    sample->z = 0.0005f;
    sample->timestamp = qurt_sysclock_get_hw_ticks();
}

static void read_magnetometer(vec3_sample_t *sample)
{
    sample->x = 25.0f;
    sample->y = -5.0f;
    sample->z = 40.0f;
    sample->timestamp = qurt_sysclock_get_hw_ticks();
}

/* Accelerometer thread */
void accel_thread(void *arg)
{
    printf("[Accel] Thread startedn");

    while (running) {
        vec3_sample_t sample;
        read_accelerometer(&sample);

        qurt_mutex_lock(&sensor_mutex);
        latest_accel = sample;
        qurt_mutex_unlock(&sensor_mutex);

        /* ~400Hz sample rate */
        qurt_timer_sleep(2500);
    }

    printf("[Accel] Thread exitingn");
    qurt_thread_exit(QURT_EOK);
}

/* Gyroscope thread */
void gyro_thread(void *arg)
{
    printf("[Gyro] Thread startedn");

    while (running) {
        vec3_sample_t sample;
        read_gyroscope(&sample);

        qurt_mutex_lock(&sensor_mutex);
        latest_gyro = sample;
        qurt_mutex_unlock(&sensor_mutex);

        /* 1kHz sample rate */
        qurt_timer_sleep(1000);
    }

    printf("[Gyro] Thread exitingn");
    qurt_thread_exit(QURT_EOK);
}

/* Magnetometer thread */
void mag_thread(void *arg)
{
    printf("[Mag] Thread startedn");

    while (running) {
        vec3_sample_t sample;
        read_magnetometer(&sample);

        qurt_mutex_lock(&sensor_mutex);
        latest_mag = sample;
        qurt_mutex_unlock(&sensor_mutex);

        /* 100Hz sample rate */
        qurt_timer_sleep(10000);
    }

    printf("[Mag] Thread exitingn");
    qurt_thread_exit(QURT_EOK);
}

/* Simplified complementary filter */
static void compute_orientation(
    const vec3_sample_t *accel,
    const vec3_sample_t *gyro,
    const vec3_sample_t *mag,
    fused_state_t *state)
{
    float dt = 0.01f;

    float accel_roll = atan2f(accel->y, accel->z) * 57.2958f;
    float accel_pitch = atan2f(-accel->x,
        sqrtf(accel->y * accel->y + accel->z * accel->z)) * 57.2958f;

    /* Trust gyro short-term, accel long-term */
    state->roll = 0.98f * (state->roll + gyro->x * dt * 57.2958f)
                + 0.02f * accel_roll;
    state->pitch = 0.98f * (state->pitch + gyro->y * dt * 57.2958f)
                 + 0.02f * accel_pitch;

    state->yaw = atan2f(mag->y, mag->x) * 57.2958f;

    state->accel = *accel;
    state->gyro = *gyro;
    state->mag = *mag;
}

/* Fusion thread (runs every 10ms) */
void fusion_thread(void *arg)
{
    qurt_timer_t fusion_timer;
    qurt_timer_attr_t timer_attr;

    printf("[Fusion] Thread startedn");

    qurt_timer_attr_init(&timer_attr);
    qurt_timer_attr_set_duration(&timer_attr,
        qurt_timer_convert_time_to_ticks(FUSION_PERIOD_US,
                                          QURT_TIME_USEC));
    qurt_timer_attr_set_signal(&timer_attr, &fusion_signal);
    qurt_timer_attr_set_signal_mask(&timer_attr, SIG_FUSION_TICK);
    qurt_timer_attr_set_type(&timer_attr, QURT_TIMER_PERIODIC);

    qurt_timer_create(&fusion_timer, &timer_attr);

    while (running) {
        unsigned int sigs = qurt_signal_wait(
            &fusion_signal,
            SIG_FUSION_TICK | SIG_SHUTDOWN,
            QURT_SIGNAL_ATTR_WAIT_ANY);

        if (sigs & SIG_SHUTDOWN) break;

        qurt_signal_clear(&fusion_signal, SIG_FUSION_TICK);

        /* Snapshot sensor data under lock */
        vec3_sample_t a, g, m;
        qurt_mutex_lock(&sensor_mutex);
        a = latest_accel;
        g = latest_gyro;
        m = latest_mag;
        qurt_mutex_unlock(&sensor_mutex);

        /* Run the fusion algorithm (no lock needed, local data) */
        fused_state_t state;
        qurt_mutex_lock(&fused_mutex);
        state = latest_fused;
        qurt_mutex_unlock(&fused_mutex);

        compute_orientation(&a, &g, &m, &state);

        /* Publish fused result */
        qurt_mutex_lock(&fused_mutex);
        latest_fused = state;
        qurt_mutex_unlock(&fused_mutex);

        /* Notify reporter */
        qurt_signal_set(&report_signal, SIG_NEW_FUSED_DATA);
    }

    qurt_timer_delete(fusion_timer);
    printf("[Fusion] Thread exitingn");
    qurt_thread_exit(QURT_EOK);
}

/* Reporting thread */
void report_thread(void *arg)
{
    int report_count = 0;

    printf("[Report] Thread startedn");

    while (running) {
        unsigned int sigs = qurt_signal_wait(
            &report_signal,
            SIG_NEW_FUSED_DATA | SIG_SHUTDOWN,
            QURT_SIGNAL_ATTR_WAIT_ANY);

        if (sigs & SIG_SHUTDOWN) break;

        qurt_signal_clear(&report_signal, SIG_NEW_FUSED_DATA);

        fused_state_t state;
        qurt_mutex_lock(&fused_mutex);
        state = latest_fused;
        qurt_mutex_unlock(&fused_mutex);

        /* Report every 100th update (once per second at 100Hz) */
        if (++report_count % 100 == 0) {
            printf("[Report] Orientation - Roll: %.2f  Pitch: %.2f  "
                   "Yaw: %.2f  (update #%d)n",
                   state.roll, state.pitch, state.yaw, report_count);
        }
    }

    printf("[Report] Thread exitingn");
    qurt_thread_exit(QURT_EOK);
}

/* Main */
int main(void)
{
    qurt_thread_t threads[5];
    qurt_thread_attr_t attr;
    int status;

    printf("=== Sensor Fusion Pipeline Starting ===n");

    /* Initialize synchronization primitives */
    qurt_mutex_init(&sensor_mutex);
    qurt_mutex_init(&fused_mutex);
    qurt_signal_init(&fusion_signal);
    qurt_signal_init(&report_signal);
    memset(&latest_fused, 0, sizeof(latest_fused));

    struct {
        const char *name;
        char *stack;
        int priority;
        void (*func)(void *);
    } thread_configs[] = {
        {"accel_reader", accel_stack,  60, accel_thread},
        {"gyro_reader",  gyro_stack,   60, gyro_thread},
        {"mag_reader",   mag_stack,    70, mag_thread},
        {"fusion",       fusion_stack, 80, fusion_thread},
        {"reporter",     report_stack, 120, report_thread},
    };

    /* Create all threads */
    for (int i = 0; i < 5; i++) {
        qurt_thread_attr_init(&attr);
        qurt_thread_attr_set_name(&attr, thread_configs[i].name);
        qurt_thread_attr_set_stack_addr(&attr, thread_configs[i].stack);
        qurt_thread_attr_set_stack_size(&attr, STACK_SIZE);
        qurt_thread_attr_set_priority(&attr, thread_configs[i].priority);

        int result = qurt_thread_create(&threads[i], &attr,
                                         thread_configs[i].func, NULL);
        if (result != QURT_EOK) {
            printf("Failed to create thread '%s': %dn",
                   thread_configs[i].name, result);
            return -1;
        }
        printf("Created thread '%s' (priority %d)n",
               thread_configs[i].name, thread_configs[i].priority);
    }

    /* Let it run for 10 seconds */
    printf("Pipeline running for 10 seconds...n");
    qurt_timer_sleep(10000000);

    /* Shutdown */
    printf("Shutting down...n");
    running = 0;
    qurt_signal_set(&fusion_signal, SIG_SHUTDOWN);
    qurt_signal_set(&report_signal, SIG_SHUTDOWN);

    /* Wait for all threads to finish */
    for (int i = 0; i < 5; i++) {
        qurt_thread_join(threads[i], &status);
    }

    /* Clean up */
    qurt_mutex_destroy(&sensor_mutex);
    qurt_mutex_destroy(&fused_mutex);
    qurt_signal_destroy(&fusion_signal);
    qurt_signal_destroy(&report_signal);

    printf("=== Sensor Fusion Pipeline Complete ===n");
    return 0;
}

یہ پائپ لائن ایک ساتھ کام کرنے والے کئی QRT نمونوں کو ظاہر کرتی ہے۔

تین سینسر ریڈر تھریڈز سب سے زیادہ ترجیح پر چلتے ہیں (ایکسلریشن اور گائرو کے لیے 60، سست میگنیٹومیٹر کے لیے 70) اور مسلسل تازہ ترین نمونے ایک mutex کے تحت مشترکہ حالت میں لکھتے ہیں۔

فیوژن تھریڈ، ہر 10 ایم ایس پر ایک متواتر ٹائمر کے ذریعے متحرک ہوتا ہے، تینوں سینسر ریڈنگز کو سنیپ شاٹ کرتا ہے، رول، پچ اور یاؤ کا حساب لگانے کے لیے تکمیلی فلٹرز چلاتا ہے، اور فیوژن کے نتائج شائع کرتا ہے۔

جب بھی نیا فیوزڈ ڈیٹا دستیاب ہوتا ہے تو سب سے کم ترجیح (120) رپورٹنگ تھریڈ ایک سگنل وصول کرتا ہے اور فی سیکنڈ میں ایک بار سمت کو ریکارڈ کرتا ہے۔

ترجیحی تفویض

Priority 60:  Sensor readers (highest priority, never miss hardware data)
Priority 80:  Fusion engine (runs every 10ms, must finish quickly)
Priority 120: Reporter (lowest priority, only logging)

ترجیحی تفویض سخت قوانین کی پیروی کرتا ہے۔ یعنی ہارڈ ویئر کے قریب تھریڈز کو زیادہ ترجیح حاصل ہوتی ہے۔ اگر فیوژن تھریڈ میں بہت زیادہ وقت لگتا ہے تو رپورٹر انتظار کرتا ہے۔ تاخیر والے لاگ پیغامات قابل قبول ہیں کیونکہ ان کے حقیقی وقت کے نتائج نہیں ہوتے ہیں۔ اگر سینسر ریڈنگ میں تاخیر ہوتی ہے تو، فیوژن الگورتھم باسی ڈیٹا پر کام کرے گا۔

ڈرونز یا روبوٹس کو کنٹرول کرنے والی حقیقی دنیا کی ایپلی کیشنز میں، پرانے IMU ڈیٹا کا مطلب ہے سمت کے غلط تخمینے، جو جسمانی خرابیوں کا باعث بن سکتے ہیں۔

QRT ایپلیکیشنز کو ڈیبگ کرنا

کیو آر ٹی ڈیبگنگ لینکس ڈیبگنگ سے زیادہ محدود ہے۔ موجود نہیں ہے gdb میں TUI استعمال کرتا ہوں، اور کریشوں کی وجہ سے خرابی کے پیغامات اکثر غیر مددگار ہوتے ہیں۔ مندرجہ ذیل تکنیکوں سے ایک عملی ڈیبگنگ ٹول کٹ بنتی ہے۔

ڈیبگنگ پرنٹ ایف

#include 

void debug_example(void)
{
    printf("[%s:%d] value = %dn", __func__, __LINE__, some_var);
}

QRT سپورٹ printf ایک نیم میزبان میکانزم کے ذریعے۔ سمیلیٹر میں، آؤٹ پٹ stdout پر جاتا ہے۔ ہارڈ ویئر میں، یہ DIAG بفر پر جاتا ہے (Android میں logcat کی طرح)۔ یہ QRT کی ترقی میں سب سے عام ڈیبگنگ تکنیک ہے۔

QRT ایرر کوڈز

switch (result) {
    case QURT_EOK:
        break;
    case QURT_EINVALID:
        printf("Invalid argumentn");
        break;
    case QURT_EFAILED:
        printf("General failuren");
        break;
    case QURT_EMEM:
        printf("Out of memoryn");
        break;
    case QURT_ENOTALLOWED:
        printf("Operation not allowed (check permissions)n");
        break;
    case QURT_ETIMEOUT:
        printf("Operation timed outn");
        break;
    default:
        printf("Unknown error: %dn", result);
}

ہمیشہ QRT API کالز کی واپسی کی قیمت چیک کریں۔ غلطی کے کوڈز جن کا آپ اکثر سامنا کریں گے وہ ہیں:

QURT_EINVALID اس کا مطلب عام طور پر غلط پیرامیٹرز ہوتا ہے (غیر منسلک اسٹیک، نال پوائنٹر، حد سے باہر کی ترجیح)۔ QURT_EMEM اس کا مطلب ہے کہ دانا کے پاس اپنے اندرونی ڈھانچے کے لیے کافی میموری نہیں ہے۔ QURT_ENOTALLOWED اکثر آپ کے ہارڈ ویئر کے ساتھ اجازت کے مسئلے کی نشاندہی کرتا ہے۔

تھریڈ اسٹیٹ چیک

void dump_thread_info(void)
{
    qurt_thread_t tid = qurt_thread_get_id();
    char name[QURT_THREAD_ATTR_NAME_MAXLEN];

    qurt_thread_get_name(name, sizeof(name));

    printf("Thread: %s (ID: %lu)n", name, tid);
}

یہ فنکشن موجودہ تھریڈ کا نام اور ID پرنٹ کرتا ہے۔ یہ اس وقت مفید ہے جب آپ کے پاس ایک ہی لاگ آؤٹ پٹ پر متعدد تھریڈز لکھے ہوئے ہوں اور آپ کو یہ فرق کرنے کی ضرورت ہو کہ ہر پیغام کو کس تھریڈ نے تیار کیا ہے۔

اسٹیک اوور فلو کا پتہ لگانا

#define STACK_CANARY 0xDEADBEEF

static char my_stack[STACK_SIZE] __attribute__((aligned(8)));

void init_stack_canary(void)
{
    /* Write canary at the bottom of the stack */
    ((unsigned int *)my_stack)[0] = STACK_CANARY;
    ((unsigned int *)my_stack)[1] = STACK_CANARY;
}

void check_stack_canary(void)
{
    if (((unsigned int *)my_stack)[0] != STACK_CANARY ||
        ((unsigned int *)my_stack)[1] != STACK_CANARY) {
        printf("STACK OVERFLOW DETECTED!n");
    }
}

QRT اسٹیک اوور فلو کا پتہ نہیں لگاتا ہے۔ یہ کینری پیٹرن تھریڈ شروع ہونے سے پہلے اسٹیک کے نیچے ایک معلوم قدر لکھتا ہے۔ اگر اسٹیک باؤنڈری سے نیچے کی طرف بڑھتا ہے تو کینری ویلیو اوور رائٹ ہو جاتی ہے۔ کینریز کو وقتاً فوقتاً چیک کرنا (یا جب کوئی دھاگہ نکلتا ہے) اوور فلو پکڑتا ہے جو نامعلوم اور غیر متعلقہ تنازعات کے طور پر ظاہر ہو سکتا ہے۔

مسدس سمیلیٹر کا استعمال کرتے ہوئے

# Run with instruction tracing
hexagon-sim --timing --pmu_statsfile stats.txt 
    --cosim_file osam.cfg 
    -- bootimg.pbn -- my_app.so

# The stats file gives you:
# - Total cycles
# - Cache hit/miss rates
# - Stall cycles
# - Instructions per cycle (IPC)

کہ --timing جھنڈا سائیکل کے درست تخروپن کی اجازت دیتا ہے۔ --pmu_statsfile کارکردگی کاؤنٹر ڈیٹا فائل میں لکھتا ہے۔ اعداد و شمار کی فائل کل سائیکل، کیش ہٹ اور مس ریٹس، اسٹال سائیکل، اور ہدایات فی سائیکل (IPC) کی رپورٹ کرتی ہے۔ یہ ڈیٹا اس بات کی نشاندہی کرنے کے لیے ضروری ہے کہ آیا رکاوٹ کمپیوٹ کی حدود، میموری کی حدود، یا تاخیر کی حدود ہیں۔

عام نقصانات

نقصان 1: تھریڈ کو ختم کرنا بھول جانا

/* BAD: thread function returns without exit */
void bad_thread(void *arg) {
    do_work();
    return;  /* CRASH or undefined behavior */
}

/* GOOD */
void good_thread(void *arg) {
    do_work();
    qurt_thread_exit(QURT_EOK);
}

QRT تھریڈ بغیر کال کے ان پٹ فنکشن سے لوٹ رہا ہے۔ qurt_thread_exit() اس کے نتیجے میں غیر متعینہ سلوک ہوتا ہے۔ دانا لنک رجسٹر کو اس پر سیٹ کرتا ہے: qurt_thread_exit دھاگے کی تخلیق کے دوران اسے حفاظتی جال کے طور پر استعمال کیا جاتا ہے، لیکن اس پر بھروسہ نہیں کیا جانا چاہیے۔ ہمیشہ مجھے کال کریں۔ qurt_thread_exit() واضح طور پر۔

نقصان 2: غلط دائرہ کار کے لیے مختص اسٹیک

/* BAD: stack is on the calling thread's stack */
void create_thread_bad(void) {
    char stack[4096];
    qurt_thread_attr_set_stack_addr(&attr, stack);
    qurt_thread_create(&tid, &attr, func, NULL);
}   /* stack disappears here, new thread crashes */

/* GOOD: use static or heap allocation */
static char stack[4096] __attribute__((aligned(8)));
void create_thread_good(void) {
    qurt_thread_attr_set_stack_addr(&attr, stack);
    qurt_thread_create(&tid, &attr, func, NULL);
}

اسٹیک میموری کو ان تھریڈز سے زیادہ زندہ رہنا چاہیے جو اسے استعمال کرتے ہیں۔ اسٹیک کو کسی فنکشن میں مقامی متغیر کے طور پر مختص کرنا اسٹیک کو آزاد کر دے گا جب وہ فنکشن واپس آجائے گا، لیکن تھریڈ اب بھی چل رہا ہوگا۔ محتاط لائف ٹائم مینجمنٹ کے ساتھ جامد مختص (جیسا کہ دکھایا گیا ہے) یا ہیپ ایلوکیشن کا استعمال کریں۔

نقصان 3: اس کا احساس کیے بغیر ترجیحات کو تبدیل کرنا

/* BAD: manual spinlock, no priority inheritance */
volatile int lock = 0;
while (__sync_lock_test_and_set(&lock, 1)) { /* spin */ }

/* GOOD: QuRT mutex with priority inheritance */
qurt_mutex_lock(&my_mutex);

اگر ایک اعلی ترجیحی دھاگہ کم ترجیحی دھاگے کے ذریعے رکھے گئے غیر فعال اسپن لاک پر گھومتا ہے، اور درمیانی ترجیحی دھاگہ لاک ہولڈر کو پہلے سے روکتا ہے، تو اعلی ترجیحی دھاگے کو درمیانے ترجیحی دھاگے کے ذریعے مؤثر طریقے سے روک دیا جاتا ہے۔

QRT mutexes اس مسئلے کو خودکار ترجیحی وراثت کے ذریعے حل کرتے ہیں۔ لاک ہولڈر کو عارضی طور پر سب سے زیادہ ترجیحی ویٹر کی ترجیح پر ترقی دی جاتی ہے۔ یہ علاج دستی اسپن لاکس پر لاگو نہیں ہوتا ہے۔

نقصان 4: غیر منسلک میموری

/* BAD */
char stack[4096];

/* GOOD */
char stack[4096] __attribute__((aligned(8)));

/* For DMA buffers, you often need 256-byte alignment */
char dma_buffer[1024] __attribute__((aligned(256)));

تھریڈ اسٹیک 8 بائٹ سیدھ میں ہونا ضروری ہے۔ ڈی ایم اے بفرز کو عام طور پر 256 بائٹ سیدھ کی ضرورت ہوتی ہے۔ غلط ترتیب شدہ میموری ہیکساگون فن تعمیر میں سخت خرابیوں کا سبب بنتی ہے، جو کم سے کم تشخیصی آؤٹ پٹ پیدا کرتی ہے۔

نقصان 5: ISR کے تناظر میں بلاک کرنا

/* BAD: mutex_lock may block indefinitely */
void isr_handler(void *arg) {
    qurt_mutex_lock(&some_mutex);
    qurt_mutex_unlock(&some_mutex);
}

/* GOOD: non-blocking try_lock with fallback */
void isr_handler(void *arg) {
    if (qurt_mutex_try_lock(&some_mutex) == QURT_EOK) {
        /* Quick update */
        qurt_mutex_unlock(&some_mutex);
    } else {
        /* Defer to processing thread */
        qurt_signal_set(&deferred_signal, DEFERRED_WORK);
    }
}

QRT ISR تھریڈز تکنیکی طور پر بلاکنگ APIs کو کال کر سکتے ہیں، لیکن ایک اعلی ترجیحی مداخلت والے ہینڈلر میں ایسا کرنے سے بلاک کرنے کی حالت کے حل ہونے تک رکاوٹ پروسیسنگ رک جائے گی۔ استعمال کریں qurt_mutex_try_lock() اگر غیر مسدود کرنے کی کوشش کے لیے تالا دستیاب نہیں ہے، تو کام کو کم ترجیحی دھاگے تک موخر کرنے کے لیے سگنل کا استعمال کیا جاتا ہے۔

کارکردگی کی اصلاح

ہیکساگونل ویکٹر ایکسٹینشن (HVX) کا استعمال

#include 
#include 

/* Process 128 bytes at once with HVX */
void vectorized_gain(int16_t *audio, int num_samples, int16_t gain)
{
    HVX_Vector *vptr = (HVX_Vector *)audio;
    HVX_Vector vgain = Q6_Vh_vsplat_R(gain);
    int num_vectors = num_samples * sizeof(int16_t) / sizeof(HVX_Vector);

    for (int i = 0; i < num_vectors; i++) {
        vptr[i] = Q6_Vh_vmpy_VhVh_sat(vptr[i], vgain);
    }
}

HVX ہیکساگون DSP پر 128 بائٹ SIMD آپریشن فراہم کرتا ہے۔ کہ Q6_Vh_vsplat_R اندرونی فنکشن ویکٹر رجسٹر کی تمام لین میں اسکیلر ویلیو کو نشر کرتا ہے۔ Q6_Vh_vmpy_VhVh_sat دو آدھے الفاظ کے ویکٹرز کی سیچوریٹنگ ضرب کو انجام دیتا ہے۔ ایک واحد HVX انسٹرکشن 64 16 بٹ نمونوں پر کارروائی کرتی ہے، جو آڈیو اور سگنل پروسیسنگ کے کام کے بوجھ کے لیے اسکیلر کوڈ پر زبردست رفتار فراہم کرتی ہے۔

گرم ڈیٹا کے لیے L2 کیشے لاکنگ

void lock_cache_example(void)
{
    extern float fft_twiddle_factors[];
    size_t twiddle_size = 1024 * sizeof(float);

    /* Pin data in L2 to prevent eviction */
    qurt_mem_l2cache_lock((unsigned int)fft_twiddle_factors,
                           twiddle_size);

    /* When done: */
    qurt_mem_l2cache_unlock((unsigned int)fft_twiddle_factors,
                             twiddle_size);
}

qurt_mem_l2cache_lock() میموری کے علاقے کو L2 کیشے میں پن کرکے، یہ دیگر کیش ٹریفک کو میموری کے علاقے کو ہٹانے سے روکتا ہے۔ یہ تلاش کرنے والے جدولوں اور مستقل ڈیٹا کے لیے مفید ہے جن تک کثرت سے ہاٹ لوپس تک رسائی حاصل کی جاتی ہے (مثال کے طور پر، FFT گردش کے عوامل)۔

اس تکنیک کو منتخب طریقے سے استعمال کریں، کیونکہ L2 میں بہت زیادہ ڈیٹا لاک کرنے سے دوسرے تھریڈز کے لیے دستیاب کیش کم ہو جاتا ہے۔

گرم راستوں پر متحرک میموری سے بچیں۔

/* BAD: malloc in the audio processing loop */
void process_audio_bad(void) {
    while (1) {
        float *temp = malloc(1024 * sizeof(float));
        process(temp);
        free(temp);
    }
}

/* GOOD: pre-allocate everything */
static float temp_buffer[1024];
void process_audio_good(void) {
    while (1) {
        process(temp_buffer);
    }
}

malloc اور free عمل درآمد کا وقت غیر مقررہ ہے کیونکہ یہ دستیاب فہرست کو عبور کرتا ہے، بلاکس کو تقسیم یا ملایا جا سکتا ہے، اور بدترین صورت میں یہ کرنل سے اضافی میموری کی درخواست کر سکتا ہے۔

48 kHz پر چلنے والے ریئل ٹائم آڈیو پروسیسنگ لوپ میں ایک ہی سست الاٹیشن قابل سماعت نمونے کا سبب بن سکتی ہے۔ ابتداء کے دوران، تمام بفرز پہلے سے مختص اور دوبارہ استعمال کیے جاتے ہیں۔

API فوری حوالہ

┌─────────────────────────────────────────────────────────────────┐
│                    QuRT API Quick Reference                     │
├─────────────────┬───────────────────────────────────────────────┤
│ THREADS         │                                               │
│  create         │ qurt_thread_create(&id, &attr, func, arg)     │
│  exit           │ qurt_thread_exit(status)                      │
│  join           │ qurt_thread_join(id, &status)                 │
│  get id         │ qurt_thread_get_id()                          │
│  sleep          │ qurt_timer_sleep(usec)                        │
├─────────────────┼───────────────────────────────────────────────┤
│ MUTEX           │                                               │
│  init           │ qurt_mutex_init(&mutex)                       │
│  lock           │ qurt_mutex_lock(&mutex)                       │
│  try lock       │ qurt_mutex_try_lock(&mutex)                   │
│  unlock         │ qurt_mutex_unlock(&mutex)                     │
│  destroy        │ qurt_mutex_destroy(&mutex)                    │
├─────────────────┼───────────────────────────────────────────────┤
│ SIGNALS         │                                               │
│  init           │ qurt_signal_init(&signal)                     │
│  wait           │ qurt_signal_wait(&sig, mask, attr)            │
│  set            │ qurt_signal_set(&signal, mask)                │
│  clear          │ qurt_signal_clear(&signal, mask)              │
│  destroy        │ qurt_signal_destroy(&signal)                  │
├─────────────────┼───────────────────────────────────────────────┤
│ TIMERS          │                                               │
│  create         │ qurt_timer_create(&timer, &attr)              │
│  delete         │ qurt_timer_delete(timer)                      │
│  sleep          │ qurt_timer_sleep(usec)                        │
│  ticks          │ qurt_sysclock_get_hw_ticks()                  │
├─────────────────┼───────────────────────────────────────────────┤
│ MEMORY          │                                               │
│  cache flush    │ qurt_mem_cache_clean(addr, sz, FLUSH)         │
│  cache inval    │ qurt_mem_cache_clean(addr, sz, INVALIDATE)    │
│  l2 lock        │ qurt_mem_l2cache_lock(addr, size)             │
│  l2 unlock      │ qurt_mem_l2cache_unlock(addr, size)           │
├─────────────────┼───────────────────────────────────────────────┤
│ SEMAPHORE       │                                               │
│  init           │ qurt_sem_init_val(&sem, count)                │
│  down (wait)    │ qurt_sem_down(&sem)                           │
│  up (post)      │ qurt_sem_up(&sem)                             │
│  destroy        │ qurt_sem_destroy(&sem)                        │
├─────────────────┼───────────────────────────────────────────────┤
│ BARRIER         │                                               │
│  init           │ qurt_barrier_init(&barrier, count)            │
│  wait           │ qurt_barrier_wait(&barrier)                   │
│  destroy        │ qurt_barrier_destroy(&barrier)                │
└─────────────────┴───────────────────────────────────────────────┘

یہ جدول زمرہ کے لحاظ سے منظم سب سے زیادہ استعمال ہونے والے QRT API فنکشنز کی فہرست دیتا ہے۔ بائیں کالم آپریشن کا نام بتاتا ہے اور دائیں کالم فنکشن کے دستخط دکھاتا ہے۔

  • تھریڈ آپریشنز میں تخلیق، ختم، شمولیت، اور نیند کے آپریشن شامل ہیں۔

  • Mutex آپریشنز لاکنگ، ٹرائی لاک، اور انلاکنگ فراہم کرتے ہیں۔

  • سگنل آپریشن بٹ ماسک پر مبنی اطلاعات کے ذریعے انتظار کرنے، سیٹ کرنے اور صاف کرنے کی حمایت کرتے ہیں۔ ٹائمر کے کام تخلیق، حذف، نیند، اور ہارڈویئر ٹک کاؤنٹرز کو پڑھنے کو سنبھالتے ہیں۔

  • میموری آپریشنز میں کیش فلشز اور غلطیاں (کراس پروسیسر بفرز کے لیے ضروری) اور کارکردگی کے لیے اہم ڈیٹا کے لیے L2 کیش لاک شامل ہیں۔

  • سیمفور اور بیریئر آپریشنز ہم وقت سازی کے پرائمیٹوز کو پورا کرتے ہیں۔

اگلے اقدامات

اس ہینڈ بک میں QRT پروگرامنگ کے بنیادی اصولوں کا احاطہ کیا گیا ہے، بشمول تھریڈ مینجمنٹ، سنکرونائزیشن، میموری، ٹائمرز، انٹرپٹس، پائپس، فاسٹ آر پی سی، اور ملٹی سینسر فیوژن پائپ لائن۔ گہرائی سے سیکھنے کے اگلے مراحل قدرتی پیشرفت کی پیروی کرتے ہیں۔

سب سے پہلے، ہیکساگون SDK ڈاؤن لوڈ کریں اور سمیلیٹر میں شامل مثال پروجیکٹس کو چلائیں۔ کی مثال $HEXAGON_SDK_ROOT/examples/ یہ FastRPC کے ذریعے حقیقی ARM-DSP کمیونیکیشن پیٹرن کو ظاہر کرتا ہے اور ایک مکمل اور کام کرنے والے پروجیکٹ کو یقینی بنانے کا بہترین طریقہ ہے۔

QRT یوزر گائیڈ پڑھیں۔ $HEXAGON_SDK_ROOT/docs/. اس میں اس مضمون میں زیر بحث تمام APIs کا تفصیل سے احاطہ کیا گیا ہے، نیز بہت سے ایسے ہیں جن کا احاطہ نہیں کیا گیا ہے (جیسے QRT کا TLB مینجمنٹ اور پاور مینجمنٹ انٹرفیس)۔

HVX، ہیکساگون ویکٹر ایکسٹینشن کے ساتھ تجربہ کریں۔ HVX وہ جگہ ہے جہاں ہیکساگون DSP کی اصل طاقت کام میں آتی ہے، اور ویکٹرائزڈ DSP کوڈ لکھنے کا طریقہ سیکھنا آپ کے لیے دستیاب واحد سب سے بڑا پرفارمنس لیور ہے۔

آخر میں، اپنے کوڈ کو اصلی ہارڈ ویئر پر چلانے کے لیے ایک ڈویلپمنٹ بورڈ (جیسے Qualcomm RB5) خریدیں۔ سمیلیٹر درستگی کی تصدیق کرتے ہیں، لیکن صرف اصلی ہارڈویئر ہی وقت کے رویے، کیشے کے اثرات، اور آپ کے کوڈ اور DSP پر چلنے والے دوسرے سافٹ ویئر کے درمیان تعامل کو ظاہر کرتا ہے۔

Hexagon SDK دستاویزات یہاں پر واقع ہے: (HEXAGON_SDK_ROOT/docs/. QRT API حوالہ یہاں موجود ہے: )HEXAGON_SDK_ROOT/docs/qurt/. Qualcomm Developer Network (developer.qualcomm.com) اضافی وسائل، فورمز، اور ایپلیکیشن نوٹس فراہم کرتا ہے۔ ہیکساگون ڈی ایس پی آرکیٹیکچر حوالہ خود ہارڈ ویئر کے لیے حتمی گائیڈ ہے۔

QRT ایک درست آلہ ہے۔ آپ اسے چھو نہیں سکتے، لیکن یہ دنیا کے سب سے طاقتور ڈی ایس پی آرکیٹیکچرز میں سے ایک میں ریئل ٹائم پروسیسنگ پر مائیکرو سیکنڈ لیول کنٹرول فراہم کرتا ہے۔ سیکھنے کا منحنی خطوط بہت بڑا ہے، لیکن ایک بار جب آپ ایسا کر لیں، تو آپ سمجھ جائیں گے کہ اربوں ڈیوائسز اپنے انتہائی اہم کاموں کے لیے اس چھوٹے OS پر کیوں بھروسہ کرتے ہیں۔

اوپر تک سکرول کریں۔