The Linux Programming Interaface
C20: Signal - fundametal concepts
Định nghĩa
Signal là tín hiệu Kernel sử dụng để gửi đến cho một process, hoặc cũng có thể là một tín hiệu được gửi từ một process sang process khác. Trong một số trường hợp, process có thể gửi "signal" đến chính nó. Quá trình "send" một signal được "response" bởi process bằng một loạt các hành động, gọi là "signal reactions".
Signal được đánh dấu bằng các số nguyên trong signal.h. Một số các signal được định nghĩa mặc định các "reaction". Các reaction này có thể định nghĩa lại bằng các "user signal handler" bởi programmer -> hoạt động này gọi là "disposition".
Gửi signal bằng "kill" system call
Cơ bản, kill() có thể gửi một tín hiệu bất kì đến một hoặc một nhóm process. Nhưng tại sao lại được đặt tên là kill()?
C24: Process Creation
The fork() creates a new process, the child, which is an almost exact duplicate of the calling process, the parent.
Conceptually, we can consider fork() as creating copies of the parent’s text, data, heap, and stack segments -> Trong một số trường hợp, quá trình copy toàn bộ dữ liệu này sẽ wasteful. Đặc biệt khi child
process gọi đến các hàm exec()
, exec()
tiến hành thay thế toàn bộ các vùng nhớ của parrent
process bằng vùng nhớ mới -> xuất hiện kỹ thuật copy-and-write trong quá trình copy dữ liệu.
Ngoài ra, để tránh lãng phí trong quá trình copy dữ liệu, system call vfork() mới được phát triển, đặc biệt sử dụng trong các trường hợp child
process gọi đến exec()
. Two features distinguish the vfork() system call from fork() and make it more efficient:
- No duplication of virtual memory pages or page tables is done for the child process. Instead, the child shares the parent’s memory until it either performs a successful exec() or calls _exit() to terminate.
- Execution of the parent process is suspended until the child has performed an exec() or _exit().
C25: Process Termination
There are two ways to terminate a process
- Abnormal: a termination signal is sent to process.
- Normal: process calls
_exit()
itself._exit()
là hàm được định nghĩa trong thư viện chuẩn C, trực tiếp gọi đến các hàm termination process của Linux.
Exit status
Refer Exit Status in gnu.org.
EXIT_SUCCESS
& EXIT_FAILURE
được định nghĩa trong stdlib.h
được sử dụng là các giá trị trả về khi terminate một process.
_exit()
vs exit()
Trong một số chương trình bình thường, _exit()
không được gọi trực tiếp mà được gọi thông quan exit()
. Về cơ bản, exit()
thực hiện một số tác vụ trước khi gọi _exit()
ví dụ như: flush out stdio buffer.
exit()
vs return n
Việc gọi đến hàm return n
trong hàm main()
tương đương với quá trình gọi đến exit()
.
C26: Monitoring Child Processes
Wait for child process(es)
The system call wait() waits for one of the children of the calling process to terminate and returns the termination status of that child in the buffer pointed to by status.
wait()
được sử dụng để chờ tất cả các child
process terminate. Trường hợp thành công wait()
trả về giá trị PID của child
process vừa mới terminate vaf trả về -1 trong trường hợp lỗi, bao gồm các trường hợp lỗi sau:
- Không còn một
child
process nào đang tồn tại. Khi đóECHILD
được thiết lập choerrno
-> đặc điểm này được sử dụng để monitor tất cả cácchild
process bằng đoạn mã dưới đây:
while ((childPid = wait(NULL)) != -1)
continue;
if (errno != ECHILD) /* An unexpected error... */
errExit("wait");
- Unexpected Error
Trong trường hợp các child
processes cùng terminate một thời điểm, thứ tự trả về của wait()
là không xác định.
Wait for specific child process
wait()
có một số giới hạn, trong đó không thể chờ một child
process xác định. waitpid() được thiết kế để thực hiện được điều đó.
pid_t waitpid(pid_t pid, int *status, int options);
Trong trường hợp tham số pid được thiết lập là -1 thì waitpid()
tương đương với wait()
.
Orphans and Zombies
Một child
process được gọi là Orphaned khi parrent
của process đó terminate trước. Khi đó "orphaned child" được quản lý bởi init
process.
Zombie là trạng thái của một child
process trong trường hợp process đó terminate nhưng parrent
process chưa kịp gọi đến wait()
. Sau khi terminate, các tài nguyên của child
đã được giải phóng nhưng thông tin về process đó vẫn tồn tại trong Kernel's process table cho đến khi parrent
gọi đến wait()
.
Khi có quá nhiều "zombie process" trong hệ thông sẽ làm đầy Kernel's process table và có thể cản trở quá trình tạo mới một process. Do "zombie process" không thể bị kill bởi các signal nên quá trình loại bỏ các process này phải chờ đến khi parrent
process exit. Khi đó, init
process tiếp nhận các zombie này như các "orphaned process" và tiến hành terminate (*).
*But how init
kill zombies?