نقل العملية إلى كمبيوتر آخر! 

ذات مرة ، شارك زميل أفكاره حول واجهة برمجة التطبيقات الخاصة بمجموعات الحوسبة الموزعة ، وأجبت مازحة: "من الواضح أن واجهة برمجة التطبيقات المثالية ستكون مكالمة بسيطة telefork()حتى تستيقظ عمليتك على كل جهاز مجموعة ، مع إرجاع قيمة معرف المثيل." ولكن في النهاية ، استولت علي هذه الفكرة. لم أتمكن من فهم سبب كونه غبيًا وبسيطًا ، وأبسط بكثير من أي API للعمل عن بُعد ، ولماذا لا تبدو أنظمة الكمبيوتر قادرة على ذلك. وبدا أنني أيضًا أفهم كيف يمكن تنفيذ ذلك ، وكان لي بالفعل اسم جيد ، وهو أصعب جزء في أي مشروع. لذا عملت.

خلال عطلة نهاية الأسبوع الأولى ، قدم نموذجًا أوليًا أساسيًا ، وعطلة نهاية الأسبوع الثانية جلبت عرضًا يمكنإلى آلة افتراضية عملاقة في السحابة ، وإيقاف عرض تتبع المسار على نوى متعددة ، ثم إجراء العملية عن بعد. كل هذا ملفوف بواجهة برمجة تطبيقات بسيطة.

يُظهر الفيديو أن العرض على VM 64 نواة في السحابة يكتمل في 8 ثوانٍ (بالإضافة إلى 6 ثوانٍ للتلفريك ذهابًا وإيابًا). يستغرق العرض نفسه محليًا في حاوية على جهاز الكمبيوتر المحمول 40 ثانية:


كيف يمكن نقل العملية عن بعد؟ إليك ما يجب أن تشرحه هذه المقالة! الفكرة الأساسية هي أنه على مستوى منخفض ، لا تحتوي عملية Linux إلا على عدد قليل من المكونات. تحتاج فقط إلى طريقة لاستعادة كل منها من الجهة المانحة ، ونقلها عبر الشبكة ونسخها إلى العملية المستنسخة.

قد تعتقد: "ولكن كيف يمكن تكرار [شيء صعب ، مثل اتصال TCP]؟" هل حقا. في الواقع ، نحن لا نتسامح مع مثل هذه الأشياء المعقدة لإبقاء الرمز بسيطًا. أي أنه مجرد عرض توضيحي تقني ممتع ربما لا يجب استخدامه في الإنتاج. لكنها لا تزال تعرف كيف تنقل فئة واسعة من المهام الحسابية في الغالب!

كيف تبدو


قمت بتطبيق الكود كمكتبة Rust ، ولكن من الناحية النظرية يمكنك لف البرنامج في C API ثم تشغيله من خلال روابط FFI للتنقل حتى عملية Python. التنفيذ هو فقط حوالي 500 سطر من التعليمات البرمجية (بالإضافة إلى 200 سطر من التعليقات):

use telefork::{telefork, TeleforkLocation};

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let destination = args.get(1).expect("expected arg: address of teleserver");

    let mut stream = std::net::TcpStream::connect(destination).unwrap();
    match telefork(&mut stream).unwrap() {
        TeleforkLocation::Child(val) => {
            println!("I teleported to another computer and was passed {}!", val);
        }
        TeleforkLocation::Parent => println!("Done sending!"),
    };
}

قمت أيضًا بكتابة مساعد يسمى yoyoteleforks إلى الخادم ، ويقوم بالإغلاق المرسل ، ثم يعمل teleforks مرة أخرى. وهذا يخلق الوهم بأنه يمكنك بسهولة تشغيل جزء من التعليمات البرمجية على خادم بعيد ، على سبيل المثال ، بقوة معالجة أكبر بكثير.

// load the scene locally, this might require loading local scene files to memory
let scene = create_scene();
let mut backbuffer = vec![Vec3::new(0.0, 0.0, 0.0); width * height];
telefork::yoyo(destination, || {
  // do a big ray tracing job on the remote server with many cores!
  render_scene(&scene, width, height, &mut backbuffer);
});
// write out the result to the local file system
save_png_file(width, height, &backbuffer);

تشريح عملية لينكس


دعنا نرى كيف تبدو العملية في Linux (التي يعمل عليها نظام تشغيل المضيف الأم telefork):



  • (memory mappings): , . «» 4 . /proc/<pid>/maps. , , .

    • , , ( ).
  • : , . , , - , , , . , .
  • : , . - , . , , , TCP-, .
    • . stdin/stdout/stderr, 0, 1 2.
    • , , , .
  • متفرقات : هناك بعض الأجزاء الأخرى من حالة العملية التي تختلف في تعقيد النسخ المتماثل. ولكن في معظم الحالات لا يهم ، على سبيل المثال ، brk (مؤشر كومة). يمكن استعادة بعضها فقط بمساعدة حيل غريبة أو مكالمات نظام خاصة مثل PR_SET_MM_MAP ، مما يعقد عملية الاسترداد.

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

كيفية إجراء عملية هاتفية


لم أكن أول من يفكر في إعادة إنشاء العمليات على جهاز آخر. لذا ، فإن مصحح rr يجعل التسجيل والتشغيل متشابهين للغاية . أرسلت عدة أسئلة إلى مؤلف هذا البرنامج rocallahan ، وأخبرني عن نظام CRIU للهجرة "الساخنة" للحاويات بين المضيفين. يمكن لـ CRIU نقل عملية Linux إلى نظام آخر ، ويدعم استرداد جميع أنواع واصفات الملفات والحالات الأخرى ، ومع ذلك ، فإن التعليمات البرمجية معقدة حقًا وتستخدم العديد من مكالمات النظام التي تتطلب تجميعات kernel خاصة وأذونات الجذر. باستخدام الرابط من صفحة ويكي CRIU ، وجدت DMTCP التي تم إنشاؤها من أجل لقطات المهام الموزعة على أجهزة الكمبيوتر العملاقة بحيث يمكن إعادة تشغيلها لاحقًا ، وهذا البرنامجتبين أن الرمز أبسط .

لم تجبرني هذه الأمثلة على التخلي عن محاولات تطبيق نظامي الخاص ، نظرًا لأن هذه برامج معقدة للغاية تتطلب عداءًا خاصًا وبنية تحتية خاصة ، وأردت تنفيذ أبسط عمليات الانتقال عن بُعد كمكالمة مكتبة. لذلك درست أجزاء من شفرة المصدر rr، CRIU ، DMTCP ، وبعض أمثلة ptrace - وقمت بتجميع الإجراء الخاص بي telefork. طريقي يعمل بطريقته الخاصة ، إنه مزيج من التقنيات المختلفة.

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

فيما يلي نظرة عامة مبسطة على كلتا العمليتين. إذا كنت تريد أن تفهم بالتفصيل ، أقترح قراءة شفرة المصدر . يتم تضمينه في ملف واحد ويتم التعليق عليه بإحكام لقراءته وفهم كيفية عمل كل شيء.

إرسال عملية باستخدام telefork


teleforkتستقبل الوظيفة دفقًا بقدرة على الكتابة ، تنقل بواسطتها حالة العملية بالكامل.

  1. «» . , , . fork .
  2. . /proc/<pid>/maps , . proc_maps crate.
  3. . DMTCP, , , . , [vdso], , , .
  4. . , , process_vm_readv , .
  5. سجلات النقل . يمكنني استخدام خيار PTRACE_GETREGSاستدعاء نظام ptrace . يسمح لك بالحصول على جميع قيم سجل العملية الفرعية. ثم أكتبهم فقط في رسالة على القناة.

تشغيل مكالمات النظام في عملية تابعة


لتحويل العملية المستهدفة إلى نسخة من العملية الواردة ، ستحتاج إلى فرض العملية لتنفيذ مجموعة من مكالمات النظام على نفسها ، دون الوصول إلى أي رمز ، لأننا حذفنا كل شيء. نقوم بإجراء مكالمات النظام عن بعد باستخدام ptrace ، وهو استدعاء نظام عالمي لمعالجة وفحص العمليات الأخرى:

  1. syscall. syscall , . , process_vm_readv [vdso] , , , syscall Linux, . , [vdso].
  2. , PTRACE_SETREGS. syscall, rax Linux, rdi, rsi, rdx, r10, r8, r9.
  3. اتخذ خطوة واحدة باستخدام المعلمة PTRACE_SINGLESTEPلتنفيذ الأمر syscall.
  4. قراءة السجلات مع PTRACE_GETREGSاستعادة قيمة الإرجاع syscall ومعرفة ما اذا كان نجح في ذلك.

قبول العملية في telepad


باستخدام هذا والبدائية الموصوفة بالفعل ، يمكننا إعادة إنشاء العملية:

  1. شوكة عملية طفل مجمدة . على غرار الإرسال ، لكننا هذه المرة نحتاج إلى عملية فرعية يمكننا معالجتها لتحويلها إلى استنساخ للعملية المنقولة.
  2. تحقق من بطاقات تخصيص الذاكرة . هذه المرة نحن بحاجة إلى معرفة جميع بطاقات تخصيص الذاكرة الموجودة من أجل إزالتها وإفساح المجال للعملية الواردة.
  3. . , munmap.
  4. . mremap, .
  5. . mmap , process_vm_writev .
  6. . PTRACE_SETREGS , , rax. raise(SIGSTOP), . , telepad.
    • يتم استخدام قيمة عشوائية حتى يتمكن خادم telefork من نقل واصف الملف لاتصال TCP الذي دخلت فيه العملية ، ويمكنه إرسال البيانات مرة أخرى أو ، في حالة yoyo، الانتقال الفوري إلى نفس الاتصال.
  7. أعد تشغيل العملية باستخدام المحتوى الجديد PTRACE_DETACH.

تنفيذ أكثر كفاءة


لم يتم تصميم بعض أجزاء من تطبيق Telefork الخاص بي بشكل مثالي. أعرف كيفية إصلاحها ، ولكن في الشكل الحالي أحب النظام ، وأحيانًا يكون من الصعب إصلاحه. فيما يلي بعض الأمثلة المثيرة للاهتمام:

  • (vDSO). mremap vDSO , DMTCP, , . vDSO, . - , CPU glibc vDSO . , vDSO, syscall, rr, vDSO vDSO .
  • brk . DMTCP, , brk , brk . , , — PR_SET_MM_MAP, .
  • . Rust « », , FS GS, , , - glibc pid tid, . CRIU, PID TID .
  • . , , , / , / FUSE. , TCP-, DMTCP CRIU , perf_event_open.
  • . fork() Unix , , .


أعتقد أنك فهمت بالفعل أنه باستخدام الواجهات ذات المستوى المنخفض المناسبة ، يمكنك تنفيذ بعض الأشياء المجنونة التي بدت مستحيلة لشخص ما. فيما يلي بعض الأفكار حول كيفية تطوير الأفكار الأساسية للعمل عن بعد. على الرغم من أن معظم ما سبق يمكن تنفيذه بالكامل فقط على نواة جديدة تمامًا أو ثابتة:

  • التليفون العنقودي . كان المصدر الأولي للإلهام لـ Telefork هو فكرة تدفق العملية إلى جميع الأجهزة في مجموعة الحوسبة. قد يتحول حتى إلى تطبيق UDP multicast أو أساليب نظير إلى نظير لتسريع التوزيع عبر المجموعة بأكملها. ربما تريد أيضًا أن يكون لديك بدائيات اتصال.
  • . CRIU , - userfaultfd. , SIGSEGV mmap. , ,  — .
  • ! , . userfaultfd userfaultfd, , , MESI, . , , .  — , . , , , . : syscall, -, syscall, . , . , , , . , , . , , ( , ) , .


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

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

أود أيضًا أن أقدم هذه الأشياء كأفكار لطرق بديلة لتشغيل أنظمة الكمبيوتر. لماذا تعد واجهات برمجة تطبيقات حوسبة الكتل الخاصة بنا أكثر تعقيدًا بكثير من برنامج بسيط يترجم الوظائف إلى مجموعة؟ لماذا برمجة نظام الشبكة أكثر تعقيدًا بكثير من مؤشرات الترابط؟ بالطبع ، يمكنك تقديم جميع أنواع الأسباب الجيدة ، ولكنها عادة ما تستند إلى مدى صعوبة تقديم مثال على الأنظمة الموجودة. أو ربما مع التجريد الصحيح أو بجهد كافٍ ، سيعمل كل شيء بسهولة وسلاسة؟ في الأساس ، لا يوجد شيء مستحيل.





All Articles