处理信号和进程编写我自己的 shell
Handle signals and process writing my own shell
我正在尝试制作一个 shell 来提示命令并等待用户输入。
我希望我的 shell 在用户按下 ctrl-c
时打印另一个提示,并在用户按下 ctrl-d
时退出。
这是主循环:
int my_shell(char **env)
{
char *cmd_line;
while (true)
{
print_prompt();
cmd_line = get_cmd();
process(cmd_line);
}
return (0);
}
我能够捕捉到 ctrl-c
和 ctrl-d
信号,但我不知道如何构造主循环以在合适的地方退出。我尝试使用几个 fork()
、wait()
、getpid()
,但我做错了。
这是我的尝试之一:
int extern_pid;
int intern_pid;
int my_shell(char **env)
{
char *cmd_line;
extern_pid = getpid();
while (true)
{
if (fork() == 0)
{
intern_pid = getpid();
print_prompt();
cmd_line = get_cmd();
process(cmd_line);
exit(0);
}
else
{
wait(0);
}
}
return (0);
}
以及那些信号处理程序:
void ctrlc_handler(int signal)
{
if (getpid() == intern_pid)
exit(0);
}
void ctrld_handler(int signal)
{
if (getpid() == extern_pid)
exit(0);
}
注意:ctrl-d
信号在 get_cmd()
函数中处理。
不必使用 fork 创建子进程来处理自定义 shell 中的 Ctrl-C 信号。一种可能性是将信号处理程序与 sigsetjmp/siglongjmp 一起使用。
程序如下:
- 信号处理程序已安装
- 在主循环开始之前,使用sigsetjmp
将调用环境存储在env中
- 在信号处理程序中,调用 siglongjmp() 恢复由 sigsetjmp() 保存的环境,并执行非本地跳转到调用 sigsetjmp 的位置
由于始终可以调用信号处理程序,甚至可能在调用 sigsetjmp() 之前,因此必须确保可以调用 siglongjmp()。这是通过设置一个名为 jmp_set.
的可变变量 sig_atomic_t 来完成的
函数 process
只知道一个名为 exit
的内部命令。正如问题的评论中已经指出的那样,如果用户在一行的开头输入 Ctrl-D 作为第一个字符,这会自动在 getchar 调用中产生 EOF。函数 get_cmd 然后 returns 命令 exit
在这里。或者,用户可以输入命令exit
结束程序。
在函数 process
中,该位置标有注释,您可能希望使用 fork/exec 调用外部程序。 returns 一个布尔值是否应该退出程序。可能在这里也可以相应地评估来自外部程序调用的状态代码。
这是一个小型的、自包含的示例程序,包含您的 ctrlc_handler、get_cmd 和过程函数布局,使用 sigsetjmp()/siglongjmp() 扩展,当然不完整,但也许起点:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <setjmp.h>
#define MAX_CMD_LENGTH 1024
static const char *const EXIT_CMD = "exit";
static sigjmp_buf env;
static volatile sig_atomic_t jmp_set;
static void ctrlc_handler(int signal) {
if (jmp_set == 0)
return;
if (signal == SIGINT) {
siglongjmp(env, 1);
}
}
static char *get_cmd(void) {
char *cmd_buf = calloc(MAX_CMD_LENGTH, sizeof(char));
if (!cmd_buf) {
perror("malloc failed\n");
exit(1);
}
char *ptr = cmd_buf;
int ch = getchar();
while (ch != EOF && ch != '\n' && ptr < cmd_buf + MAX_CMD_LENGTH - 1) {
*ptr++ = (char) ch;
ch = getchar();
}
if (ch == EOF) {
strcpy(cmd_buf, EXIT_CMD);
}
return cmd_buf;
}
static bool process(char *cmd) {
if (strcmp(cmd, EXIT_CMD) == 0) {
return true;
}
// a call to fork together with a function from the
// exec family could be used to call external programs
printf("process '%s'\n", cmd);
return false;
}
static int cnt = 0;
int main(void) {
if (signal(SIGINT, ctrlc_handler) == SIG_ERR) {
perror("signal error");
exit(1);
}
if (sigsetjmp(env, 1)) {
printf("\n");
cnt++;
}
jmp_set = 1;
bool exit;
do {
printf("%d> ", cnt);
char *cmd = get_cmd();
exit = process(cmd);
free(cmd);
} while (!exit);
return 0;
}
我正在尝试制作一个 shell 来提示命令并等待用户输入。
我希望我的 shell 在用户按下 ctrl-c
时打印另一个提示,并在用户按下 ctrl-d
时退出。
这是主循环:
int my_shell(char **env)
{
char *cmd_line;
while (true)
{
print_prompt();
cmd_line = get_cmd();
process(cmd_line);
}
return (0);
}
我能够捕捉到 ctrl-c
和 ctrl-d
信号,但我不知道如何构造主循环以在合适的地方退出。我尝试使用几个 fork()
、wait()
、getpid()
,但我做错了。
这是我的尝试之一:
int extern_pid;
int intern_pid;
int my_shell(char **env)
{
char *cmd_line;
extern_pid = getpid();
while (true)
{
if (fork() == 0)
{
intern_pid = getpid();
print_prompt();
cmd_line = get_cmd();
process(cmd_line);
exit(0);
}
else
{
wait(0);
}
}
return (0);
}
以及那些信号处理程序:
void ctrlc_handler(int signal)
{
if (getpid() == intern_pid)
exit(0);
}
void ctrld_handler(int signal)
{
if (getpid() == extern_pid)
exit(0);
}
注意:ctrl-d
信号在 get_cmd()
函数中处理。
不必使用 fork 创建子进程来处理自定义 shell 中的 Ctrl-C 信号。一种可能性是将信号处理程序与 sigsetjmp/siglongjmp 一起使用。
程序如下:
- 信号处理程序已安装
- 在主循环开始之前,使用sigsetjmp 将调用环境存储在env中
- 在信号处理程序中,调用 siglongjmp() 恢复由 sigsetjmp() 保存的环境,并执行非本地跳转到调用 sigsetjmp 的位置
由于始终可以调用信号处理程序,甚至可能在调用 sigsetjmp() 之前,因此必须确保可以调用 siglongjmp()。这是通过设置一个名为 jmp_set.
的可变变量 sig_atomic_t 来完成的函数 process
只知道一个名为 exit
的内部命令。正如问题的评论中已经指出的那样,如果用户在一行的开头输入 Ctrl-D 作为第一个字符,这会自动在 getchar 调用中产生 EOF。函数 get_cmd 然后 returns 命令 exit
在这里。或者,用户可以输入命令exit
结束程序。
在函数 process
中,该位置标有注释,您可能希望使用 fork/exec 调用外部程序。 returns 一个布尔值是否应该退出程序。可能在这里也可以相应地评估来自外部程序调用的状态代码。
这是一个小型的、自包含的示例程序,包含您的 ctrlc_handler、get_cmd 和过程函数布局,使用 sigsetjmp()/siglongjmp() 扩展,当然不完整,但也许起点:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <setjmp.h>
#define MAX_CMD_LENGTH 1024
static const char *const EXIT_CMD = "exit";
static sigjmp_buf env;
static volatile sig_atomic_t jmp_set;
static void ctrlc_handler(int signal) {
if (jmp_set == 0)
return;
if (signal == SIGINT) {
siglongjmp(env, 1);
}
}
static char *get_cmd(void) {
char *cmd_buf = calloc(MAX_CMD_LENGTH, sizeof(char));
if (!cmd_buf) {
perror("malloc failed\n");
exit(1);
}
char *ptr = cmd_buf;
int ch = getchar();
while (ch != EOF && ch != '\n' && ptr < cmd_buf + MAX_CMD_LENGTH - 1) {
*ptr++ = (char) ch;
ch = getchar();
}
if (ch == EOF) {
strcpy(cmd_buf, EXIT_CMD);
}
return cmd_buf;
}
static bool process(char *cmd) {
if (strcmp(cmd, EXIT_CMD) == 0) {
return true;
}
// a call to fork together with a function from the
// exec family could be used to call external programs
printf("process '%s'\n", cmd);
return false;
}
static int cnt = 0;
int main(void) {
if (signal(SIGINT, ctrlc_handler) == SIG_ERR) {
perror("signal error");
exit(1);
}
if (sigsetjmp(env, 1)) {
printf("\n");
cnt++;
}
jmp_set = 1;
bool exit;
do {
printf("%d> ", cnt);
char *cmd = get_cmd();
exit = process(cmd);
free(cmd);
} while (!exit);
return 0;
}