3 คะแนน โดย GN⁺ 2024-12-28 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • เพื่อทำความเข้าใจการทำงานภายในของคอมพิวเตอร์และวิธีที่ภาษาโปรแกรมถูกรัน ผู้เขียนได้ลงมือสร้าง VM ที่เขียนด้วย C ราว 250 บรรทัด เพื่อรันโปรแกรมแอสเซมบลีบนสถาปัตยกรรมเพื่อการศึกษา LC-3 ด้วยตัวเอง
  • สิ่งที่นำมาสร้างคือ โมเดลคอมพิวเตอร์ขนาดเล็ก ที่มีตำแหน่งหน่วยความจำ 16 บิตจำนวน 65,536 ตำแหน่ง, รีจิสเตอร์ 10 ตัว, opcode 16 แบบ, condition flag, trap routine และ memory-mapped register
  • ลูปการทำงานจะอ่านคำสั่งที่ PC ชี้อยู่แล้วเพิ่มค่า จากนั้นถอดรหัส opcode เพื่อรันคำสั่งอย่าง ADD, LDI, BR, JMP, TRAP ตามโครงสร้างแบบ fetch-decode-execute
  • การโหลดโปรแกรมจะอ่านค่า origin 16 บิตตัวแรกของ object file แล้ววางลงในหน่วยความจำ พร้อมสลับไบต์ให้เข้ากับรูปแบบ little-endian ที่คอมพิวเตอร์สมัยใหม่ส่วนใหญ่ใช้ จากรูปแบบ big-endian ของ LC-3
  • การรับคีย์บอร์ดและแสดงผลคอนโซลจัดการผ่าน trap routine และ memory-mapped register ของ KBSR/KBDR โดยต้องมีโค้ดจัดการ input buffering ของเทอร์มินัลที่ต่างกันระหว่าง Unix/macOS และ Windows

เป้าหมายและสมมติฐานของบทสอน

  • พาเขียน LC-3 virtual machine ด้วยตัวเองและตามดูขั้นตอนการรันโปรแกรมภาษาแอสเซมบลี
  • โค้ดสุดท้ายมีขนาดประมาณ 250 บรรทัด ใน C และมีทั้ง lc3.c สำหรับ Unix กับ lc3-win.c สำหรับ Windows
  • พื้นฐานที่ต้องมีคือการอ่าน C หรือ C++ เบื้องต้น และ เลขคณิตฐานสอง
  • โค้ดทั้งหมดอยู่ใน GitHub repo และตัวบทสอนเองอยู่ในรูปแบบ literate program ที่นำบล็อกโค้ดมาประกอบเป็นซอร์สสุดท้าย

เครื่องเสมือนทำหน้าที่อะไร

  • VM คือโปรแกรมที่ทำงานคล้าย CPU และฮาร์ดแวร์บางส่วน
    • ทำงานคำนวณทางคณิตศาสตร์
    • อ่านและเขียนหน่วยความจำ
    • โต้ตอบกับอุปกรณ์ I/O
    • เข้าใจ ภาษาเครื่อง ของตัวเองและใช้มันรันโปรแกรม
  • ขึ้นอยู่กับเป้าหมายของ VM มันอาจจำลองฮาร์ดแวร์จริงอย่างใกล้เคียง หรือสร้างสถาปัตยกรรมเสมือนใหม่เพื่อให้พัฒนาซอฟต์แวร์ได้สะดวกขึ้นก็ได้
  • JVM เป็นตัวอย่างเด่นของ VM ที่ทำหน้าที่เป็นแพลตฟอร์มรันมาตรฐาน และบนอุปกรณ์ที่มี JVM ก็สามารถรันโปรแกรม Java, Kotlin, Clojure ได้โดยไม่ต้องแก้ไข
  • การรันแบบแยกสภาพแวดล้อมก็เป็นการใช้งานสำคัญของ VM เช่นกัน
    • ใน garbage collection นั้น VM สามารถสังเกต stack และการอ้างอิงหน่วยความจำจากภายนอกโปรแกรมที่กำลังรันอยู่ได้
    • Ethereum smart contract จะรันอยู่ใน VM ที่ไม่สามารถเข้าถึง file system, network, disk ฯลฯ ได้

องค์ประกอบของสถาปัตยกรรม LC-3

  • สิ่งที่จะนำมาสร้างคือ LC-3 ซึ่งใช้สอนโครงสร้างคอมพิวเตอร์และภาษาแอสเซมบลีในมหาวิทยาลัย
  • หน่วยความจำของ LC-3 มี 65,536 ตำแหน่ง และแต่ละตำแหน่งเก็บค่า 16 บิต
    • ความจุรวมคือ 128KB
    • ใน C จะแทนด้วยอาร์เรย์ uint16_t memory[MEMORY_MAX]
  • รีจิสเตอร์มีทั้งหมด 10 ตัว
    • R0~R7: รีจิสเตอร์ใช้งานทั่วไป 8 ตัว
    • PC: ที่อยู่หน่วยความจำของคำสั่งถัดไปที่จะรัน
    • COND: condition flag ของผลการคำนวณครั้งล่าสุด
  • คำสั่งของ LC-3 ทุกคำสั่งมีขนาด 16 บิต และ 4 บิตซ้ายสุดคือ opcode
    • มีการกำหนด opcode ไว้ 16 แบบ
    • รวมถึง OP_BR, OP_ADD, OP_LD, OP_ST, OP_JSR, OP_AND, OP_LDR, OP_STR, OP_RTI, OP_NOT, OP_LDI, OP_STI, OP_JMP, OP_RES, OP_LEA, OP_TRAP
  • condition flag ใช้บอกเครื่องหมายของผลลัพธ์จากการคำนวณล่าสุด
    • FL_POS: ค่าบวก
    • FL_ZRO: 0
    • FL_NEG: ค่าลบ

แอสเซมบลีกับภาษาเครื่อง

  • สิ่งที่ LC-3 VM รันจริง ๆ ไม่ใช่แอสเซมบลีที่มนุษย์อ่านได้ แต่เป็น อาร์เรย์คำสั่งภาษาเครื่อง 16 บิต
  • assembler จะเปลี่ยนแอสเซมบลี LC-3 ที่เขียนเป็นข้อความให้กลายเป็นคำสั่งไบนารี 16 บิต
  • ตัวอย่าง Hello World มีลำดับการทำงานดังนี้
    • .ORIG x3000: ระบุที่อยู่หน่วยความจำที่จะโหลดโปรแกรม
    • LEA R0, HELLO_STR: โหลดที่อยู่ของสตริงเข้า R0
    • PUTS: แสดงสตริงที่ R0 ชี้อยู่
    • HALT: หยุดโปรแกรม
    • .STRINGZ "Hello World!": เก็บข้อมูลสตริงไว้ในโปรแกรม
  • .ORIG, .STRINGZ ไม่ใช่คำสั่งของ CPU แต่เป็น assembler directive
  • เงื่อนไขและการวนซ้ำถูกเขียนด้วยคำสั่งกระโดดที่ใกล้เคียงกับ goto เช่น BRn LOOP

ขั้นตอนหลักของลูปการรัน

  • การทำงานของ VM จะวนซ้ำขั้นตอนเดิม
    • อ่านคำสั่งจากตำแหน่งที่รีจิสเตอร์ PC ชี้อยู่
    • เพิ่มค่า PC
    • ดึง opcode จาก 4 บิตบนของคำสั่ง
    • รันโค้ดที่ตรงกับ opcode
    • แล้วกลับไปอ่านคำสั่งถัดไป
  • ที่อยู่เริ่มต้นพื้นฐานคือ 0x3000
  • บางคำสั่งสามารถเปลี่ยนค่า PC โดยตรงเพื่อกระโดดเส้นทางการรันได้
    • เพราะมีคำสั่ง branch และ jump จึงทำให้โครงสร้างที่ดูเหมือนแค่เพิ่ม PC ไปเรื่อย ๆ ยังรองรับลูปและการรันแบบมีเงื่อนไขได้
  • ลูป main ใช้ switch (op) เพื่อเรียกโค้ดตาม opcode
    • รองรับ OP_ADD, OP_AND, OP_NOT, OP_BR, OP_JMP, OP_JSR, OP_LD, OP_LDI, OP_LDR, OP_LEA, OP_ST, OP_STI, OP_STR, OP_TRAP
    • OP_RES, OP_RTI เป็น opcode ที่ไม่ได้ใช้และอาจจัดการด้วย abort()

วิธี implement คำสั่ง

  • ADD จะนำสองค่ามาบวกกัน เก็บไว้ในรีจิสเตอร์ปลายทาง และอัปเดต condition flag
  • ADD มีสองโหมด
    • โหมดรีจิสเตอร์: อ่าน operand ตัวที่สองจากรีจิสเตอร์อีกตัว
    • โหมด immediate: อ่าน operand ตัวที่สองจาก imm5 ซึ่งอยู่ใน 5 บิตล่างของคำสั่ง
  • ค่าที่สั้นกว่า 16 บิตอย่าง imm5 ต้องถูกขยายเป็นค่า 16 บิตด้วย sign extension
    • ค่าบวกจะเติม 0
    • ค่าลบจะเติม 1 เพื่อรักษาค่าเดิมไว้
  • คำสั่งที่เขียนค่าลงรีจิสเตอร์จะอัปเดต R_COND ผ่าน update_flags
    • ถ้าค่าเป็น 0 ให้ FL_ZRO
    • ถ้าบิตสูงสุดเป็น 1 ให้ FL_NEG
    • นอกนั้นให้ FL_POS
  • LDI คือคำสั่ง “load indirect”
    • ทำ sign extension ให้ PCoffset9 ของคำสั่ง
    • นำไปบวกกับ PC ปัจจุบันเพื่อได้ที่อยู่หน่วยความจำ
    • ใช้ค่าที่เก็บอยู่ในตำแหน่งนั้นเป็นที่อยู่อีกชั้น แล้วอ่านข้อมูลปลายทาง
    • นำค่าที่อ่านได้ไปเก็บในรีจิสเตอร์ปลายทางและอัปเดต condition flag

ชุดคำสั่งหลัก

  • การคำนวณและบิตไวส์
    • ADD: บวก
    • AND: บิต AND
    • NOT: บิต NOT
  • การควบคุมลำดับการทำงาน
    • BR: เทียบ condition flag กับบิตเงื่อนไขของคำสั่งแล้วเปลี่ยน PC
    • JMP: ตั้งค่า PC เป็นค่าของรีจิสเตอร์ที่กำหนด
    • RET: ในสเปกระบุเป็นคีย์เวิร์ดแยก แต่จริง ๆ เป็นกรณีพิเศษของ JMP
    • JSR, JSRR: บันทึก PC ปัจจุบันไว้ใน R7 แล้วกระโดดไปยังตำแหน่งของ subroutine
  • การอ่านหน่วยความจำ
    • LD: อ่านจากตำแหน่ง offset อ้างอิงจาก PC
    • LDI: อ่านผ่านที่อยู่ทางอ้อมอีกหนึ่งชั้น
    • LDR: อ่านจากที่อยู่ที่คำนวณจาก base register และ offset
    • LEA: เก็บ effective address เองลงในรีจิสเตอร์
  • การเขียนหน่วยความจำ
    • ST: เก็บลงตำแหน่ง offset อ้างอิงจาก PC
    • STI: เก็บโดยตามที่อยู่ทางอ้อม
    • STR: เก็บลงตำแหน่งที่คำนวณจาก base register และ offset

Trap routine และ I/O

  • LC-3 มี trap routine สำหรับงานทั่วไปและการเข้าถึงอุปกรณ์ I/O
  • trap routine สามารถมองได้เหมือนระบบปฏิบัติการหรือ API ของ LC-3
  • trap code ถูกกำหนดไว้ดังนี้
    • TRAP_GETC = 0x20: รับอักขระจากคีย์บอร์ดโดยไม่ echo บนเทอร์มินัล
    • TRAP_OUT = 0x21: แสดงอักขระ
    • TRAP_PUTS = 0x22: แสดง word string
    • TRAP_IN = 0x23: รับอักขระแล้ว echo บนเทอร์มินัล
    • TRAP_PUTSP = 0x24: แสดง byte string
    • TRAP_HALT = 0x25: หยุดโปรแกรม
  • ใน LC-3 simulator อย่างเป็นทางการ trap routine ถูกเขียนด้วยแอสเซมบลี แต่ใน VM นี้จะ implement เป็นฟังก์ชัน C
  • PUTS จะเริ่มแสดงผลจากที่อยู่ที่เก็บใน R0 ไปเรื่อย ๆ จนกว่าจะเจอ x0000
    • สตริงของ LC-3 ไม่ได้เก็บตัวอักษรแบบ 1 ไบต์ต่อเนื่องเหมือน C string แต่เก็บ หนึ่งอักขระต่อหนึ่งตำแหน่งหน่วยความจำ
    • เนื่องจากแต่ละตำแหน่งเป็น 16 บิต เวลาพิมพ์ใน C จึงต้องแปลงเป็น char
  • trap HALT จะพิมพ์ "HALT" แล้วเปลี่ยน execution flag เป็น 0 เพื่อออกจากลูป VM

การโหลด program image

  • เมื่อแปลงโปรแกรมแอสเซมบลี LC-3 เป็นภาษาเครื่องแล้ว จะได้ไฟล์ที่มีอาร์เรย์ของคำสั่งและข้อมูล
  • 16 บิต แรกของ object file คือ origin ที่บอกว่าควรวางโปรแกรมไว้ตรงไหนในหน่วยความจำ
  • ตัวโหลดจะอ่าน origin ก่อน แล้วคัดลอกข้อมูลที่เหลือไปยังหน่วยความจำตั้งแต่ที่อยู่ origin
  • โปรแกรม LC-3 ใช้รูปแบบ big-endian
    • คอมพิวเตอร์สมัยใหม่ส่วนใหญ่ใช้ little-endian จึงต้องใช้ swap16 กับ uint16_t แต่ละตัวที่โหลดมา
    • บนเครื่อง big-endian อย่าง PPC Mac รุ่นเก่าไม่ควรสลับไบต์
  • read_image จะเปิดไฟล์ในโหมดไบนารี เรียก read_image_file แล้วปิดไฟล์

Memory-mapped register

  • รีจิสเตอร์พิเศษที่ไม่ได้เข้าถึงผ่านตารางรีจิสเตอร์ทั่วไป จะถูกแมปไว้กับที่อยู่หน่วยความจำเฉพาะ
  • ใน LC-3 มี memory-mapped register ที่ต้อง implement อยู่สองตัว
    • MR_KBSR = 0xFE00: keyboard status register
    • MR_KBDR = 0xFE02: keyboard data register
  • KBSR บอกว่ามีการกดปุ่มหรือไม่ และ KBDR เก็บว่าปุ่มใดถูกกด
  • GETC จะบล็อกการทำงานจนกว่าจะมีอินพุตเข้ามา แต่ KBSR และ KBDR เปิดทางให้โปรแกรม poll สถานะอุปกรณ์เพื่อยังตอบสนองต่อไปได้ระหว่างรออินพุต
  • การอ่านหน่วยความจำจะไม่อ่านจากอาร์เรย์โดยตรง แต่ผ่าน mem_read
    • ถ้าที่อยู่คือ MR_KBSR จะเรียก check_key() เพื่อตรวจสถานะคีย์บอร์ด
    • ถ้ามีปุ่มถูกกด จะตั้งบิตสูงสุดของ KBSR และเก็บค่า getchar() ลงใน KBDR
    • ถ้าไม่มีปุ่มถูกกด จะตั้ง KBSR เป็น 0

การจัดการเทอร์มินัลตามแพลตฟอร์ม

  • เพื่อให้จัดการอินพุตคีย์บอร์ดและพฤติกรรมของเทอร์มินัลได้ถูกต้อง จำเป็นต้องตั้งค่า input buffering แยกตามแพลตฟอร์ม
  • ฝั่ง Linux/macOS/UNIX ใช้ termios, select เป็นต้น
    • ปิด canonical mode และ echo
    • ใช้ select ตรวจว่าพร้อมรับอินพุตหรือไม่
  • ฝั่ง Windows ใช้ GetStdHandle, GetConsoleMode, SetConsoleMode, _kbhit เป็นต้น
    • ปรับ echo และ line input
    • ใช้ WaitForSingleObject กับ _kbhit เพื่อตรวจการกดปุ่ม
  • ตอนเริ่มโปรแกรมจะเรียก disable_input_buffering() และตอนจบจะเรียก restore_input_buffering()
  • เมื่อได้รับ SIGINT จะกู้คืนค่าเทอร์มินัล พิมพ์ขึ้นบรรทัดใหม่ แล้วจบการทำงาน

การรัน VM และการดีบัก

  • ตัวอย่างการ build VM มีดังนี้
gcc lc3.c -o lc3-vm
  • เมื่อต้องการรัน ให้ส่ง object file ของ LC-3 ที่ประกอบแล้วเป็นอาร์กิวเมนต์
lc3-vm path/to/2048.obj
  • object file ตัวอย่างที่ให้มาคือ 2048.obj และ rogue.obj
  • ตัวอย่าง 2048 ควบคุมด้วยปุ่ม WASD
  • ถ้าโปรแกรมทำงานไม่ถูกต้อง มีโอกาสสูงว่าเกิดจากข้อผิดพลาดในการ implement คำสั่ง
    • แนะนำให้อ่านซอร์สแอสเซมบลี LC-3 ไปพร้อมกับใช้ดีบักเกอร์รันคำสั่งของ VM ทีละขั้น
    • หากพบจุดที่มันไม่กระโดดไปยังคำสั่งที่คาดไว้ ให้ย้อนกลับไปตรวจสเปกและ implementation ของคำสั่งนั้นอีกครั้ง

ตัวเลือกเสริม: implementation แบบ C++ generic

  • ยังมีวิธี implement แบบ C++ ที่สั้นกว่าซึ่งกล่าวถึงเป็นตัวเลือกเสริม
  • เพราะหลายคำสั่งใช้ขั้นตอนซ้ำกัน เช่น sign extension, offset อิง PC, การคำนวณที่อยู่ทางอ้อม จึงสามารถมองการรันคำสั่งเป็น pipeline ของขั้นตอนย่อยได้
  • ใช้ C++ template และ bit flag เพื่อให้แต่ละ opcode รวมเฉพาะขั้นตอนที่จำเป็นตอน compile
  • วิธีนี้ช่วยลดโค้ดซ้ำ และยังใกล้เคียงกับรูปแบบการเดินสายของฮาร์ดแวร์จริงที่แต่ละขั้นตอนกินพื้นที่ทางกายภาพบนชิป
  • แนวคิดนี้อ้างอิงจาก Bisqwit’s NES emulator

เอกสารและการมีส่วนร่วม

  • atul-g ได้ช่วยทำ reference card ที่สรุปการทำงานของทั้งระบบ
  • implementation หลายภาษาถูกรวบรวมไว้ภายใต้ GitHub topic lc3
    • รวมถึง C, C++, Go, Haskell, Java, JavaScript, Kotlin, Lua, OCaml, Python, Ruby, Rust, Swift, TypeScript, Zig เป็นต้น
  • หากต้องการให้ implementation ของตัวเองแสดงในรายการ ก็เพียงติด GitHub topic lc3
  • การรองรับแพลตฟอร์ม Windows ได้รับการช่วยพัฒนาโดย inkydragon
  • ในโปรเจกต์มี good first issue ที่เกี่ยวกับ integration test

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

 
GN⁺ 2024-12-28
ความคิดเห็นจาก Hacker News
  • ตอนเป็นวัยรุ่น ในคาบวิชาเบื้องต้นวิทยาการคอมพิวเตอร์ที่ community college ผมได้ออกแบบ ชุดคำสั่ง CPU ง่าย ๆ แล้วสร้าง virtual machine กับ assembler เอง จากนั้นก็เขียนและรันโปรแกรม assembly ดู
    มันง่ายอย่างน่าประหลาด และทำให้คอมพิวเตอร์ดูลึกลับน้อยลงมาก
    ผมคิดว่าวิธีแบบนี้น่าจะใช้เรียนรู้ทุกชั้นของการคอมพิวติ้งได้ ตั้งแต่การออกแบบ CPU จริงสำหรับ FPGA ไปจนถึงการเขียนระบบปฏิบัติการง่าย ๆ และโปรแกรมที่รันอยู่บนนั้น
    ถ้าไม่นับประสิทธิภาพและความปลอดภัยที่คอมพิวติ้งสมัยใหม่ต้องการ แล้วตั้งเป้าแค่ว่า “ขอให้ทำงานได้” สาขานี้ก็เรียบง่ายกว่าที่คิด

    • ฟังดูเป็นคาบเรียนที่น่าสนุก และดูคล้ายกับ https://www.nand2tetris.org/ หรือหนังสือ Code ของ Charles Petzold มาก
    • ทันทีที่ขยับจาก CPU ในจินตนาการแบบยุคแรก ๆ ไปเป็น CPU รุ่นแรก ๆ ที่ผลิตจริงอย่าง 80286 ความซับซ้อนก็พุ่งขึ้นอย่างรวดเร็ว
      ถ้าจำไม่ผิด อย่างน้อยก็มี memory segmentation, protected mode และ MMU เข้ามาเกี่ยวข้อง
    • ในคาบ CS 101 ก็มีระบบแบบนั้นเหมือนกัน
      เป็นคอมพิวเตอร์/assembler ง่าย ๆ ที่เขียนด้วย BASIC บน PDP และหนึ่งในงานที่ได้รับคือให้ทำการคูณอย่างง่ายโดยใช้ลูปบวกซ้ำ
      เพื่อนผมกลับแก้โปรแกรมแทน แล้วสร้าง คำสั่ง MUL ใหม่ขึ้นมา ซึ่งอาจารย์ไม่ชอบเลยแม้แต่น้อย
    • องค์ประกอบพื้นฐานแบบเรียบง่ายนั้นง่ายจริง ๆ แต่ยังห่างจาก ผลลัพธ์ระดับเชิงพาณิชย์ ที่ผู้ใช้จริงมองเห็นและจับต้องได้บนคอมพิวเตอร์อีกเป็นร้อย ๆ ชั้น
      คนที่มีความอยากรู้อยากเห็นและอยากเรียนรู้สามารถเรียนชั้นพื้นฐานเหล่านี้ได้ไม่ยาก แต่ไม่ใช่สำหรับคนที่ต้องการ “รีบหาเงินและพร้อมเข้าทำงานให้เร็วที่สุด”
    • หลักสูตร nand2tetris ดูเหมือนจะทำสิ่งนี้พอดี
  • หนังสือที่มีคนแนะนำ:

    1. Virtual Machines: Versatile Platforms for Systems and Processes ของ Smith และ Nair — ดูเหมือนเป็นหนังสือที่ไล่ภาพรวมหัวข้อนี้อย่างครอบคลุม
    2. Virtual Machines ของ Iain Craig — ดูเหมือนเป็นหนังสือเชิงปฏิบัติมากขึ้นที่ว่าด้วยภาษาและ virtual machine
    3. Virtual Machine Design and Implementation in C/C++ ของ Bill Blunden — ดูเหมือนเป็นคู่มือเชิงปฏิบัติที่เน้นการนำไป implement
      ถ้าใครเคยอ่านหนังสือเหล่านี้แล้วช่วยเพิ่มความคิดเห็น ก็น่าจะเป็นประโยชน์กับทุกคน
    • ผมไม่แน่ใจว่าหัวข้อนี้แคบพอจะสรุปภาพรวมได้ในหนังสือเล่มเดียวหรือไม่
      emulator ของ Nintendo, hypervisor ที่ใช้ VT-x, ระบบปฏิบัติการ multitasking แบบดั้งเดิม, interpreter ของภาษา scripting ใหม่, SQL query optimizer, regex matcher, security monitor ที่รันโค้ดผู้เล่นที่ไม่น่าเชื่อถือบน game server ฯลฯ ดูแทบไม่มีข้อพิจารณาร่วมกันเลย แต่ทั้งหมดล้วนเป็น virtual machine
      แม้แต่ในรูปแบบ terminfo ที่กำหนด escape sequence ของ terminal แบบ character cell ก็ยังมี virtual machine แบบ stack-based อยู่
      ถ้ามองลึกลงไป สิ่งที่ทำให้คอมพิวเตอร์เป็นคอมพิวเตอร์ในความหมายปัจจุบันก็คือ virtual machine และบทความ Entscheidungsproblem ปี 1936 ของ Turing ก็ผูกอยู่กับแนวคิดที่ว่า virtual machine สามารถเลียนแบบกันและกันได้
  • หลังจากดูซีรีส์ breadboard CPU ของ Ben Eater แล้ว ผมก็มีแต่ความอยากจะลอง ออกแบบและ emulate CPU เอง
    หวังว่าจะหาเวลานั่งออกแบบมันได้

  • ผมมองว่า architecture เพื่อการศึกษาอย่าง Brookshear Machine หรือ Little Computer ไม่ได้เหมือน architecture จริงเลย จึงไม่ใช่แค่ไร้ประโยชน์ แต่ถึงขั้นเป็นโทษ
    ผมเคยเห็นนักเรียนที่เรียนคาบซึ่งใช้สิ่งเหล่านี้ กลับเข้าใจคอมพิวเตอร์บิดเบี้ยวมากกว่าคนที่ไม่เคยเรียนคาบใดเลย
    สำหรับคนส่วนใหญ่ที่อยากเรียนรู้นิดหน่อยว่าคอมพิวเตอร์ของตัวเองทำงานอย่างไร วิชาระบบปฏิบัติการ จะเหมาะกว่า และถ้าในนั้นมีเวลาทำได้แค่ tutorial สั้น ๆ หนึ่งอัน ผมแนะนำ “Writing my own bootloader”
    https://dev.to/frosnerd/writing-my-own-boot-loader-3mld
    คำพูดนี้ไม่ได้หมายความว่า tutorial “Write your own VM” แย่ แต่จากประสบการณ์ของผม สำหรับคนส่วนใหญ่ที่จะทำมัน หัวข้ออื่นจะเป็นประโยชน์มากกว่า

    • ผมเพิ่งลอง LC-3 และในโปรเจกต์ปัจจุบันตั้งใจจะใช้ LC-3 เป็น target machine ที่ไม่เหมาะนัก เพื่อเรียน dynamic recompilation นิดหน่อย
      ช่วยอธิบายเพิ่มเติมได้ไหมว่าทำไม LC-3 ถึงไม่ดีสำหรับการศึกษาสถาปัตยกรรมคอมพิวเตอร์
      ผมเข้าใจว่ามันต่างจากฮาร์ดแวร์จริงโดยสิ้นเชิงและเรียบง่ายเกินไป แต่ก็สงสัยว่าแม้มองจากมุมการเขียน CPU emulator มันก็แย่ด้วยหรือไม่
    • นึกถึง MIX เก่า ๆ ของ Knuth
      มันเป็นเครื่องเลขฐานสิบที่น่าจะถูกสร้างขึ้นได้ในยุค 1960 แต่หลังทศวรรษ 1970 ก็ไม่มีใครสร้างเครื่องแบบนั้นแล้ว
      ระบบแบบนั้นสอนพื้นฐานได้หลายอย่าง แต่เทคนิคใน https://en.wikipedia.org/wiki/Hacker%27s_Delight ส่วนใหญ่พึ่งพาวิธีแทนค่าตัวเลขที่พบได้ทั่วไป จึงเรียนรู้ได้ยาก
    • อยากรู้ว่า LC-3 มีจุดไหนที่ไม่ถูกใจเป็นพิเศษ
      ผมไม่ค่อยรู้ เลยลองดู Wikipedia แวบหนึ่ง หลังจากเห็นการ์ตูนแล้วคาดว่าจะเจออะไรแปลก ๆ แต่ดูผ่าน ๆ ก็ไม่ได้ชวนช็อกขนาดนั้น
      ให้ความรู้สึกเหมือนผสม s/360, x86 เล็กน้อย, ARM นิดเดียว หรือ architecture ตระกูล RISC อื่น ๆ เข้าด้วยกัน และแม้จะมีส่วนที่ถูกละไว้กับส่วนแปลก ๆ อยู่มาก แต่เป้าหมายดูเหมือนเป็นการไปให้ถึง implementation ที่ทำงานได้เร็ว
      อยากรู้ว่าอะไรทำให้มองว่ามัน “ไม่ใช่แค่ไร้ประโยชน์ แต่ถึงขั้นเป็นโทษ” สำหรับการศึกษา
    • แนะนำให้ใช้ architecture 8 บิต รุ่นเก่าอย่าง 6502 หรือ Z80
      ในวิชาวิทยาการคอมพิวเตอร์จำนวนมากในอินเดีย ดูเหมือนยังใช้ 8086/8088 กันอยู่
    • LC-3 มีรูปแบบการ addressing ที่ค่อนข้างแปลก
      โดยเฉพาะสามารถทำ double-indirect load ผ่าน word แบบ PC-relative ที่อยู่ตรงกลางได้
      แต่ถึงอย่างนั้น การลบก็ต้องสร้างจากการ negate และการ negate ก็ต้องสร้างจาก NOT กับ ADD ,,#-1
      ถ้าคิดถึงพื้นที่ encoding ของคำสั่งที่มีจำกัด ผมว่า NOT d,s = XOR d,s,#-1 น่าจะเป็นการใช้งานที่ดีกว่า
  • ถ้าจะให้แยกอย่างเคร่งครัด สิ่งนี้ไม่ใช่เครื่องเสมือน แต่เป็น อีมูเลเตอร์
    ในเชิงความหมายเชิงอธิบาย คำนี้อาจใช้ได้ และก่อนยุคการทำ hardware virtualization ก็มีความกำกวมอยู่บ้าง แต่ในปัจจุบัน การใช้คำว่า “Virtual Machine” ที่พบกันอย่างท่วมท้นมักหมายถึงสภาพแวดล้อมที่ใช้ฟีเจอร์ hardware virtualization อย่าง VT-x

    • ไม่เห็นด้วยว่าคำนี้ “พบกันอย่างท่วมท้น” และก็ไม่คิดว่าเป็นการแบ่งแยกที่ถูกต้องสมบูรณ์นัก
      JVM ถูกเผยแพร่อย่างกว้างขวาง, Ethereum VM ก็เรียกว่า EVM, https://www.linuxfoundation.org/hubfs/LF%20Research/The_Stat... ก็อธิบาย BPF และ eBPF ซ้ำ ๆ ว่าเป็น “virtual machines” และ https://webassembly.org/ ก็เริ่มต้นว่า “WebAssembly (ย่อว่า Wasm) เป็นรูปแบบคำสั่งไบนารีสำหรับเครื่องเสมือนแบบสแตก”
      “เครื่องเสมือน” ยังคงเป็นคำที่ใช้เรียกเครื่องจักรเสมือนที่พบได้บ่อยที่สุด
      โดยส่วนตัวชอบสำนวนอย่าง “fictive machine”, “fictious machine”, “imaginary computer”, “fantastic automaton” มากกว่า แต่คงไม่น่าจะถูกนำมาใช้กัน
      ไม่สามารถใช้คำว่า “อีมูเลเตอร์” แทน “เครื่องเสมือน” ได้เสมอไป
      wasmtime อาจเรียกว่าเป็นอีมูเลเตอร์ได้ แต่การเรียก WebAssembly เองว่าอีมูเลเตอร์นั้นไม่ถูกต้อง และ WebAssembly คือเครื่องเสมือนที่ wasmtime อีมูเลต
      การเรียกอีมูเลเตอร์ว่าเครื่องเสมือนก็พบได้บ่อย และอินสแตนซ์ของอีมูเลเตอร์ที่กำลังทำงานอยู่ก็เป็นเครื่องเสมือนในอีกความหมายหนึ่งเช่นกัน
      การเรียกสภาพแวดล้อม hardware virtualization ว่า “เครื่องเสมือน” ก็สมเหตุสมผล และทับซ้อนกับความหมายสุดท้ายนี้อยู่บ้าง
      ในสภาพแวดล้อมปัจจุบัน การใช้คำนั้นอาจพบได้อย่างท่วมท้นก็จริง แต่ไม่ได้แปลว่าที่อื่นจะต้องเป็นเช่นนั้นเสมอไป
    • ขอแสดงความไม่เห็นด้วยอย่างสุภาพ
      ในความหมายที่บริสุทธิ์ที่สุด เครื่องเสมือน ก็เป็นเพียงคอมพิวเตอร์ที่สร้างขึ้นมาเท่านั้น ไม่ได้สื่อโดยนัยว่าจะใช้ทำอะไรหรือทำงานอย่างไร
      ในบทความก็ยกตัวอย่างการอีมูเลตคอนโซลคลาสสิก แต่จากนิยามที่ให้มา เห็นได้ชัดว่ายังมีเครื่องเสมือนที่เป็นไปได้อีกมากมาย
      ประเด็นสำคัญคือเครื่องเสมือนเป็นแนวคิดเชิงนามธรรม และมีหลายประเภทมาก
      ซิมูเลเตอร์ อีมูเลเตอร์ ไฮเปอร์ไวเซอร์ ฯลฯ ล้วนเป็นเครื่องเสมือน และยังมีเครื่องเสมือนรูปแบบแปลก ๆ ที่ยังไม่มีชื่อเรียกด้วย
      ไม่ได้ตั้งใจจะพูดจาเสียมารยาท ตรงกันข้ามคือพยายามให้เกียรติ และอยากทำให้คำนี้ชัดเจนสำหรับคนที่กำลังเรียนรู้
    • คิดว่าการแบ่งแยกที่กำลังปกป้องอยู่นั้นไม่มีอยู่จริง
      “เครื่องเสมือน” เป็นคำที่ใช้กันทั่วไปกับซอฟต์แวร์ใด ๆ ที่รัน machine code หรือ bytecode ไม่ว่าจะด้วยเหตุผลใดก็ตาม
      อาจรวมถึง virtualization ได้ แต่ก็มักใช้กับ language runtime ด้วย เช่น JVM ของ Java หรือ YARV (Yet Another Ruby VM) ของ Ruby
      ที่จริงแล้ว พื้นที่ที่ไม่ค่อยได้ยินคำนี้บ่อยนักกลับเป็นด้านอีมูเลชัน ส่วนหนึ่งก็เพราะอีมูเลเตอร์สมัยใหม่ส่วนใหญ่เอนเอียงไปใช้เทคนิค dynamic recompilation กับซอฟต์แวร์เป้าหมาย แทนที่จะอีมูเลตทั้งระบบ
    • นี่คือ VM ในความหมายเดียวกับ JVM หรือ Java Virtual Machine
      ถ้าเป็นระดับ Java ก็น่าจะถือได้ว่าเป็น “การใช้ที่พบกันอย่างท่วมท้น”