Manajemen Memori¶
Tidak seperti bahasa pemrograman seperti C/C++, MicroPython menyembunyikan detail manajemen memori dari pengembang dengan mendukung manajemen memori otomatis. Manajemen memori otomatis adalah teknik yang digunakan oleh sistem operasi atau aplikasi untuk secara otomatis mengelola alokasi dan dealokasi memori. Hal ini menghilangkan tantangan seperti lupa membebaskan memori yang dialokasikan untuk suatu objek. Manajemen memori otomatis juga menghindari masalah kritis penggunaan memori yang sudah dibebaskan. Manajemen memori otomatis hadir dalam berbagai bentuk, salah satunya adalah pengumpulan sampah (garbage collection/GC).
Pengumpul sampah biasanya memiliki dua tanggung jawab;
Mengalokasikan objek baru di memori yang tersedia.
Membebaskan memori yang tidak terpakai.
Ada banyak algoritma GC, tetapi MicroPython menggunakan kebijakan Mark and Sweep untuk mengelola memori. Algoritma ini memiliki fase mark yang menelusuri heap dan menandai semua objek yang masih hidup, sementara fase sweep melewati heap dan mengklaim kembali semua objek yang tidak ditandai.
Fungsionalitas pengumpulan sampah di MicroPython tersedia melalui modul bawaan gc:
>>> x = 5
>>> x
5
>>> import gc
>>> gc.enable()
>>> gc.mem_alloc()
1312
>>> gc.mem_free()
2071392
>>> gc.collect()
19
>>> gc.disable()
>>>
Bahkan ketika gc.disable() dipanggil, pengumpulan masih dapat dipicu dengan gc.collect().
Memori MicroPython dari Kode C¶
Kesadaran akan pengumpul sampah diperlukan saat menulis kode C yang mengalokasikan memori dari "Python heap" (yaitu fungsi m_malloc(), m_malloc0(), m_free(), dll).
Fase mark dari pengumpul sampah memindai pointer hidup ke memori heap mulai dari root berikut:
Stack dari runtime Python utama (atau REPL).
Stack dari setiap "thread Python", untuk port yang mengimplementasikan thread Python di atas thread atau task sistem operasi native.
"Root pointer" yang didefinisikan dalam kode C menggunakan makro
MP_REGISTER_ROOT_POINTER. Ini adalah cara yang direkomendasikan untuk memiliki pointer dengan cakupan statis ke Python heap.Alokasi yang dilacak menggunakan fungsi
m_tracked_calloc(),m_tracked_reallocdanm_tracked_free(). Fungsi-fungsi khusus ini memungkinkan alokasi blok memori yang selalu dianggap hidup oleh pengumpul sampah. Mirip dengan alokasi memori di C, memori ini hanya dibebaskan dengan memanggilm_tracked_free()atau melalui soft reset. Ada sedikit overhead penggunaan memori dan runtime untuk setiap alokasi yang dilacak. Fitur ini tidak diaktifkan secara default di semua port.
Pengumpul sampah kemudian secara rekursif memindai dan menandai semua memori yang ditunjuk oleh root pointer, hingga semua alamat habis. Ini cukup untuk menemukan semua objek Python yang masih digunakan oleh runtime MicroPython.
Namun, memori berikut tidak akan dipindai oleh pengumpul sampah dan bisa dibebaskan sebelum waktunya:
Variabel C statis atau global yang berisi pointer ke memori heap.
Pointer yang tidak menunjuk ke "kepala" dari buffer yang dialokasikan (yaitu ke alamat tepat yang dikembalikan oleh
m_malloc()), melainkan ke alamat di dalam buffer yang dialokasikan (misalnya, pointer ke struct bersarang). Untuk alasan kinerja, pengumpul sampah tidak menandai buffer yang melingkupi dalam kasus-kasus ini.Stack dari thread atau task RTOS mana pun yang tidak menjalankan kode Python atau tidak terdaftar secara manual sebagai "thread Python" (untuk port yang mendukung thread atau task native).
Cara menghindari use-after-free dalam skenario ini:
Gunakan API alokasi yang dilacak
m_tracked_calloc(),m_tracked_realloc()danm_tracked_free().Daftarkan root pointer (lihat di atas), alih-alih menyimpan pointer dalam variabel statis.
Restrukturisasi kode, misalnya dengan memiliki API di mana kode Python menginisialisasi objek Python singleton (diimplementasikan dalam C) yang menyimpan semua pointer yang relevan, alih-alih menyimpannya dalam variabel statis.
Catatan
Soft Reset selalu menghapus Python heap dan membebaskan semua memori. Penting untuk tidak menyimpan pointer apa pun ke heap setelah soft reset, karena mereka akan menjadi dangling pointer ke memori yang sudah dibebaskan.
Beberapa port mendukung "C heap" juga (lihat Alokasi Memori Dinamis C), dalam hal ini Anda dapat mengalokasikan memori yang akan tetap valid setelah soft reset dengan memanggil fungsi C standar malloc, dll.
Model Objek¶
Semua objek MicroPython direferensikan melalui tipe data mp_obj_t. Ini biasanya berukuran word (yaitu ukuran yang sama dengan pointer pada arsitektur target), dan biasanya bisa 32-bit (STM32, RP2, nRF, Unix x86) atau 64-bit (Unix x64). Ukurannya juga bisa lebih dari ukuran word untuk representasi objek tertentu, misalnya OBJ_REPR_D memiliki mp_obj_t berukuran 64-bit pada arsitektur 32-bit.
Sebuah mp_obj_t merepresentasikan objek MicroPython, misalnya integer, float, tipe, dict, atau instance kelas. Beberapa objek, seperti boolean dan integer kecil, memiliki nilainya tersimpan langsung dalam nilai mp_obj_t dan tidak memerlukan memori tambahan. Objek lain memiliki nilainya tersimpan di tempat lain di memori (misalnya di heap yang dikelola pengumpul sampah) dan mp_obj_t-nya berisi pointer ke memori tersebut. Bagian dari mp_obj_t adalah tag yang memberi tahu jenis objek apa itu.
Lihat py/mpconfig.h untuk detail spesifik dari representasi yang tersedia.
Penandaan Pointer
Karena pointer diratakan ke word, ketika disimpan dalam mp_obj_t, bit-bit rendah dari handle objek ini akan bernilai nol. Misalnya pada arsitektur 32-bit, 2 bit rendah akan nol:
********|********|********|******00
Bit-bit ini dicadangkan untuk menyimpan tag. Tag menyimpan informasi tambahan alih-alih memperkenalkan field baru untuk menyimpan informasi tersebut dalam objek, yang mungkin tidak efisien. Dalam MicroPython, tag memberitahu apakah kita berurusan dengan integer kecil, string yang di-intern (kecil), atau objek konkret, dan semantik yang berbeda berlaku untuk masing-masing.
Untuk integer kecil, pemetaannya adalah:
********|********|********|*******1
Di mana tanda bintang menyimpan nilai integer yang sebenarnya. Untuk string yang di-intern atau objek langsung (misalnya True), tata letak nilai mp_obj_t adalah, masing-masing:
********|********|********|*****010
********|********|********|*****110
Sementara objek konkret yang bukan keduanya di atas mengambil bentuk:
********|********|********|******00
Tanda bintang di sini sesuai dengan alamat objek konkret di memori.
Alokasi Objek¶
Nilai integer kecil disimpan langsung dalam mp_obj_t dan akan dialokasikan di tempat, bukan di heap atau di tempat lain. Dengan demikian, pembuatan integer kecil tidak memengaruhi heap. Hal yang sama berlaku untuk string yang di-intern yang sudah memiliki data tekstualnya tersimpan di tempat lain, dan nilai langsung seperti None, False dan True.
Semua hal lain yang merupakan objek konkret dialokasikan di heap dan struktur objeknya sedemikian rupa sehingga sebuah field dicadangkan di header objek untuk menyimpan tipe objek tersebut.
+++++++++++
+ +
+ type + object header
+ +
+++++++++++
+ + object items
+ +
+ +
+++++++++++
Unit alokasi terkecil heap adalah sebuah blok, yang berukuran empat machine word (16 byte pada mesin 32-bit, 32 byte pada mesin 64-bit). Struktur lain yang juga dialokasikan di heap melacak alokasi objek di setiap blok. Struktur ini disebut bitmap.
Bitmap melacak apakah sebuah blok "bebas" atau "sedang digunakan" dan menggunakan dua bit untuk melacak status ini untuk setiap blok.
Pengumpul sampah mark-sweep mengelola objek yang dialokasikan di heap, dan juga memanfaatkan bitmap untuk menandai objek yang masih digunakan. Lihat py/gc.c untuk implementasi lengkap dari detail ini.
Alokasi: tata letak heap
Heap diatur sedemikian rupa sehingga terdiri dari blok-blok dalam pool. Sebuah blok dapat memiliki berbagai properti:
ATB (allocation table byte): Jika diset, maka blok tersebut adalah blok normal
FREE: Blok bebas
HEAD: Kepala dari rantai blok
TAIL: Dalam ekor dari rantai blok
MARK: Blok kepala yang ditandai
FTB (finaliser table byte): Jika diset, maka blok tersebut memiliki finaliser