913 lines
24 KiB
C
913 lines
24 KiB
C
/*
|
||
* 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);
|
||
}
|