3 คะแนน โดย GN⁺ 2023-07-22 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • โปรเจกต์ย่อย rjvm ที่ทำขึ้นเพื่อเรียนรู้ Rust เป็น JVM แบบของเล่นเพื่อการเรียนรู้ แต่ก็พัฒนาไปถึงขั้นอ่าน .class และ jar จริง รวมถึงรัน Java bytecode ได้
  • ฟีเจอร์ที่ไม่รองรับคือ thread, reflection, annotation, I/O, JIT compiler และ string interning แต่มี control flow, การสร้าง object, การเรียก method, exception และ garbage collection
  • ตัวอย่างใช้ rt.jar จริงของ OpenJDK 7 ทำให้ class อย่าง java.lang.StackTraceElement ถูกโหลดจาก JDK จริง
  • โค้ดแบ่งเป็น Rust crate สามตัว ได้แก่ reader, vm, vm_cli ครอบคลุมตั้งแต่การ parse .class ไปจนถึง call stack, native method และการ model value กับ object
  • milestone สุดท้ายคือ stop-the-world semispace copying collector และหลังจากบรรลุเป้าหมายแล้วโปรเจกต์ก็หยุดลง โดยไม่มีแผนแก้ bug ที่ทราบอยู่แล้ว

rjvm JVM ภาษา Rust เพื่อการเรียนรู้

  • rjvm คือ toy Java Virtual Machine ที่สร้างขึ้นเพื่อเรียนรู้ Rust
  • โค้ดเปิดเผยอยู่บน GitHub
  • เป็น implementation เพื่อการเรียนรู้มากกว่าจะเป็น JVM ที่จริงจัง จึงไม่รองรับฟีเจอร์ต่อไปนี้
    • thread
    • reflection
    • annotation
    • I/O
    • JIT compiler
    • string interning
  • generics เดิมอยู่ในรายการที่ไม่รองรับ แต่ในความเป็นจริงใช้งานได้

พัฒนาไปถึงขั้นไหนแล้ว

  • rjvm ไม่ได้เป็นแค่ parser ธรรมดา แต่ implement พฤติกรรมของ JVM หลายอย่างเอง
    • control flow เช่น if, for
    • primitive type และการสร้าง object
    • การเรียก virtual method และ static method
    • exception handling
    • garbage collection
    • การ resolve class จากไฟล์ jar
  • test code มีตัวอย่างที่ใช้ Throwable, Exception, StackTraceElement
  • เนื่องจากไม่มี I/O จริง จึงใช้ native method ชื่อ tempPrint แทน System.out.println
  • ตัวอย่างใช้ rt.jar จริงของ OpenJDK 7 และ java.lang.StackTraceElement ก็นำมาจาก JDK จริง

Rust crate สามตัว

  • โปรเจกต์นี้เป็นโปรเจกต์ Rust มาตรฐาน และประกอบด้วย crate สามตัว
    • reader: มี type สำหรับอ่านไฟล์ .class และ model เนื้อหาภายใน
    • vm: ให้ virtual machine ที่รันโค้ดได้ในรูปแบบ library
    • vm_cli: มี command-line launcher แบบง่าย คล้าย executable java
  • กำลังพิจารณาแยก crate reader ออกเป็น repository ต่างหากและเผยแพร่บน crates.io

การ parse ไฟล์ .class

  • Java code ถูก compile ด้วย javac เป็นไฟล์ .class และโดยทั่วไปเผยแพร่เป็นไฟล์ .jar ซึ่งเป็นรูปแบบ zip
  • หากต้องการรัน Java code ต้องโหลดไฟล์ .class ที่มี bytecode ซึ่ง compiler สร้างขึ้นก่อน
  • ไฟล์ class มีข้อมูลที่จำเป็นต่อการรันและการ resolve type อยู่ด้วยกัน
    • metadata เช่นชื่อ class และชื่อ source file
    • ชื่อ superclass
    • interface ที่ implement
    • field และ type ของ field, annotation
    • method descriptor, clause throws, annotation, ข้อมูล generics
    • bytecode, exception handler table, line number table
  • crate reader parse ไฟล์ class แล้วคืนค่าเป็น Rust struct ที่ model class และเนื้อหาภายใน

การรัน method และ call stack

  • API หลักของ crate vm คือ Vm::invoke ใช้สำหรับรัน method
  • method แต่ละตัวที่กำลังรันมี CallFrame หนึ่งตัวอยู่ใน CallStack
  • เมื่อรัน main ตอนแรก call stack จะว่าง และจะสร้าง frame ใหม่เพื่อเริ่มรัน
  • ทุกครั้งที่เรียก function จะเพิ่ม frame ใหม่เข้าไปใน call stack และเมื่อ method รันจบ frame นั้นจะถูกนำออก
  • method ส่วนใหญ่ implement ด้วย Java bytecode แต่ rjvm รองรับ native method ด้วย
    • native method คือ method ที่ JVM เอง implement โดยตรง ไม่ใช่ Java bytecode
    • ตัวอย่างเช่น System::currentTimeMillis, System::arraycopy, Throwable::fillInStackTrace
    • ใน rjvm implement เป็น Rust function
  • JVM เป็น stack-based virtual machine ดังนั้นคำสั่ง bytecode ส่วนใหญ่ทำงานบน value stack
  • แต่ละ call frame ผูกกับ value stack และชุด local variable ที่ระบุด้วย index

การ model value และ object

  • Value ใช้ model ค่าที่เก็บใน local variable, stack element และ object field ได้
  • Value implement ด้วย Rust enum และมี state ต่อไปนี้
    • Uninitialized
    • Int(i32)
    • Long(i64)
    • Float(f32)
    • Double(f64)
    • Object(AbstractObject<'a>)
    • Null
  • sum type อย่าง enum ของ Rust เหมาะกับการแสดงว่าค่าหนึ่งอาจเป็นหนึ่งในหลาย type
  • การจัดเก็บ object ตอนแรกเริ่มจาก struct Object แบบง่ายที่ใช้ Vec<Value> เก็บ reference ไปยัง class และค่า field
  • หลังจาก implement garbage collector แล้ว เปลี่ยนเป็น implementation ระดับต่ำลงที่ใช้ pointer และ cast จำนวนมาก
  • ปัจจุบัน AbstractObject model object หรือ array จริง โดยเป็น pointer ไปยัง byte array ที่มี header word หลายตัวและค่า field

การรันคำสั่ง bytecode

  • การรัน method คือกระบวนการประมวลผลคำสั่ง bytecode ทีละคำสั่ง
  • คำสั่ง JVM มีมากกว่า 200 คำสั่ง และใน bytecode จะ encode ด้วย 1 byte
  • คำสั่งจำนวนมากมี argument ตามมา และบางคำสั่งมีความยาวแบบแปรผัน
  • rjvm model คำสั่ง Java bytecode ด้วย type Instruction
  • เมื่อรัน method จะรักษา value stack และ array ของ local variable ไว้ และกำหนดค่าเริ่มต้นของ program counter ซึ่งเป็น address ของคำสั่งถัดไปที่จะรันเป็น 0
  • โดยทั่วไปหลังรันคำสั่งแล้ว program counter จะขยับไปคำสั่งถัดไป แต่คำสั่ง jump สามารถย้ายไปตำแหน่งอื่นได้
  • คำสั่ง jump ใช้ implement control flow statement เช่น if, for, while
  • มีชุดคำสั่งแยกต่างหากสำหรับเรียก method อื่น
    • วิธีตัดสินใจว่าจะเรียก method ใดมีทั้ง virtual lookup และ static lookup เป็นต้น
    • หลัง resolve method เป้าหมายแล้ว จะเพิ่ม frame ใหม่เข้าไปใน call stack และเริ่มรัน
    • หาก return value ไม่ใช่ void จะ push return value ลง stack แล้วกลับมารันต่อ

Exception handling

  • exception ทำให้ control flow ปกติถูกรบกวน, method return ก่อนเวลา และอาจ propagate ไปตาม call stack จึง implement ได้ซับซ้อน
  • catch block แต่ละตัวสอดคล้องกับ entry หนึ่งใน exception table ของ method
  • entry ใน exception table มีข้อมูลที่จำเป็นสำหรับค้นหา handler
    • ช่วง program counter ที่มีผล
    • address ของคำสั่งแรกใน catch block
    • ชื่อ class ของ exception ที่ block นั้นจับ
  • CallFrame::execute_instruction ใช้ Result ของ Rust เพื่อแสดงผลลัพธ์ของการรันคำสั่ง
  • ผลลัพธ์ของการรันคำสั่งแบ่งเป็นสี่ state
    • สำเร็จและรัน method ปัจจุบันต่อ
    • สำเร็จและเป็นคำสั่ง return ทำให้ method ปัจจุบันจบพร้อม return value
    • ล้มเหลวเพราะ error ภายใน VM
    • ล้มเหลวเพราะมี Java exception ถูก throw
  • loop รัน method จะ parse คำสั่ง ขยับ program counter ไป address ถัดไป แล้วรันคำสั่ง
  • เมื่อเกิด exception จะค้นหา exception handler ที่ตรงกับตำแหน่งคำสั่งปัจจุบัน
    • หากไม่มี handler จะ propagate exception ไปยัง caller
    • หากมี handler จะ push object exception กลับลง stack และรันต่อจากตำแหน่ง handler ของ catch
  • Result และ pattern matching ของ Rust เหมาะกับการแสดงพฤติกรรมนี้ในโครงสร้างโค้ด

Garbage collection

  • milestone สำคัญสุดท้ายของ rjvm คือการ implement garbage collector
  • อัลกอริทึมที่เลือกคือ stop-the-world semispace copying collector
  • เนื่องจากไม่มี thread วิธี stop-the-world จึงเกิดขึ้นได้ตามธรรมชาติ
  • implementation เป็นรูปแบบที่เรียบง่ายกว่าของ Cheney's algorithm และโค้ดอยู่ใน gc.rs
  • วิธีนี้แบ่งหน่วยความจำที่ใช้ได้ออกเป็น semispace สองส่วน
    • ส่วนหนึ่งเป็น active area สำหรับ allocate object
    • อีกส่วนเหลือไว้เป็นพื้นที่ที่ยังไม่ใช้
    • เมื่อ active area เต็ม จะ copy object ที่ยังมีชีวิตไปยัง semispace อีกฝั่ง
    • อัปเดต reference ทั้งหมดของ object ให้ชี้ไปยังสำเนาใหม่
    • สลับบทบาทของ semispace ทั้งสอง
  • ขั้นตอนนี้ถูกเปรียบว่าเป็นวิธีที่คล้ายกับ blue-green deployment
  • ข้อดีข้อเสียของอัลกอริทึมนี้ชัดเจน
    • ใช้หน่วยความจำสูงสุดได้เพียงครึ่งเดียว จึง สิ้นเปลืองหน่วยความจำ มาก
    • การ allocate เร็วมาก เพราะใช้วิธีเพิ่ม pointer
    • เนื่องจาก copy และ compact object จึงไม่ต้องจัดการ memory fragmentation
    • การ compact object อาจเพิ่ม performance จากการใช้ cache line ได้ดีขึ้น
  • Java VM จริงโดยทั่วไปใช้ generational garbage collector ที่ซับซ้อนกว่า เช่น G1 หรือ parallel GC

จุดสิ้นสุดของโปรเจกต์

  • ระหว่างสร้าง rjvm ได้เรียนรู้ Rust และการ implement virtual machine ไปมาก โดยเฉพาะพอใจกับการ implement garbage collector ที่ใช้งานได้จริง
  • garbage collector ยังไม่ได้สมบูรณ์มากนัก แต่ใช้งานได้จริง
  • เนื่องจากบรรลุเป้าหมายเดิมแล้ว โปรเจกต์จึงหยุดไว้ที่นี่
  • มี bug ที่ทราบอยู่แล้ว แต่ไม่มีแผนจะแก้ไข
  • Rust เป็นภาษาที่ใช้งานอย่างสนุกในการ implement JVM และในบทความต่อๆ ไปจะเจาะรายละเอียด implementation ของ rjvm และวิธีทำงานของ JVM มากขึ้น

1 ความคิดเห็น

 
GN⁺ 2023-07-22
ความเห็นจาก Hacker News
  • ส่วนที่ยากในการทำ garbage collection คือการทำให้แน่ใจว่าการอ้างอิงทั้งหมดถูกจับเป็น root อย่างถูกต้อง โดยเฉพาะกับตัวเก็บกวาดแบบย้ายตำแหน่งวัตถุ
    เมธอด do_garbage_collection ถูกทำเครื่องหมายเป็น unsafe[1] แต่ไม่มีคำอธิบายว่าฝั่งที่เรียกต้องรับประกันอะไรบ้างจึงจะเรียกได้อย่างปลอดภัย
    เลยสงสัยว่ารับประกันได้อย่างไรว่ารีเฟอเรนซ์ทั้งหมดไปยัง heap จะถูกจับเป็น root ซึ่งนี่ไม่ใช่ปัญหาเล็กน้อยเลย[2][3][4]
    อีกอย่าง พอลอง clone รีโพซิทอรีแล้วรัน cargo test ดู ปรากฏว่าทุกเทสล้มเหลวด้วยข้อผิดพลาด should be able to add entries to the classpath: InvalidEntry(".../vm/rt.jar"): vm/tests/integration/real_code_tests.rs:15:10
    [1] https://github.com/andreabergia/rjvm/blob/be9c54066c64a82879...
    [2] https://manishearth.github.io/blog/2021/04/05/a-tour-of-safe...
    [3] https://without.boats/blog/shifgrethor-iii/
    [4] https://coredumped.dev/2022/04/11/implementing-a-safe-garbag...

    • VM ตัวนี้เก็บ virtual call stack ของตัวเองไว้แทนที่จะใช้ native call stack จึงค่อนข้างตรงไปตรงมา
      ดังนั้นจึงสามารถไล่ดูสแตกนั้นเพื่อหาอาร์กิวเมนต์และตัวแปรภายใน แล้วใช้เป็น root ได้
      วิธีนี้มีต้นทุนด้านประสิทธิภาพ แต่ทำให้การติดตามของ garbage collection ง่ายขึ้นมาก และยังทำให้ฟีเจอร์พื้นฐานด้านการทำงานพร้อมกันและการควบคุมโฟลว์อย่าง coroutine หรือ continuation ทำได้ง่ายขึ้นด้วย
    • ตัวเก็บกวาดขยะโดยพื้นฐานแล้วก็เหมือนงานบัญชีที่ต้องละเอียดรอบคอบ เลยถือว่าค่อนข้างง่าย แต่ทันทีที่เริ่มทำ concurrent garbage collection ทุกอย่างจะยากราวกับนรก
  • เป็นโปรเจ็กต์ที่ดีและขอแสดงความยินดี
    แต่ส่วนที่ว่า “สิ่งที่ยังไม่รองรับ: generics” ฟังดูแปลกนิดหน่อย
    เลยสงสัยว่าบน JVM นั้นการรองรับ generics ควรหมายถึงอะไร
    ในระดับไบต์โค้ด เพราะมี type erasure อยู่แล้ว ไม่ใช่ว่าจะมองทุกอย่างเป็น Object หรือก็คือ reference type ได้เลยหรือ? หรือว่าหมายถึงตัวพาร์สคำนิยามคลาส? ถึงอย่างนั้นก็ดูเหมือนไม่มีตรรกะสำหรับตรวจสอบว่าคลาสไฟล์ถูกต้องหรือไม่ นอกเหนือจากไวยากรณ์พื้นฐาน

    • เรื่อง generics มีคนพูดแบบเดียวกันบน reddit และก็ถูกต้อง
      สิ่งที่ต้องทำจริง ๆ ก็คงแค่อ่าน Signature attribute ที่เก็บข้อมูล generics ของคลาส เมธอด และฟิลด์ (https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.ht...)
      เพิ่งลองเทสดูเมื่อกี้ โค้ดด้านล่างก็ทำงานได้ :-)
      public class Generic { public static void main(String[] args) { List strings = new ArrayList(10); strings.add("hey"); strings.add("hackernews"); for (String s : strings) { tempPrint(s); } } private static native void tempPrint(String value); }
    • อาจจะหมายถึงคำสั่ง checkcast ก็ได้: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.ht...
      ตัวอย่างเช่นจะถูกสร้างเมื่อเขียนโค้ดแบบ final Main value = list.get(0);
      http://henrikeichenhardt.blogspot.com/2013/05/how-are-java-g...
    • โดยรวมก็ใช่ Generics มีผลกับ reflection อยู่บ้างในบางกรณี แต่ตอนนี้ก็ยังไม่รองรับ reflection อยู่แล้ว และโดยทั่วไปจะถูกแทนที่ด้วยคลาสหรืออินเทอร์เฟซที่ใกล้เคียงที่สุดตั้งแต่ตอนคอมไพล์
      ในทางกลับกัน การไม่มี string interning นี่แปลกมาก เพราะมันก็ไม่ได้ยากอะไรนักในการทำ และถ้าไม่มีสิ่งนี้ก็คงเรียกว่าเป็น JVM ได้ยาก
      การที่สตริงเท่ากันในเชิง reference นั้นสำคัญและเป็นส่วนหนึ่งของ JLS
      ส่วนการไม่มีเธรดก็ทำให้ความพยายามทั้งหมดนี้ยังอยู่ในระดับโปรเจ็กต์ของเล่น
  • เจ๋งมาก ตอนที่ผมเข้าร่วมงาน Java ในปี 1992 มันยังใช้ชื่อว่า Oak และกลุ่มที่ผมอยู่ก็มองไปในทิศทางของการเขียนระบบปฏิบัติการทั้งตัวด้วย Java
    แนวคิดคือถ้าเหลือไว้แค่ส่วนขั้นต่ำที่จำเป็นใน “ภาษาเครื่อง” หรือเมธอดเนทีฟ ก็จะช่วยลดพื้นผิวการโจมตีของระบบปฏิบัติการแบบฝังตัวได้
    เดิมที Java มีเป้าหมายให้รันบนอุปกรณ์อย่างทีวีหรือเครื่องใช้ไฟฟ้า และในตอนนั้นเมธอดเนทีฟก็ทำด้วย C ไม่ใช่ Rust
    JVM ที่เขียนด้วย Rust เพิ่ม ความปลอดภัยของหน่วยความจำ ให้กับกระบวนการทั้งหมดนี้ได้อย่างมาก

    • ส่วนตัวผมคิดว่า Android บรรลุแนวคิดนั้นได้ในระดับหนึ่ง
      ตรรกะของระบบปฏิบัติการจำนวนมากเขียนด้วย Java หรือ Kotlin แต่ขณะเดียวกันก็ผสมกับ system service ที่เป็น native code อยู่มาก และเชื่อมต่อกันด้วย Binder IPC ที่ทั้งโด่งดังและมีชื่อเสีย
    • ไอเดียนี้เห็นได้ชัดว่าเคยถูกลองมาหลายครั้ง [0, 1]
      [0] - https://en.wikipedia.org/wiki/Singularity_(operating_system)
      [1] - https://en.wikipedia.org/wiki/Midori_(operating_system)
    • เหมือนจะเคยมี JavaOS อยู่ช่วงหนึ่ง แม้จะไม่ใช่สำหรับผู้ใช้ทั่วไป: https://en.wikipedia.org/wiki/JavaOS
    • นอกจากคอมเมนต์พี่น้องเหล่านี้แล้ว ยังมี SavageJE, microEJ, bare metal Java runtime ของ PTC และ Aonix, และ SquawkVM ของ SunSPOT ด้วย
  • ยังมี implementation ของ JVM 17 ที่เขียนด้วย Go อย่าง https://jacobin.org/ ที่น่าดูเหมือนกัน

    • และยังมี JVM ที่เขียนด้วย Scala ซึ่งรันอยู่บน JVM เอง คือ https://github.com/lihaoyi/Metascala
    • ในฐานะชื่อโปรเจกต์โปรแกรมมิงก็ถือว่าน่าสนใจมาก
      Jacobins คือชมรมการเมืองปฏิวัติในช่วงการปฏิวัติฝรั่งเศสยุคทศวรรษ 1790 และยังเป็นชื่อของนิตยสารที่ https://jacobin.com ด้วย
  • สงสัยว่าติดข้อจำกัดเพราะ พารามิเตอร์ lifetime ใน signature นี้หรือเปล่า
    fn execute_instruction(&mut self, vm: &mut Vm<'a>, call_stack: &mut CallStack<'a>, instruction: Instruction) -> Result, MethodCallFailed<'a>>
    ถ้าเพิ่ม lifetime เข้าไปใน Err variant ของ Result และ lifetime นั้นเป็น invariant ซึ่งในที่นี้ดูเหมือนจะเป็นเช่นนั้นเพราะ vm กับ call_stack ปกติแล้วจะทำให้ใช้ตัวดำเนินการ ? หรือ early return ไม่ได้[1]
    ซึ่งทำให้การจัดการข้อผิดพลาดยืดยาวและอ่านยากขึ้น เลยสงสัยว่าในทางปฏิบัติก็เจอประสบการณ์แบบนั้นหรือไม่
    [1] https://users.rust-lang.org/t/nll-and-early-return-not-allow...

    • ขอแก้ไขนิดหนึ่ง ตรงนี้ดูเหมือนจะไม่ใช่ปัญหานั้น เพราะ lifetime แบบ invariant 'a ไม่ได้ถูกใช้กับ mutable reference ของ vm หรือ call_stack
      ปัญหาไม่ได้อยู่ที่ความเป็น invariant โดยตัวมันเอง แต่อยู่ที่วิธีที่ Rust อนุมาน lifetime ของ mutable reference ซึ่งโค้ดนี้หลบประเด็นนั้นไปได้
      ถ้าอย่างนั้นผมก็ไม่แน่ใจว่า 'a ที่ติดอยู่กับ VM และ CallStack มีความหมายอะไร
      ถ้าสามารถสร้างมันด้วย lifetime ใดก็ได้ที่ไม่มีข้อจำกัด แม้แต่ 'static[1][2][3] ก็เท่ากับว่าไม่ได้บังคับข้อจำกัดอะไรเลย
      เลยสงสัยว่าพารามิเตอร์ lifetime นี้ทำหน้าที่อะไรอยู่ตรงนี้ และทำไมถึงไม่เอาออก
      [1] https://github.com/andreabergia/rjvm/blob/be9c54066c64a82879...
      [2] https://github.com/andreabergia/rjvm/blob/be9c54066c64a82879...
      [3] https://github.com/andreabergia/rjvm/blob/be9c54066c64a82879...
  • เป็นโปรเจกต์สำหรับการเรียนรู้ที่ยอดเยี่ยม และดีใจที่ผู้เขียนกำลังสนุกกับมัน
    การทำ VM ตั้งแต่ศูนย์นั้นสนุกมากจริง ๆ และผมก็ได้เรียนรู้อะไรเยอะจากการทำแบบนั้นในอดีต
    ถ้าอยากลองใส่ garbage collection เข้าไป อาจลองดู MMTk ได้ (https://www.mmtk.io/)
    มันมีอัลกอริทึมตัวเก็บกวาดคุณภาพสูงที่ออกแบบมาให้เสียบใช้กับ VM ได้หลายตัว และเขียนด้วย Rust

    • เผื่อไว้เป็นข้อมูล MMTK รองรับเฉพาะ x86
      ผมเคยจะใช้กับโปรเจกต์เล่น ๆ แต่สุดท้ายต้องยอมแพ้เพราะใช้ Mac
  • ทำออกมาได้ดีมาก
    การสร้าง VM เป็นเรื่องสนุกเสมอ และเมื่อรวมกับระบบชนิดข้อมูลของ Rust ก็น่าจะเป็นประสบการณ์การเรียนรู้ที่น่าสนใจ
    ถ้ากำลังหางานอยู่ ลองติดต่อมาทาง Twitter, Mastodon หรืออีเมลบริษัทก็ได้ น่าจะหาได้จาก user ID ตรงนี้

  • พอเห็นโปรเจ็กต์เจ๋ง ๆ แบบนี้ก็รู้สึกท่วมท้นขึ้นมา
    ถ้าผู้เขียนต้นฉบับช่วยอธิบายหน่อยก็คงดีว่าเริ่มต้น Rust อย่างไร และต้องฝึกพื้นฐานถึงระดับไหนถึงจะลองทำอะไรแบบนี้ได้

    • รู้สึกเหมือนกันเลย
      ไม่อยากออกนอกประเด็นมากนัก แต่ช่วงนี้ส่วนตัวก็กำลังต่อสู้กับความรู้สึกแบบนี้อยู่พอสมควร
      ฉันทำงานเป็นนักพัฒนาซอฟต์แวร์มืออาชีพมาเกือบ 10 ปี และเมื่อดูจากตำแหน่งงานปัจจุบันกับความสามารถในการส่งมอบผลิตภัณฑ์จริง ก็รู้ว่าตัวเองมีความสามารถและไม่ใช่นักพัฒนาจอมปลอม
      แต่ช่วงนี้พออ่านบล็อกของนักพัฒนา ก็รู้สึกท่วมท้นเหมือนตัวเองยังรู้น้อยเกินไปและไม่ใช่นักพัฒนา “ตัวจริง”
      น่าจะเป็นเพราะฉันสร้างภาพนักพัฒนาในอุดมคติไว้ในหัว แล้วเอาตัวเองไปเทียบกับมาตรฐานในจินตนาการนั้น
      ฉันชื่นชมคนที่มีความรู้ลึกและถ่ายทอดได้ชัดเจนกระชับ แต่ก็อดคิดไม่ได้ว่าทำไมฉันถึงไม่เป็นแบบนั้น
      พอเลิกงานแล้วต้องดูแลครอบครัว ก็แทบไม่เหลือพลังจะทำอะไรต่อ ถึงจะรู้ว่าชีวิตไม่ได้มีแค่การเขียนโปรแกรม แต่ก็ยังอยากเรียนรู้และเติบโตมากขึ้น
      รู้ว่ามันไม่ทั้งดีต่อสุขภาพและไม่สมเหตุสมผล แต่ช่วงนี้เป็นความรู้สึกที่สลัดออกได้ยาก
    • พูดตรง ๆ ว่าทุกครั้งที่เปิด HN ก็รู้สึก impostor syndrome อยู่ประมาณครึ่งหนึ่ง
      เมื่อก่อนเคยมีประสบการณ์กับ VM อยู่บ้าง และเมื่อหลายปีก่อนก็เคยเขียนบล็อกเป็นซีรีส์สั้น ๆ เกี่ยวกับเรื่องนี้
      ที่ทำงานเก่าก็เคยลองไปยุ่งกับ JVM bytecode นิดหน่อยเพื่อแก้ปัญหาเฉพาะทางมาก ๆ ของลูกค้า
      อีกทั้งเมื่อหลายปีก่อนก็ได้อ่าน https://craftinginterpreters.com/ ที่ยอดเยี่ยมมาก และได้ไอเดียจากตรงนั้น
      แต่โปรเจ็กต์นี้ใหญ่และซับซ้อนมากอย่างไม่ต้องสงสัย
      มันใช้เวลานาน และก็ถูกปล่อยทิ้งไว้เป็นช่วง ๆ เหมือน side project หลายตัวของฉัน แต่ก็ดีใจที่ทำจนเสร็จ :-)
    • ฉันไม่ใช่ผู้เชี่ยวชาญ Rust และก็ไม่ใช่ผู้เขียนต้นฉบับด้วย แต่ขอยกตัวอย่างจากเทคโนโลยีอีกอย่างคือ socket
      ไม่นานมานี้ฉันเพิ่งลงลึกกับ socket ซึ่งเมื่อ 2 สัปดาห์ก่อนยังมีแค่ความเข้าใจระดับสูงที่ได้จากการอ่าน man page, เอกสาร, บล็อกโพสต์ ฯลฯ แบบคร่าว ๆ
      ฉันอยากเข้าใจพื้นฐานของระบบเครือข่าย เลยตัดสินใจอ่านให้ได้มากที่สุด และหลังจากนั้นหนึ่งสัปดาห์ก็เรียนรู้จนเขียนโค้ด socket ด้วย Python และ C ได้
      ฉันรู้จัก Python ค่อนข้างดีอยู่แล้ว ดังนั้นพอได้ลงลึกแล้วกลับมาดูไลบรารี sockets ก็เข้าใจได้ดีขึ้น
      ถ้าอยากเก่งเทคโนโลยี A ในภาษา X มากขึ้น ฉันแนะนำให้อ่านหรือดูเกี่ยวกับเทคโนโลยี A ให้มากที่สุด แล้วลองสร้างอะไรสักอย่างด้วยภาษา Y
      จากนั้นพอกลับมาที่ภาษา X คุณก็จะคุ้นกับแนวคิดรอบ ๆ เทคโนโลยี A ไปมากพอสมควรแล้ว
    • แค่แยกมันออกเป็นส่วนเล็ก ๆ ก็พอ
      VM ของภาษาง่าย ๆ ตัวหนึ่งก็น่าจะมีการแทนอ็อบเจ็กต์ในหน่วยความจำ, bytecode interpreter, ตัวเก็บขยะอย่างง่าย, และตัวโหลด
      bytecode interpreter มองได้ว่าเป็นสแตก วิธีแทนฟังก์ชันบนสแตกนั้น และลูปที่ตีความแต่ละ bytecode พร้อมขยับ program counter
    • สิ่งสำคัญคือคุณเขียนโค้ดในเวลาว่างมากแค่ไหน โดยเฉลี่ยแล้วสัปดาห์ละกี่ชั่วโมง
      ถ้าเป็น 0 ชั่วโมง แน่นอนว่าไม่ใช่เรื่องให้ตำหนิ เพราะก็มีเรื่องอื่นให้โฟกัสได้มากมาย แต่ถ้าใครทำ side project เฉลี่ยสัปดาห์ละ 10~20 ชั่วโมงต่อเนื่องเป็นปี ๆ จะมีผลงานน่าประทับใจออกมาก็ไม่ใช่เรื่องน่าแปลกใจ
  • ขอโปรโมตโปรเจ็กต์คล้าย ๆ กันแบบหน้าด้าน ๆ หน่อย: https://github.com/tenaf0/rust-jvm3

  • ฉันกำลังทำระบบปฏิบัติการฟรีสำหรับเครื่องโคลน 386(486) AT อยู่ เป็นแค่งานอดิเรก ไม่ได้ใหญ่และเป็นมืออาชีพแบบ gnu :-)

    • มี no-std tutorial สำหรับเขียน demo kernel ด้วย Rust อยู่ที่: https://os.phil-opp.com
      osdev.org, sandpile.org, RBIL, freevga ก็เป็นแหล่งอ้างอิงที่น่าดูเช่นกัน
      ปัญหาปวดหัวที่สุดคือการรองรับฮาร์ดแวร์
      ยังมีหนังสือกระดาษเก่า ๆ ดี ๆ อีกมากที่รวมสูตรอย่างพวกพอร์ต I/O ที่เชื่อถือได้หรือฮาร์ดแวร์ทริกที่ไม่มีเอกสารกำกับ
      Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4
      Microsoft MS-DOS Programmer's Reference ซึ่งรวมถึงการเรียก real-mode BIOS ด้วย
      PC Interrupts
      Undocumented PC
      PC Intern
      Programmer's Guide To The EGA, VGA, And Super VGA Cards
      Graphics Programming Black Book Special Edition
      และยังคุ้มค่าที่จะลองสำรวจความก้าวหน้าในการพัฒนาระบบปฏิบัติการหลังยุค single kernel, microkernel, และ hybrid ด้วย
      สถาปัตยกรรมแบบ capability-based อย่าง seL4 มีข้อดีโดยเนื้อแท้หลายอย่างทั้งด้านประสิทธิภาพและความปลอดภัย รวมถึง capability และ IPC ที่ยอดเยี่ยม
      ชั้นความเข้ากันได้กับ POSIX ก็สำคัญเช่นกัน ระบบปฏิบัติการแบบ embedded ที่ไม่มีแนวคิดเรื่อง thread หรือ process ก็ยังสามารถทำ POSIX ได้
      hypervisor สามารถเพิ่มได้ง่ายกว่ามากถ้ามี Intel VT-[xd] และถ้าไม่มีก็ถอยไปใช้การจำลองแทนได้ การจำลองแบบแปลงคำสั่งให้ประสิทธิภาพดีมาก
      คุณต้องชำนาญในการทำ interrupt handler ให้เป็นแบบทั่วไปและรวดเร็ว หลีกเลี่ยง race condition และใช้แพตเทิร์นแบบ lock-free
      รวมถึงต้องเขียนใหม่หรือดัก trap สำหรับคำสั่งที่ยังไม่รองรับ เช่น x87 และ MMX
      เหตุผลที่ microkernel แบบบริสุทธิ์ล้มเหลวก็เพราะความซับซ้อนในการจัดลำดับและจัดการทรัพยากรหลายชนิดแบบ transactional เพิ่มสูงขึ้น
      สถาปัตยกรรม microkernel มีข้อดีด้านความปลอดภัยและการปฏิบัติการอยู่มากในเชิงทฤษฎี แต่ในรูปแบบที่บริสุทธิ์ก็ไม่ได้แพร่หลายอย่างกว้างขวาง