【发布时间】:2017-04-29 03:54:32
【问题描述】:
我想知道在 C 中的信号上执行处理程序后究竟会发生什么,更具体地说是在 SIGCHLD 信号上。
我实际上是在构建一个 shell,我需要这样做:
- 用户输入带有“&”作为最后一个参数的命令
- 使用 fork 创建一个新进程
- 父进程返回主函数,等待用户的新输入
- 子进程在后台执行
- 子进程结束,触发SIGCHLD
- 父进程处理他孩子的死亡
我的代码完成了所有这些工作,但在处理函数返回时,其行为在最后表现得很奇怪。
我的实现
主循环
int
main()
{
// SIGNALS HANDLING :
sigaction(SIGTERM, NULL, NULL);
sigaction(SIGQUIT, NULL, NULL);
// Current location
char* path = malloc(PATHMAX);
int argc;
char** argv;
// main loop
while(1)
{
getcwd(path, PATHMAX);
printf("%s$ ",path);
argc = 0;
// Parsing
argv = malloc(MAX_INPUT); // user's input is limited to 100000 characters
getParameters(argv, &argc);
// First we check if the user wants to execute the task in background
if ( strcmp(argv[argc-1],"&") == 0 ) {
// builtin functions can't be executed in background:
int isExit = (strcmp(argv[0],"exit") == 0) ? 1 : 0;
int isCd = (strcmp(argv[0],"cd") == 0) ? 1 : 0;
if(isExit || isCd) {
printf("Built-in functions can't be executed in background. \n");
} else {
// Execute the job in background
// First we delete the last arg
argv[argc-1] = NULL;
argc--;
execBackground(argv,argc);
}
} else {
// Execute built-in functions
if(!(exeBuiltin(argv, argc))) {
// Execute jobs if no builtin functions were executed
exeJob(argv,argc);
}
}
free(argv);
}
return 0;
}
用户的输入处理
// max 100000 characters
char input[MAX_INPUT];
// read keyboard inputs
fgets(input,MAX_INPUT,stdin);
内置函数(cd 和 exit)
int exeBuiltin(char** argv, int argc) {
// execute builtin functions if it exists
char* builtin[2];
builtin[0] = "exit";
builtin[1] = "cd";
// run through the arguments
for(int i = 0; i < argc; i++) {
// exit
if (strcmp(argv[i],builtin[0]) == 0) {
exitBuiltin(EXIT_SUCCESS);
} else if (strcmp(argv[i],builtin[1]) == 0) {
// cd
if (argc >= 2) {
cdBuiltin(argv[i+1]);
return 1;
}
}
}
return 0;
}
static void cdBuiltin(char* path) {
if(chdir(path) == -1) {
printf("%s\n",strerror(errno));
};
}
static void exitBuiltin(int signal) {
exit(signal);
}
信号和处理程序之间的链接:
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = child_handler;
sigaction(SIGCHLD, &sa, NULL);
处理程序:
static void child_handler(int sig)
{
int status;
/* kills process
pid is a global variable that is set when I call fork() */
if(waitpid(pid, &status, 0) != -1) {
if (status == 0) // Verify child process terminated without error.
{
// success
printf("\nBackground job exited with code 0\n");
}
if (status == 1)
{
// error
printf("\nBackground job exited\n");
}
}
return;
}
问题
当我调用类似以下内容时会出现问题:
sudo apt-get update &
cd ../
然后当 sudo 命令终止时(调用处理程序并打印“后台作业退出,代码 0”),即使命令“cd ../”已经执行,它也会被执行再次 .
输出看起来像:
$ /home/ubuntu/workspace$ sudo apt-get update &
$ /home/ubuntu/workspace$ cd ../
$ /home/ubuntu$
Background job exited with code 0
$ /home$
附加信息:
cd 是一个内置函数,它只执行:chdir(path) 并给出路径,并且在 main 中每个循环的开头(在调用命令之后)简单地输出带有 printf("%s$",path) 的路径。
问题
为了了解真正的问题是什么,我相信我应该了解在我的 child_handler 函数结束时会发生什么。
如果我在主进程的代码中的某个点,比如等待用户输入,而后台进程终止,则调用 child_handler,然后呢?它是否改变了主进程的任何内容,或者它仍然是代码中的同一点,等待输入?
感谢您的帮助。
【问题讨论】:
-
不确定 hat 是否能解决您的问题(因为我看不到代码),但大多数阻塞系统调用可以在被信号中断时由系统重新启动(取决于 SA_RESTART 标志)。
-
你的 shell 将如何处理在后台运行 2 个或更多命令的 shell 脚本(例如
long-running-command file1.txt & long-running-command file2.txt & long-running-command file3.txt)?那么管道(ls | grep -v 'xy.*t' | wc)呢?后台会运行多个进程,因此单个全局变量将严重不足。但是,这与您当前的问题无关。 -
您将需要显示更多代码。问题不在您展示的代码中,因此我们需要查看更多代码。请查看如何创建 MCVE (minimal reproducible example),然后创建一个足以重现您的问题而无需调用
sudo或apt-get的命令(sleep是模拟较慢程序的好工具)。跨度> -
@Jonathan Leffler 实际上,我不必担心多个后台进程,因为在我的练习表(大学练习)中指定外壳将被限制为一个前台和一个后台进程。
-
@tofro 那么,前台进程会从 main 的开头重新开始代码吗?
标签: c linux process signals system-calls