8 điểm bởi GN⁺ 2023-07-24 | 1 bình luận | Chia sẻ qua WhatsApp
  • Đây là phần mở đầu của một dự án tự học nhằm hiểu đến cùng điều gì thực sự xảy ra khi chạy một chương trình trên máy tính
  • Câu hỏi cốt lõi bắt đầu từ việc liệu chương trình có được chạy trực tiếp trên CPU hay không, rồi mở rộng sang cách system call hoạt động và cách nhiều chương trình chạy đồng thời
  • Ngoài các bài giảng đại học, rất khó tìm được tài liệu hệ thống toàn diện, nên tác giả phải tự sàng lọc các tài liệu có chất lượng khác nhau và những thông tin mâu thuẫn
  • Sau vài tuần nghiên cứu và ghi chép gần 40 trang, tác giả hiểu rõ hơn quá trình từ khi máy tính khởi động đến lúc chạy chương trình
  • Tác giả cho rằng ngay cả những độc giả nghĩ mình đã biết cũng có thể học được điều mới; nếu không có thời gian, nên đọc Chương 3 trước

Điểm xuất phát để hiểu việc chạy chương trình

  • Điểm xuất phát của bài viết là chính xác điều gì xảy ra khi chạy một chương trình trên máy tính
  • Tác giả đã có một mức kiến thức low-level nhất định, nhưng gặp khó khăn khi kết nối nhiều mảnh kiến thức thành một luồng thống nhất
  • Các câu hỏi phát triển theo ba hướng
    • Chương trình có thật sự chạy trực tiếp trên CPU không
    • System call là gì và thực tế hoạt động như thế nào
    • Nhiều chương trình chạy đồng thời bằng cách nào

Quá trình tìm hiểu và thứ tự đọc

  • Vì không có nhiều tài liệu hệ thống toàn diện, tác giả phải tự nghiên cứu từ nhiều nguồn, và chất lượng cũng như nội dung giữa các tài liệu khác nhau
  • Qua vài tuần nghiên cứu và gần 40 trang ghi chú, tác giả hiểu rõ hơn cách máy tính hoạt động từ quá trình khởi động đến khi chạy chương trình
  • Mục tiêu là tự tạo ra một bài giải thích vững chắc, đúng kiểu tài liệu mà tác giả từng muốn tìm
  • Với những độc giả cảm thấy mình đã biết nội dung hoặc không có nhiều thời gian, tác giả khuyên đọc Chương 3 trước
  • Bài tiếp theo là Chương 1: The “Basics”

1 bình luận

 
GN⁺ 2023-07-24
Ý kiến trên Hacker News
  • Tôi là người tạo ra cái này. Cảm ơn mọi người đã đọc và đúng chất Hacker News, đã chỉ ra rất nhiều điểm cần sửa. Putting the "You" in CPU vẫn đang được làm dở, ban đầu tôi định trau chuốt thêm và bổ sung nội dung rồi khoảng tuần sau mới đăng lên HN
    Tôi 17 tuổi, đã nghỉ học trung học một năm trước và bắt đầu làm toàn thời gian tại Hack Club(https://hackclub.com/). Tôi đã lập trình từ khi còn nhớ được, và khoảng 6 năm trước bắt đầu học tại nhà để tập trung hơn vào lập trình và các mối quan tâm khác
    Tất cả đều là tự học nên tôi chưa từng học lớp hệ thống nào ở đại học, và tôi không hài lòng với câu trả lời của mình cho câu hỏi “điều gì xảy ra khi chạy một thứ gì đó”. Vì vậy tôi đã dành rất nhiều thời gian để học sâu nhất có thể, và trong quá trình đó nhận ra rằng hệ điều hành và phần cứng thật sự rất thú vị để học, nhưng tài liệu trực tuyến về chủ đề này thì thường rất tệ
    Khi tìm hiểu, nhiều khi tôi rơi vào các file PDF slide bài giảng từ năm 2014, hoặc các câu trả lời StackOverflow thực ra là sai hoặc đơn giản hóa quá mức. Vì thế tôi viết Putting the "You" in CPU với hy vọng nó trở thành tài liệu tốt hơn cho những người muốn tự học lĩnh vực này. Nó chưa hoàn hảo vì tôi vẫn còn phải viết thêm vài đoạn về SMP, nhưng tôi nghĩ nó tốt hơn nhiều so với hầu hết tài liệu tôi từng thấy. Đây cũng là lần đầu tôi tự làm hình minh họa và sơ đồ; càng về các chương sau càng khá hơn, và tôi khá tự hào về vài hình ở phần cuối
    Toàn bộ được mở mã nguồn trên GitHub: https://github.com/hackclub/putting-the-you-in-cpu

    • Dạo này tôi cũng hay nghĩ “những tài liệu trực tuyến kiểu này quá tệ”. Đặc biệt là khi tìm những thứ như sơ đồ khối chi tiết của kernel Linux, cách bảng trang hoạt động, so sánh ABI gọi hàm theo từng nền tảng, chi tiết các giao thức mạng cấp thấp, hay các số PID Linux được đặt trước
      Thỉnh thoảng tôi tìm được câu trả lời trong các thread Reddit nhưng thường lẫn nhiều nhiễu, và tôi cũng nghi ngờ Reddit có phải nơi đáng tin để lưu giữ tri thức không. StackOverflow thì chủ yếu là các câu hỏi giải quyết một tác vụ cụ thể nên câu trả lời tất yếu ngắn, vì thế đặc biệt không hữu ích
      Với kiến thức phổ thông thì giờ tôi bỏ qua tìm kiếm Google đã hỏng và vào thẳng Wikipedia, nhưng Wikipedia cũng có giới hạn về mức độ sâu khi viết về chủ đề kỹ thuật. Tôi tự hỏi liệu có cần một wiki tập hợp toàn diện các chi tiết về cấu trúc bên trong máy tính không. Tài liệu rải rác thì có rất nhiều, nhưng nếu gom được vào một chỗ thì sẽ thật tuyệt
    • Tôi cũng từng có những câu hỏi tương tự ở khoảng tuổi đó, và tìm thấy nhiều câu trả lời thú vị trong mã nguồn Linux. Tất nhiên lúc đó mọi thứ đơn giản hơn nhiều. Đặc biệt cộng đồng osdev và tài liệu kiến trúc hệ thống của Intel là cú hích lớn; theo tôi nhớ thì phần 3a hoặc 3b có nhiều nội dung đáng giá
      Tài liệu đó cho biết nhiều hơn về thế giới có thể trở thành như thế nào, hơn là hiện tại nó đang ra sao. Làm OSDEV sẽ học được nhiều thứ thú vị, chẳng hạn cách bộ xử lý 32-bit từng dùng PAE để định địa chỉ 36-bit, hay cách bộ xử lý 64-bit dùng định địa chỉ 52-bit để vắt thêm tiền
      Bạn cũng sẽ hiểu vì sao máy tính khởi động ở chế độ 16-bit rồi phải làm đủ trò nhào lộn assembly cấp hệ thống để đi tới long mode, hay vì sao chế độ 8-bit vẫn còn tồn tại. Nếu bạn thích học về nạp binary thì vẫn còn nhiều thứ khiến bạn ngạc nhiên. Ví dụ, ngay cả trong chế độ 64-bit vẫn còn phân đoạn bộ nhớ, nhưng trong long mode nó bị ép thành ánh xạ phẳng
    • Công trình tuyệt vời. Đây là tài liệu tốt để nhập môn phát triển hệ điều hành, nhiều chi tiết nhưng dễ tiêu hóa. Phần cuối của chú thích #2 trong chương 2 nên được chỉnh nhẹ. Cần phản ánh rằng mô hình bất đồng bộ của một số ngôn ngữ lập trình là đa nhiệm hợp tác. Bên trong một tiến trình, dùng đa nhiệm hợp tác thường được xem là an toàn, nhưng chuyện có đáng mong muốn hay không lại là vấn đề khác. Theo tôi biết, Erlang giữ cơ chế tiền nhiệm để đảm bảo độ vững chắc
  • Tôi thích những tài liệu như thế này. Dạo này tôi đang đặt chân vào thế giới RISC-V, và có vẻ rõ ràng rằng những người muốn thúc đẩy phần mềm tự do và mã nguồn mở tiến lên cần loại kiến thức này để phần mềm chạy tốt trên nhiều máy tính đơn bo mới xuất hiện

    • Nhân tiện, mã hóa lệnh RISC-V là một trong những thứ khá kỳ quặc để khám phá, có lẽ chỉ trừ Thumb-2. Có lý do hợp lý cho thiết kế đó, nhưng nếu định đọc hex dump thì RISC-V sẽ không dễ dãi với bạn đâu. Dù từ hầu hết các góc nhìn khác nó đơn giản là vậy
    • Nếu đã học từ bậc cử nhân trở lên thì tôi nghĩ lập trình viên nói chung đều đã biết những nội dung này, ít nhất là nếu họ không bỏ qua các môn kiến trúc máy tínhhệ điều hành
      Trong trường hợp của tôi, trong quá trình học máy tính hoạt động thế nào, việc định nghĩa kiến trúc tập lệnh, triển khai trình mô phỏng và assembler, rồi tự viết assembly là khá thường gặp. Người chăm hơn còn triển khai bằng FPGA hoặc viết backend LLVM
      Phía hệ điều hành cũng tương tự. Tự triển khai kernel để học, hoặc ít nhất chỉnh sửa một kernel hiện có, là lộ trình khá phổ biến
  • Nếu muốn biết thêm máy tính hoạt động thế nào ở mức rất cơ bản, tôi thật sự khuyên đọc cuốn Code của Charles Petzold. Sách bắt đầu từ các nguyên lý đầu tiên rồi đi lên tới mức khá cao

    • Nếu bạn thích kiểu học bằng cách tự xây dựng, Nandgame https://nandgame.com cũng rất hay
  • Một trong những điểm tài liệu này làm tốt là chỉ ra kỳ vọng và kết quả học được thực tế trong quá trình thu thập thông tin. Kiểu như “tôi tưởng là x vì y, nhưng thực ra lại là a vì b!”
    Gỡ bỏ hiểu lầm thường là bước cốt lõi để hình thành tri thức mới, và sẽ hữu ích khi tác giả thừa nhận rằng người đọc phải vượt qua hiểu lầm của chính mình trong lúc xử lý thông tin. Điều đó thân thiện với độc giả hơn nhiều so với thái độ “đây là đáp án đúng, nghĩ khác thì là đồ ngốc”

  • Chương 6 giải thích sai về giá trị trả về của fork(). Trái với những gì được viết, tiến trình cha phải nhận PID của tiến trình con, còn tiến trình con nhận 0. Tôi đã tạo pull request cho phần này

    • Cảm ơn rất nhiều, tôi đã nhầm. Tôi đã sửa ví dụ mã và merge rồi
  • Trong phần giải thích rằng add eax, 512 được dịch thành 05 00 02 00 00, byte đầu tiên 05 là opcode để cộng một số 16-bit vào thanh ghi EAX, còn các byte còn lại là 512 (0x200) theo little-endian.
    Nhưng giải thích này chỉ nói về 3 byte đầu trong lệnh dài 5 byte. Tôi thắc mắc các byte 00 còn lại là gì. Có lẽ ý định là viết rằng opcode đó cộng một số 32-bit vào EAX chăng

    • Không phải “he” mà là “she”, và đúng vậy. Có vẻ chỗ đó lẽ ra phải ghi là 32-bit. Bắt lỗi hay lắm
    • Nếu đang nói về x86 thì đúng. Thanh ghi eax có độ rộng 32-bit, còn 16 bit thấp của cùng thanh ghi đó được gọi là ax
  • Bài viết hay. Là một lập trình viên web tự học, tôi thấy những tài liệu như thế này rất có giá trị. Nó giúp hiểu cụ thể hơn cách mọi thứ thực sự vận hành, thoát khỏi nhiều lớp trừu tượng

  • Trông như một tài liệu tốt, đi vào trọng tâm khá nhanh, và cũng hơi thú vị. Nó không giống “hướng dẫn monad” như tôi đã nghĩ. Tôi sẽ đọc tiếp

    • Rất vui vì bạn thích. Sau này nếu có phản hồi thì mong bạn cho biết
  • Tài liệu hay. Tuy vậy tôi vẫn còn một thắc mắc về kernel. Trong [0], bài viết giải thích rằng shebang được xử lý trong kernel và vì nó lấy từ buf chứ không tải toàn bộ tệp, nên luôn bị cắt theo độ dài của buf
    Bốn năm trước, có người khó chịu vì kernel cắt các đường dẫn dài quá 128 ký tự, nên đã tăng gấp đôi kích thước bộ đệm, khiến điểm bị cắt cũng tăng gấp đôi. Vì vậy ngày nay trên Linux, nếu dòng shebang vượt quá 256 ký tự thì phần sau 256 ký tự sẽ biến mất hoàn toàn
    Tôi không hiểu vì sao lại âm thầm cắt đường dẫn tệp của ai đó. Không phải nơi nào khác mà chính kernel thì càng không nên làm vậy. Đường dẫn tệp dài 256 byte có thể là vô lý, nhưng chuỗi đường dẫn + đối số dài 256 byte thì gần như chắc chắn sẽ xuất hiện lúc nào đó. Âm thầm làm hỏng script là sai
    [0]: https://cpu.land/how-to-run-a-program

    • Với tư cách tác giả, tôi rất đồng ý. Không nên phải hiểu đến tận logic thực thi của kernel chỉ để nắm được giới hạn của shebang. Đây là một thiết kế ngớ ngẩn, và có lẽ tôi nên nhấn mạnh điểm đó hơn
      Vài năm trước, có người đã thử vá kernel để các shebang quá dài ít nhất sẽ thất bại rõ ràng thay vì âm thầm thất bại. Nhưng việc đó gây vấn đề trên NixOS. Đúng kiểu Nix, họ đã dùng nhiều shebang quá dài với các đường dẫn nix-store dài. Trước đây chúng bị cắt âm thầm nhưng vẫn chạy, còn đột nhiên tất cả các script đó đều thất bại, làm phá vỡ tương thích ngược. Vì vậy bản vá phải bị hoàn tác, và từ đó không có thay đổi tương tự nào xuất hiện nữa
      Xem thêm chi tiết tại https://lwn.net/Articles/779997/
    • Không nên nhét các đối số phức tạp vào shebang. Shebang dùng để cho biết vị trí trình thông dịch của tệp hiện tại
      Bản thân shebang thậm chí còn không xử lý đúng hơn một đối số. Nếu viết #!/bin/program -args somescript thì nó sẽ được tách thành ['/bin/program','-args somescript'], gần như luôn không phải kết quả mong muốn. Điều này chỉ giới hạn trên Linux, và shebang cũng không thật sự khả chuyển phổ quát trên mọi Unix
      Nếu muốn làm việc phức tạp, tốt hơn là dùng #!/bin/sh, rồi trong chính tệp hiện tại exec với các đối số cần thiết
      Một điểm còn thiếu trong liên kết là có thể dùng env để tìm trình thông dịch trong đường dẫn hiện tại. Ví dụ #!/usr/bin/env python3 sẽ tìm python3 mà người dùng đang dùng mà không cần biết vị trí của nó
      Điều này có thể hữu ích cho các script chạy trong môi trường ảo, nơi hoạt động bằng cách ghi đè PATH tới trình thông dịch Python. Tuy nhiên không nên làm vậy với chương trình được cài đặt. Vì không nên để chương trình đã cài đặt cho phép người dùng kiểm soát trình thông dịch nào sẽ được dùng, nên cần dùng đường dẫn tuyệt đối
      env có tùy chọn -S, có thể phân tích dòng lệnh từ một chuỗi đơn để xử lý giới hạn này. Dù vậy, với người đã tránh các mẹo vặt trong dòng shebang suốt nhiều năm, cùng lắm đây vẫn có vẻ là một thực hành đáng ngờ
    • Điều đáng ngạc nhiên hơn là bộ đệm không được đặt bằng kích thước block của đĩa. Thông thường là 512 byte, nhưng không phải lúc nào cũng vậy