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ớ, Cache4. 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:
| Giai đoạn | Công cụ | Đầu vào | Đầu ra | Mô tả |
|---|---|---|---|---|
| Tiền xử lý | cpp | hello.c | hello.i | Xử lý #include, #define,… |
| Biên dịch | cc1 | hello.i | hello.s | Chuyển sang hợp ngữ |
| Hợp dịch | as | hello.s | hello.o | Chuyển sang mã nhị phân (object file) |
| Liên kết | ld | hello.o + printf.o | hello | Ghé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 int và float 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 kernelVấ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áp | Thờ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:
Khi chạy hello, chuỗi "hello, world\n" được:
- Đọc từ disk vào main memory
- 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ớ
| Cấp | Loại | Tốc độ | Dung lượng | Chi phí |
|---|---|---|---|---|
| L0 | CPU Registers | Cực nhanh (<1ns) | Vài bytes | Cao nhất |
| L1 | SRAM Cache | ~1ns | ~32KB | Rất cao |
| L2 | SRAM Cache | ~4ns | ~256KB | Cao |
| L3 | SRAM Cache | ~10ns | ~8MB | Trung bình |
| L4 | DRAM (RAM) | ~100ns | ~GB | Thấp |
| L5 | HDD/SSD | ~ms | ~TB | Rất thấp |
| L6 | Remote/Network | ~100ms+ | Không giới hạn | Thấ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 |
| GCC | Trình biên dịch C trên Linux |
| GDB | Debug ở mức assembly (command line) |
| IDA Pro | Dịch ngược (reverse engineering) với giao diện đồ họa |
| Valgrind | Phá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