You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A signal is a small message that notifies a process that an event of some type has occurred in the system
Similar to exceptions and interrupts
Sent from the kernel to a process
Signal type is identified by small integer ID’s (1 - 30)
Only information in a signal is its ID and the fact that it arrives.
Sending a Signal
Kernel sends a signal to a destination process by updating some state in the context of the destination process. It will send a signal for one of the following reasons:
Kernel has detected a system event such as divide-by-zero (SIGFPE) or the termination of a child process (SIGCHILD)
Another process has invoked the kill system call to explicitly request the kernel to send a signal to the destination process
因此发送信号的流程可能是 kernel → process,也可以是 process → kernel → process
ctrl + c 会发送一个 SIGINT 信号到前台进程组中的每一个进程,结果将是终止前台作业 —— 你输入 ps 看不到这个前台进程了
ctrl + z 会发送一个 SIGTSTP 信号到前台进程组中的每一个进程,默认情况是停止(挂起)前台作业 —— 你输入 ps 仍然可以看到这个前台进程
Receiving a Signal
A destination process receives a signal when it is forced by the kernel to react in some way to the delivery of the signal. Some possible way to react:
Ignore the signal (do nothing)
Terminate the process (with optional core dump)
Catch the signal by executing a user-level function called signal handler
Pending and Blocked Signal
A signal is pending if sent but not yet received
There can be at most one pending signal of any particular type
Important, signals are not queued
If a process has a pending signal of type k, then subsequent signals of type k that are sent to the process are discarded
A process can block the receipt of certain signals
Blocked signals can be delivered, but will not be received until the signal is unblocked
A pending signal is received at most once
Pending / Blocked Bits
Kernel maintains pending and blocked bit vectors in the context of each process
pending bits: represents the set of pending signals
Kernel sets bit k in pending when a signal of type k is delivered
Kernel clears bit k in pending when a signal of type k is received
blocked bits: represents the set of blocked signals
在这里我们可以先写几个工具函数。帮助我们快速创建一个新的 signal set,并且 block / unblock 信号量。
/***************** * Signal Setters *****************//** * Setting up a new empty signal set. * * First we will initialize the signal set pointed to by mask to empty, with all signals excluded from the set. * And then we will add SIGCHLD, SIGINT, and SIGTSTP to the set. * * After these calls, mask would be a signal set that includes SIGCHLD, SIGINT, and SIGTSTP. * And we can use this signal set to block / unblock / or change the way these signals are handled by a process. * * @param mask */voidset_signals(sigset_t*mask) {
sigemptyset(mask);
sigaddset(mask, SIGCHLD);
sigaddset(mask, SIGINT);
sigaddset(mask, SIGTSTP);
}
/** * We will call sigprocmask function to block all signals that are currently in * the set pointed to by mask. * And this function will store the previous mask into prev_mask. So we can * restore (unblock) the signal set with the prev_mask. * * @param mask * @param prev_mask */voidblock_signals(sigset_t*mask, sigset_t*prev_mask) {
sigprocmask(SIG_BLOCK, mask, prev_mask);
}
/** * Unblock the signal set with the prev_mask. * * @param prev_mask */voidunblock_signals(sigset_t*prev_mask) {
sigprocmask(SIG_SETMASK, prev_mask, NULL);
}
structcmdline_tokens {
intargc; ///< Number of arguments passedchar*argv[MAXARGS]; ///< The arguments listchar*infile; ///< The filename for input redirection, or NULLchar*outfile; ///< The filename for output redirection, or NULLbuiltin_statebuiltin; ///< Indicates if argv[0] is a builtin commandchar_buf[MAXLINE_TSH]; ///< Internal backing buffer (do not use)
};
typedefenumbuiltin_state {
BUILTIN_NONE=8, ///< Not a builtin commandBUILTIN_QUIT=9, ///< `quit` (exit the shell)BUILTIN_JOBS=10, ///< `jobs` (list running jobs)BUILTIN_BG=11, ///< `bg` (run job in background)BUILTIN_FG=12///< `fg` (run job in foreground)
} builtin_state;
/** * @brief * * In this function, we will parse the commands input from the user. And based on * the type of command, call different handler functions. * * NOTE: The shell is supposed to be a long-running process, so this function * (and its helpers) should avoid exiting on error. This is not to say * they shouldn't detect and print (or otherwise handle) errors! */voideval(constchar*cmdline) {
parseline_returnparse_result;
structcmdline_tokenstoken;
intfd_in=STDIN_FILENO;
intfd_out=STDOUT_FILENO;
// Parse command lineparse_result=parseline(cmdline, &token);
sigset_tmask, prev_mask;
/* When our command declared outfile */if (token.outfile!=NULL) {
fd_out=open(token.outfile, O_WRONLY | O_CREAT | O_TRUNC, DEF_MODE);
if (fd_out<0) {
if (errno==EACCES)
sio_printf("%s: Permission denied\n", token.outfile);
elseif (errno==ENOENT)
sio_printf("%s: No such file or directory\n", token.outfile);
return;
}
}
/* When our command declared infile */if (token.infile!=NULL) {
fd_in=open(token.infile, O_RDONLY, DEF_MODE);
if (fd_in<0) {
if (errno==EACCES)
sio_printf("%s: Permission denied\n", token.infile);
elseif (errno==ENOENT)
sio_printf("%s: No such file or directory\n", token.infile);
return;
}
}
if (parse_result==PARSELINE_ERROR||parse_result==PARSELINE_EMPTY) {
return;
}
set_signals(&mask);
/* Implement command handlers */switch (token.builtin) {
caseBUILTIN_QUIT: // For Quit command, exit directlyexit(0);
break;
caseBUILTIN_FG: // FG and BG command has similar logiccaseBUILTIN_BG:
handle_fgbg(&token, mask, prev_mask);
break;
caseBUILTIN_JOBS: // Check the list of jobsblock_signals(&mask, &prev_mask);
list_jobs(fd_out);
unblock_signals(&prev_mask);
break;
caseBUILTIN_NONE: // Consider as a path or executable fileblock_signals(&mask, &prev_mask);
handle_none(fd_out, fd_in, token, prev_mask, cmdline, parse_result);
unblock_signals(&prev_mask);
default:
break;
}
return;
}
Step 2. list_jobs
当我们输入的指令是 BUILTIN_JOBS 时,我们会调用 list_jobs 函数,来展现当前的 jobs 列表。
/** * @brief * * This function will handle SIGINT signal (Ctrl-C). The SIGINT signal will * interrupt and terminate a process. * We will check if there is a fg job exist, and then sends the SIGINT signal * to all processes in the foreground process group. * * The current value of errno is saved at the start and restored at the end to * ensure the handler does not interfere with the normal execution flow by changing errno * */voidsigint_handler(intsig) { // Handle Ctrl-C -> DONEint_errno=errno;
if (fg_pid>0)
killpg(fg_pid, sig);
errno=_errno;
return;
}
/** * @brief * * This function will handle SIGTSTP signal (Ctrl-Z). The SIGTSTP signal will * pause the current foreground job. * We will check if there is a fg job exist, and then sends the SIGTSTP signal * to all processes in the foreground process group * * The current value of errno is saved at the start and restored at the end to * ensure the handler does not interfere with the normal execution flow by changing errno * */voidsigtstp_handler(intsig) { // Handle Ctrl-Z -> DONEint_errno=errno;
if (fg_pid>0)
killpg(fg_pid, sig);
errno=_errno;
return;
}
/** * If not a builtin command, we will consider it as a path or executable file * Then we will create a new process try to run this file. * * If we define infile or outfile in our command, first we will run dup2 function * to redirection the pointer from the file descriptor to the file table. * And then we will try to execute the file. * * If it is a fg job, we will run it in the foreground explicitly. * And if it a bg job, we will run it in the backward and only print one line * information in the shell. * * @param fd_out * @param fd_in * @param token * @param prev_mask * @param cmdline * @param parse_result */voidhandle_none(
intfd_out,
intfd_in,
structcmdline_tokenstoken,
sigset_tprev_mask,
constchar*cmdline,
parseline_returnparse_result) {
pid_tpid=fork();
if (pid==0) { // In the child processsigprocmask(SIG_SETMASK, &prev_mask, NULL); // unblockif (fd_in<0||fd_out<0) {
exit(1);
}
/* If we define outfile or infile, use dup2 function to complete the redirection logic */if (fd_out!=STDOUT_FILENO) {
dup2(fd_out, STDOUT_FILENO);
close(fd_out);
}
if (fd_in!=STDIN_FILENO) {
dup2(fd_in, STDIN_FILENO);
close(fd_in);
}
setpgid(pid, pid);
/* Run the executable program */if (execv(token.argv[0], token.argv) <0) {
if (errno==EACCES)
sio_printf("%s: Permission denied\n", token.argv[0]);
elseif (errno==ENOENT)
sio_printf("%s: No such file or directory\n", token.argv[0]);
exit(-1);
}
}
else
{ // In the parent processif (parse_result==PARSELINE_FG) {
fg_pid=pid; // Record current pidfg_is_running=FG_RUNNING; // Mark fg job is running// add the fg job to the job listadd_job(pid, FG, cmdline);
// run the job & waiting for job completedwhile (fg_is_running==FG_RUNNING) {
sigsuspend(&prev_mask);
}
}
elseif (parse_result==PARSELINE_BG)
{
add_job(pid, BG, cmdline); // bg job -> add to job list only// print bg job informationsio_printf("[%d] (%d) %s\n", job_from_pid(pid), pid, cmdline);
}
}
}
Step 5. sigchld_handler
当 child process 在执行的时候,实际上就会触发我们的 sigchld_handler
/** * @brief * sigchld_handler function handle the `SIGCHLD` signal, which is sent to a process * when its child processes terminate or change state. * * In this function, we reap the child process by using waitpid function. * And based on different child states, we will print different info in the shell. * */voidsigchld_handler(intsig) {
int_errno=errno;
sigset_tmask, prev_mask;
/* Block signals */set_signals(&mask);
block_signals(&mask, &prev_mask);
intstatusp;
pid_tpid;
while ((pid=waitpid(-1, &statusp, WNOHANG | WUNTRACED | WCONTINUED)) >0) {
jid_tjid=job_from_pid(pid);
if (WIFCONTINUED(statusp)) {
/* * If we receive `fg` or `bg` command, we will come into this branch * `fg` or `bg` will turn a stop job to a FG job or BG job. * So first we get the job's current state `jstate` * If `jstate` is ST, then based on the command we receive, we will * turn this job to a FG job / BG job. */job_statejstate=job_get_state(jid);
if (jstate==ST) {
if (st_to_fgbg==TO_FG) {
job_set_state(jid, FG);
} elseif (st_to_fgbg==TO_BG) {
job_set_state(jid, BG);
}
}
}
else
{
if (pid==fg_pid) {
// foreground job ended, set fg_is_running to zerofg_is_running=FG_NO_RUNNING;
}
if (WIFSIGNALED(statusp)) { // SIGINTintsignum=WTERMSIG(statusp);
delete_job(jid);
sio_printf("Job [%d] (%d) terminated by signal %d\n", jid, pid, signum);
}
if (WIFSTOPPED(statusp)) { // SIGTSTPintsignum=WSTOPSIG(statusp);
job_set_state(jid, ST);
sio_printf("Job [%d] (%d) stopped by signal %d\n", jid, pid, signum);
}
if (WIFEXITED(statusp)) { // exit() or return from main()delete_job(jid);
}
}
}
/* Unblock signals */unblock_signals(&prev_mask);
/* Reset the error number */errno=_errno;
}
Step 6. handle_fgbg
/** * Handle FG / BG command. * 1) First we will try to get the job id or pid from the command. * And then we will get the job id based on the pid / or get the pid based on job id. * 2) Second, based on the command type, we will run similar but different logic: * 2.1) If it is a `bg` command. Send SIGCONT signal directly. * 2.2) If it is a 'fg' command. * - If target job is ST state, then send a SIGCONT signal * - If target job is a background job, set state to FG * * @param token * @param mask * @param prev_mask */voidhandle_fgbg(structcmdline_tokens*token, sigset_tmask, sigset_tprev_mask) {
/* check if the command is valid */if (token->argc!=2) {
if (token->builtin==BUILTIN_FG)
sio_printf("fg command requires PID or %%jobid argument\n");
elsesio_printf("bg command requires PID or %%jobid argument\n");
return;
}
/* get value from the command */constchar*type=token->argv[0]; // `bg` or `fg`constchar*id=token->argv[1]; // pid or jid/* block the signal */block_signals(&mask, &prev_mask);
/* * Get jid and pid for this job * 1). If the id start with '%', the command gives us job id. We should get * pid based on this job id. * 2). Else if the id start with a number, the command gives us a pid. * We should get job id based on this pid. * 3). Print an error message. */pid_tpid;
jid_tjid;
if ('0' <= id[0] &&id[0] <= '9')
{
pid= (pid_t)atoi(id);
jid=job_from_pid(pid);
}
elseif (id[0] =='%')
{
jid= (jid_t)atoi(&id[1]);
if (job_exists(jid) == false) {
sio_printf("%s: No such job\n", id);
unblock_signals(&prev_mask);
return;
}
pid=job_get_pid(jid);
}
else
{
sio_printf("%s: argument must be a PID or %%jobid\n", type);
unblock_signals(&prev_mask);
return;
}
/* Get target job state, and move target job to fg / bg */job_statecurState=job_get_state(jid);
if (token->builtin==BUILTIN_BG)
{
st_to_fgbg=TO_BG;
sio_printf("[%d] (%d) %s\n", jid, pid, job_get_cmdline(jid));
killpg(pid, SIGCONT);
}
elseif (token->builtin==BUILTIN_FG)
{
fg_pid=pid;
fg_is_running=FG_RUNNING;
if (curState==ST)
{
st_to_fgbg=TO_FG;
killpg(pid, SIGCONT);
}
elseif (curState==BG)
{
job_set_state(jid, FG);
}
/* Wait until the current FG job stop running */while (fg_is_running==FG_RUNNING) {
sigsuspend(&prev_mask);
}
}
/* unblock the signal set */unblock_signals(&prev_mask);
}
The text was updated successfully, but these errors were encountered:
CMU 15213: Shell Lab
CMU 15213 的倒数第二个 Lab,对应着 CSAPP 这本书的信号与异常那一章。这个 Lab 的目的是为了让学生更加熟悉进程控制以及信号量。在这个 Lab 中,我们需要实现一个自己的命令行工具 tiny shel(类似于 Linux Shell),tiny shell 支持简单的 Job Control 和 IO 重定向。
在真正的开始之前,writeup 还提供了一些基本的知识
tsh> /bin/ls
)tsh> quit
),后面几位是传递给该命令的命令行参数&
结束,那么就是一个 background jobsigchld_handler
函数中完成这一步SIGINT
SIGTSTP
信号并将其发送给当前的 foreground job 的整个 process group读完 writeup 后,我们的主要任务是完成以下函数
eval
: Main routine that parses, interprets, and executes the command line.sigchld_handler
: Handles SIGCHLD signals.sigint_handler
: Handles SIGINT signals (sent by Ctrl-C).sigtstp_handler
: Handles SIGTSTP signals (sent by Ctrl-Z).在了解了这些之后,就可以开始写代码了!这次 Lab 实际上思路都很简单直接,只需要按照测试用例一步一步实现即可
Signal
这里先补充一点信号的基本知识
A signal is a small message that notifies a process that an event of some type has occurred in the system
Sending a Signal
Kernel sends a signal to a destination process by updating some state in the context of the destination process. It will send a signal for one of the following reasons:
因此发送信号的流程可能是
kernel → process
,也可以是process → kernel → process
Receiving a Signal
A destination process receives a signal when it is forced by the kernel to react in some way to the delivery of the signal. Some possible way to react:
Pending and Blocked Signal
A signal is pending if sent but not yet received
A process can block the receipt of certain signals
A pending signal is received at most once
Pending / Blocked Bits
Kernel maintains pending and blocked bit vectors in the context of each process
pending bits: represents the set of pending signals
blocked bits: represents the set of blocked signals
Step 0. 实现阻塞信号的工具函数
为了在并发环境中提供信号处理的原子性和确定性。在我们的 Tiny Shell 解析完命令并且处理这些命令的时候,我们需要将相关的信号量给阻塞住。以此来
在这里我们可以先写几个工具函数。帮助我们快速创建一个新的 signal set,并且 block / unblock 信号量。
Step 1. 实现一个基本的
eval
eval
函数中,我们需要解析输入的命令,根据 writeup,我们大致需要解决的命令类型包括Quit
Foreground_Job
Background_Job
Builtin Jobs
Path / Executable File
start code 已经给出了完整的
parse_result = parseline(cmdline, &token)
的逻辑。我们可以通过token.builtin
属性来确定当前命令的类型接下来就是一个简单的 switch case 逻辑(或者 if … else 也可以)
其中 FG Job 和 BG Job 其实有着相似的逻辑,因此这里我直接使用了一个
handle_fgbg
函数来处理,第一遍做这个 lab 的时候也可以先分开创建 handler 函数Step 2.
list_jobs
当我们输入的指令是
BUILTIN_JOBS
时,我们会调用list_jobs
函数,来展现当前的 jobs 列表。由于
list_jobs
已经由 start code 给出,所以这项任务在上面的写法中已经完成了~关于 job list,我们只需要后续注意调用
add_job
和delete_job
函数将任务从 job list 中增删即可。Step 3. Signal Handler
观察 main 函数,里面有一部分的逻辑是注册 signal handler,它通过调用
Signal
函数,指明了当我们的程序收到指定的信号后,该使用什么函数来处理这些信号。其中
sigint_handler
和sigtstp_handler
都非常简单,可以在这里直接给出大致思路就是我们有一个全局变量
fg_pid
标识当前的前台任务,然后我们要判断当前是否存在一个前台任务,如果任务存在的话,我们就调用killpg
函数将信号发送过去Step 4.
handle_none
当我们输入的并不是一个 builtin command 的时候,我们需要考虑我们输入的可能是一个 path 或者 executable file,因此此时我们需要使用 fork 函数创建一个子进程来执行它。
此时,我们的思路其实是
execv
函数尝试执行当前的命令sigsuspend
等待前台任务结束Step 5.
sigchld_handler
当 child process 在执行的时候,实际上就会触发我们的
sigchld_handler
Step 6.
handle_fgbg
The text was updated successfully, but these errors were encountered: