计算机系统基础实训六-ShellLab实验

news2024/10/6 18:30:55

实验目的与要求

1、让学生更加理解进程控制的概念和具体操作方法;

2、让学生更加理解信号的概念和具体使用方法;

3、让学生更加理解Unix shell程序的原理和实现方法;

实验原理与内容

shell是一种交互式的命令行解释器,能代表用户运行程序。shell反复打印一个提示符等待stdin上的命令行,然后按照命令行的内容执行命令,如下图所示:

命令行是由空格分隔的ASCII字符串。命令行的第一个字符串要么是一个内置命令的名称,要么是一个可执行文件的路径名,剩下的字符串则为命令行参数。如果命令行的第一个字符串是一个内置命令,则shell会立即在当前进程中执行该命令。如果命令行的第一个字符串不是一个内置命令,shell会假定该字符串是一个可执行文件的路径名,在这种情况下shell会创建一个子进程并在子进程的上下文中加载和执行该可执行文件。每运行一个可执行文件将创建一个子进程,这些子进程组成shell的工作集,工作集中的各个子进程可以通过Unix管道进行连接。

如果命令行以符号“&”结尾,那么程序会在后台执行,这意味着shell不会等待程序的终止,shell会立即打印提示符并等待下一个命令输入。否则,程序会在前台执行,这意味着shell会等待程序终止后才能接收下一个命令行的输入。因此,在某一时刻系统中最多只能有一个前台任务,但是可以有任意数量的后台任务。

例如,输入命令行“jobs”会使得shell执行“jobs”这个内置命令。如果输入“/bin/ls -l -d”,则shell会在前台运行“/bin/ls”可执行文件,一般来说shell会保证程序会从可执行文件的main()函数开始执行,main()函数的声明如下所示:

int main(int argc, char *argv[])

在这个例子里参数argc和argv会有以下的值:

·argc == 3

·argv[0] == “/bin/ls”

·argv[1]== “-l”

·argv[2]== “-d”

如果输入命令“/bin/ls -l -d &”,则shell会在后台执行“/bin/ls”程序。

Unix shell支持任务控制的概念,它允许用户将任务在后台和前台之间来回切换,并更改任务中进程的状态(运行、停止或终止)。输入ctrl-c将导致一个SIGINT信号被传递到前台任务中的每个进程。这个SIGINT信号的默认操作是终止进程。类似地,输入ctrl-z会传递SIGTSTP信号给前台任务中的每个进程。SIGTSTP的默认操作是让一个进程处于停止状态,直到接收SIGCONT唤醒信号。Unix shell还提供各种支持任务控制的内置命令。例如:

• jobs: 列出正在运行或已经停止的后台任务.  

• bg <job>: 将一个停止的后台任务启动起来.

• fg <job>: 将一个正在执行的或已经停止的后台任务切换到前台并运行起来.  

• kill <job>:终止一个任务.

本实验的内容是编写一个简单的shell程序“tsh”,“tsh”要具有以下功能:

(1)命令行提示符字符串应为“tsh> ”。

(2)用户输入的命令行应由一个命令名称以及0个或多个参数所组成,命令名称和各个参数之间用一个或多个空格隔开。如果命令名称是一个内置命令,则tsh将在当前进程马上执行该命令然后才能接收下一个命令行输入。否则tsh应该假设该命令名称是一个可执行文件,并在子进程的上下文中加载和执行该可执行文件。

(3)tsh不需要有管道和IO重定向功能。

(4)输入ctrl-c(或ctrl-z)将发送一个SIGINT(或SIGTSTP)信号到当前前台任务以及该任务的任何子进程(它fork出来的任何子进程)。如果当前没有前台任务,则信号不应该产生任何作用。

(5)如果命令行是以“&”结尾的话,则命令要在后台执行,否则要在前台执行。

(6)tsh要给每一个任务分配一个正整数作为进程ID(PID)或任务ID(JID)。JID在命令行通过“%”进行引用,例如“%5”代表JID 5。而PID在命令行中直接通过数字引用,例如“5”代表PID 5。

(7)tsh要支持下列内置命令:

    quit:终止并退出tsh程序;

    jobs:列出所有后台任务;

    bg <job>:给<job>发送一个SIGCONT信号使其在后台继续运行起来,<job>参数可以是PID或JID。

    fg <job>:给<job>发送一个SIGCONT信号使其在前台继续运行起来,<job>参数可以是PID或JID。

(8)tsh要回收其所有的僵死子进程。如果任何进程因接收到没有在其进程中被捕获的信号而终止,那么tsh应该识别到这个事件,并打印一条带有进程PID和对该信号描述的信息。

实验设备与软件环境

1.Linux操作系统—64位 Ubuntu 18.04

2. C编译环境(gcc)

3. 计算机

实验过程与结果(可贴图)

1、BuiltinCmd()函数的实现:通过比较用户输入与预设的内置指令列表,决定是否直接在当前进程内执行对应功能。若匹配成功,则直接调用相应函数并返回1;否则,表明该指令非内置,返回0以进一步处理

2、Eval()函数解析逻辑

主要的思路:可以通过书上P525的eval函数写法和所需的功能来完成函数

  • 创建处理输入的数据的函数,调用parseline函数解析命令行
  • 使用builtin_cmd( )函数判断内置命令,如果是的话直接执行
  • 如果不是内置命令的话,就先阻塞信号,再调用fork创建子进程
  • 在子进程中,首先接触阻塞,设置id号,调用execve来执行job
  • 父进程判断作业是否后台运行,是的话调用addjob函数将子进程job加入job链表中,解除阻塞,调用waifg函数等待前台运行完成。不是的话后台工作则打印进程组jid和子进程pid以及命令行字符串。

在执行新程序之前,子程序必须确保解开对SIGCHLD信号的阻塞。父进程必须使用sigprocmask在它派生子进程之前也就是调用fork()函数之前阻塞SIGCHLD信号,之后解除阻塞;在通过调用addjob将子进程添加到作业列表之后,再次使用sigprocmask,解除阻塞。

自动测试tsh:

·按test01.txt测试,对比tshref的运行输出;

·按rtest01.txt测试,对比tshref的运行输出;

得出的结果一致

·trace03.txt测试tsh(对比tshref)命令及前台作业功能,正常:

3、sigchld_handler()函数:处理信号(课本P544)

函数原型:void sigchld_handler(int sig),参数为信号类型

一个进程可以通过调用 waitpid() 函数来等待它的子进程停止。如果回收成功,则返回为子进程的 PID, 如果 WNOHANG, 则返回为 0, 如果其他错误,则为 -1。

用while循环调用waitpid直到它所有的子进程停止。

3、sigchld_handler()函数:处理信号(课本P544)

函数原型:void sigchld_handler(int sig),参数为信号类型

一个进程可以通过调用 waitpid() 函数来等待它的子进程停止。如果回收成功,则返回为子进程的 PID, 如果 WNOHANG, 则返回为 0, 如果其他错误,则为 -1。

用while循环调用waitpid直到它所有的子进程停止。

4、sigint_handler()函数

调用函数fgpid返回前台进程pid

如果当前进程pid不为0,那么调用kill函数发送SIGINT信号给前台进程组

在2中调用kill函数如果返回值为-1表示进程不存在。输出error

5、waitfg()函数:等待一个前台作业结束,或者说是阻塞一个前台的进程直到这个进程变为后台进程

函数原型:void waitfg(pid_t pid) ,参数为进程ID

判断当前的前台的进程组pid是否和当前进程的pid是否相等,如果相等则sleep直到前台进程结束。

4、do_bgfg()函数:实现内置命令bg 和 fg

函数原型:void do_bgfg(char **argv),参数为argv 参数列表

首先通过getjobjid函数判断输入的pid/jid对应的进程是否存在;

无论是bg还是fg,都需要让其继续运行(不论为停止状态还是运行状态);

如果是bg,将作业状态设置为BG表示后台运行;

如果是fg,将作业状态设置为FG表示前台运行,并等待前台作业终止;

前3关做完了,后面的关卡看一下trace__的文件

第四关-后台与前台任务处理:当命令以"&"%"标识符结束时,表明任务需在后台异步执行,shell随即打印提示符准备接收新的命令。否则,任务在前台执行,即shell等待该任务完成才继续。此外,确保了前台仅单一任务,但可同时有多个后台任务并发。

第五关-处理jobs内置命令:分别运行了前台echo、后台myspin、前台echo、后台myspin,然后需要实现一个内置命令job,功能是显示目前任务列表中的所有任务的所有属性

第六关-信号处理机制:集成对SIGINT和SIGTSTP信号的响应,分别对应于用户按下Ctrl-C与Ctrl-Z。信号处理逻辑确保无前台任务时,信号不触发任何动作;反之,适配对任务发送信号并执行默认行为——终止或暂停。

第七关-仅将SIGINT转发给前台作业:第七关测试的是只将SIGINT转发给前台作业。这里的命令行其实根据前面的就很好理解了,就是给出两个作业,一个在前台工作,另一个在后台工作,接下来传递SIGINT指令,然后调用内置指令jobs来查看此时的工作信息,来对比出是不是只将SIGINT转发给前台作业。

第八关-仅将SIGTSTP转发到前台作业:需要将SIGTSTP转发给前台作业。根据这个信号的作用,也就是该进程会停止直到下一个SIGCONT也就是挂起,让别的程序继续运行。这里也就是运行了后台程序,然后使用jobs来打印出进程的信息。

第九关-进程bg内置命令:在第八关的测试文件之上的一个更加完整的测试,这里也就是在停止后,输出进程信息之后,使用bg命令来唤醒进程2,也就是刚才被挂起的程序,接下来继续使用Jobs命令来输出结果。

第十关-进程fg内置命令:将后台的进程更改为前台正在运行的程序。测试文中进程1根据&可以知道,进程1是一个后台进程。先使用fg命令将其转化为前台的一个程序,接下来停止进程1,然后打印出进程信息,这时候进程1应该是前台程序同时被挂起了,接下来使用fg命令使其继续运行,使用jobs来打印出进程信息

第十一关-将SIGINT转发给前台进程组中的每个进程:需要将SIGINT发给前台进程组中的每个进程。ps –a 显示所有进程,这里是有两个进程的,mysplit创建了一个子进程,接下来发送指令SIGINT,所以进程组中的所有进程都应该停止,接下来调用pl来查看该进程组中的每个进程是否都停止了。

第十二关-将SIGTSTP转发到前台进程组中的每个进程:该测试程序是为了测试将SIGTSTP转发给前台进程组中的每个进程。与上一关相同,只需要相应的进程被挂起即可。

第十三关-重新启动进程组中的每个已停止的进程:该程序是为了测试重新启动进程组中的每个停止的进程。这里也就是使用fg来唤醒整个工作,中间使用ps -a来查看停止整个工作和唤醒整个工作的区别。

第十四关-简单的错误处理:为了测试简单的错误处理。这里的测试文件,也就是测试fg和bg后面的参数,我们知道fg和bg后面需要一个JID或者是PID,其中JID是加上%的整型数。其余参数都应该报错,或是没有参数也应该报错。接下来测试的功能,都在上面的关卡测试过了

第十五关-结合在一起

第十六关-测试shell是否能够处理来自其他进程而不是终端的SIGTSTP和SIGINT信号:这个测试文件的具体含义就是,用户程序向job 2传送了中止信号,所以最后会输出进程2被中止的信息。同时,mystop需要自己停止才能给别的进程发送信号,所以中间也会出现进程1被中止的信息

实验总结

在本次Shell实验室实践中,我深入探索了构建小型Shell程序的核心机制,特别是在进程控制、信号处理及作业管理方面的应用。实验的关键环节如下:

    命令解析与执行逻辑定制:我首先开发了Builtin_Cmd()函数,该函数通过比对用户输入与内置命令列表,如quit、jobs、bg、fg,迅速执行对应操作或返回控制流。若非内置,则通过eval()函数处理。此函数负责解析用户输入,通过parseline()解析命令行,确定作业是前台(FG)还是后台(BG)执行。

    进程与作业管理:针对作业执行,我实现了后台(&)命令的逻辑,允许作业在后台独立运行而不阻塞shell。利用fork()创建子进程,并通过setpgid确保子进程拥有独立进程组,避免后台作业接收前台信号干扰。addjob()管理作业状态,记录进程ID与作业ID,PID与JID的映射。

    信号响应机制:我设计了信号处理器,如sigint_handler()与st_handler(),以响应SIGINT(Ctrl+C)和SIGTSTP(Ctrl+Z)信号。确保前台作业正确响应信号,后台作业不受影响。此外,sigchld_handler()确保僵死进程得以回收,通过waitpid()监控并清理。

    作业控制命令实现:do_bgfg()和do_fg()内置命令,分别用于将作业从前台切换到后台或恢复到前台。通过getjobjid()检索作业信息,无论作业状态如何,bg命令将作业设为后台,fg则等待作业前台完成。

    错误处理与测试:我进行了全面的测试,如通过trace文件模拟实际场景,如后台echo命令后跟&、jobs列出作业、以及fg与bg命令的交互,确保了作业控制逻辑的准确性。此外,我解决了子进程成为僵尸进程未回收的问题,确保了系统资源的有效管理。

    通过本实验,不仅增强了对Unix Shell程序构建原理的深入理解,而且提升了信号处理、进程控制、作业管理及异常处理等实用技巧。实验成果体现在能够编写出一个具备基本功能的简化Shell,能有效处理用户命令、管理作业,以及正确响应信号,展现了良好的系统交互能力。

所有代码

/* 
 * tsh - A tiny shell program with job control
 * 
 * <Put your name and login ID here>
 */
#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>

/* 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.
 */

/* Global variables */
extern char **environ;      /* defined in libc */
char prompt[] = "tsh> ";    /* command line prompt (DO NOT CHANGE) */
int verbose = 0;            /* if true, print additional output */
int nextjid = 1;            /* next job ID to allocate */
char sbuf[MAXLINE];         /* for composing sprintf messages */

struct job_t {              /* The job struct */
    pid_t pid;              /* job PID */
    int jid;                /* job ID [1, 2, ...] */
    int state;              /* UNDEF, BG, FG, or ST */
    char cmdline[MAXLINE];  /* command line */
};
struct job_t jobs[MAXJOBS]; /* The job list */
/* End global variables */


/* Function prototypes */

/* Here are the functions that you will implement */
void eval(char *cmdline);
int builtin_cmd(char **argv);
void do_bgfg(char **argv);
void waitfg(pid_t pid);

void sigchld_handler(int sig);
void sigtstp_handler(int sig);
void sigint_handler(int sig);

/* Here are helper routines that we've provided for you */
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); 
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);
struct job_t *getjobpid(struct job_t *jobs, pid_t pid);
struct job_t *getjobjid(struct job_t *jobs, int jid); 
int pid2jid(pid_t pid); 
void listjobs(struct job_t *jobs);

void usage(void);
void unix_error(char *msg);
void app_error(char *msg);
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler);

/*
 * main - The shell's main routine 
 */
int main(int argc, char **argv) 
{
    char c;
    char cmdline[MAXLINE];
    int emit_prompt = 1; /* emit prompt (default) */

    /* Redirect stderr to stdout (so that driver will get all output
     * on the pipe connected to stdout) */
    dup2(1, 2);

    /* Parse the command line */
    while ((c = getopt(argc, argv, "hvp")) != EOF) {
        switch (c) {
        case 'h':             /* print help message */
            usage();
	    break;
        case 'v':             /* emit additional diagnostic info */
            verbose = 1;
	    break;
        case 'p':             /* don't print a prompt */
            emit_prompt = 0;  /* handy for automatic testing */
	    break;
	default:
            usage();
	}
    }

    /* Install the signal handlers */

    /* These are the ones you will need to implement */
    Signal(SIGINT,  sigint_handler);   /* ctrl-c */
    Signal(SIGTSTP, sigtstp_handler);  /* ctrl-z */
    Signal(SIGCHLD, sigchld_handler);  /* Terminated or stopped child */

    /* This one provides a clean way to kill the shell */
    Signal(SIGQUIT, sigquit_handler); 

    /* Initialize the job list */
    initjobs(jobs);

    /* Execute the shell's read/eval loop */
    while (1) {

	/* Read command line */
	if (emit_prompt) {
	    printf("%s", prompt);
	    fflush(stdout);
	}
	if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))
	    app_error("fgets error");
	if (feof(stdin)) { /* End of file (ctrl-d) */
	    fflush(stdout);
	    exit(0);
	}

	/* Evaluate the command line */
	eval(cmdline);
	fflush(stdout);
	fflush(stdout);
    } 

    exit(0); /* control never reaches here */
}
  
/* 
 * eval - Evaluate the command line that the user has just typed in
 * 
 * If the user has requested a built-in command (quit, jobs, bg or fg)
 * then execute it immediately. Otherwise, fork a child process and
 * run the job in the context of the child. If the job is running in
 * the foreground, wait for it to terminate and then return.  Note:
 * each child process must have a unique process group ID so that our
 * background children don't receive SIGINT (SIGTSTP) from the kernel
 * when we type ctrl-c (ctrl-z) at the keyboard.  
*/
void eval(char *cmdline) 
{
    char* argv[MAXARGS];   //execve()函数的参数
    int state = UNDEF;  //工作状态,FG或BG 
    sigset_t set;
    pid_t pid;  //进程id
    // 处理输入的数据
    if(parseline(cmdline, argv) == 1)  //解析命令行,返回给argv数组
        state = BG;
    else
        state = FG;
    if(argv[0] == NULL)  //命令行为空直接返回
        return;
    // 如果不是内置命令
    if(!builtin_cmd(argv))
    {
        if(sigemptyset(&set) < 0)
            unix_error("sigemptyset error");
        if(sigaddset(&set, SIGINT) < 0 || sigaddset(&set, SIGTSTP) < 0 || sigaddset(&set, SIGCHLD) < 0)
            unix_error("sigaddset error");
        //在它派生子进程之前阻塞SIGCHLD信号,防止竞争 
        if(sigprocmask(SIG_BLOCK, &set, NULL) < 0)
            unix_error("sigprocmask error");

        if((pid = fork()) < 0)  //fork创建子进程失败 
            unix_error("fork error");
        else if(pid == 0)  //fork创建子进程
        {
            // 子进程的控制流开始
            if(sigprocmask(SIG_UNBLOCK, &set, NULL) < 0)  //解除阻塞
                unix_error("sigprocmask error");
            if(setpgid(0, 0) < 0)  //设置子进程id 
                unix_error("setpgid error");
            if(execve(argv[0], argv, environ) < 0){
                printf("%s: command not found\n", argv[0]);
                exit(0);
            }
        }
        // 将当前进程添加进job中,无论是前台进程还是后台进程
        addjob(jobs, pid, state, cmdline);
        // 恢复受阻塞的信号 SIGINT SIGTSTP SIGCHLD
        if(sigprocmask(SIG_UNBLOCK, &set, NULL) < 0)
            unix_error("sigprocmask error");

        // 判断子进程类型并做处理
        if(state == FG)
            waitfg(pid);  //前台作业等待
        else
            printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);  //将进程id映射到job id   
    }
    return;
}

/* 
 * parseline - Parse the command line and build the argv array.
 * 
 * Characters enclosed in single quotes are treated as a single
 * argument.  Return true if the user has requested a BG job, false if
 * the user has requested a FG job.  
 */
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++;
	delim = strchr(buf, '\'');
    }
    else {
	delim = strchr(buf, ' ');
    }

    while (delim) {
	argv[argc++] = buf;
	*delim = '\0';
	buf = delim + 1;
	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;
}

/* 
 * builtin_cmd - If the user has typed a built-in command then execute
 *    it immediately.  
 */
int builtin_cmd(char **argv) 
{
    if(!strcmp(argv[0], "quit"))  //如果命令是quit,退出
        exit(0);
    else if(!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg"))  //如果是bg或者fg命令,执行do_fgbg函数 
        do_bgfg(argv);
    else if(!strcmp(argv[0], "jobs"))  //如果命令是jobs,列出正在运行和停止的后台作业
        listjobs(jobs);
    else
        return 0;     /* not a builtin command */
    return 1;
}

/* 
 * do_bgfg - Execute the builtin bg and fg commands
 */
void do_bgfg(char **argv)
{
    int num;
    struct job_t *job;
    // 没有参数的fg/bg应该被丢弃
    if(!argv[1]){  //命令行为空
        printf("%s command requires PID or %%jobid argument\n", argv[0]);
        return ;
    }
    // 检测fg/bg参数,其中%开头的数字是JobID,纯数字的是PID
    if(argv[1][0] == '%'){  //解析jid
        if((num = strtol(&argv[1][1], NULL, 10)) <= 0){
            printf("%s: argument must be a PID or %%jobid\n",argv[0]);//失败,打印错误消息
            return;
        }
        if((job = getjobjid(jobs, num)) == NULL){
            printf("%%%d: No such job\n", num); //没找到对应的job 
            return;
        }
    } else {
        if((num = strtol(argv[1], NULL, 10)) <= 0){
            printf("%s: argument must be a PID or %%jobid\n",argv[0]);//失败,打印错误消息
            return;
        }
        if((job = getjobpid(jobs, num)) == NULL){
            printf("(%d): No such process\n", num);  //没找到对应的进程 
            return;
        }
    }

    if(!strcmp(argv[0], "bg")){
        // bg会启动子进程,并将其放置于后台执行
        job->state = BG;  //设置状态 
        if(kill(-job->pid, SIGCONT) < 0)  //采用负数发送信号到进程组 
            unix_error("kill error");
        printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
    } else if(!strcmp(argv[0], "fg")) {
        job->state = FG;  //设置状态 
        if(kill(-job->pid, SIGCONT) < 0)  //采用负数发送信号到进程组 
            unix_error("kill error");
        // 当一个进程被设置为前台执行时,当前tsh应该等待该子进程结束
        waitfg(job->pid);
    } else {
        puts("do_bgfg: Internal error");
        exit(0);
    }
    return;
}


/* 
 * waitfg - Block until process pid is no longer the foreground process
 */
void waitfg(pid_t pid)
{
    struct job_t *job = getjobpid(jobs, pid);
    if(!job) return;

    // 如果当前子进程的状态没有发生改变,则tsh继续休眠
    while(job->state == FG)
        // 使用sleep的这段代码会比较慢,最好使用sigsuspend
        sleep(1);

    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, jid;
    pid_t pid;
    struct job_t *job;

    if(verbose)
        puts("sigchld_handler: entering");

    /*
    以非阻塞方式等待所有子进程
    waitpid 参数3:
        1.     0     : 执行waitpid时, 只有在子进程 **终止** 时才会返回。
        2. WNOHANG   : 若子进程仍然在运行,则返回0 。
                注意只有设置了这个标志,waitpid才有可能返回0
        3. WUNTRACED : 如果子进程由于传递信号而停止,则马上返回。
                只有设置了这个标志,waitpid返回时,其WIFSTOPPED(status)才有可能返回true
    */
    while((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0){

        // 如果当前这个子进程的job已经删除了,则表示有错误发生
        if((job = getjobpid(jobs, pid)) == NULL){
            printf("Lost track of (%d)\n", pid);
            return;
        }

        jid = job->jid;
        //接下来判断三种状态 
        // 如果这个子进程收到了一个暂停信号(还没退出) 
        if(WIFSTOPPED(status)){
            printf("Job [%d] (%d) stopped by signal %d\n", jid, job->pid, WSTOPSIG(status));
            job->state = ST;  //状态设为挂起 
        }
        // 如果子进程通过调用 exit 或者一个返回 (return) 正常终止
        else if(WIFEXITED(status)){
            if(deletejob(jobs, pid))
                if(verbose){
                    printf("sigchld_handler: Job [%d] (%d) deleted\n", jid, pid);
                    printf("sigchld_handler: Job [%d] (%d) terminates OK (status %d)\n", jid, pid, WEXITSTATUS(status));
                }
        }
        // 如果子进程是因为一个未被捕获的信号终止的,例如SIGKILL
        else {
            if(deletejob(jobs, pid)){  //清除进程
                if(verbose)
                    printf("sigchld_handler: Job [%d] (%d) deleted\n", jid, pid);
            }
            printf("Job [%d] (%d) terminated by signal %d\n", jid, pid, WTERMSIG(status));  //返回导致子进程终止的信号的数量
        }
    }

    if(verbose)
        puts("sigchld_handler: exiting");

    return;
}


/* 
 * sigint_handler - The kernel sends a SIGINT to the shell whenver the
 *    user types ctrl-c at the keyboard.  Catch it and send it along
 *    to the foreground job.  
 */
void sigint_handler(int sig)
{
    if(verbose)
        puts("sigint_handler: entering");
    pid_t pid = fgpid(jobs);

    if(pid){
        // 发送SIGINT给前台进程组里的所有进程
        // 需要注意的是,前台进程组内的进程除了当前前台进程以外,还包括前台进程的子进程。
        // 最多只能存在一个前台进程,但前台进程组内可以存在多个进程
        if(kill(-pid, SIGINT) < 0)
            unix_error("kill (sigint) error");
        if(verbose){
            printf("sigint_handler: Job (%d) killed\n", pid);
        }
    }
    if(verbose)
        puts("sigint_handler: exiting");
    return;
}


/*
 * sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever
 *     the user types ctrl-z at the keyboard. Catch it and suspend the
 *     foreground job by sending it a SIGTSTP.  
 */
void sigtstp_handler(int sig)
{
    if(verbose)
        puts("sigstp_handler: entering");

    pid_t pid = fgpid(jobs);
    struct job_t *job = getjobpid(jobs, pid);

    if(pid){
        if(kill(-pid, SIGTSTP) < 0)
            unix_error("kill (tstp) error");
        if(verbose){
            printf("sigstp_handler: Job [%d] (%d) stopped\n", job->jid, pid);
        }
    }
    if(verbose)
        puts("sigstp_handler: exiting");
    return;
}


/*********************
 * 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 - Add a job to the job list */
int addjob(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;
	    strcpy(jobs[i].cmdline, cmdline);
  	    if(verbose){
	        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;
}

/* deletejob - Delete a job whose PID=pid from the job list */
int deletejob(struct job_t *jobs, pid_t pid) 
{
    int i;

    if (pid < 1)
	return 0;

    for (i = 0; i < MAXJOBS; i++) {
	if (jobs[i].pid == pid) {
	    clearjob(&jobs[i]);
	    nextjid = maxjid(jobs)+1;
	    return 1;
	}
    }
    return 0;
}

/* fgpid - Return PID of current foreground job, 0 if no such job */
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  - Find a job (by PID) on the job list */
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  - Find a job (by JID) on the job list */
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 - Map process ID to job 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;
}

/* listjobs - Print the job list */
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);
	}
    }
}
/******************************
 * end job list helper routines
 ******************************/


/***********************
 * Other helper routines
 ***********************/

/*
 * usage - print a help message
 */
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-style error routine
 */
void unix_error(char *msg)
{
    fprintf(stdout, "%s: %s\n", msg, strerror(errno));
    exit(1);
}

/*
 * app_error - application-style error routine
 */
void app_error(char *msg)
{
    fprintf(stdout, "%s\n", msg);
    exit(1);
}

/*
 * Signal - wrapper for the sigaction function
 */
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);
}

/*
 * sigquit_handler - The driver program can gracefully terminate the
 *    child shell by sending it a SIGQUIT signal.
 */
void sigquit_handler(int sig) 
{
    printf("Terminating after receipt of SIGQUIT signal\n");
    exit(1);
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1852814.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从关键新闻和最新技术看AI行业发展(2024.6.3-6.16第二十五期) |【WeThinkIn老实人报】

写在前面 【WeThinkIn老实人报】旨在整理&挖掘AI行业的关键新闻和最新技术&#xff0c;同时Rocky会对这些关键信息进行解读&#xff0c;力求让读者们能从容跟随AI科技潮流。也欢迎大家提出宝贵的优化建议&#xff0c;一起交流学习&#x1f4aa; 欢迎大家关注Rocky的公众号&…

Node.js 系列之 Express 框架入门实战教程

目录 1 Node.js是什么2 Express初体验3 Express 路由3.1 什么是路由3.2 路由的使用3.3 获取路由参数 4 常见响应设置4.1 express 响应方法4.2 原生响应方法 5 express 中间件5.1 中间件作用5.2 中间件类型5.3 全局中间件5.4 路由中间件 6 获取请求体数据7 路由模块化 1 Node.js…

51单片机学习记录(二)————外部中断

文章目录 前言一、中断1.中断的定义 二、51中断1.中断源2.中断的配置3.中断允许控制寄存器4.中断触发方式51中断有两种触发方式&#xff1a; 三、外部中断1.外部中断引脚2.外部中断配置&#xff08;以外部中断0为例&#xff09; 总结 前言 一个学习嵌入式的小白~ 有错误评论区…

【ARM】PK51如何将BL51链接器切换成LX51链接器

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 解决客户在使用PK51进行项目研发的时候&#xff0c;想要使用LX51链接器进行使用。 2、 问题场景 客户在使用51芯片进行开发的时候&#xff0c;发现工程中使用的是BL51链接器&#xff0c;而不是LX51链接器&#xff…

【非常实验】如何在移动设备上运行 Docker?

本章就从在 DevOps 中最基本但也是最强大的工具 Docker 开始。最近,我在尝试更多Termux的可能性,于是就想着试试Docker适不适合arm架构。 我用的是天玑9000芯片,而不是高通,所以显示不出来 Qualcomm。所以我决定从在手机上运行 docker 开始,但这可能吗?让我们一起来看看吧…

Node.js实现短链接(ShortLink):shortid、epxress让URL更简单

文章目录 一、短链接介绍二、插件介绍1、epxress2、shortid 三、实现方案1、安装依赖&#xff1a;2、实现原理 四、示例代码五、测试生产短链接 一、短链接介绍 短链接是指仅包含一个网址的链接形式&#xff0c;通俗一些就是将一个很长很复杂的的网址变成一个简短易记的链接。…

基于CSDN的Markdown文本编辑器的博客界面优化 | HTML | 文本标签 | 图像标签 | 个人主页引导

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 今天毛毛张分享的内容是如何在CSDN的Markdown编辑器中实现上图的效果&#xff0c;如果觉得能帮助到你的话就点击个人主页点点关注吧❗ 文章目录 1.前言2.基础知识3.字…

游戏工厂:AI(AIGC/ChatGPT)与流程式游戏开发

游戏工厂&#xff1a;AI&#xff08;AIGC/ChatGPT&#xff09;与流程式游戏开发 码客 卢益贵 ygluu 关键词&#xff1a;AI&#xff08;AIGC、ChatGPT、文心一言&#xff09;、流程式管理、好莱坞电影流程、电影工厂、游戏工厂、游戏开发流程、游戏架构、模块化开发 一、前言…

ArcGIS批量投影转换的妙用(地理坐标系转换为平面坐标系)

​ 点击下方全系列课程学习 点击学习—>ArcGIS全系列实战视频教程——9个单一课程组合系列直播回放 这次文章我们来介绍一下&#xff0c;如何巧妙用要素数据集来实现要素的批量投影。不需要ArcGIS的模型构建器与解决。 例如&#xff0c;有多个要素要将CGCS_2000地理坐标系投…

单机、集群和分布式

目录 1.概述 2.单机服务器 单机版的服务器的性能&#xff0c;设计上的瓶颈&#xff1f; 3.集群 解决瓶颈1&#xff1a; 没有解决瓶颈2&#xff1a; 没有解决瓶颈3&#xff1a; 集群的优点&#xff1f; 集群的缺点&#xff1f; 4.分布式 分布式的优点&#xff1f; 分…

1970-2022年中国碳排放1KM栅格数据

【数据简介】 数据名称&#xff1a;1970-2022年中国碳排放栅格数据&#xff08;1KM) 区域范围&#xff1a;全国 数据格式&#xff1a;tif文件 数据大小:800M 数据来源&#xff1a;欧盟委员会全球大气排放数据库&#xff08;EDGAR) 部分数据预览&#xff1a; 原文链接http…

君子小人的格局、境界

子曰&#xff1a;君子怀德&#xff0c;小人怀土&#xff1b;君子怀刑&#xff0c;小人怀惠。 直译&#xff1a;君子怀念道德&#xff0c;小人怀念乡土&#xff1b;君子关心法度&#xff0c;小人关心恩惠。 这里的君子与小人只是体现格局、境界的不同&#xff1b; 君子怀的是德…

算法基础精选题单 动态规划(dp)(递推+线性dp)(个人题解)

前言&#xff1a; 一些简单的dp问题。 正文&#xff1a; 题单&#xff1a;237题】算法基础精选题单_ACM竞赛_ACM/CSP/ICPC/CCPC/比赛经验/题解/资讯_牛客竞赛OJ_牛客网 (nowcoder.com) 递推&#xff1a; NC235911 走楼梯&#xff1a; #include<bits/stdc.h> using na…

JAVA期末复习题1

目录 Java 填空题整理及解析 1. 说出Java的特点&#xff1a; 2. Java的运行机制是先编译再解释运行。 3. 请按照以下分类补全对应的数据类型&#xff1a; 4. 在有限次数循环时&#xff0c;一般选择for循环结构&#xff1b;未知循环次数时&#xff0c;可以选择while循环结构…

一文读懂LLM API应用开发基础(万字长文)

前言 Hello&#xff0c;大家好&#xff0c;我是GISer Liu&#x1f601;&#xff0c;一名热爱AI技术的GIS开发者&#xff0c;上一篇文章中我们详细介绍了LLM开发的基本概念&#xff0c;包括LLM的模型、特点能力以及应用&#xff1b;&#x1f632; 在本文中作者将通过&#xff1a…

用VPS部署聊天机器人有哪些优势?

VPS足以帮助您将人工智能 (AI) 的功能无缝融入聊天机器人并增强客户支持。聊天机器人已迅速成为改善用户体验的首选解决方案&#xff0c;因为它们全天候在线且可编程回答各种问题。 聊天机器人在客户支持方面的作用不容置疑。但所有出色的解决方案都需要出色的网络托管。 VPS…

三国之家网站的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;论坛管理&#xff0c;公告管理&#xff0c;三国视频管理&#xff0c;基础数据管理&#xff0c;三国图文管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#…

(2024.6.23)最新版MAVEN的安装和配置教程(超详细)

1.什么是MAVEN Maven是一个自动化构建工具&#xff0c;主要用于Java项目&#xff0c;它由Apache软件基金会维护。Maven能够自动化完成编译、测试、打包、发布等构建过程&#xff0c;可以大大提高开发效率&#xff0c;保证项目的质量。 下面我们从几个方面来介绍一下MAVEN的功能…

《人生苦短,我用python·四》pybind11多场景使用

引言 Pybind11作为一个强大的工具&#xff0c;不仅可以轻松地将简单的C函数和类暴露给Python&#xff0c;还可以处理更复杂的场景&#xff0c;比如支持C标准库容器、处理C异常、以及自定义数据结构的转换。本文将深入介绍Pybind11的一些高级用法&#xff0c;帮助你在实际项目中…

为什么用excel求出的和是错误的?

Excel中求和结果错误的原因可能有几种常见的情况&#xff1a;1. **数据格式问题**&#xff1a;有时候数字可能被错误地视为文本格式。这种情况下&#xff0c;Excel 在求和时会忽略这些单元格。你可以通过将这些单元格的格式改为数值格式来解决。2. **隐藏的行或列**&#xff1a…