Teleport proses ke komputer lain!โ€‰

Suatu kali, seorang rekan berbagi pemikirannya tentang API untuk cluster komputasi terdistribusi, dan saya bercanda menjawab: "Jelas, API yang ideal akan menjadi panggilan sederhana telefork()sehingga proses Anda bangun di setiap mesin cluster, mengembalikan nilai instance ID." Tetapi pada akhirnya, ide ini menguasai saya. Saya tidak bisa mengerti mengapa itu sangat bodoh dan sederhana, jauh lebih sederhana daripada API untuk pekerjaan jarak jauh, dan mengapa sistem komputer tampaknya tidak mampu seperti itu. Saya juga tampaknya mengerti bagaimana ini bisa diterapkan, dan saya sudah memiliki nama baik, yang merupakan bagian paling sulit dari setiap proyek. Jadi saya mulai bekerja.

Selama akhir pekan pertama, ia membuat prototipe dasar, dan akhir pekan kedua membawa demo yang bisamemproses ke mesin virtual raksasa di cloud, mengusir rendering lintasan menelusuri pada beberapa core, dan kemudian telefork proses kembali. Semua ini dibungkus dengan API sederhana.

Video menunjukkan bahwa rendering pada VM 64-core di cloud selesai dalam 8 detik (ditambah 6 detik untuk telefork bolak-balik). Render yang sama secara lokal dalam sebuah wadah di laptop saya membutuhkan waktu 40 detik:


Bagaimana mungkin melakukan proses teleport? Inilah yang harus dijelaskan artikel ini! Ide dasarnya adalah bahwa pada level rendah, proses Linux hanya memiliki beberapa komponen. Anda hanya perlu cara untuk memulihkan masing-masing dari donor, mentransfernya melalui jaringan dan menyalinnya ke proses kloning.

Anda mungkin berpikir: "Tapi bagaimana cara mereplikasi [sesuatu yang sulit, seperti koneksi TCP]?" Betulkah. Faktanya, kami tidak mentolerir hal-hal rumit seperti itu untuk menjaga kode tetap sederhana. Artinya, itu hanya demo teknis yang menyenangkan yang mungkin tidak boleh digunakan dalam produksi. Tapi dia masih tahu bagaimana cara memindahkan kelas yang luas dari sebagian besar tugas komputasi!

Seperti apa bentuknya


Saya menerapkan kode sebagai perpustakaan Rust, tetapi secara teoritis Anda dapat membungkus program dalam API C dan kemudian menjalankan melalui ikatan FFI untuk melakukan teleport bahkan proses Python. Implementasinya hanya sekitar 500 baris kode (ditambah 200 baris komentar):

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!"),
    };
}

Saya juga menulis bantuan yang disebut yoyoteleforks ke server, melakukan penutupan yang ditransmisikan, dan kemudian telefork kembali. Ini menciptakan ilusi bahwa Anda dapat dengan mudah menjalankan sepotong kode pada server jarak jauh, misalnya, dengan kekuatan pemrosesan yang jauh lebih besar.

// 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);

Anatomi proses Linux


Mari kita lihat seperti apa prosesnya di Linux (di mana induk OS berjalan telefork):



  • (memory mappings): , . ยซยป 4 . /proc/<pid>/maps. , , .

    • , , ( ).
  • : , . , , - , , , . , .
  • : , . - , . , , , TCP-, .
    • . stdin/stdout/stderr, 0, 1 2.
    • , , , .
  • Lain-lain : Ada beberapa bagian lain dari proses yang bervariasi dalam kompleksitas replikasi. Tetapi dalam kebanyakan kasus mereka tidak masalah, misalnya, brk (heap pointer). Beberapa di antaranya dapat dipulihkan hanya dengan bantuan trik aneh atau panggilan sistem khusus seperti PR_SET_MM_MAP , yang mempersulit pemulihan.

Dengan demikian, implementasi dasar teleforkdapat dilakukan dengan pemetaan memori sederhana dan register dari utas utama. Ini harus cukup untuk program sederhana yang terutama melakukan perhitungan tanpa berinteraksi dengan sumber daya OS, seperti file (pada prinsipnya, untuk melakukan teleportasi sudah cukup untuk membuka file dalam sistem dan menutupnya sebelum memanggil telefork).

Bagaimana cara Telefork suatu proses


Saya bukan orang pertama yang berpikir tentang menciptakan kembali proses di komputer lain. Jadi, debugging rr dan merekam debugger melakukan hal yang sangat mirip . Saya mengirim beberapa pertanyaan kepada penulis program ini @rocallahan , dan dia memberi tahu saya tentang sistem CRIU untuk migrasi "panas" kontainer antar host. CRIU dapat mentransfer proses Linux ke sistem lain, mendukung pemulihan semua jenis deskriptor file dan status lainnya, namun kode ini sangat kompleks dan menggunakan banyak panggilan sistem yang memerlukan rakitan kernel khusus dan izin root. Menggunakan tautan dari halaman wiki CRIU, saya menemukan DMTCP dibuat untuk snapshot dari tugas yang didistribusikan pada superkomputer sehingga mereka dapat dimulai kembali nanti, dan program iniKode itu ternyata lebih sederhana .

Contoh-contoh ini tidak memaksa saya untuk meninggalkan upaya untuk mengimplementasikan sistem saya sendiri, karena ini adalah program yang sangat kompleks yang memerlukan pelari dan infrastruktur khusus, dan saya ingin menerapkan teleportasi proses yang paling sederhana sebagai panggilan perpustakaan. Jadi saya mempelajari fragmen kode sumber rr, CRIU, DMTCP, dan beberapa contoh ptrace - dan mengumpulkan prosedur saya sendiri telefork. Metode saya bekerja dengan caranya sendiri, ini adalah campuran berbagai teknik.

Untuk melakukan teleport suatu proses, Anda perlu melakukan beberapa pekerjaan dalam proses asli yang memanggil telefork, dan beberapa bekerja di sisi panggilan fungsi, yang menerima proses streaming di server dan membuatnya kembali dari aliran (fungsitelepad) Mereka dapat terjadi pada saat yang sama, tetapi semua serialisasi juga dapat dilakukan sebelum mengunduh, misalnya, menjatuhkannya ke file, dan kemudian mengunduhnya.

Berikut ini adalah ikhtisar yang disederhanakan dari kedua proses. Jika Anda ingin memahami secara detail, saya sarankan membaca kode sumber . Itu terkandung dalam satu file dan sangat erat berkomentar untuk membaca agar dan memahami bagaimana semuanya bekerja.

Mengirimkan proses menggunakan telefork


Fungsi teleforkmenerima aliran dengan kemampuan menulis, yang digunakan untuk mentransfer seluruh keadaan prosesnya.

  1. ยซยป . , , . fork .
  2. . /proc/<pid>/maps , . proc_maps crate.
  3. . DMTCP, , , . , [vdso], , , .
  4. . , , process_vm_readv , .
  5. Daftar Transfer . Saya menggunakan opsi PTRACE_GETREGSuntuk panggilan sistem ptrace . Ini memungkinkan Anda untuk mendapatkan semua nilai register dari proses anak. Lalu saya hanya menuliskannya di pesan di saluran.

Menjalankan panggilan sistem dalam proses anak


Untuk mengubah proses target menjadi salinan dari proses masuk, Anda harus memaksa proses untuk mengeksekusi sekelompok panggilan sistem itu sendiri, tanpa akses ke kode apa pun, karena kami telah menghapus semuanya. Kami membuat panggilan sistem jarak jauh menggunakan ptrace , panggilan sistem universal untuk memanipulasi dan memeriksa proses lain:

  1. syscall. syscall , . , process_vm_readv [vdso] , , , syscall Linux, . , [vdso].
  2. , PTRACE_SETREGS. syscall, rax Linux, rdi, rsi, rdx, r10, r8, r9.
  3. Ambil satu langkah menggunakan parameter PTRACE_SINGLESTEPuntuk menjalankan perintah syscall.
  4. Baca register dengan PTRACE_GETREGSuntuk mengembalikan nilai pengembalian syscall dan lihat apakah itu berhasil.

Proses penerimaan di telepad


Dengan menggunakan ini dan primitif yang sudah dijelaskan, kita dapat membuat ulang proses:

  1. Garpu proses anak yang beku . Mirip dengan pengiriman, tetapi kali ini kita membutuhkan proses anak yang dapat kita manipulasi untuk mengubahnya menjadi klon dari proses yang ditransfer.
  2. Periksa kartu alokasi memori . Kali ini kita perlu mengetahui semua kartu alokasi memori yang ada untuk menghapusnya dan memberikan ruang untuk proses yang masuk.
  3. . , munmap.
  4. . mremap, .
  5. . mmap , process_vm_writev .
  6. . PTRACE_SETREGS , , rax. raise(SIGSTOP), . , telepad.
    • Nilai arbitrer digunakan sehingga server telefork dapat mentransfer deskriptor file dari koneksi TCP yang dimasukkan proses, dan dapat mengirim data kembali atau, jika yoyo, teleport kembali ke koneksi yang sama.
  7. Mulai ulang proses dengan konten baru menggunakan PTRACE_DETACH.

Implementasi yang lebih kompeten


Beberapa bagian dari implementasi telefork saya tidak dirancang dengan sempurna. Saya tahu cara memperbaikinya, tetapi dalam bentuk saat ini saya suka sistemnya, dan terkadang mereka sangat sulit untuk memperbaikinya. Berikut ini beberapa contoh menarik:

  • (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 , , .


Saya pikir Anda sudah mengerti bahwa dengan antarmuka tingkat rendah yang tepat, Anda dapat menerapkan beberapa hal gila yang tampaknya mustahil bagi seseorang. Berikut adalah beberapa pemikiran tentang bagaimana mengembangkan ide-ide dasar telefork. Meskipun banyak dari hal di atas mungkin dapat sepenuhnya diimplementasikan hanya pada kernel yang sama sekali baru atau tetap:

  • Telefork klaster . Sumber awal inspirasi untuk telefork adalah ide untuk mengalirkan suatu proses ke semua mesin dalam sebuah cluster komputasi. Bahkan dapat berubah untuk mengimplementasikan metode UDP-multicast atau peer-to-peer untuk mempercepat distribusi di seluruh cluster. Anda mungkin juga ingin memiliki komunikasi primitif.
  • . CRIU , - userfaultfd. , SIGSEGV mmap. , ,  โ€” .
  • ! , . userfaultfd userfaultfd, , , MESI, . , , .  โ€” , . , , , . : syscall, -, syscall, . , . , , , . , , . , , ( , ) , .


Saya sangat menyukainya, karena di sini adalah contoh dari salah satu teknik favorit saya - menyelam ke lapisan abstraksi yang kurang dikenal, yang relatif mudah memenuhi apa yang kami pikir hampir mustahil. Perhitungan teleportasi mungkin tampak tidak mungkin atau sangat sulit. Anda mungkin berpikir bahwa itu akan memerlukan metode seperti membuat serial seluruh negara, menyalin biner yang dapat dieksekusi ke mesin jarak jauh, dan meluncurkannya di sana dengan bendera baris perintah khusus untuk memuat ulang negara. Tapi tidak, semuanya jauh lebih sederhana. Di bawah bahasa pemrograman favorit Anda adalah lapisan abstraksi di mana Anda dapat memilih subset fungsi yang cukup sederhana - dan selama akhir pekan menerapkan teleportasi perhitungan paling murni dalam bahasa pemrograman apa pun dalam 500 baris kode. kupikirbahwa penyelaman seperti itu ke tingkat abstraksi lain sering kali mengarah pada solusi yang lebih sederhana dan lebih universal. Proyek saya yang lain seperti iniNumderline .

Sepintas, proyek-proyek semacam itu tampaknya merupakan peretasan ekstrem, dan sebagian besar memang demikian. Mereka melakukan hal-hal yang tidak diharapkan oleh siapa pun, dan ketika mereka rusak, mereka melakukannya pada tingkat abstraksi, di mana program serupa seharusnya tidak berfungsi - misalnya, deskriptor file Anda menghilang secara misterius. Tetapi kadang-kadang Anda dapat dengan tepat mengatur tingkat abstraksi dan menyandikan segala situasi yang mungkin terjadi, sehingga pada akhirnya semuanya akan bekerja dengan lancar dan ajaib. Saya pikir contoh yang baik di sini adalah rr (walaupun Telefork berhasil memecatnya) dan migrasi cloud dari mesin virtual secara real time (pada kenyataannya, Telefork pada level hypervisor).

Saya juga suka menyajikan hal-hal ini sebagai ide untuk cara alternatif bekerja sistem komputer. Mengapa API komputasi cluster kami jauh lebih rumit daripada program sederhana yang menerjemahkan fungsi menjadi sebuah cluster? Mengapa pemrograman sistem jaringan jauh lebih rumit daripada multithreaded? Tentu saja, Anda dapat memberikan segala macam alasan bagus, tetapi biasanya didasarkan pada seberapa sulitnya membuat contoh sistem yang ada. Atau mungkin dengan abstraksi yang tepat atau dengan upaya yang memadai, semuanya akan bekerja dengan mudah dan mulus? Pada dasarnya, tidak ada yang mustahil.





All Articles