8 คะแนน โดย GN⁺ 2023-07-24 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • นี่คือบทนำของโปรเจกต์การเรียนรู้ส่วนบุคคลที่พยายามทำความเข้าใจให้ถึงที่สุดว่า จริง ๆ แล้วเกิดอะไรขึ้น เมื่อรันโปรแกรมบนคอมพิวเตอร์
  • คำถามหลักต่อยอดไปสู่เรื่องว่าโปรแกรมถูกรันบน CPU โดยตรงหรือไม่, system call ทำงานอย่างไร, และหลายโปรแกรมทำงานพร้อมกันได้อย่างไร
  • นอกเหนือจากการเรียนในมหาวิทยาลัย การหาสื่อเกี่ยวกับ ระบบคอมพิวเตอร์ ที่ครอบคลุมนั้นทำได้ยาก จึงต้องคัดกรองข้อมูลที่ขัดแย้งกันจากแหล่งที่มีคุณภาพต่างกันด้วยตัวเอง
  • หลังจากค้นคว้าอยู่หลายสัปดาห์และจดโน้ตยาวเกือบ 40 หน้า ก็ทำให้เข้าใจกระบวนการตั้งแต่เริ่มเปิดเครื่องไปจนถึงการรันโปรแกรมได้ดีขึ้น
  • ผู้เขียนบอกว่าแม้แต่ผู้อ่านที่รู้สึกว่ารู้อยู่แล้วก็อาจมีเรื่องใหม่ให้เรียนรู้ และถ้าไม่มีเวลา แนะนำให้อ่านบทที่ 3 ก่อน

จุดเริ่มต้นของการทำความเข้าใจการรันโปรแกรม

  • บทความนี้เริ่มต้นจากคำถามว่า เกิดอะไรขึ้นอย่างแม่นยำ เมื่อเรารันโปรแกรมบนคอมพิวเตอร์
  • แม้จะมีความรู้ระดับล่างอยู่พอสมควร แต่ก็ยังยากที่จะเชื่อมชิ้นส่วนต่าง ๆ ให้เป็นกระบวนการเดียวกัน
  • คำถามแตกออกเป็นสามทาง
    • โปรแกรมถูก รันบน CPU โดยตรง จริงหรือไม่
    • system call คืออะไร และในทางปฏิบัติมันทำงานอย่างไร
    • หลายโปรแกรมทำงานพร้อมกันได้อย่างไร

กระบวนการค้นคว้าและลำดับการอ่าน

  • เพราะมีสื่อเกี่ยวกับระบบคอมพิวเตอร์แบบครอบคลุมไม่มาก จึงต้องค้นคว้าจากหลายแหล่งด้วยตัวเอง และแต่ละแหล่งก็มีทั้งคุณภาพและเนื้อหาที่แตกต่างกัน
  • จากการค้นคว้าหลายสัปดาห์และโน้ตยาวเกือบ 40 หน้า ทำให้เข้าใจการทำงานของคอมพิวเตอร์ได้ดีขึ้น ตั้งแต่กระบวนการเริ่มต้นไปจนถึงการรันโปรแกรม
  • เป้าหมายคือการสร้างบทความอธิบายที่แน่นและครบถ้วนแบบที่ผู้เขียนเองอยากหาให้ได้
  • สำหรับผู้อ่านที่รู้สึกว่ารู้อยู่แล้วหรือมีเวลาน้อย ผู้เขียนแนะนำให้อ่าน บทที่ 3 ก่อน
  • บทความถัดไปจะต่อด้วย บทที่ 1: The “Basics”

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

 
GN⁺ 2023-07-24
ความคิดเห็นจาก Hacker News
  • ผมเป็นคนทำสิ่งนี้เอง ขอบคุณที่อ่านและช่วยชี้จุดแก้ไขหลายอย่างแบบสมกับเป็น Hacker News. Putting the "You" in CPU ยังอยู่ระหว่างทำงานเต็มที่ เดิมทีตั้งใจว่าจะขัดเกลาและเพิ่มเนื้อหาให้มากกว่านี้ แล้วค่อยโพสต์ลง HN ราวสัปดาห์หน้า
    ผมอายุ 17 ปี เมื่อ 1 ปีก่อนออกจากโรงเรียนมัธยม แล้วเริ่มทำงานเต็มเวลาให้ Hack Club(https://hackclub.com/) เท่าที่จำได้ก็เขียนโปรแกรมมาตลอด และราว 6 ปีก่อนเริ่มเรียนแบบโฮมสคูลเพื่อจะได้โฟกัสกับการเขียนโปรแกรมและความสนใจอื่น ๆ มากขึ้น
    ทุกอย่างเรียนด้วยตัวเอง เลยไม่เคยเรียนวิชาระบบของมหาวิทยาลัย และไม่ค่อยพอใจกับคำตอบของตัวเองต่อคำถามว่า “เมื่อรันอะไรสักอย่างแล้วเกิดอะไรขึ้น” ดังนั้นจึงใช้เวลามหาศาลเรียนให้ลึกที่สุดเท่าที่ทำได้ และระหว่างนั้นก็พบว่า ระบบปฏิบัติการและฮาร์ดแวร์ เป็นเรื่องที่เรียนแล้วสนุกมากจริง ๆ แต่ แหล่งข้อมูลออนไลน์ ในหัวข้อนี้มักแย่มาก
    เวลาค้นคว้ามักไปเจอ PDF สไลด์บรรยายจากปี 2014 หรือคำตอบใน StackOverflow ที่จริง ๆ แล้วผิดหรือทำให้เรียบง่ายเกินไปบ่อย ๆ เลยเขียน Putting the "You" in CPU ด้วยความหวังว่าจะเป็นแหล่งข้อมูลที่ดีกว่าสำหรับคนที่อยากเรียนด้านนี้ด้วยตัวเอง ยังไม่สมบูรณ์เพราะต้องเขียนเพิ่มอีกสองสามย่อหน้าเกี่ยวกับ SMP แต่ผมคิดว่ามันดีกว่าแหล่งข้อมูลส่วนใหญ่ที่เคยเห็นมาก ภาพและไดอะแกรมก็เป็นครั้งแรกที่ลองทำเอง และยิ่งไปถึงบทท้าย ๆ ก็ยิ่งดีขึ้น ภาพบางภาพช่วงหลัง ๆ ผมค่อนข้างภูมิใจเลย
    ทั้งหมดเป็นโอเพนซอร์สอยู่บน GitHub: https://github.com/hackclub/putting-the-you-in-cpu

    • ช่วงนี้ผมเองก็คิดบ่อย ๆ ว่า “แหล่งข้อมูลออนไลน์แบบนี้แย่มาก” โดยเฉพาะเวลาหาไดอะแกรมบล็อกแบบละเอียดของเคอร์เนล Linux, วิธีทำงานของ page table, การเปรียบเทียบ ABI การเรียกฟังก์ชันในแต่ละแพลตฟอร์ม, รายละเอียดโปรโตคอลเครือข่ายระดับต่ำ, หมายเลข PID ของ Linux ที่ถูกสงวนไว้ อะไรทำนองนั้น
      บางครั้งหาคำตอบได้จากเธรดใน Reddit แต่โดยมากมีสัญญาณรบกวนปะปนอยู่ และก็สงสัยด้วยว่า Reddit เป็นที่ที่เชื่อถือได้สำหรับเก็บความรู้หรือไม่ ส่วน StackOverflow เน้นคำถามเพื่อแก้งานเฉพาะอย่าง คำตอบจึงสั้นโดยเลี่ยงไม่ได้ เลยไม่ค่อยช่วยเป็นพิเศษ
      ความรู้ทั่วไปตอนนี้ผมข้ามการค้นหาของ Google ที่พัง ๆ ไปเข้า Wikipedia ตรง ๆ แต่ Wikipedia เองก็มีขีดจำกัดว่าจะลงลึกหัวข้อทางเทคนิคได้แค่ไหน ผมคิดว่าเราอาจต้องมี วิกิ ที่รวบรวมรายละเอียดโครงสร้างภายในคอมพิวเตอร์อย่างครอบคลุม แหล่งข้อมูลกระจัดกระจายมีเยอะ แต่ถ้ารวมไว้ที่เดียวคงดีมาก
    • ผมก็เคยมีคำถามคล้าย ๆ กันช่วงอายุประมาณนั้น และพบคำตอบที่น่าสนใจมากมายในซอร์สโค้ดของ Linux แน่นอนว่าสมัยนั้นมันเรียบง่ายกว่านี้มาก โดยเฉพาะ ชุมชน osdev กับเอกสารสถาปัตยกรรมระบบของ Intel เป็นแรงผลักดันสำคัญมาก และเท่าที่จำได้ส่วน 3a หรือ 3b มีของดีเยอะ
      เอกสารนั้นบอกให้รู้มากกว่าว่าโลกตอนนี้เป็นอย่างไร แต่ว่ามันอาจเป็นอย่างไรได้บ้าง การทำ OSDEV ทำให้ได้เรียนรู้เรื่องสนุก ๆ เยอะ เช่น โปรเซสเซอร์ 32 บิตเคยทำ address แบบ 36 บิตด้วย PAE อย่างไร หรือโปรเซสเซอร์ 64 บิตทำ address แบบ 52 บิตเพื่อรีดเงินเพิ่มอย่างไร
      ยังได้รู้ด้วยว่าทำไมคอมพิวเตอร์ต้องเริ่มจากโหมด 16 บิตแล้วต้องใช้ท่ากายกรรมแอสเซมบลีระดับระบบเพื่อไปถึง long mode ทำไมโหมด 8 บิตยังเหลืออยู่ ฯลฯ ถ้าชอบเรียนเรื่องการโหลดไบนารี ยังมีเรื่องให้ประหลาดใจอีกมาก ตัวอย่างเช่นในโหมด 64 บิตก็ยังมี memory segmentation เหลืออยู่ แต่ใน long mode จะถูกบังคับให้เป็นการแมปแบบแบน
    • งานยอดเยี่ยมมาก เป็นแหล่งข้อมูลที่ดีสำหรับเริ่มต้น การพัฒนาระบบปฏิบัติการ รายละเอียดเยอะ แต่ย่อยง่าย ตอนท้ายของหมายเหตุ #2 ในบทที่ 2 น่าจะปรับเล็กน้อย ควรสะท้อนให้เห็นว่า asynchronous paradigm ของภาษาโปรแกรมบางภาษาเป็น cooperative multitasking โดยทั่วไปการใช้ cooperative multitasking ภายในโปรเซสถือว่าปลอดภัย แต่เป็นเรื่องที่ควรทำหรือไม่นั้นเป็นอีกประเด็นหนึ่ง เท่าที่รู้ Erlang ยังคงใช้แบบ preemptive เพื่อความทนทาน
  • ผมชอบแหล่งข้อมูลแบบนี้ ช่วงนี้กำลังก้าวเข้าสู่โลก RISC-V และเห็นได้ชัดว่าคนที่อยากผลักดันซอฟต์แวร์เสรีและโอเพนซอร์สไปข้างหน้า จำเป็นต้องมีความรู้แบบนี้เพื่อให้ซอฟต์แวร์ทำงานได้ดีบนคอมพิวเตอร์บอร์ดเดี่ยวรุ่นใหม่ ๆ หลายตัว

    • หมายเหตุว่า การเข้ารหัสคำสั่งของ RISC-V นับว่าแปลกพอตัวในบรรดาสิ่งที่ให้สำรวจได้ ยกเว้นประมาณ Thumb-2 มันมีเหตุผลที่ดีที่ถูกออกแบบมาแบบนั้น แต่ถ้าคิดจะอ่าน hex dump แล้ว RISC-V คงไม่ปรานีคุณนัก ถึงแม้จากมุมมองอื่น ๆ ส่วนใหญ่จะเรียบง่ายก็ตาม
    • ถ้าเรียนถึงระดับปริญญาตรีขึ้นไป ผมคิดว่าโปรแกรมเมอร์โดยทั่วไปก็น่าจะรู้เรื่องพวกนี้อยู่แล้วไม่ใช่หรือ อย่างน้อยถ้าไม่ได้โดดวิชา สถาปัตยกรรมคอมพิวเตอร์ กับ ระบบปฏิบัติการ
      ในกรณีของผม ระหว่างเรียนว่าคอมพิวเตอร์ทำงานอย่างไร การกำหนดสถาปัตยกรรมชุดคำสั่ง การทำ simulator กับ assembler และการเขียน assembly เอง เป็นเรื่องที่พบได้ทั่วไป คนที่ขยันกว่านั้นก็อาจ implement ด้วย FPGA หรือเขียน LLVM backend
      ฝั่งระบบปฏิบัติการก็เหมือนกัน การเรียนด้วยการ implement kernel เอง หรืออย่างน้อยแก้ไข kernel ที่มีอยู่แล้ว ถือเป็นกระบวนการที่ค่อนข้างพบได้ทั่วไป
  • ถ้าอยากรู้มากขึ้นว่าคอมพิวเตอร์ทำงานอย่างไรในระดับพื้นฐานมาก ๆ ขอแนะนำหนังสือ Code ของ Charles Petzold จริง ๆ เริ่มจากหลักการแรกสุดแล้วไต่ขึ้นไปถึงระดับที่ค่อนข้างสูง

    • ถ้าชอบเรียนด้วยการลงมือสร้างเอง Nandgame https://nandgame.com ก็ดีเช่นกัน
  • หนึ่งในจุดที่แหล่งข้อมูลนี้ทำได้ดี คือการชี้ให้เห็น สิ่งที่คาดไว้กับผลลัพธ์การเรียนรู้จริง ในกระบวนการรวบรวมข้อมูล ประมาณว่า “คาดว่าเป็น x เพราะ y แต่จริง ๆ แล้วเป็น a เพราะ b!”
    การคลี่คลายความเข้าใจผิดมักเป็นขั้นตอนสำคัญในการก่อรูปความรู้ใหม่ และการที่ผู้เขียนยอมรับว่าผู้อ่านต้องก้าวข้ามความเข้าใจผิดของตัวเองระหว่างประมวลผลข้อมูลนั้นช่วยได้มาก เป็นมิตรต่อผู้อ่านกว่าท่าทีแบบ “นี่คือคำตอบที่ถูก และถ้าคิดต่างก็โง่” มาก

  • บทที่ 6 อธิบาย ค่าที่ fork() คืนกลับ ผิด ตรงข้ามกับที่เขียนไว้ ควรเป็นว่าฝั่ง parent จะได้ PID ของ child คืนกลับ และฝั่ง child จะได้ 0 คืนกลับ ผมทำ pull request สำหรับส่วนนี้แล้ว

    • ขอบคุณมากจริง ๆ ผมพลาดเอง แก้ตัวอย่างโค้ดและ merge แล้ว
  • ในคำอธิบายที่ว่า add eax, 512 ถูกแปลเป็น 05 00 02 00 00 นั้น ระบุว่าไบต์แรก 05 คือ opcode สำหรับบวกตัวเลข 16 บิตเข้ากับรีจิสเตอร์ EAX และไบต์ที่เหลือคือ 512 (0x200) แบบ little-endian
    แต่คำอธิบายนี้อธิบายแค่ 3 ไบต์แรกจากคำสั่งขนาด 5 ไบต์ แล้ว 00 ที่เหลือคืออะไร เลยสงสัยอยู่ บางทีตั้งใจจะเขียนว่า opcode นี้บวก ตัวเลข 32 บิต เข้ากับ EAX หรือเปล่า

    • ไม่ใช่ “he” แต่เป็น “she” และใช่ ตรงนั้นน่าจะต้องเขียนว่า 32 บิต จับจุดได้ดีมาก
    • ถ้าหมายถึง x86 ก็ถูกแล้ว รีจิสเตอร์ eax มีความกว้าง 32 บิต และ 16 บิตล่างของรีจิสเตอร์เดียวกันเรียกว่า ax
  • เป็นบทความที่ดี ในฐานะเว็บเดเวลลอปเปอร์ที่เรียนเอง รู้สึกว่าสื่อแบบนี้มีคุณค่ามาก ช่วยให้หลุดออกจาก abstraction หลายชั้นและเข้าใจอย่างเป็นรูปธรรมมากขึ้นว่าสิ่งต่าง ๆ ทำงานจริงอย่างไร

  • ดูเป็นสื่อที่ดี เข้าประเด็นหลักค่อนข้างเร็ว และมีความสนุกนิดหน่อย ไม่ได้ให้ความรู้สึกเหมือน “บทสอน monad” เท่าที่คาดไว้ คิดว่าจะอ่านต่อ

    • ดีใจที่ชอบ ถ้ามีฟีดแบ็กภายหลังก็บอกได้เลย
  • เป็นสื่อที่ดี แต่ยังมีข้อสงสัยอย่างหนึ่งเกี่ยวกับเคอร์เนล ใน [0] อธิบายว่า shebang ถูกประมวลผลในเคอร์เนล และเพราะไม่ได้โหลดทั้งไฟล์ แต่ดึงจาก buf จึงถูกตัดที่ความยาวของ buf เสมอ
    เมื่อ 4 ปีก่อน มีคนรำคาญที่เคอร์เนลตัดพาธที่ยาวเกิน 128 อักขระ จึงเพิ่มขนาดบัฟเฟอร์เป็นสองเท่า ทำให้จุดที่ถูกตัดยาวขึ้นเป็นสองเท่า ดังนั้นทุกวันนี้บน Linux ถ้าบรรทัด shebang ยาวเกิน 256 อักขระ ส่วนหลังจาก 256 อักขระจะหายไปหมด
    ไม่เข้าใจว่าทำไมถึงตัดพาธไฟล์ของใครสักคนแบบเงียบ ๆ ไม่ใช่ที่ไหนอื่น แต่เป็น เคอร์เนล ที่ไม่ควรทำแบบนั้น พาธไฟล์ยาว 256 ไบต์อาจดูไม่สมเหตุสมผล แต่สตริงพาธ+อาร์กิวเมนต์ยาว 256 ไบต์ วันหนึ่งแทบจะต้องเกิดขึ้นแน่ การทำให้สคริปต์พังแบบเงียบ ๆ เป็นเรื่องผิด
    [0]: https://cpu.land/how-to-run-a-program

    • ในฐานะผู้เขียน เห็นด้วยอย่างยิ่ง ไม่ควรต้องเข้าใจไปถึงลอจิกการรันของเคอร์เนลเพื่อจะเข้าใจข้อจำกัดของ shebang นี่เป็นการออกแบบที่โง่ และคิดว่าคงควรเน้นจุดนี้ให้มากกว่านี้
      เมื่อไม่กี่ปีก่อน มีคนพยายามแพตช์เคอร์เนลเพื่อให้ shebang ที่ยาวเกินไปอย่างน้อยก็ล้มเหลวอย่างชัดเจน แทนที่จะล้มเหลวแบบเงียบ ๆ แต่เกิดปัญหากับ NixOS เพราะตามสไตล์ Nix มีการใช้ shebang ที่ยาวเกินไปซึ่งเต็มไปด้วยพาธ nix-store ยาว ๆ อยู่แล้ว ก่อนหน้านั้นมันถูกตัดแบบเงียบ ๆ แต่ก็ยังทำงานได้ จู่ ๆ สคริปต์เหล่านั้นทั้งหมดก็ล้มเหลว ทำให้ ความเข้ากันได้ย้อนหลัง พัง จึงต้อง revert แพตช์นั้น และหลังจากนั้นก็ไม่มีการเปลี่ยนแปลงคล้าย ๆ กันออกมาอีก
      ดูรายละเอียดได้ที่ https://lwn.net/Articles/779997/
    • ไม่ควรใส่อาร์กิวเมนต์ซับซ้อนลงใน shebang shebang มีไว้เพื่อบอก ตำแหน่งของอินเทอร์พรีเตอร์ ของไฟล์ปัจจุบัน
      ตัว shebang เองยังจัดการอาร์กิวเมนต์มากกว่าหนึ่งตัวได้ไม่ดีด้วยซ้ำ ถ้าเขียน #!/bin/program -args somescript มันจะถูกแยกเป็น ['/bin/program','-args somescript'] ซึ่งแทบจะไม่ใช่ผลลัพธ์ที่ต้องการเลย นี่เป็นเฉพาะ Linux และ shebang ก็ไม่ได้พกพาได้ทั่วไปบน Unix ทุกตัวด้วย
      ถ้าต้องการทำเรื่องซับซ้อน ควรใช้ #!/bin/sh แล้ว exec ด้วยอาร์กิวเมนต์ที่ต้องการจากไฟล์ปัจจุบันจะดีกว่า
      จุดหนึ่งที่ลิงก์ไม่ได้พูดถึงคือสามารถใช้ env เพื่อหาอินเทอร์พรีเตอร์จากพาธปัจจุบันได้ เช่น #!/usr/bin/env python3 จะหา python3 ที่ผู้ใช้ใช้อยู่ให้โดยไม่ต้องรู้ตำแหน่ง
      วิธีนี้อาจมีประโยชน์กับสคริปต์ที่จะรันใน virtual environment ซึ่งทำงานโดย override PATH ไปยัง Python interpreter แต่ไม่ควรทำแบบนี้กับโปรแกรมที่ติดตั้งแล้ว เพราะไม่ควรปล่อยให้โปรแกรมที่ติดตั้งแล้วถูกควบคุมว่าใช้ interpreter ใดโดยผู้ใช้ ควรใช้พาธแบบ absolute
      env มีออปชัน -S ที่ช่วยจัดการข้อจำกัดนี้ได้ โดย parse command line จากสตริงเดียว ถึงอย่างนั้น ในฐานะคนที่หลีกเลี่ยงลูกเล่นในบรรทัด shebang มาหลายปี อย่างมากก็ยังดูเป็นแนวปฏิบัติที่น่าสงสัย
    • ที่น่าแปลกใจกว่าคือบัฟเฟอร์ไม่ได้ถูกตั้งให้เท่ากับ ขนาดบล็อก ของดิสก์ ปกติคือ 512 ไบต์ แต่ก็ไม่เสมอไป