Luyện Phỏng Vấn IT — 2000+ Câu Hỏi Phỏng Vấn IT Có Đáp Án 2026
Linux & OS
Process là đơn vị thực thi độc lập với không gian bộ nhớ riêng (virtual address space, heap, stack, file descriptors). Thread là đơn vị thực thi nhẹ hơn bên trong process: các thread cùng process chia sẻ heap/code/file descriptors nhưng có stack riêng. Tạo process tốn kém hơn thread (fork copy page tables), giao tiếp giữa process phức tạp hơn (IPC), nhưng cô lập tốt hơn — crash một process không ảnh hưởng process khác.
Multi-process phù hợp: Chrome (mỗi tab là process riêng để cô lập crash/security), Node.js cluster module để tận dụng CPU cores. Multi-thread phù hợp: ứng dụng cần chia sẻ dữ liệu nhiều và frequent (Java web server, game engine). Với Python, GIL (Global Interpreter Lock) ngăn true parallelism trong threads — phải dùng multiprocessing. Go goroutines là green threads cực nhẹ (~2KB stack), runtime scheduler map M goroutines lên N OS threads.
Context switching là quá trình OS lưu trạng thái CPU (registers, program counter, stack pointer) của process/thread hiện tại vào PCB (Process Control Block), rồi nạp trạng thái của process/thread kế tiếp để chạy.
Chi phí bao gồm:
- Thời gian lưu/nạp CPU state (~microseconds)
- Cache invalidation — khi switch process, CPU cache (L1/L2) bị flush, process mới phải warm up cache lại từ đầu (cold start)
- TLB flush khi switch process (vì địa chỉ ảo khác nhau). Thread switching trong cùng process nhẹ hơn vì không flush TLB. Ảnh hưởng thực tế: hệ thống với quá nhiều thread (C10K problem) tốn nhiều thời gian context switching hơn là làm việc thực. Đó là lý do Node.js single-threaded event loop và Go goroutines thắng trong high-concurrency — ít context switch hơn 1000 OS threads. Lập trình viên nên tránh: vòng lặp tight với
sleep(1ms), quá nhiều thread chờ I/O — dùng async/await thay thế
Các thuật toán scheduling khác nhau về cách ưu tiên process: FCFS đơn giản nhưng tệ với latency, Round Robin fair nhất, SJF tối ưu throughput nhưng gây starvation.
- FCFS (First-Come First-Served): process đến trước chạy trước — đơn giản nhưng convoy effect: một process dài block tất cả process ngắn phía sau, average waiting time cao.
- Round Robin (RR): mỗi process chạy một time quantum (10-100ms) rồi preempt — fair, responsive, phù hợp time-sharing systems; quantum quá nhỏ tốn context switch, quá lớn thành FCFS.
- SJF (Shortest Job First): chạy process có burst time ngắn nhất trước — tối ưu average waiting time nhưng starvation cho process dài, và không biết trước burst time trong thực tế.
- Priority Scheduling: mỗi process có độ ưu tiên, process ưu tiên cao chạy trước — có thể bị starvation (giải quyết bằng aging: tăng priority theo thời gian chờ).
- Multilevel Feedback Queue (Linux CFS): kết hợp nhiều queue, tự động điều chỉnh — đây là algorithm Linux kernel dùng, fair nhưng ưu tiên interactive process hơn batch.
Concurrency là khả năng xử lý nhiều task đồng thời về mặt logic — các task có thể interleave nhau (chạy một ít rồi nhường, rồi lại chạy) nhưng không nhất thiết cùng lúc trên nhiều CPU. Parallelism là thực sự chạy nhiều task cùng một lúc trên nhiều CPU core. Rob Pike (Go): 'Concurrency là về cấu trúc, Parallelism là về thực thi.'
Ví dụ: Node.js event loop là concurrent nhưng không parallel (single-threaded) — handle 10,000 I/O operations concurrently nhờ non-blocking I/O, nhưng chỉ 1 lúc chạy 1 JavaScript callback. Go runtime chạy goroutines concurrently VÀ parallel: GOMAXPROCS goroutines có thể thực sự chạy song song trên nhiều OS thread. Thực tế lập trình: concurrency giải quyết throughput (nhiều I/O operations); parallelism giải quyết CPU-bound tasks (image processing, ML inference). Async/await trong JavaScript/Python là concurrency pattern, không phải parallelism.
IPC là các cơ chế để các process độc lập trao đổi dữ liệu, vì chúng không chia sẻ bộ nhớ trực tiếp.
- Pipe (anonymous pipe): luồng dữ liệu một chiều giữa parent-child process.
- Named pipe (FIFO): pipe có tên trong filesystem, không cần parent-child relationship.
- Shared Memory: vùng nhớ được map vào address space của nhiều process — nhanh nhất vì không copy data, nhưng cần đồng bộ (mutex/semaphore).
- Message Queue: process gửi/nhận messages qua kernel queue (POSIX mqueue, System V) — asynchronous, có thể buffer.
- Socket: Unix domain socket (local) hoặc TCP/UDP socket (network) — linh hoạt nhất, dùng cả local và remote; microservices giao tiếp qua HTTP/gRPC là IPC qua socket.
- Signal: notification đơn giản (SIGTERM, SIGKILL, SIGUSR1) — limited data.
- Memory-mapped file (mmap): file được map vào memory, nhiều process cùng đọc/ghi — Node.js dùng cho large file processing.
Thực tế: Nginx worker processes giao tiếp với master qua pipe và shared memory.
Deadlock xảy ra khi nhiều process chờ nhau giải phóng resource mà không process nào có thể tiếp tục.
Coffman
- xác định 4 điều kiện cần thiết đồng thời:
- Mutual Exclusion: resource chỉ 1 process dùng tại một thời điểm
- Hold and Wait: process giữ ít nhất 1 resource và chờ thêm resource khác
- No Preemption: resource không thể bị lấy bắt buộc
- Circular Wait: tồn tại chuỗi vòng P1→P2→...→Pn→P1 chờ nhau
Phòng tránh bằng cách phá vỡ một điều kiện: Lock Ordering (phá Circular Wait): luôn acquire lock theo thứ tự cố định — ví dụ database luôn lock bảng A trước bảng B. Trylock với timeout (phá Hold and Wait): nếu không lấy được lock sau timeout thì release tất cả và retry. Banker's Algorithm: OS kiểm tra trước khi cấp resource. Thực tế: Java synchronized blocks, Go channel patterns, và database transaction isolation level đều phải xử lý deadlock — PostgreSQL phát hiện deadlock và rollback một transaction.
Mutex (Mutual Exclusion Lock) là binary lock: chỉ có 2 trạng thái locked/unlocked, và quan trọng là chỉ thread nào lock thì mới được unlock (ownership semantics). Dùng để bảo vệ critical section — chỉ 1 thread access vào một lúc.
Semaphore là counter: counting semaphore cho phép N thread cùng access (semaphore=N); binary semaphore (N=1) tương tự mutex nhưng không có ownership — thread A có thể signal semaphore mà thread B đã wait.
Dùng mutex khi: bảo vệ shared data (HashMap, linked list) — một thread đọc/ghi tại một thời điểm. Dùng semaphore khi: giới hạn concurrent access (connection pool tối đa 10 connections), producer-consumer synchronization (producer signal khi có item mới). Deadlock risk: mutex thường dùng recursive mutex để tránh self-deadlock. Go dùng sync.Mutex và sync.RWMutex (nhiều reader, 1 writer); channel thường là cách idiomatic hơn để synchronize trong Go.
Thread pool là tập hợp các worker threads được tạo sẵn và tái sử dụng, thay vì tạo/hủy thread mới cho mỗi task — vì tạo thread tốn ~1MB stack, time để OS allocate, và context switch overhead.
- Hoạt động: task queue nhận công việc, worker thread nhàn rỗi lấy task từ queue, thực thi, rồi chờ task tiếp theo.
- ThreadPoolExecutor (Java),
worker_threadspool (Node.js), Goroutine worker pattern (Go). - Sizing là bài toán quan trọng: với CPU-bound tasks, pool size = số CPU cores (thêm thread không giúp, chỉ tốn context switch); với I/O-bound tasks, pool size lớn hơn số CPU (vì thread chờ I/O không chiếm CPU) — formula tham khảo:
threads = cores * (1 + wait_time/service_time). - Pool quá nhỏ: task queue dài, high latency.
- Pool quá lớn: memory tốn kém, nhiều context switch, performance giảm.
- Java Tomcat default pool size 200; Node.js libuv I/O thread pool default 4 (UV_THREADPOOL_SIZE).
Virtual memory là abstraction layer giữa process và RAM vật lý: mỗi process có không gian địa chỉ ảo riêng (64-bit: 128TB), OS + MMU (Memory Management Unit) dịch virtual address sang physical address qua page table.
Lợi ích:
- Isolation: process A không thể đọc bộ nhớ process B dù cùng máy
- Overcommit: tổng virtual memory của tất cả process có thể vượt RAM thực (OS swap ít-dùng pages ra disk)
- Shared libraries: nhiều process share cùng physical pages của libc nhưng map vào virtual address space riêng
- Memory-mapped files (mmap): file map vào virtual memory, OS lazily load pages khi access
- Copy-on-Write (CoW) khi fork: parent và child share physical pages cho đến khi một bên write
Nhược điểm: page table overhead (~8MB per process với 4-level paging); TLB miss latency. Thực tế: docker stats show virtual memory rất lớn nhưng RSS (Resident Set Size) là RAM thực dùng; OOM Killer kill process dùng nhiều RAM nhất khi system hết memory.
Paging chia physical memory thành các frame kích thước cố định (4KB, 2MB, 1GB huge pages), và virtual memory thành pages cùng kích thước — OS map pages lên frames qua page table. Fragmentation chỉ là internal (page cuối có thể dư), không có external fragmentation. Segmentation chia memory thành các segment logic kích thước khác nhau (code, data, stack, heap) — phản ánh structure của program; có external fragmentation vì segment size biến động.
x86-64 trên thực tế dùng paging là primary mechanism: 4-level page table (PML4→PDPT→PD→PT) với page size 4KB mặc định; segmentation vẫn tồn tại nhưng flat segmentation (base=0, limit=max) — essentially disabled. Linux dùng huge pages (2MB/1GB) để giảm TLB pressure cho database workloads: PostgreSQL và Redis benefit từ transparent huge pages. Thực tế lập trình: stack overflow = page fault khi access stack guard page; segfault = access unmapped virtual address.
Memory leak xảy ra khi program allocate memory nhưng không release, dần dần RSS tăng cho đến khi OOM.
Nguyên nhân phổ biến:
- global/module-level variables tích lũy data.
- event listeners không được removeListener.
- closures capture large objects.
- circular references (trong ngôn ngữ reference-counted).
- unbounded cache/map.
- timer setInterval không clearInterval.
Trong Node.js: dùng node --inspect + Chrome DevTools Memory tab để heap snapshot và so sánh; clinic.js heapprofiler cho production; process.memoryUsage().heapUsed monitor; WeakMap/WeakRef cho cache để GC tự thu dọn khi key không còn reference.
Trong Go: goroutine leak (goroutine blocked trên channel mãi mãi, không bao giờ exit) là phổ biến hơn memory leak; dùng pprof heap/goroutine profiler; runtime.ReadMemStats để monitor.
Best practice: giới hạn size của in-memory cache; dùng context cancellation để goroutines tự cleanup; integration test monitor memory growth theo thời gian.
File system quản lý cách data được lưu trên storage: cấu trúc directory tree, metadata (inode: permissions/timestamps/size/data block pointers), free space management, journaling để tránh corruption khi crash.
ext4 (Linux): journaling filesystem, extents thay vì block maps, delayed allocation, dir_index (HTree), max file 16TB, max volume 1EB — workhorse của Linux servers, stable và battle-tested. NTFS (Windows): journaling, Master File Table (MFT), support NTFS permissions/ACL, Alternate Data Streams, compression/encryption built-in, max 16EB — nhưng Linux support read/write NTFS qua ntfs-3g/ntfs3. APFS (Apple): Copy-on-Write (không corrupt khi crash), native encryption, snapshots (Time Machine), clones (copy-on-write files — cp instant), space sharing giữa volumes trong container — optimized cho SSD/Flash.
Thực tế DevOps: Docker image layers dùng overlay2 filesystem; ZFS (FreeBSD/Linux) thêm checksums và RAID tích hợp cho NAS; volume mount giữa host và container cần match permissions.
Signal là cơ chế notification asynchronous mà OS hoặc process khác gửi đến process: SIGTERM (15, terminate request — default cho kill PID), SIGKILL (9, force kill — không thể catch/ignore), SIGINT (2, Ctrl+C), SIGHUP (1, terminal closed/reload config), SIGUSR1/SIGUSR2 (user-defined, Node.js dùng SIGUSR1 để enable debugger), SIGCHLD (child process terminated), SIGPIPE (write to broken pipe).
Graceful shutdown là critical trong production: khi nhận SIGTERM, server stop accepting new connections, drain in-flight requests, close DB connections, flush logs — rồi mới exit. Node.js graceful shutdown: process.on('SIGTERM', async () => { await server.close(); await db.disconnect(); process.exit(0); }).
Kubernetes gửi SIGTERM trước khi terminationGracePeriodSeconds (default 30s), sau đó gửi SIGKILL — phải handle SIGTERM để zero-downtime deploy. Go: signal.NotifyContext(ctx, syscall.SIGTERM) để propagate cancellation đến tất cả goroutines khi nhận signal.
Race condition xảy ra khi kết quả của chương trình phụ thuộc vào thứ tự/timing của các concurrent operations — thứ tự không được control → kết quả unpredictable. Ví dụ kinh điển (balance):
Thread 1: read balance=100, Thread 2: read balance=100
Thread 1: balance += 50 → 150, Thread 2: balance -= 30 → 70
Thread 1: write 150, Thread 2: write 70 (lost update!)
Expected: 100 + 50 - 30 = 120Detect: Go race detector (go test -race, go run -race); Java ThreadSanitizer; Helgrind (Valgrind tool); code review tìm shared mutable state không có synchronization. Ngăn chặn:
- Mutex/lock bảo vệ critical section;
- Atomic operations (
sync/atomicGo,java.util.concurrent.atomic); - Immutable data — không thể race nếu không ai write;
- Message passing thay vì shared state (Go channels: "Don't communicate by sharing memory, share memory by communicating");
- Database-level: optimistic locking (version column) hoặc pessimistic locking (SELECT FOR UPDATE);
- CAS (Compare-And-Swap) cho lock-free algorithms
Monitor là high-level synchronization construct: object chứa mutex (đảm bảo mutual exclusion) + condition variables (cho phép threads wait/notify). Producer-Consumer problem: producer thêm item vào bounded buffer, consumer lấy ra; khi buffer đầy producer phải chờ; khi buffer rỗng consumer phải chờ. Implement với Java (built-in monitor):
synchronized void produce(Item item) {
while (buffer.isFull()) wait(); // release lock, wait
buffer.add(item);
notifyAll(); // wake up waiting consumers
}
synchronized Item consume() {
while (buffer.isEmpty()) wait();
Item item = buffer.remove();
notifyAll(); // wake up waiting producers
return item;
}Go implementation thường dùng channel:
ch := make(chan Item, bufferSize)
// Producer
ch <- item
// Consumer
item := <-chGo channel built-in implement producer-consumer pattern với blocking semantics. Semaphore approach: dùng counting semaphore empty (=N) và full (=0) + mutex — classic Dijkstra solution. Java BlockingQueue: production-ready implementation.
select/poll là O(n) scan không scale; epoll (Linux) và kqueue (BSD) dùng kernel-managed event notification, O(1) per event, scale đến hàng triệu FDs; io_uring là next-gen zero-copy async I/O.
select (POSIX): monitor tối đa 1024 FDs (FD_SETSIZE), truyền full set mỗi syscall (O(n) scan), không biết FD nào ready phải scan toàn bộ, không scale. poll: tương tự select nhưng không giới hạn 1024 FDs (dùng dynamic array), vẫn O(n) scan. epoll (Linux 2.6+): kernel-side interest list — epoll_create, epoll_ctl(ADD/MOD/DEL), epoll_wait chỉ trả về ready FDs (O(1) per event). Edge-triggered (ET) mode: notify chỉ khi state change → must drain toàn bộ; Level-triggered (LT, default): notify khi FD vẫn readable/writable → simpler. kqueue (FreeBSD/macOS): tổng quát hơn epoll — kevent struct monitor FDs, signals, timers, processes, sockets; kevent() syscall combine register và wait. Không phải chỉ network — có thể monitor file changes (không cần inotify riêng). io_uring (Linux 5.1+): next-gen async I/O — submission queue + completion queue, zero-copy, batch syscalls, giảm context switch drastically. Đang được adopt bởi Rust tokio, Node.js 18+.
MLFQ ưu tiên interactive processes bằng cách tự động hạ priority của CPU-bound tasks; Linux CFS thay thế MLFQ bằng vruntime tracking để đảm bảo công bằng mà vẫn ưu tiên interactive.
MLFQ: nhiều queue với priority khác nhau (Q0 highest priority, Q_n lowest). Rules: process mới vào Q0 (highest); nếu dùng hết time quantum → demote xuống queue thấp hơn (CPU-bound); nếu yield trước hết quantum (I/O-bound) → giữ hoặc promote; priority boost định kỳ (tất cả lên Q0) để tránh starvation. Kết quả: interactive processes (editor, browser) luôn responsive; CPU-bound background tasks chạy khi không có interactive job. Linux CFS (Completely Fair Scheduler): không dùng fixed time quantum — track vruntime (virtual runtime) của mỗi process, chọn process có vruntime nhỏ nhất để chạy tiếp. Process I/O-bound có vruntime nhỏ (ít chạy) → được ưu tiên cao khi wakeup. Nice value (-20 đến +19) tăng/giảm vruntime growth rate. Real-time scheduling: SCHED_FIFO, SCHED_RR cho realtime processes — preempt CFS processes. Practical: nice, renice command điều chỉnh priority; chrt set realtime policy; container CPU limits dùng CFS bandwidth controller (cpu.cfs_quota_us).
File descriptor là abstraction cốt lõi của Linux I/O; pipe là unidirectional IPC qua kernel buffer; /proc là virtual FS cho phép introspect kernel state — ba concept liên kết chặt với nhau.
File Descriptor (FD): là integer đại diện cho open file/socket/pipe/device. Process bắt đầu với 3 FDs: 0 (stdin), 1 (stdout), 2 (stderr). ulimit -n show max FDs per process (default 1024, production nên tăng lên 65536+). Leak FD: process mở file/socket nhưng không close → FD exhaustion → Too many open files. Check: lsof -p PID | wc -l. Pipe: anonymous pipe | trong shell — kernel buffer (thường 65536 bytes), unidirectional, một đầu write (stdout của process A), một đầu read (stdin của process B). Named pipe (FIFO): tồn tại trong filesystem, không cần parent-child. /proc filesystem: virtual filesystem kernel expose information: /proc/PID/fd/ (FDs của process), /proc/PID/maps (memory mapping), /proc/PID/status (process info), /proc/meminfo (RAM usage), /proc/cpuinfo, /proc/net/tcp (TCP connections). Mọi thứ là file — cat /proc/1/cmdline là command của init process. Docker stats đọc từ /proc và cgroups.
Linux process có 5 trạng thái chính: R (running), S (interruptible sleep), D (uninterruptible — không thể kill), Z (zombie), T (stopped) — trạng thái D là dấu hiệu storage issue nghiêm trọng.
Xem process state bằng ps aux hoặc top (cột STATUS/S):
- R (Running/Runnable): đang chạy trên CPU hoặc trong run queue chờ CPU
- S (Sleeping/Interruptible sleep): đang wait (I/O, event, timer) nhưng có thể bị interrupt bởi signal — phổ biến nhất
- D (Disk sleep/Uninterruptible sleep): đang wait I/O không thể interrupt — kernel dùng để bảo vệ critical disk I/O operation. Process ở trạng thái D không respond SIGTERM/SIGKILL — không thể kill! Phổ biến khi NFS mount bị lỗi, disk hang, hoặc storage timeout. Quá nhiều D processes là dấu hiệu storage subsystem có vấn đề
- Z (Zombie): process đã exit nhưng parent chưa wait() để thu dọn. Process zombie không tốn CPU/memory nhưng tốn PID slot. Dấu hiệu parent không handle SIGCHLD
- T (Stopped): bị dừng bởi signal (SIGSTOP, SIGTSTP = Ctrl+Z) hoặc debugger. Resume bằng SIGCONT
- I: Idle kernel thread (không phải running, không phải sleeping)
Debug: ps aux | grep ' D ' tìm D processes; strace -p PID xem syscall nào đang block.
Khi process A gọi fork(), OS tạo process B là bản copy của A. Nếu copy toàn bộ memory ngay lập tức thì tốn kém và lãng phí (nhất là khi child chỉ exec một program khác sau fork). Copy-on-Write (CoW): sau fork, parent và child share cùng physical memory pages, đều chỉ có read-only mapping. Khi một bên (thường là child) write vào page → hardware fault → OS copy chỉ page đó ra một bản riêng → write vào bản copy. Pages không bị write thì không bao giờ copy — save memory và time.
Tại sao quan trọng với Redis RDB snapshot: khi Redis fork một child process để ghi RDB snapshot ra disk, child và parent share toàn bộ memory via CoW. Child đọc data để serialize (không write). Parent tiếp tục serve requests và write. Mỗi page parent write → copy của page đó. Nếu Redis đang write nhiều trong khi snapshot → nhiều page bị copy → memory tăng đột biến (used_memory + used_memory_rss diverge).
Monitoring: rdb_last_bgsave_time_sec, latest_fork_usec, Redis INFO stats. Tip: Redis fork nhanh nhất khi không có memory fragmentation và THP disabled.
Inode chứa metadata file (không phải tên); hard link là alias đến cùng inode; symlink là pointer đến path — khác nhau về behavior khi target bị xóa và khi cross-filesystem.
Inode (Index Node): metadata của file trong filesystem — chứa permissions, owner, timestamps, file size, block pointers (địa chỉ data blocks trên disk). KHÔNG chứa filename! Filename chỉ là entry trong directory trỏ đến inode number. stat file.txt hiển thị inode info. df -i xem inode usage (có thể hết inodes dù còn disk space). Hard link: tạo thêm directory entry trỏ đến cùng inode. ln file.txt hardlink.txt. Cả hai file là same data, same inode, same permissions. Xóa một file không xóa data — data chỉ xóa khi link count = 0 (tất cả hard links bị xóa). Không thể hard link qua filesystem khác hay directory. Symbolic link (symlink): là special file chứa path đến target. ln -s /path/to/target symlink. Symlink có inode riêng. Nếu target bị xóa → dangling symlink. Có thể symlink qua filesystem và directory. Ứng dụng: /etc/nginx/sites-enabled/ chứa symlinks đến /etc/nginx/sites-available/ — enable/disable site bằng cách tạo/xóa symlink. current symlink → deployment rollback.
Linux page cache là phần RAM kernel dùng để cache file content từ disk — khi đọc file lần đầu, kernel load data vào page cache; lần sau đọc cùng file sẽ phục vụ từ RAM (không động đến disk). Write-back: write vào file chỉ update page cache (dirty pages), kernel flush xuống disk theo định kỳ hoặc khi fsync() được gọi.
Vì vậy write thường nhanh (vào RAM), nhưng data chưa an toàn trên disk cho đến khi flush. Tại sao I/O nhanh: vì page cache absorb phần lớn I/O — database với shared_buffers nhỏ vẫn nhanh vì OS page cache bù đắp. free -h hiển thị buff/cache = page cache size. vmstat bi/bo = block in/out (disk I/O thực sự). Drop cache để benchmark thực sự:
sync; echo 3 > /proc/sys/vm/drop_cacheswrite-back vs write-through: write-back (default) = nhanh nhưng có thể mất data nếu power fail; write-through (O_SYNC hoặc fsync) = chậm hơn nhưng durable.
Database (PostgreSQL, MySQL) tự gọi fsync tại write-ahead log để đảm bảo durability.
GC tự động reclaim memory không còn được reference. Thuật toán cơ bản: Mark-and-Sweep (mark reachable objects từ roots, sweep unmarked); Reference Counting (Python: mỗi object đếm số references, về 0 thì free — không xử lý được circular references nên có cyclic GC bổ sung).
JVM (G1GC/ZGC): generational GC (young gen/old gen/metaspace) vì hầu hết objects chết sớm; minor GC thường xuyên cho young gen; major/full GC cho old gen — là nguồn Stop-The-World pause. ZGC (experimental từ Java 11, production-ready từ Java 15) và Shenandoah: concurrent GC với pause < 1ms dù heap hàng trăm GB.
Go GC: non-generational, concurrent tri-color mark-and-sweep; GOGC=100 trigger GC khi heap tăng gấp đôi; target < 1ms STW.
V8 (Node.js): generational, Orinoco concurrent GC; young gen dùng Scavenge (semi-space copy); old gen dùng Mark-Compact.
Thực tế: GC pressure là vấn đề performance — giảm bằng cách tái sử dụng objects (Go sync.Pool), giảm allocation trong hot path, tune heap size.
Linux có 5 I/O model, từ đơn giản nhất đến hiệu suất cao nhất:
- Blocking I/O: syscall (read/write) block thread cho đến khi data sẵn sàng — đơn giản nhưng thread không làm gì được trong lúc chờ.
- Non-blocking I/O: syscall trả về EAGAIN ngay nếu chưa có data — application phải poll liên tục (busy waiting, tốn CPU).
- I/O Multiplexing (select/poll/epoll): một thread monitor nhiều file descriptors, block trên select/epoll_wait cho đến khi ít nhất 1 fd ready — đây là nền tảng của event loop (Node.js, Nginx).
- Signal-driven I/O (SIGIO): kernel gửi signal khi fd ready — khó dùng, hiếm gặp.
- Async I/O (AIO/io_uring): initiate I/O và tiếp tục làm việc khác, được notify qua callback/completion queue khi xong — truly async, không block.
io_uring (Linux 5.1+) là next-gen: zero-copy, batching syscalls, giảm context switch đáng kể; Node.js 18+ đang integrate. Thực tế: Node.js libuv dùng epoll (Linux)/kqueue (macOS)/IOCP (Windows); Java NIO/Netty cũng dùng epoll; Go runtime có custom network poller trên epoll/kqueue.
VM (Virtual Machine) dùng hypervisor (VMware, KVM, VirtualBox) virtualize toàn bộ hardware, mỗi VM có OS kernel riêng — strong isolation, có thể chạy Windows trên Linux host; overhead: mỗi VM tốn hàng GB RAM cho OS, boot time hàng phút. Container share kernel của host OS, chỉ isolate userspace — nhỏ hơn (MB thay vì GB), start trong milliseconds, dense packing (hàng trăm containers/host).
Container không phải magic: đó chỉ là Linux process với namespace isolation và cgroup resource limits. Docker thực ra tạo:
- Namespaces: PID namespace (container có PID 1 riêng), Network namespace (interface riêng), Mount namespace (filesystem riêng), UTS (hostname riêng), IPC, User namespace
- cgroups giới hạn CPU/memory/disk I/O/network
- Union filesystem (overlay2) cho image layers.
docker runthực sự gọiclone(CLONE_NEWPID | CLONE_NEWNET | ...)
Bảo mật: container escape là lỗ hổng khai thác syscall để thoát namespace; privileged container nguy hiểm vì bỏ qua nhiều restriction.
Linux namespaces cung cấp process isolation, cgroups giới hạn resource usage — kết hợp là nền tảng của container; Kubernetes dùng cả hai để implement Pod isolation và CPU/memory limits.
Namespaces cung cấp isolation view: mỗi process thấy một view riêng của system resources. Linux có 8 namespace types: PID (process IDs), Network (interfaces, routing tables), Mount (filesystem mounts), UTS (hostname), IPC (shared memory, semaphores), User (UID/GID mapping), Cgroup, Time. Khi tạo container, Docker/containerd tạo new namespace set, clone process vào đó — process chỉ thấy resources trong namespace của nó.
cgroups (control groups) giới hạn và accounting resource usage: cpu (shares, quota, period), memory (limit, swap), blkio (I/O bandwidth), network (tc), pids (max processes).
Kubernetes Pod là nhóm containers chia sẻ Network namespace (cùng IP, cùng localhost) và IPC namespace — đó là sao containers trong cùng Pod giao tiếp qua localhost. Kubelet cấu hình cgroups per-container dựa trên resources.requests/limits: CPU limit dùng cpu.cfs_quota_us, Memory limit dùng memory.limit_in_bytes — exceed memory limit → OOMKilled; throttle CPU khi vượt quota. cgroups v2 (systemd default) unified hierarchy, better accounting, được Kubernetes adopt từ v1.25.
Readers-Writers problem: nhiều reader có thể đọc đồng thời (read không conflict), nhưng writer cần exclusive access (writer conflict với cả reader và writer khác). Solution với RWMutex: reader acquire read-lock (shared), writer acquire write-lock (exclusive). Go:
var mu sync.RWMutex
// Reader:
mu.RLock()
defer mu.RUnlock()
// Writer:
mu.Lock()
defer mu.Unlock()Trade-offs:
- Reader-priority (First Readers-Writers): ưu tiên reader, writer có thể starvation nếu liên tục có reader mới
- Writer-priority (Second Readers-Writers): ưu tiên writer, reader có thể starvation
- Fair (Third Readers-Writers): queue-based, FIFO — không starvation
Go sync.RWMutex implement writer-priority: khi writer đang chờ, new readers bị block để tránh writer starvation. Java ReentrantReadWriteLock cho phép chọn fairness policy. Khi nào dùng RWMutex: read-heavy workloads với ít write (config cache, in-memory index). Overhead cao hơn regular Mutex — nếu write/read ratio cao, regular Mutex có thể nhanh hơn.
Khi Linux kernel không còn free memory (và không thể swap), OOM (Out-of-Memory) Killer được kích hoạt để kill một process và giải phóng memory. Cách OOM Killer chọn victim: tính điểm oom_score cho mỗi process (0-1000) — dựa trên: RSS (physical memory dùng), swap usage, thời gian chạy (lâu hơn → điểm thấp hơn, less likely to kill), nice value.
Process có oom_score cao nhất bị kill. Bảo vệ process quan trọng:
# Giảm oom_score_adj → khó bị kill hơn
echo -1000 > /proc/$(pgrep redis-server)/oom_score_adj
# -1000 = không bao giờ bị kill (chỉ root)
# Xem score hiện tại
cat /proc/$(pgrep nginx)/oom_scoreKubernetes: oom_score_adj được set tự động theo QoS class: Guaranteed pods (-997), Burstable (2-999), BestEffort (1000 = bị kill đầu tiên). Best practices: set memory limits hợp lý trong K8s; monitor /proc/meminfo, MemAvailable; dùng cgroups memory.limit_in_bytes để limit per-container thay vì để OOM Killer action ở system level.