/*
* unix shell with job control
*內建命令是fg,bg,jobs,echo,quit,&
* @copyright 官加文
*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<ctype.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<errno.h>
/* 宏定義一些常數 */
#define MAXLINE 1024 //最大的行大小
#define MAXARGS 128 //最多的參數
#define MAXJOBS 16 //在任何時間內最多的作業
#define MAXJID 1<<16 //最大的作業ID,爲65536
/*
* 1<<16
* 左移16位
* 0000000000000001(2)==1
* 1000000000000000(2)==2^16==65536
*/
/* 定義作業的狀態 */
#define UNDEF 0 //undefined
#define FG 1 //在前臺運行
#define BG 2 //在後臺運行
#define ST 3 //stopped
/*
* 作業狀態: FG(foreground), BG(background), ST(stopped)
* 作業狀態變化說明(開始狀態 -> 變化狀態):
* FG -> ST : ctrl-z
* ST -> FG : fg command
* ST -> BG : bg command
* BG -> FG : fg command
* 沒有FG -> BG的狀態,至多一個作業運行在前臺
*/
/* 全局變量 */
/* c標準庫定義了一個全局變量environ, 保存着環境變量
* extern引入外部文件的全局變量
*/
extern char **environ;
char prompt[] = "tsh>"; //命令提示符
int verbose = 0; //如果爲真,打印額外的輸出
int nextjid = 1; //分配下一個作業ID[1,2,3....]
char sbuf[MAXLINE]; //for composing sprintf(把格式化的數據寫入某個字符串) messages
struct job_t {
pid_t pid; //作業的進程組ID
int jid; //作業ID,即作業標識符 [1,2, ...]
int state; //UNDEF, BG, FG, or ST
char cmdline[MAXLINE]; //命令行
};
struct job_t jobs[MAXJOBS]; //作業列表
/* 函數原型 */
void eval(char *cmdline); //解析命令行
int builtin_cmd(char **argv); //判斷是否是內建命令
void do_bgfg(char **argv); //實現bg,fg兩個內建命令
void do_echo(char **argv);
void waitfg(pid_t pid); //等待前臺進程終止
/* 信號處理函數 */
void sigchld_handler(int sig); //SIGCHLD
void sigtstp_handler(int sig); //SIGTSTP
void sigint_handler(int sig); //SIGINT
void sigquit_handler(int sig); //SIGQUIT,進程在退出時會產生core文件,(相當於程序錯誤信號)
void mask_sigchld_signal(int how, int sig);
int parseline(const char *cmdline, char **argv);
/* 關於作業控制的函數 */
void clear_job(struct job_t *job);
void init_jobs(struct job_t *jobs);
int max_jid(struct job_t *jobs);
int add_job(struct job_t *jobs, pid_t pid, int state, char *cmdline);
int delete_job(struct job_t *jobs, pid_t pid);
pid_t fgpid(struct job_t *jobs); //前臺進程組長ID
struct job_t *get_job_pid(struct job_t *jobs, pid_t pid);
struct job_t *get_job_jid(struct job_t *jobs, int jid);
int pid2jid(pid_t pid);
void list_jobs(struct job_t *jobs);
void usage(void); //usage是用法的意思(for -h命令行參數)
void unix_error(char *msg);
void app_error(char *msg);
typedef void handler_t(int); //將void定義爲handler_t
handler_t *Signal(int signum, handler_t *handler);
void Kill(pid_t pid, int signum);
/* 主程序(main routine) */
int main(int argc, char **argv)
{
char c;
char cmdline[MAXLINE];
int emit_prompt = 1; //默認輸出命令提示符
/* 重定向標準錯誤流(stderr 文件描述符爲2)
* 到標準輸出流(stdout 文件描述符爲1)
* 這樣驅動就能得到所有輸出在被連接到stdout的管道上
* dup2(oldfd,newfd),被指向oldfd
*/
dup2(1, 2);
/* 解析(parse)命令行參數 */
while ((c = getopt(argc, argv, "hvp")) != EOF) {
switch (c) {
case 'h':
usage();
break;
case 'v':
verbose = 1; //輸出額外的診斷信息,如果命令行參數有-v時則verbose置爲1
break;
case 'p':
emit_prompt = 0;
break;
default:
usage(); //如果參數有誤,默認參數爲-h
}
}
/* 安裝信號處理程序 */
Signal(SIGINT, sigint_handler); //ctrl-c,signum爲2
Signal(SIGTSTP, sigtstp_handler); //ctrl-z,signum爲20
Signal(SIGCHLD, sigchld_handler); //子進程終止或停止,比如SIGKILL殺死,signum爲17
/* 和SIGINT類似, 但由QUIT字符(通常是Ctrl-\)來控制.
* 進程在因收到SIGQUIT退出時會產生core文件,
* 在這個意義上類似於一個程序錯誤信號,
* 提供一個乾淨的方式殺死shell
*/
/* This one provides a clean way to kill the shell */
Signal(SIGQUIT, sigquit_handler);
/* 初始化作業列表 */
init_jobs(jobs);
/* 讀取命令行 */
while (1) {
if (emit_prompt) {
printf("%s ", prompt);
fflush(stdout); //清除緩衝區,爲了立刻看到輸出的東西
}
/* ferror返回0表示未出錯,返回非零表示出錯 */
if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))
app_error("fgets error\n");
if (feof(stdin)) { //ctrl-d 是文件結束
fflush(stdout); //清空緩衝區
exit(0);
}
/* 求命令行的值 */
eval(cmdline);
fflush(stdout);
}
exit(0); /* 控制永遠不會到達這裏 */
}
void eval(char *cmdline)
{
char *argv[MAXARGS];
char buf[MAXLINE];
int bg;
pid_t pid;
strcpy(buf, cmdline);
bg = parseline(cmdline, argv);
if (argv[0] == NULL)
return;
if (!builtin_cmd(argv)){
/* 解決經典父進程和子進程的競爭問題:
* 在調用fork之前阻塞SIGCHLD,保證在子
* 進程被添加到作業列表之後回收該子進程
* 即處理add_job與子進程結束的競爭條件
*/
mask_sigchld_signal(SIG_BLOCK, SIGCHLD); //阻塞SIGCHLD信號
if ((pid = fork()) == 0){
//解除阻塞,因爲子進程繼承了父進程被阻塞信號的集合
mask_sigchld_signal(SIG_UNBLOCK, SIGCHLD);
/* After the fork, but before the execve,
* the child process should call
* setpgid(0, 0), which puts the child in
* a new process group whose group ID is
* identical to the child’s PID. This
* ensures that there will be only one
* process, your shell, in the
* foreground process group.
*/
if (setpgid(0, 0) < 0) { /* put the child in a new process group */
unix_error("eval: setpgid failed");
}
if (execve(argv[0], argv, environ) < 0){
printf("%s: command not found\n", argv[0]);
exit(0);
}
}
else {
//父進程
int status;
if (!bg) {
add_job(jobs, pid, FG, cmdline);
//printf("success add FG process\n");
}
else {
add_job(jobs, pid, BG, cmdline);
//printf("success add BG process\n");
}
/* add_job之後解除阻塞 */
mask_sigchld_signal(SIG_UNBLOCK, SIGCHLD);
if (!bg) {
/* 等待前臺進程終止 */
waitfg(pid);
}else {
printf("[%d]+ (%d) %s", pid2jid(pid), pid, cmdline);
}
}
}
}
int parseline(const char *cmdline, char **argv)
{
static char array[MAXLINE]; /* holds local copy of command line */
char *buf = array; /* ptr that traverses command line */
char *delim; /* points to first space delimiter */
int argc; /* number of args */
int bg; /* background job? */
strcpy(buf, cmdline);
buf[strlen(buf)-1] = ' '; /* replace trailing '\n' with space */
while (*buf && (*buf == ' ')) /* ignore leading spaces */
buf++;
/* Build the argv list */
argc = 0;
if (*buf == '\'') { //*buf是單個字符
buf++;
delim = strchr(buf, '\'');
}
else {
delim = strchr(buf, ' ');
}
while (delim) {
argv[argc++] = buf; /* 以ls -l爲例,此處buf是"ls-l",argv[0]爲"ls -l"*/
*delim = '\0'; /* delim從" -l"變爲 " ", buf 變爲"ls",argv[0]變爲"" */
buf = delim + 1; /* buf 變爲 "-l" */
while (*buf && (*buf == ' ')) /* ignore spaces */
buf++;
if (*buf == '\'') {
buf++;
delim = strchr(buf, '\'');
}
else {
delim = strchr(buf, ' ');
}
}
argv[argc] = NULL;
if (argc == 0) /* ignore blank line */
return 1;
/* should the job run in the background? */
if ((bg = (*argv[argc-1] == '&')) != 0) {
argv[--argc] = NULL;
}
return bg;
}
int builtin_cmd(char **argv)
{
if (!strcmp("quit",argv[0]))
exit(0);
if (!strcmp("&",argv[0]))
return 1;
/* 增加jobs命令 */
if (!strcmp("jobs", argv[0])) {
list_jobs(jobs);
return 1;
}
/* 增加echo命令 */
if (!strcmp("echo", argv[0])) {
do_echo(argv);
return 1;
}
/* 增加fg,bg命令 */
if (!strcmp(argv[0],"fg") || !strcmp(argv[0], "bg")) {
//printf("bg/fg\n");
do_bgfg(argv);
return 1;
}
return 0;
}
/* 執行內建的fg和bg命令 */
void do_bgfg(char **argv)
{
int i, jobid;
char *id = argv[1];
struct job_t *job;
if (id == NULL) {
printf("%s command requires PID or %%jobid argument\n", argv[0]);
return; //退出函數
}
if (id[0] == '%') {
jobid = atoi(++id); //是++id,不是id++
if ((job = get_job_jid(jobs, jobid)) == NULL) {
printf("%s: No such job\n", id);
return;
}
}
else {
if (atoi(id) != 0) {
if ((job = get_job_pid(jobs, atoi(id))) == NULL) {
printf("(%s): No such process\n", id);
return;
}
}
}
if (!strcmp(argv[0], "fg")) {
job->state = FG;
//喚醒job,SIGCONT讓終止的進程繼續,signum爲18
Kill(-1 * job->pid, SIGCONT); //乘-1是要發送給整個進程組的意思
waitfg(job->pid);
}
if (!strcmp(argv[0], "bg")) {
job->state = BG;
//喚醒job,SIGCONT讓終止的進程繼續
Kill(-1 * job->pid, SIGCONT); //乘-1是要發送給整個進程組的意思
}
return;
}
void do_echo(char **argv)
{
int i;
for (i=1; argv[i] != NULL; i++) {
printf("%s ", argv[i]);
}
printf("\n");
}
//一直阻塞,直到不再爲前臺進程組
void waitfg(pid_t pid)
{
while (pid == fgpid(jobs)) { //死循環監聽
sleep(0);
}
return;
}
/*****************
* Signal handlers
*****************/
/*
* sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
* a child job terminates (becomes a zombie), or stops because it
* received a SIGSTOP or SIGTSTP signal. The handler reaps all
* available zombie(殭屍) children, but doesn't wait for any other
* currently running children to terminate.
*/
/* 子進程成爲殭屍進程的方法有很多種*/
void sigchld_handler(int sig)
{
int status, child_sig;
pid_t pid;
struct job_t *job;
//WUNTRACED | WNOHANG很重要,不要傻等,不然遇到直接cat的命令會一直阻塞
while ((pid = waitpid(-1, &status, WUNTRACED | WNOHANG)) > 0) {
//printf("Handler child %d\n", (int) pid);
if (WIFSTOPPED(status)) { //子進程處理停止信號(這裏假設所有的停止信號都是SIGTSTP,即ctrl-z)
sigtstp_handler(WSTOPSIG(status)); //直接cat命令獲得信號21(後臺進程從終端讀),但是我也不知道怎麼來的
}
else if (WIFSIGNALED(status)) { //子進程因爲未捕獲的信號而停止
//printf("yes\n");
child_sig = WTERMSIG(status);
if (child_sig == SIGINT)
sigint_handler(child_sig);
else
unix_error("sigchld_handler: unknown signal\n");
}
else {
//printf("yes\n");
//後臺作業也應該被刪除
delete_job(jobs, pid); //如果正常退出
}
}
return;
}
/* SIGINT和SIGTSTP都是針對於前臺進程組,所以要獲得前臺進程組的 */
void sigint_handler(int sig)
{
//printf("terminated sig\n");
pid_t pid;
pid = fgpid(jobs); //獲得前臺進程ID,不考慮管道,即一個作業只有一個進程
int jid = pid2jid(pid);
if (pid != 0) { //如果有前臺進程則不返回0
printf("Job [%d] (%d) terminated by signal %d", jid, pid, sig);
delete_job(jobs,pid); /* 順序不能變 */
Kill(-pid, sig); //發送SIGINT信號
}
return;
}
void sigtstp_handler(int sig)
{
//printf("stop sig\n");
pid_t pid;
pid = fgpid(jobs);
int jid = pid2jid(pid);
struct job_t *job;
if (pid != 0) {
printf("Job [%d] (%d) stopped by signal %d\n", jid, pid, sig);
job = get_job_pid(jobs, pid);
job->state=ST;
Kill(-pid, sig); //發送SIGTSTP信號
}
}
void sigquit_handler(int sig)
{
printf("Terminating after receipt of SIGQUIT signal(core dumped)\n"); //receipt是收到的意思
exit(1);
}
/* 信號處理函數結束 */
void mask_sigchld_signal(int how, int sig)
{
sigset_t signals; //信號集
if (sigemptyset(&signals) < 0)
unix_error("sigemptyset failed\n");
if (sigaddset(&signals, sig) < 0)
unix_error("sigaddset failed\n");
if (sigprocmask(how, &signals, NULL) < 0)
unix_error("sigprocmask failed\n");
return;
}
/* job函數開始 */
void clear_job(struct job_t *job)
{
job->pid = 0;
job->jid = 0;
job->state = UNDEF;
job->cmdline[0] = '\0';
}
void init_jobs(struct job_t *jobs)
{
int i;
for (i=0; i<MAXJOBS; i++)
clear_job(&jobs[i]);
}
/* 返回最大分配的作業ID */
int max_jid(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;
}
/* 增加作業到作業列表 */
int add_job(struct job_t *jobs, pid_t pid, int state, char *cmdline)
{
int i;
if (pid < 1)
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++;
if (nextjid > MAXJOBS)
nextjid = 1; //如果作業ID超過了作業列表的大小,則重新置爲1
strcpy(jobs[i].cmdline, cmdline);
if (verbose){ //如果命令行參數有-v時則執行此語句塊
printf("Added job [%d] %d %s\n",jobs[i].jid, jobs[i].pid, jobs[i].cmdline);
}
return 1;
}
}
printf("Tried to create too many jobs\n"); //當作業列表被佔滿時
return 0;
}
/* 刪除作業 */
int delete_job(struct job_t *jobs, pid_t pid)
{
//printf("delete job\n");
int i;
if (pid < 1)
return 0;
for (i=0; i<MAXJOBS; i++){
if (jobs[i].pid == pid){
clear_job(&jobs[i]);
nextjid = max_jid(jobs)+1; //很有技巧
return 1;
}
}
return 0;
}
/* 返回當前前臺進程的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;
}
/* 根據PID返回所查詢的作業 */
struct job_t *get_job_pid(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;
}
/* 根據JID返回所查詢的作業 */
struct job_t *get_job_jid(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;
}
/* 把進程ID映射到作業ID */
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;
}
void list_jobs(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\n", jobs[i].cmdline);
}
}
//exit(0); 會直接退出整個程序
}
/* 打印幫助信息 */
void usage(void)
{
printf("Usage: shell [-hvp]\n");
printf(" -h: print this message\n");
printf(" -v: print additional diagnostic information\n"); //diagnostic是診斷的意思
printf(" -p: do not emit a cmmand prompt\n");
exit(1);
}
void unix_error(char *msg)
{
fprintf(stdout, "%s: %s\n", msg, strerror(errno));
exit(1);
}
void app_error(char *msg)
{
fprintf(stdout, "%s\n", msg);
exit(1);
}
/* sigaction的包裝函數 */
handler_t *Signal(int signum, handler_t *handler)
{
struct sigaction action, old_action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask); /* block sigs of type being handled */
action.sa_flags = SA_RESTART; /* restart syscalls if possible */
if (sigaction(signum, &action, &old_action) < 0)
unix_error("Signal error");
return (old_action.sa_handler);
}
void Kill(pid_t pid, int signum)
{
int rc;
if ((rc = kill(pid, signum)) < 0)
unix_error("kill error");
}
深入理解計算機系統第八章shell實驗
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.