Đây là chuyên đề Operating System dành cho việc học backend và ôn tập phỏng vấn, tổng hợp kiến thức cơ bản về Operating System, process và thread, inter-process communication, lock và synchronization, memory management, virtual memory, zero-copy, I/O multiplexing, file system, Linux và Shell.
CPU scheduling không chỉ là tên các algorithm như FCFS, RR, CFS. Khi kiểm tra sự cố trong production, còn phải hiểu vì sao thread bị đưa khỏi CPU, chi phí của context switch nằm ở đâu, cũng như load average cao nhưng CPU usage thấp có ý nghĩa gì.
Đằng sau các vấn đề này là cùng một nhóm ràng buộc: số core CPU có hạn, task cần xếp hàng, scheduler chịu trách nhiệm quyết định task nào chạy trước; các metric hệ thống giúp xác định task đang tranh chấp CPU, chờ I/O, hay bị kẹt ở kernel, interrupt hoặc lớp virtualization.
Thread A đã lấy được resource 1, thread B cũng đã lấy được resource 2. Tiếp theo, thread A muốn tiếp tục chạy thì cần resource 2; thread B muốn tiếp tục chạy thì lại cần resource 1.
Hai thread đều không ném exception, cũng không phải CPU bị chạy hết công suất. Hiện tượng nhìn thấy trên production có thể chỉ là vài request không trả về trong thời gian dài, còn worker thread trong thread pool dần bị chiếm hết.
Cách trực quan nhất để viết một interface lưu file là: nhận path, open một file, write dữ liệu vào đó, cuối cùng close. Khi số lượng file ít, concurrency thấp và máy không gặp sự cố, quy trình này có vẻ không khó.
Nhưng khi phỏng vấn hỏi sâu hơn, vấn đề sẽ xuất hiện. fd mà open() trả về thực sự trỏ tới đâu? Tên file có nằm trong inode không? Vì sao hai hard link có thể nhìn thấy cùng một nội dung? write() trả về thành công có nghĩa là dữ liệu đã được ghi xuống disk chưa? File log đã bị xóa, vì sao df -h vẫn hiển thị disk đầy?
System call đi từ user mode vào kernel mode chỉ là điểm bắt đầu để hiểu đường đi này. Xem sâu hơn qua một lần read(), ta sẽ gặp một số vấn đề liên quan chặt chẽ:
read()đi vào kernel bằng cách nào?- Vì sao clock interrupt có thể khiến thread đang chạy dừng lại?
- Vì sao Page Fault đôi khi là hành vi bình thường, nhưng đôi khi lại trở thành
SIGSEGV? - System call đã vào kernel thì có nhất thiết xảy ra thread context switch không?
Viết một TCP server, cách trực quan nhất là thread chính accept một connection, rồi giao cho một thread mới read, xử lý và write. Khi số connection ít, cách này hoạt động rất tốt.
Nhưng một khi số connection tăng lên hàng chục nghìn, vấn đề sẽ xuất hiện. Trong không ít bản phân phối Linux, mỗi thread mới mặc định sẽ dự trù vài MB stack, cấu hình thường gặp là 8 MB (giá trị thực tế phụ thuộc vào ulimit -s, runtime library và thuộc tính thread). Dù stack page của mười nghìn connection được cấp phát theo nhu cầu, address space đã dự trù, stack page thực sự được dùng và metadata của thread cộng lại vẫn rất lớn; nghiêm trọng hơn là hàng nghìn, hàng chục nghìn thread cùng tranh nhau vài CPU core, chỉ riêng context switch giữa các thread đã ngốn hơn nửa CPU, thời gian thực sự làm việc còn lại chẳng bao nhiêu. Chưa kể phần lớn connection thực ra đang idle — mỗi connection chiếm một thread nhưng chỉ ngồi chờ dữ liệu.
Ý tưởng trực giác nhất khi hai process muốn trao đổi một đoạn dữ liệu là: process A ghi dữ liệu vào memory của mình, rồi process B đọc trực tiếp là xong.
Tuy nhiên, cách này không thực hiện được trong operating system. Mỗi process có một virtual address space độc lập; địa chỉ 0x7f... trong process A và địa chỉ 0x7f... trong process B không trỏ tới cùng một vùng memory. Các process ở user space không thể tùy ý truy cập memory của nhau; nếu không, isolation về quyền sẽ mất ý nghĩa.
Giới thiệu ngắn gọn một số khái niệm và lệnh Linux thường gặp mà lập trình viên Java cần biết.
Làm quen với Linux
Giới thiệu Linux
Có thể khái quát Linux là gì qua ba điểm sau:
- Hệ điều hành kiểu Unix: Linux là một operating system tự do, mã nguồn mở và tương tự Unix.
- Linux về bản chất là Linux kernel: Nói chính xác, từ Linux chỉ biểu thị Linux kernel; bản thân Linux kernel không thể trở thành một operating system hoạt động bình thường. Vì vậy mới có nhiều Linux distribution.
- Cha đẻ của Linux (Linus Benedict Torvalds): Một nhân vật huyền thoại trong lĩnh vực lập trình, một cao thủ thực thụ và hình mẫu đáng ngưỡng mộ. Ông là tác giả đầu tiên của Linux kernel, sau đó khởi xướng open-source project này và giữ vai trò kiến trúc sư chính của Linux kernel. Ông cũng khởi xướng Git, một open-source project, và là developer chủ chốt.
Khi xem thông tin memory của một process thông thường, bạn sẽ thấy nhiều con số trông khá trái với trực giác: process có virtual address space riêng, phạm vi địa chỉ có thể rất lớn; physical memory thực sự chiếm dụng lại là một chuyện khác; cùng một shared library còn có thể được nhiều process dùng chung.
Nhiều độc giả phàn nàn rằng kiến thức về hệ điều hành khá phức tạp, họ không có nhiều kiên nhẫn để đọc, nhưng khi phỏng vấn lại thường xuyên gặp phải. Vì vậy, tôi mang đến các câu hỏi thường gặp về hệ điều hành đã được tôi tổng hợp!
Bài viết 《Tổng hợp câu hỏi phỏng vấn hệ điều hành thường gặp (phần trên)》 sẽ bắt đầu từ kiến thức cơ bản về hệ điều hành, sau đó tập trung hệ thống hóa các trọng tâm thường gặp như user mode/kernel mode, system call, process và thread, IPC, process scheduling, deadlock. Bài viết phù hợp để nhanh chóng xây dựng danh sách câu hỏi phỏng vấn, đồng thời cũng thích hợp làm điểm bắt đầu để bổ sung kiến thức còn thiếu khi ôn tập.
