Files
csapp2025/shlab/shlab-handout/tsh.c
2025-05-20 22:09:48 +08:00

913 lines
24 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* tsh - 一个支持作业控制的微型shell程序
*
* 功能特性:
* - 支持内置命令jobs, fg, bg
* - 支持前台/后台作业控制
* - 支持作业状态管理(运行中/停止)
* - 处理Ctrl-C(中断)、Ctrl-Z(停止)等信号
* - 支持最多16个并发作业
*
* 主要数据结构:
* - job_t: 表示一个作业包含PID、作业ID、状态和命令行
* - jobs数组: 保存所有作业的列表
*
* 作业状态说明:
* - UNDEF: 未定义
* - FG: 前台运行
* - BG: 后台运行
* - ST: 已停止
*
* ChengJingyu 202302723005
*/
/* 最大命令行长度 */
#define MAXLINE 1024 /* max line size */
/* 每条命令最大参数数量 */
#define MAXARGS 128 /* max args on a command line */
/* 最大并发作业数 */
#define MAXJOBS 16 /* max jobs at any point in time */
/* 最大作业ID值 */
#define MAXJID 1 << 16 /* max job ID */
/* 作业状态常量定义 */
#define UNDEF 0 /* 未定义状态 */
#define FG 1 /* 前台运行状态 */
#define BG 2 /* 后台运行状态 */
#define ST 3 /* 停止状态 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// #include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
/* Misc manifest constants */
#define MAXLINE 1024 /* max line size */
#define MAXARGS 128 /* max args on a command line */
#define MAXJOBS 16 /* max jobs at any point in time */
#define MAXJID 1 << 16 /* max job ID */
/* Job states */
#define UNDEF 0 /* undefined */
#define FG 1 /* running in foreground */
#define BG 2 /* running in background */
#define ST 3 /* stopped */
/*
* Jobs states: FG (foreground), BG (background), ST (stopped)
* Job state transitions and enabling actions:
* FG -> ST : ctrl-z
* ST -> FG : fg command
* ST -> BG : bg command
* BG -> FG : fg command
* At most 1 job can be in the FG state.
*/
/* 全局变量定义 */
extern char **environ; /* 环境变量数组定义在libc中 */
char prompt[] = "tsh> "; /* 命令行提示符(请勿修改) */
int verbose = 0; /* 调试模式标志,为真时打印额外信息 */
int nextjid = 1; /* 下一个要分配的作业ID */
char sbuf[MAXLINE]; /* 用于构造sprintf消息的缓冲区 */
/* 作业结构体定义 */
struct job_t {
pid_t pid; /* 进程ID */
int jid; /* 作业ID [1, 2, ...] */
int state; /* 状态: UNDEF(未定义), BG(后台), FG(前台), ST(停止) */
char cmdline[MAXLINE]; /* 命令行字符串 */
};
struct job_t jobs[MAXJOBS]; /* 作业列表数组 */
/* 全局变量定义结束 */
/* 函数原型声明 */
/* 需要实现的核心功能函数 */
void eval(char *cmdline); /* 评估并执行命令行 */
int builtin_cmd(char **argv); /* 执行内置命令 */
void do_bgfg(char **argv); /* 处理fg/bg命令 */
void waitfg(pid_t pid); /* 等待前台作业完成 */
/* 信号处理函数 */
void sigchld_handler(int sig); /* 子进程状态改变处理 */
void sigtstp_handler(int sig); /* Ctrl-Z处理 */
void sigint_handler(int sig); /* Ctrl-C处理 */
/* 提供的辅助函数 */
int parseline(const char *cmdline, char **argv); /* 解析命令行 */
void sigquit_handler(int sig); /* 退出信号处理 */
/* 作业管理函数 */
void clearjob(struct job_t *job); /* 清除作业条目 */
void initjobs(struct job_t *jobs); /* 初始化作业列表 */
int maxjid(struct job_t *jobs); /* 获取最大作业ID */
int addjob(struct job_t *jobs, pid_t pid, int state,
char *cmdline); /* 添加作业 */
int deletejob(struct job_t *jobs, pid_t pid); /* 删除作业 */
pid_t fgpid(struct job_t *jobs); /* 获取前台作业PID */
struct job_t *getjobpid(struct job_t *jobs, pid_t pid); /* 通过PID获取作业 */
struct job_t *getjobjid(struct job_t *jobs, int jid); /* 通过JID获取作业 */
int pid2jid(pid_t pid); /* PID转JID */
void listjobs(struct job_t *jobs); /* 列出所有作业 */
void listjob(struct job_t *job); /* 列出单个作业 */
/* 工具函数 */
void usage(void); /* 打印用法信息 */
void unix_error(char *msg); /* Unix风格错误处理 */
void app_error(char *msg); /* 应用风格错误处理 */
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler); /* 信号处理包装函数 */
/*
* main - shell的主循环函数
*
* 功能:
* 1. 解析命令行参数
* 2. 安装信号处理程序
* 3. 初始化作业列表
* 4. 进入读取-评估循环
*
* 参数:
* argc - 参数个数
* argv - 参数数组
*
* 返回值:
* 正常情况不会返回若返回则退出码为0
*/
int main(int argc, char **argv) {
char c;
char cmdline[MAXLINE];
int emit_prompt = 1; /* 是否显示提示符默认为1(显示) */
/* 将标准错误重定向到标准输出,确保所有输出都通过管道传递 */
dup2(1, 2);
/* 解析命令行参数 */
while ((c = getopt(argc, argv, "hvp")) != EOF) {
switch (c) {
case 'h': /* 打印帮助信息 */
usage();
break;
case 'v': /* 启用详细输出模式 */
verbose = 1;
break;
case 'p': /* 不显示提示符(用于自动化测试) */
emit_prompt = 0;
break;
default:
usage();
}
}
/* 安装信号处理程序 */
/* 需要实现的信号处理函数 */
Signal(SIGINT, sigint_handler); /* 处理Ctrl-C中断信号 */
Signal(SIGTSTP, sigtstp_handler); /* 处理Ctrl-Z停止信号 */
Signal(SIGCHLD, sigchld_handler); /* 处理子进程状态变化 */
/* 提供一个干净终止shell的方式 */
Signal(SIGQUIT, sigquit_handler);
/* 初始化作业列表 */
initjobs(jobs);
/* shell主循环读取-评估-执行 */
while (1) {
/* 读取命令行输入 */
if (emit_prompt) {
printf("%s", prompt); /* 显示提示符 */
fflush(stdout);
}
if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))
app_error("fgets error"); /* 读取错误处理 */
if (feof(stdin)) /* 处理文件结束(Ctrl-D) */
{
fflush(stdout);
exit(0);
}
/* 评估并执行命令行 */
eval(cmdline);
fflush(stdout);
fflush(stdout);
}
exit(0); /* 控制流不会到达这里 */
}
/*
* eval - 评估并执行用户输入的命令行
*
* 功能:
* 1. 如果是内置命令(quit/jobs/bg/fg),直接执行
* 2. 否则fork子进程执行命令
* 3. 如果是前台作业,等待其完成
* 4. 设置独立的进程组,防止后台作业接收终端信号
*
* 参数:
* cmdline - 用户输入的命令行字符串
*/
void eval(char *cmdline) {
char *argv[MAXARGS]; /* execve()的参数列表 */
char buf[MAXLINE]; /* 存储修改后的命令行 */
int bg; /* 作业是否应该在后台运行 */
pid_t pid; /* 子进程ID */
sigset_t mask_all, mask_one, pre;
/* 初始化信号屏蔽集合 */
sigfillset(&mask_all); /* 屏蔽所有信号 */
sigemptyset(&mask_one); /* 初始化空信号集 */
sigaddset(&mask_one, SIGCHLD); /* 添加SIGCHLD信号 */
/* 解析命令行 */
strcpy(buf, cmdline);
bg = parseline(buf, argv); /* 解析命令行并判断是否后台运行 */
if (argv[0] == NULL) /* 忽略空行 */
return;
/* 处理非内置命令 */
if (!builtin_cmd(argv)) {
/* 阻塞SIGCHLD信号防止addjob前收到SIGCHLD */
sigprocmask(SIG_BLOCK, &mask_one, &pre);
/* 创建子进程执行命令 */
if ((pid = fork()) == 0) { /* 子进程执行用户作业 */
setpgid(0, 0); /* 设置新的进程组,防止后台作业接收终端信号 */
sigprocmask(SIG_SETMASK, &pre, NULL); /* 恢复信号屏蔽 */
/* 执行命令 */
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
/* 父进程添加作业到作业列表 */
addjob(jobs, pid, bg ? BG : FG, cmdline); /* 添加作业到作业列表 */
sigprocmask(SIG_SETMASK, &pre, NULL); /* 恢复信号屏蔽 */
/* 处理前台/后台作业 */
if (!bg) {
waitfg(pid); /* 等待前台作业完成 */
} else {
listjob(getjobpid(jobs, pid)); /* 列出后台作业信息 */
}
}
return;
}
/*
* parseline - 解析命令行并构建argv数组
*
* 功能:
* 1. 解析命令行参数,处理单引号内的内容作为一个参数
* 2. 判断是否为后台作业(&结尾)
*
* 参数:
* cmdline - 输入的命令行字符串
* argv - 输出的参数数组
*
* 返回值:
* 如果是后台作业返回1否则返回0
*/
int parseline(const char *cmdline, char **argv) {
static char array[MAXLINE]; /* 存储命令行副本 */
char *buf = array; /* 遍历命令行的指针 */
char *delim; /* 参数分隔符位置 */
int argc; /* 参数个数 */
int bg; /* 是否为后台作业 */
/* 初始化处理 */
strcpy(buf, cmdline);
buf[strlen(buf) - 1] = ' '; /* 替换末尾的换行符为空格 */
while (*buf && (*buf == ' ')) /* 跳过前导空格 */
buf++;
/* 构建argv参数列表 */
argc = 0;
if (*buf == '\'') { /* 处理单引号参数 */
buf++;
delim = strchr(buf, '\'');
} else {
delim = strchr(buf, ' ');
}
/* 循环解析所有参数 */
while (delim) {
argv[argc++] = buf; /* 添加参数到数组 */
*delim = '\0'; /* 终止当前参数 */
buf = delim + 1;
while (*buf && (*buf == ' ')) /* 跳过空格 */
buf++;
/* 查找下一个参数分隔符 */
if (*buf == '\'') {
buf++;
delim = strchr(buf, '\'');
} else {
delim = strchr(buf, ' ');
}
}
argv[argc] = NULL; /* 参数列表以NULL结尾 */
if (argc == 0) /* 忽略空行 */
return 1;
/* 检查是否为后台作业 */
if ((bg = (*argv[argc - 1] == '&')) != 0) {
argv[--argc] = NULL; /* 移除&符号 */
}
return bg;
}
/*
* builtin_cmd - 执行内置命令
*
* 功能:
* 1. 处理quit命令: 退出shell
* 2. 处理jobs命令: 列出所有作业
* 3. 处理fg/bg命令: 转发给do_bgfg处理
* 4. 忽略单独的&符号
*
* 参数:
* argv - 命令参数数组
*
* 返回值:
* 如果是内置命令返回1否则返回0
*/
int builtin_cmd(char **argv) {
if (!strcmp(argv[0], "quit")) /* 退出命令 */
exit(0);
if (!strcmp(argv[0], "&")) /* 忽略单独的& */
return 1;
if (!strcmp(argv[0], "jobs")) { /* 列出作业 */
listjobs(jobs);
return 1;
}
if (!strcmp(argv[0], "fg") || !strcmp(argv[0], "bg")) { /* 前台/后台命令 */
do_bgfg(argv);
return 1;
}
return 0; /* 不是内置命令 */
}
/*
* do_bgfg - 执行bg和fg内置命令
*
* 功能:
* 1. 解析PID或%JID参数
* 2. 将作业转为前台或后台运行
* 3. 发送CONT信号继续作业
*
* 参数:
* argv - 命令参数数组
*/
void do_bgfg(char **argv) {
struct job_t *job;
char *name = argv[0]; /* 命令名(fg/bg) */
char *num = argv[1]; /* PID或%JID参数 */
/* 参数验证 */
if (num == NULL) {
printf("%s command requires PID or %%jobid argument\n", name);
return;
}
/* 处理%JID格式 */
if (*num == '%') {
num = num + 1; /* 跳过% */
if (strspn(num, "0123456789") != strlen(num)) { /* 检查数字 */
printf("%s: argument must be a PID or %%jobid\n", name);
return;
}
job = getjobjid(jobs, atoi(num)); /* 通过JID获取作业 */
if (job == NULL) {
printf("%%%s: No such job\n", num);
return;
}
}
/* 处理PID格式 */
else {
if (strspn(num, "0123456789") != strlen(num)) { /* 检查数字 */
printf("%s: argument must be a PID or %%jobid\n", name);
return;
}
job = getjobpid(jobs, (pid_t)atoi(num)); /* 通过PID获取作业 */
if (job == NULL) {
printf("(%s): No such process\n", num);
return;
}
}
/* 执行fg/bg命令 */
if (!strcmp(argv[0], "fg")) { /* 前台命令 */
kill(job->pid, SIGCONT); /* 发送继续信号 */
job->state = FG; /* 设为前台作业 */
waitfg(job->pid); /* 等待前台作业完成 */
} else if (!strcmp(argv[0], "bg")) { /* 后台命令 */
kill(job->pid, SIGCONT); /* 发送继续信号 */
job->state = BG; /* 设为后台作业 */
listjob(job); /* 列出作业信息 */
}
}
/*
* waitfg - 等待前台作业完成
*
* 功能:
* 1. 阻塞当前进程,直到指定的前台作业完成
* 2. 使用sigsuspend安全地等待信号避免竞争条件
* 3. 处理SIGCHLD信号确保前台作业结束时能及时检测到
*
* 参数:
* pid - 要等待的前台作业的进程ID
*
* 实现说明:
* 1. 使用信号屏蔽确保原子性操作
* 2. 通过检查作业列表中的前台作业状态来判断是否继续等待
* 3. 使用sigsuspend替代pause避免竞争条件
*/
void waitfg(pid_t pid) {
/*
* 示例说明(trace06.txt):
* ./myspin 4 # 子进程前台执行主进程进入pause
* INT #
* (1)
* CTRL_C发送INT信号给主进程中断pause状态,进入INT处理函数,
* 该函数把INT信号转发给前台子进程处理完成后返回上次中断的位置(2)。
* (2)
* INT中断处理函数返回后继续执行下一条指令,
* 发现while循环条件依然成立又进入pause.
* (3)
* 前台子进程被INT信号终止结束生命发送CHLD信号给主进程
* 中断pause状态进入CHLD处理函数
* 该函数回收僵尸进程删除对应的job,处理完成后返回上次中断的位置。
* (4)
* CHLD中断返回后继续执行下一条指令
* 发现while循环条件不再成立跳出while循环。
*
* 注意:如果(3)的中断位置在while条件执行后pause执行前
* 那么使用pause会导致中断处理函数返回后一直pause
* 而sigsuspend可以避免这种情况。
*/
sigset_t mask, prev;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD); /* 屏蔽SIGCHLD信号 */
sigprocmask(SIG_BLOCK, &mask, &prev); /* 设置信号屏蔽 */
/* 循环等待前台作业完成 */
while (fgpid(jobs)) {
if (verbose)
printf("%ld pausing...\n", time(NULL));
sigsuspend(&prev); /* 原子地解除屏蔽并等待信号 */
if (verbose)
printf("%ld interrupt return\n", time(NULL));
}
sigprocmask(SIG_SETMASK, &prev, NULL); /* 恢复原始信号屏蔽 */
}
/*****************
* Signal handlers
*****************/
/*
* sigchld_handler - 处理子进程状态变化的信号处理函数
*
* 功能:
* 1. 当子进程终止(成为僵尸进程)或停止时内核会发送SIGCHLD信号
* 2. 回收所有可用的僵尸子进程
* 3. 不等待其他正在运行的子进程终止
*
* 参数:
* sig - 信号编号
*/
void sigchld_handler(int sig) {
if (verbose)
printf("%ld interrupt by SIGCHLD(%d)\n", time(NULL), sig);
int olderrno = errno;
pid_t pid;
int status;
/* 循环处理所有终止或停止的子进程 */
while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED | WCONTINUED)) > 0) {
if (WIFEXITED(status)) { /* 子进程正常退出 */
deletejob(jobs, pid); /* 从作业列表中删除作业 */
} else if (WIFSIGNALED(status)) { /* 子进程被信号终止 */
printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid,
WTERMSIG(status));
deletejob(jobs, pid); /* 从作业列表中删除作业 */
} else if (WIFSTOPPED(status)) { /* 子进程被信号停止 */
printf("Job [%d] (%d) stopted by signal %d\n", pid2jid(pid), pid,
WSTOPSIG(status));
getjobpid(jobs, pid)->state = ST; /* 更新作业状态为停止 */
} else if (WIFCONTINUED(status)) { /* 子进程被继续 */
/* 目前不做特殊处理 */
}
}
/* 错误处理 */
if (pid == -1 && errno != ECHILD) {
unix_error("sigchld_handler error");
}
errno = olderrno; /* 恢复errno */
}
/*
* sigint_handler - 处理Ctrl-C中断信号的信号处理函数
*
* 功能:
* 1. 当用户在键盘输入Ctrl-C时内核会发送SIGINT信号
* 2. 将信号转发给前台作业
*
* 参数:
* sig - 信号编号
*/
void sigint_handler(int sig) {
if (verbose)
printf("%ld interrupt by SIGINT(%d)\n", time(NULL), sig);
int olderrno = errno;
pid_t pid = fgpid(jobs); /* 获取前台作业的PID */
if (kill(pid, SIGINT) == -1) { /* 向前台作业发送SIGINT信号 */
unix_error("sigint_handler");
}
errno = olderrno; /* 恢复errno */
}
/*
* sigtstp_handler - 处理Ctrl-Z停止信号的信号处理函数
*
* 功能:
* 1. 当用户在键盘输入Ctrl-Z时内核会发送SIGTSTP信号
* 2. 将信号转发给前台作业使其停止
*
* 参数:
* sig - 信号编号
*/
void sigtstp_handler(int sig) {
if (verbose) {
printf("%ld interrupt by SIGSTP(%d)\n", time(NULL), sig);
}
int olderrno = errno;
pid_t pid = fgpid(jobs); /* 获取前台作业的PID */
if (kill(pid, SIGTSTP) == -1) /* 向前台作业发送SIGTSTP信号 */
unix_error("sigtstp_handler");
errno = olderrno; /* 恢复errno */
}
/*********************
* End signal handlers
*********************/
/***********************************************
* Helper routines that manipulate the job list
**********************************************/
/* clearjob - Clear the entries in a job struct */
void clearjob(struct job_t *job) {
job->pid = 0;
job->jid = 0;
job->state = UNDEF;
job->cmdline[0] = '\0';
}
/* initjobs - Initialize the job list */
void initjobs(struct job_t *jobs) {
int i;
for (i = 0; i < MAXJOBS; i++)
clearjob(&jobs[i]);
}
/* maxjid - Returns largest allocated job ID */
int maxjid(struct job_t *jobs) {
int i, max = 0;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].jid > max)
max = jobs[i].jid;
return max;
}
/*
* addjob - 添加作业到作业列表
*
* 功能:
* 1. 将新作业添加到第一个空闲位置
* 2. 分配作业ID并自动递增
* 3. 设置作业状态和命令行
*
* 参数:
* jobs - 作业列表数组
* pid - 进程ID
* state - 作业状态(UNDEF/FG/BG/ST)
* cmdline - 命令行字符串
*
* 返回值:
* 成功返回1失败返回0
*/
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline) {
int i;
if (pid < 1) /* 检查PID有效性 */
return 0;
/* 查找空闲位置并添加作业 */
for (i = 0; i < MAXJOBS; i++) {
if (jobs[i].pid == 0) { /* 找到空闲位置 */
jobs[i].pid = pid;
jobs[i].state = state;
jobs[i].jid = nextjid++; /* 分配作业ID并递增 */
if (nextjid > MAXJOBS) /* 作业ID循环使用 */
nextjid = 1;
strcpy(jobs[i].cmdline, cmdline); /* 保存命令行 */
if (verbose) { /* 调试输出 */
printf("%ld Added job [%d] %d %s\n", time(NULL), jobs[i].jid,
jobs[i].pid, jobs[i].cmdline);
}
return 1; /* 添加成功 */
}
}
printf("Tried to create too many jobs\n"); /* 作业列表已满 */
return 0;
}
/*
* deletejob - 从作业列表删除指定PID的作业
*
* 功能:
* 1. 根据PID查找并删除作业
* 2. 更新下一个可用的作业ID
*
* 参数:
* jobs - 作业列表数组
* pid - 要删除的进程ID
*
* 返回值:
* 成功返回1失败返回0
*/
int deletejob(struct job_t *jobs, pid_t pid) {
int i;
if (pid < 1) /* 检查PID有效性 */
return 0;
/* 查找并删除作业 */
for (i = 0; i < MAXJOBS; i++) {
if (jobs[i].pid == pid) { /* 找到匹配的作业 */
if (verbose) { /* 调试输出 */
printf("%ld deleted job [%d] %d %s\n", time(NULL), jobs[i].jid,
jobs[i].pid, jobs[i].cmdline);
}
clearjob(&jobs[i]); /* 清除作业条目 */
nextjid = maxjid(jobs) + 1; /* 更新下一个可用的作业ID */
return 1; /* 删除成功 */
}
}
return 0; /* 未找到匹配的作业 */
}
/*
* fgpid - 获取当前前台作业的PID
*
* 功能:
* 遍历作业列表查找状态为FG的作业
*
* 参数:
* jobs - 作业列表数组
*
* 返回值:
* 找到返回PID否则返回0
*/
pid_t fgpid(struct job_t *jobs) {
int i;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].state == FG)
return jobs[i].pid;
return 0;
}
/*
* getjobpid - 通过PID查找作业
*
* 功能:
* 在作业列表中查找指定PID的作业
*
* 参数:
* jobs - 作业列表数组
* pid - 要查找的进程ID
*
* 返回值:
* 找到返回作业指针否则返回NULL
*/
struct job_t *getjobpid(struct job_t *jobs, pid_t pid) {
int i;
if (pid < 1)
return NULL;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].pid == pid)
return &jobs[i];
return NULL;
}
/*
* getjobjid - 通过JID查找作业
*
* 功能:
* 在作业列表中查找指定JID的作业
*
* 参数:
* jobs - 作业列表数组
* jid - 要查找的作业ID
*
* 返回值:
* 找到返回作业指针否则返回NULL
*/
struct job_t *getjobjid(struct job_t *jobs, int jid) {
int i;
if (jid < 1)
return NULL;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].jid == jid)
return &jobs[i];
return NULL;
}
/*
* pid2jid - 将PID转换为JID
*
* 功能:
* 查找指定PID对应的作业ID
*
* 参数:
* pid - 进程ID
*
* 返回值:
* 找到返回JID否则返回0
*/
int pid2jid(pid_t pid) {
int i;
if (pid < 1)
return 0;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].pid == pid) {
return jobs[i].jid;
}
return 0;
}
/*
* listjobs - 打印所有作业信息
*
* 功能:
* 遍历作业列表,打印所有非空作业的信息
* 包括JID、PID、状态和命令行
*
* 参数:
* jobs - 作业列表数组
*/
void listjobs(struct job_t *jobs) {
int i;
for (i = 0; i < MAXJOBS; i++) {
if (jobs[i].pid != 0) {
printf("[%d] (%d) ", jobs[i].jid, jobs[i].pid);
switch (jobs[i].state) {
case BG:
printf("Running ");
break;
case FG:
printf("Foreground ");
break;
case ST:
printf("Stopped ");
break;
default:
printf("listjobs: Internal error: job[%d].state=%d ", i, jobs[i].state);
}
printf("%s", jobs[i].cmdline);
}
}
}
/*
* listjob - 打印单个作业信息
*
* 功能:
* 打印指定作业的信息
* 包括JID、PID和命令行
*
* 参数:
* job - 要打印的作业指针
*/
void listjob(struct job_t *job) {
if (job->pid != 0) {
printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
}
}
/******************************
* end job list helper routines
******************************/
/***********************
* Other helper routines
***********************/
/*
* usage - 打印帮助信息
*
* 功能:
* 打印shell的使用说明和命令行选项
*/
void usage(void) {
printf("Usage: shell [-hvp]\n");
printf(" -h print this message\n");
printf(" -v print additional diagnostic information\n");
printf(" -p do not emit a command prompt\n");
exit(1);
}
/*
* unix_error - Unix风格错误处理
*
* 功能:
* 打印Unix系统调用错误信息并退出程序
*
* 参数:
* msg - 自定义错误消息
*/
void unix_error(char *msg) {
fprintf(stdout, "%s: %s\n", msg, strerror(errno));
exit(1);
}
/*
* app_error - 应用风格错误处理
*
* 功能:
* 打印应用程序错误信息并退出程序
*
* 参数:
* msg - 错误消息
*/
void app_error(char *msg) {
fprintf(stdout, "%s\n", msg);
exit(1);
}
/*
* Signal - 信号处理包装函数
*
* 功能:
* 1. 为指定信号安装信号处理程序
* 2. 设置SA_RESTART标志使被信号中断的系统调用自动重启
* 3. 返回旧的处理程序
*
* 参数:
* signum - 信号编号
* handler - 信号处理函数
*
* 返回值:
* 旧的信号处理程序
*/
handler_t *Signal(int signum, handler_t *handler) {
struct sigaction action, old_action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask); /* 屏蔽正在处理的信号类型 */
action.sa_flags = SA_RESTART; /* 如果可能,重启被中断的系统调用 */
if (sigaction(signum, &action, &old_action) < 0)
unix_error("Signal error");
return (old_action.sa_handler);
}
/*
* sigquit_handler - SIGQUIT信号处理函数
*
* 功能:
* 1. 处理SIGQUIT信号优雅终止shell
* 2. 打印终止信息并退出
*
* 参数:
* sig - 信号编号
*/
void sigquit_handler(int sig) {
printf("Terminating after receipt of SIGQUIT signal\n");
exit(1);
}