后台和挂起进程 - 在 C 中实现作业控制 Shell
Background and suspended processes - Implementing a Job Control Shell in C
我正在 Linux 中用 C 实现作业控制 Shell 作为操作系统相关主题的项目。我有一个执行子进程管理的 main() 函数,通过链接列表提供帮助,如下所示,其中存储了后台和挂起的作业信息:
typedef struct job_
{
pid_t pgid; /* group id = process lider id */
char * command; /* program name */
enum job_state state;
struct job_ *next; /* next job in the list */
} job;
每次子进程退出或停止时,都会向父进程发送一个 SIGCHLD 以通知该情况。然后,我有一个信号处理程序,如此处所示,对于该作业状态链表的每个节点,检查该节点中表示的进程是否已退出,如果退出,则从链表中删除该节点。
这是 SIGCHLD 处理程序的代码,其中 'job_list' 是存储信息的链表:
void mySIGCHLD_Handler(int signum) {
block_SIGCHLD();
if (signum == 17) {
job *current_node = job_list->next, *node_to_delete = NULL;
int process_status, process_id_deleted;
while (current_node) {
/* Wait for a child process to finish.
* - WNOHANG: return immediately if the process has not exited
*/
waitpid(current_node->pgid, &process_status, WNOHANG);
if (WIFEXITED(process_status) != 0) {
node_to_delete = current_node;
current_node = current_node->next;
process_id_deleted = node_to_delete->pgid;
if (delete_job(job_list, node_to_delete)) {
printf("Process #%d deleted from job list\n", process_id_deleted);
} else {
printf("Process #%d could not be deleted from job list\n", process_id_deleted);
}
} else {
current_node = current_node->next;
}
}
}
unblock_SIGCHLD();
}
问题是,当处理程序被调用时,一些不应该被删除的条目因为它们代表的进程没有退出,在它们不应该被删除的时候被删除了。有人知道为什么会这样吗?
谢谢你,很抱歉让你浪费时间:(
我看到这段代码中有很多问题,但最直接的问题可能在这里:
waitpid(current_node->pgid, &process_status, WNOHANG);
if (WIFEXITED(process_status) != 0) {
时waitpid(pid, &status, WNOHANG)
returns因为进程还没有退出,所以没有向status
写入任何东西,所以后面的if
是垃圾分支。在假设 status
有意义之前,您需要检查 waitpid
的实际 return 值。
其他最重要的问题是:
内核只允许发送一个SIGCHLD
告诉你有几个进程已经退出。当你得到一个 SIGCHLD
时,你需要循环调用 waitpid(0, &status, WNOHANG)
直到它告诉你没有更多的进程等待,你需要处理(没有双关语意) 它告诉您的所有 个已退出的进程 ID。
从异步信号处理程序调用 printf
或 free
是不安全的。相反,将终止的进程添加到延迟任务列表中。确保在使用该列表的主循环代码中阻止 SIGCHLD。
不要在处理程序中自己阻止和解除阻止SIGCHLD
;有一个不可避免的竞争条件。相反,让内核通过正确设置信号处理程序以原子方式为您完成:使用 sigaction
并且不要将 SA_NODEFER
放在 sa_flags
中。 (不要 将 SA_RESTART
放在 sa_flags
中,除非你有充分的理由不这样做。)
文字数字 17 应该是信号常数 SIGCHLD
。 一些 信号编号在整个历史上所有 Unix 上都是稳定的,但 SIGCHLD
不是其中之一。
我正在 Linux 中用 C 实现作业控制 Shell 作为操作系统相关主题的项目。我有一个执行子进程管理的 main() 函数,通过链接列表提供帮助,如下所示,其中存储了后台和挂起的作业信息:
typedef struct job_
{
pid_t pgid; /* group id = process lider id */
char * command; /* program name */
enum job_state state;
struct job_ *next; /* next job in the list */
} job;
每次子进程退出或停止时,都会向父进程发送一个 SIGCHLD 以通知该情况。然后,我有一个信号处理程序,如此处所示,对于该作业状态链表的每个节点,检查该节点中表示的进程是否已退出,如果退出,则从链表中删除该节点。 这是 SIGCHLD 处理程序的代码,其中 'job_list' 是存储信息的链表:
void mySIGCHLD_Handler(int signum) {
block_SIGCHLD();
if (signum == 17) {
job *current_node = job_list->next, *node_to_delete = NULL;
int process_status, process_id_deleted;
while (current_node) {
/* Wait for a child process to finish.
* - WNOHANG: return immediately if the process has not exited
*/
waitpid(current_node->pgid, &process_status, WNOHANG);
if (WIFEXITED(process_status) != 0) {
node_to_delete = current_node;
current_node = current_node->next;
process_id_deleted = node_to_delete->pgid;
if (delete_job(job_list, node_to_delete)) {
printf("Process #%d deleted from job list\n", process_id_deleted);
} else {
printf("Process #%d could not be deleted from job list\n", process_id_deleted);
}
} else {
current_node = current_node->next;
}
}
}
unblock_SIGCHLD();
}
问题是,当处理程序被调用时,一些不应该被删除的条目因为它们代表的进程没有退出,在它们不应该被删除的时候被删除了。有人知道为什么会这样吗?
谢谢你,很抱歉让你浪费时间:(
我看到这段代码中有很多问题,但最直接的问题可能在这里:
waitpid(current_node->pgid, &process_status, WNOHANG);
if (WIFEXITED(process_status) != 0) {
时waitpid(pid, &status, WNOHANG)
returns因为进程还没有退出,所以没有向status
写入任何东西,所以后面的if
是垃圾分支。在假设 status
有意义之前,您需要检查 waitpid
的实际 return 值。
其他最重要的问题是:
内核只允许发送一个
SIGCHLD
告诉你有几个进程已经退出。当你得到一个SIGCHLD
时,你需要循环调用waitpid(0, &status, WNOHANG)
直到它告诉你没有更多的进程等待,你需要处理(没有双关语意) 它告诉您的所有 个已退出的进程 ID。从异步信号处理程序调用
printf
或free
是不安全的。相反,将终止的进程添加到延迟任务列表中。确保在使用该列表的主循环代码中阻止 SIGCHLD。不要在处理程序中自己阻止和解除阻止
SIGCHLD
;有一个不可避免的竞争条件。相反,让内核通过正确设置信号处理程序以原子方式为您完成:使用sigaction
并且不要将SA_NODEFER
放在sa_flags
中。 (不要 将SA_RESTART
放在sa_flags
中,除非你有充分的理由不这样做。)文字数字 17 应该是信号常数
SIGCHLD
。 一些 信号编号在整个历史上所有 Unix 上都是稳定的,但SIGCHLD
不是其中之一。