Bài 1: Nội Dung Môn Học


1. Giới Thiệu Môn Học

Môn Lập Trình Hệ Thống cung cấp nền tảng để hiểu cách máy tính thực sự hoạt động — từ cấp độ ngôn ngữ lập trình bậc cao cho đến mã máy, bộ nhớ, và kiến trúc hệ thống.

Giáo trình chính: Computer Systems: A Programmer’s Perspective (CS:APP2e) – Bryant & O’Hallaron, Pearson, 2010. Đây là giáo trình chuẩn của ĐH Carnegie Mellon (CMU), Mỹ.


2. Mục Tiêu Môn Học

Sau khi hoàn thành môn học, sinh viên có khả năng:

  • Hiểu và viết được hợp ngữ (assembly), biết cách chuyển đổi qua lại giữa ngôn ngữ cấp cao và mã hợp ngữ.
  • Nắm vững các khái niệm về bộ nhớ, stack, pointer, cache và kiến trúc máy tính.
  • Tối ưu hóa chương trình ở nhiều mức độ khác nhau.
  • Xây dựng phần mềm an toàn hơn, hiệu quả hơn và có tư duy hệ thống.
  • Phục vụ các kỹ thuật dịch ngược (reverse engineering), debug và kiểm lỗi phần mềm.

3. Nội Dung Chính

1. Biểu diễn kiểu dữ liệu cơ bản và các phép tính bit
2. Ngôn ngữ assembly (x86)
3. Điều khiển luồng trong C ở mức assembly
4. Thủ tục / hàm (procedure) trong C ở mức assembly
5. Biểu diễn mảng, cấu trúc dữ liệu trong C
6. Reverse engineering & Buffer Overflow (ATTT)
7. Linking trong biên dịch file thực thi
8. Phân cấp bộ nhớ, Cache

4. Quá Trình Biên Dịch Một Chương Trình C

Lấy ví dụ chương trình đơn giản nhất:

#include <stdio.h>

int main() {
    printf("hello, world\n");
}

Chương trình này đi qua 4 giai đoạn trước khi trở thành file thực thi:

graph LR A["hello.c\n(Source - text)"] -->|Preprocessor cpp| B["hello.i\n(Modified source - text)"] B -->|Compiler cc1| C["hello.s\n(Assembly - text)"] C -->|Assembler as| D["hello.o\n(Object binary)"] D -->|Linker ld| E["hello\n(Executable binary)"] F["printf.o"] --> E
Giai đoạnCông cụĐầu vàoĐầu raMô tả
Tiền xử lýcpphello.chello.iXử lý #include, #define,…
Biên dịchcc1hello.ihello.sChuyển sang hợp ngữ
Hợp dịchashello.shello.oChuyển sang mã nhị phân (object file)
Liên kếtldhello.o + printf.ohelloGhép các object file thành file thực thi

Tại sao cần hiểu điều này? Khi debug hoặc tối ưu hóa chương trình, bạn cần biết trình biên dịch đã “dịch” code của bạn thành gì ở mức máy. Nhiều lỗi bí ẩn chỉ có thể giải thích khi nhìn vào mã assembly được sinh ra.


5. Năm Vấn Đề Trọng Tâm

Vấn đề #1 – Kiểu intfloat có thực sự là số nguyên/thực không?

Trong toán học, các phép tính có tính chất quen thuộc. Nhưng trong máy tính, do giới hạn biểu diễn nhị phân, các tính chất này không phải lúc nào cũng đúng.

Câu hỏi 1: Với mọi x, có chắc x * x ≥ 0 không?

Câu hỏi 2: Có chắc (x + y) + z = x + (y + z) không?


Vấn đề #2 – Tại sao cần biết Assembly (Hợp ngữ)?

Assembly là ngôn ngữ “gần nhất” với máy tính mà con người có thể đọc được. Hiểu assembly giúp bạn:

  • 🐛 Debug lỗi sâu: Hiểu hành vi thực sự của chương trình khi có bug khó tái hiện.
  • Tối ưu hiệu suất: Biết trình biên dịch tối ưu hóa gì, và khi nào nó không làm được.
  • 🔐 Bảo mật phần mềm: Phân tích, tạo và phòng chống malware; khai thác lỗ hổng buffer overflow.
  • 🔍 Reverse engineering: Đọc hiểu chương trình khi không có source code.
  • 🛠️ Lập trình hệ thống: Viết OS, driver, firmware đòi hỏi kiểm soát chính xác phần cứng.
; Ví dụ assembly x86 - in "Hello, World"
section .data
    msg db "Hello, World", 0x0a
    len equ $ - msg

section .text
    global _start
_start:
    mov eax, 4       ; syscall: write
    mov ebx, 1       ; file descriptor: stdout
    mov ecx, msg     ; con trỏ đến chuỗi
    mov edx, len     ; độ dài chuỗi
    int 0x80         ; gọi kernel

Vấn đề #3 – Lỗi khi truy cập bộ nhớ (Memory Bug)

Ví dụ minh họa

typedef struct {
    int a[2];    // mảng 2 phần tử int (index hợp lệ: 0 và 1)
    double d;    // biến double ngay sau mảng trong bộ nhớ
} struct_t;

double fun(int i) {
    volatile struct_t s;
    s.d = 3.14;
    s.a[i] = 1073741824; /* Có thể truy cập ngoài giới hạn! */
    return s.d;
}

Câu hỏi: Kết quả của fun(0) đến fun(6) là gì?


Vấn đề #4 – Hiệu Suất Không Chỉ Phụ Thuộc Vào Độ Phức Tạp Thuật Toán

Nhiều lập trình viên nghĩ rằng chỉ cần chọn thuật toán tốt (Big-O nhỏ) là đủ. Thực tế, cách truy cập bộ nhớ có thể tạo ra sự khác biệt hàng chục lần về tốc độ.

Ví dụ: Copy ma trận 2048×2048

// Cách 1: Duyệt theo cột (column-major) - CHẬM
void copyji(int src[2048][2048], int dst[2048][2048]) {
    int i, j;
    for (j = 0; j < 2048; j++)       // vòng ngoài theo cột
        for (i = 0; i < 2048; i++)   // vòng trong theo hàng
            dst[i][j] = src[i][j];
}

// Cách 2: Duyệt theo hàng (row-major) - NHANH
void copyij(int src[2048][2048], int dst[2048][2048]) {
    int i, j;
    for (i = 0; i < 2048; i++)       // vòng ngoài theo hàng
        for (j = 0; j < 2048; j++)   // vòng trong theo cột
            dst[i][j] = src[i][j];
}
Phương phápThời gian (Intel Core i7, 2.0 GHz)
copyji (duyệt cột trước)81.8 ms
copyij (duyệt hàng trước)4.3 ms

Chênh lệch gần 19 lần dù cùng số phép tính!

Câu hỏi: Tại sao copyij nhanh hơn copyji đến vậy?


Vấn đề #5 – Máy Tính Làm Nhiều Hơn Chỉ Chạy Chương Trình

Hệ thống máy tính bao gồm nhiều thành phần tương tác với nhau:

graph TD CPU["CPU\n(ALU + Registers)"] <-->|System Bus| Bridge["I/O Bridge"] Bridge <-->|Memory Bus| RAM["Main Memory\n(DRAM)"] Bridge <-->|I/O Bus| USB["USB Controller"] Bridge <-->|I/O Bus| GPU["Graphics Adapter"] Bridge <-->|I/O Bus| Disk["Disk Controller"] USB --> KB["Keyboard / Mouse"] GPU --> Display["Display"] Disk --> HDD["Hard Disk"]

Khi chạy hello, chuỗi "hello, world\n" được:

  1. Đọc từ disk vào main memory
  2. CPU xử lý, xuất ra display qua GPU

Vấn đề hệ thống phát sinh khi có I/O, mạng, và phân cấp lưu trữ.


6. Kiến Trúc Phân Cấp Bộ Nhớ

graph TD L0["L0: CPU Registers\n(~bytes, < 1ns)"] L1["L1 Cache - SRAM\n(~KB, ~1ns)"] L2["L2 Cache - SRAM\n(~MB, ~4ns)"] L3["L3 Cache - SRAM\n(~MB, ~10ns)"] L4["Main Memory - DRAM\n(~GB, ~100ns)"] L5["Local Disk\n(~TB, ~10ms)"] L6["Remote Storage\n(Network, ~100ms+)"] L0 --> L1 --> L2 --> L3 --> L4 --> L5 --> L6
CấpLoạiTốc độDung lượngChi phí
L0CPU RegistersCực nhanh (<1ns)Vài bytesCao nhất
L1SRAM Cache~1ns~32KBRất cao
L2SRAM Cache~4ns~256KBCao
L3SRAM Cache~10ns~8MBTrung bình
L4DRAM (RAM)~100ns~GBThấp
L5HDD/SSD~ms~TBRất thấp
L6Remote/Network~100ms+Không giới hạnThấp nhất

7. Công Cụ Sử Dụng Trong Môn Học

Công cụMục đích
Linux (máy ảo/thật)Môi trường phát triển chính
GCCTrình biên dịch C trên Linux
GDBDebug ở mức assembly (command line)
IDA ProDịch ngược (reverse engineering) với giao diện đồ họa
ValgrindPhát hiện lỗi bộ nhớ
# Biên dịch và xem assembly bằng GCC
gcc -O0 -S hello.c -o hello.s    # xuất file assembly
gcc -o hello hello.c              # biên dịch thành file thực thi
objdump -d hello                  # disassemble file thực thi

# Debug với GDB
gdb ./hello
(gdb) disassemble main            # xem assembly của hàm main
(gdb) break main                  # đặt breakpoint
(gdb) run                         # chạy chương trình


Tóm Tắt – Tại Sao Môn Này Quan Trọng?