Bài 60.430 phút đọc
Level 4: Mini Shell
Sau bài này bạn sẽ làm được
- Tách token xử lý dấu nháy và thoát ký tự
- Nối nhiều tầng pipe và chuyển hướng vào ra
- Chạy nền, quản lý job, và bắt tín hiệu đúng
- Không rò rỉ mô tả tệp, dọn zombie bằng SIGCHLD
Mini Shell là dự án đầu tiên nói chuyện trực tiếp với hệ điều hành: fork, exec, pipe, dup2, và tín hiệu. Nó gói toàn bộ Phần 16 vào một chương trình mà bạn dùng nó để chạy các chương trình khác.
#Mục tiêu
terminal
./mysh
mysh$ ls -l | grep ".c" | wc -l 7 mysh$ cat data.txt > output.txt mysh$ sleep 30 & [1] 4821 mysh$ jobs [1] Running sleep 30 mysh$ cd /tmp && pwd /tmp mysh$ exit
| Chức năng | Lời gọi hệ thống | Chương |
|---|---|---|
| Chạy chương trình ngoài | fork, execvp, waitpid | Chương 40 |
| Đường ống giữa lệnh | pipe, dup2 | Chương 43 |
| Chuyển hướng vào ra | open, dup2 | Chương 41 |
| Chạy nền và job | fork không chờ, waitpid WNOHANG | Chương 40 |
| Ctrl+C, dọn zombie | sigaction, SIGINT, SIGCHLD | Chương 44 |
#Kiến trúc
Đọc dòng lệnh
fgetshoặc dùng thư việnreadlineđể có lịch sử và di chuyển con trỏ. Lịch sử lưu vào~/.mysh_history.Tách token
Chia dòng thành các từ, xử lý dấu nháy"...",'...', và thoát ký tự\. Đây là phần khó nhất, không phải phần fork.Phân tích cú pháp
Nhận ra|,>,<,>>,&,&&. Xây một cây hoặc danh sách các lệnh nối bằng ống.Lệnh nội trú hay ngoài
cd,exit,exportphải chạy trong chính tiến trình shell, không fork. Còn lại thì fork và exec.Thực thi
Với lệnh ngoài:fork, con thiết lập chuyển hướng rồiexec, cha chờ hoặc ghi nhận job nền.
#Pipe và chuyển hướng
Nối hai lệnh bằng pipe (POSIX)
/* ls | grep .c
pipe() tao mot cap mo ta tep: p[0] doc, p[1] ghi.
Noi dau RA cua ls vao dau VAO cua grep. */
int p[2];
pipe(p);
if (fork() == 0) { /* con thu nhat: ls */
dup2(p[1], STDOUT_FILENO); /* stdout -> dau ghi cua pipe */
close(p[0]); close(p[1]); /* dong ca hai ban sao */
execvp("ls", (char*[]){"ls", NULL});
_exit(127);
}
if (fork() == 0) { /* con thu hai: grep */
dup2(p[0], STDIN_FILENO); /* stdin <- dau doc cua pipe */
close(p[0]); close(p[1]);
execvp("grep", (char*[]){"grep", ".c", NULL});
_exit(127);
}
close(p[0]); close(p[1]); /* CHA phai dong CA HAI dau pipe */
wait(NULL); wait(NULL);| Chuyển hướng | Mở tệp thế nào | dup2 vào |
|---|---|---|
| > tep | open O_WRONLY O_CREAT O_TRUNC | STDOUT_FILENO |
| >> tep | open O_WRONLY O_CREAT O_APPEND | STDOUT_FILENO |
| < tep | open O_RDONLY | STDIN_FILENO |
| 2> tep | open O_WRONLY O_CREAT O_TRUNC | STDERR_FILENO |
| 2>&1 | không mở tệp | dup2 STDOUT vào STDERR |
#Tín hiệu và zombie
#Yêu cầu
Tự làm thử
- Tách token xử lý dấu nháy
"...",'...', và thoát\. - Pipe nhiều tầng không giới hạn.
- Chuyển hướng
>,>>,<,2>,2>&1. - Chạy nền
&, lệnhjobs,fg,bg. - Lệnh nội trú:
cd,pwd,exit,export,unset,history,help. - Biến môi trường, mở rộng
$VARvà~. Ctrl+Cgiết tiến trình con không giết shell;Ctrl+Dthoát.- Dọn zombie bằng bộ xử lý
SIGCHLD. - Không rò rỉ mô tả tệp, kiểm tra bằng
/proc/PID/fd. - Lịch sử lệnh lưu vào
~/.mysh_history.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Mini Shell là dự án POSIX; làm trên Linux, macOS, hoặc WSL vì cần
forkvàsigaction. - Lệnh nội trú như
cdphải chạy trong chính shell, không fork, vì chúng đổi trạng thái của shell. - Mỗi tiến trình phải đóng các đầu pipe không dùng; rò rỉ mô tả tệp làm shell treo hoặc hết fd.
- Shell bỏ qua
SIGINTcho chính nó, dùng nhóm tiến trình để Ctrl+C tới đúng lệnh, và dọn zombie bằngSIGCHLD. - Xong khi đường ống nhiều tầng chạy,
cdđổi thư mục, không rò rỉ fd, không zombie.