آلة افتراضية DIY

في بعض الأحيان يتبادر إلى الذهن فكرة للتخلص من الصعب للغاية. هذا ما حدث لي.

قررت إنشاء جهاز افتراضي (VM) ، بالنظر إلى أنه في ذلك الوقت لم تكن لدي أية أفكار ، بدا لي أن هذه فكرة رائعة. إذا كنت مهتما ، ثم انتقل إلى خفض!

نظرية


أولاً ، نظرية صغيرة. ما هو الجهاز الظاهري بشكل عام؟ هذا هو برنامج أو مجموعة من البرامج التي تسمح لك بمحاكاة نوع من الأنظمة الأساسية للأجهزة ، بمعنى آخر ، محاكي كمبيوتر.

تختلف الأجهزة الافتراضية نفسها ، على سبيل المثال ، Virtual Box هو جهاز افتراضي كلاسيكي يسمح لك بمحاكاة كمبيوتر حقيقي ، ولكن على سبيل المثال ، لا يمكن لـ JVM (جهاز Java الظاهري) القيام بذلك.

سيكون إصدار VM الخاص بي مشابهًا إلى حد ما لـ JVM ، لأنه ببساطة مشروع تدريبي أكثر منه يهدف إلى إنشاء VM قوي.

الذاكرة


لذلك ، دعونا الآن معرفة الذاكرة. لإنشاء ذاكرة ، قررت استخدام مجموعة int غير موقعة. يتم تحديد حجم المصفوفة باستخدام ماكرو ، في إصداري ، يكون حجم الذاكرة 4096 بايت (هناك 1024 عنصرًا في المصفوفة ، وبما أنه في معظم الأنظمة الأساسية ، يتم تخصيص 4 بايت للبيانات int غير الموقعة ، ثم 1024 * 4 = 4096) ، من بين أشياء أخرى ، سنحدد 8 تسجيلات بواسطة 8 خلايا في كل سيكون بالفعل 256 بايت (8 * 8 * 4 = 256). يبدو مثل هذا:

#define MEMSIZE 1024 unsigned int memory[MEMSIZE]; unsigned int reg[8][8]; 

البرمجة


لدينا ذاكرة ، ولكن الآن كيفية كتابة التعليمات البرمجية لدينا VM؟ الآن سنتعامل مع هذه المشكلة ، لتبدأ ، سنحدد الأوامر التي ستنفذها آلة لدينا:

 enum commands { /*   / List of commands */ CRG = 1, /* Change ReGister -   [1] */ CRC, /* Change Register Cell [2] */ PRG, /* Put in ReGister -       [3] */ PRC /* Put Register Cell     [4] */ }; 

كل فريق لديه علمه الخاص تحديد بعض المعلمات الإضافية.
سنصف الأعلام:

 enum flags { /*   / List of flags */ STDI = 1, /*   / Standard flag */ STDA /*   / Address flag */ }; 

يحتوي الأمر القياسي على النموذج: [command] [العلم] [البيانات] (قد يختلف مظهر بعض الأوامر) ، وبناءً على ذلك سنكتب مترجم بسيط:

 if (memory[cell] == CRG && memory[cell + 1] == STDI) { indxX = memory[cell + 2]; cell++; } else if (memory[cell] == CRC && memory[cell + 1] == STDI) { indxY = memory[cell + 2]; cell++; } else if (memory[cell] == PRG && memory[cell + 1] == STDI) { reg[indxX][0] = memory[cell + 2]; cell++; } else if (memory[cell] == PRC && memory[cell + 1] == STDI) { reg[indxX][indxY] = memory[cell + 2]; cell++; } 

indxX و indxY هي المتغيرات التي تخزن موضع المؤشر الحالي في سجل reg.
الخلية عبارة عن متغير يقوم بتخزين موضع المؤشر الحالي في صفيف الذاكرة.

لكن البرمجة مع الأرقام ليست مريحة للغاية ، لذلك باستخدام المعالج المسبق C سنصف المجمع الخاص بنا. أفهم أن كتابة asm مع وحدات الماكرو ليست جيدة جدًا ، ولكن هذا الحل مؤقت.

رمز asm لدينا يشبه هذا:

 /*  */ #define $CRG {memory[memIndx++] = CRG;} #define $CRC {memory[memIndx++] = CRC;} #define $PRG {memory[memIndx++] = PRG;} #define $PRC {memory[memIndx++] = PRC;} /*  */ #define _$STDI {memory[memIndx++] = STDI;} #define _$STDA {memory[memIndx++] = STDA;} /*  */ #define _$DATA memory[memIndx++] = 

memIndx هو متغير تخزين موضع المؤشر الحالي في صفيف الذاكرة.

وإليك الرمز الخاص بنا والذي يضع 123 في السجل على العنوان [1] [0] (التسجيل الأول ، خلية الصفر):

 $CRG /*   */ _$STDI /*   STDI */ _$DATA 1; /*   */ $CRC /*   */ _$STDI _$DATA 0; $PRC /*   */ _$STDI _$DATA 123; 

مبروك ، لدينا الآن ما يشبه أسمر لسيارتنا!

إطلاق البرامج


لقد نجحنا في جعل الجهاز الخاص بنا يقوم بتنفيذ البرامج ، لكن الكود يفتقر إلى قابلية النقل من جهاز لآخر ، لذا سنقوم الآن بإنشاء منشئ رمز الجهاز من asm (وأذكرك بأنه على عكس أجهزة الكمبيوتر الحقيقية ، يوجد لدينا آلة رمز غير معروض في شكل ثنائي ، والأعداد العشرية) ، من حيث المبدأ ، ليست صعبة للغاية ، ولكن أولاً ، دعونا نفكر في التنفيذ.

أولاً ، لدينا رمز asm ، والآن نحن بحاجة إلى ترجمته إلى أرقام ، ثم كتابة رمز الجهاز الناتج إلى ملف .ncp (برنامج رمز رقمي ، في الواقع هو ملف نصي ، ولكن لتمييزه عن كل شيء آخر ، توصلت إلى ملحق خاص بي) ، بعد ذلك نحتاج إلى تشغيل ملف .ncp ، الأمر بسيط ، نظرًا لأن المترجم الذي كتبناه سابقًا ، يتعرف على الأرقام ؛ نحتاج فقط إلى استخراج البيانات من الملف وتحويلها إلى أرقام باستخدام atoi ().

دعنا ننتقل من الأقوال إلى الأفعال:

قراءة الكود وكتابته إلى ملف:

 if (memory[i] == CRG && memory[i + 1] == STDI) { fprintf(code, "%d %d ", CRG, STDI); i++; } else if (memory[i] == CRC && memory[i + 1] == STDI) { fprintf(code, "%d %d ", CRC, STDI); i++; } else if (memory[i] == PRG && memory[i + 1] == STDI) { fprintf(code, "%d %d ", PRG, STDI); i++; } else if (memory[i] == PRC && memory[i + 1] == STDI) { fprintf(code, "%d %d ", PRC, STDI); i++; } 

الكود جزء من نص الدالة ncpGen ().

قراءة ملف وتنفيذه:

 if (prog != NULL) { fread(txt, 1, len, prog); tok = strtok(txt, " "); while (tok != NULL) { memory[i] = atoi(tok); tok = strtok(NULL, " "); if (argc == 3 && strcmp(argv[2], "-m") == 0) { printf("%d\n", memory[i]); } i++; } memInter(); } else { perror("Fail"); } 

الآن ، دعونا نحدد ماكرو بحيث يتحول الرمز إلى .ncp بدلاً من تفسير asm:

 #define _toNCP(name) {strcpy(filename, name);} {ncpGen();} 

إذا كان أي شيء ، فإن المقالة لا تقدم كل الكود ، ولكن فقط جزء صغير منه!

الكود الكامل موجود في مستودع المشروع.

شكرا جزيلا لقراءة!

Source: https://habr.com/ru/post/ar442988/


All Articles