530 likes | 537 Views
Explore the use of signals in processes and threads, learn about different types of signals, signal processing, signal tables, and signal handlers in Linux systems.
E N D
Operating Systems, 122 Practical Session 2, Signals and Assignment 1
Signals • Signals are a way of sending simple messages to processes/ threads. • Used to notify a process of important events. • Signals can be sent by other processes/ threads, or by the kernel. • Signals are defined in POSIX. • XV6 doesn’t support signals. You will add such ability in assignment 1.
Reacting to Signals • Signals are processed (by the kernel) after a process finished running in kernel mode, just before returning to user mode: • Upon returning from a system call. • Upon returning from a timer interrupt (interrupt sent by the hardware clock).
Signals: Synchronous VS. Asynchronous • Programs are synchronous: executed line by line • Signals can be synchronous or asynchronous • Synchronous: occur as a direct result of the executing instruction stream. Examples: dividing by zero, segmentation fault, etc. • Asynchronous: external to (and in some cases unrelated to) the current execution context. A mechanism for an inter-process communication. Example: receiving a termination signal from a different process.
Signals-Examples • SIGSEGV– Segmentation Faults • SIGFPE – Floating Point Error • SIGSTOP – Causes process to suspend itself • SIGCONT – Causes a suspended process to resume execution Which are synchronous? • A list of signals in Linux: http://www.ucs.cam.ac.uk/docs/course-notes/unix-courses/Building/files/signals.pdf
Signal Table • Each process has a signal table • Each signal has an entry in the table • Each signal has a column whether to ignore the signal or not (SIG_IGN) • Each signal has a column of what to do upon receiving the signal (if not ignoring it)
Blocking and Ignoring • Blocking: The signal is not delivered to the process. It remains pending until the block is removed. • Ignoring: The signal is discarded by the kernel without any action being taken. The execution of the process continues even if non-meaningful (i.e. ignoring SIGFPE or SIGSEGV). • According to POSIX, the behavior of a process is undefined after it ignores a SIGFPE, SIGILL, or SIGSEGV signal that was not generated by kill or raise.
Signal Handlers • Each signal has a default action. For example: • SIGTERM – Terminate process. • SIGFPE (floating point exception) – dump core and exit. • The default action can be changed by the process using the signal*/ sigactionsystem call. • It is highly recommended to refrain from using the signal call in your code, as we will see later. Nonetheless it is important to be familiar with it since it appears in many legacy programs.
Signal Handlers • Five default actions: • Ignore: ignores the signal; no action taken. • Exit: forces the process to exit. • Core: forces the process to exit and create a core file. • Stop: stops the process. • Continue: resume Resume execution of a stoppedprocess. • Some functions are not safe to call from within a signal handler, such as printf, malloc, etc. A useful technique to overcome this is to use a signal handler to set a flag and then check that flag from the main program and print a message if required. Further reading: http://www.ibm.com/developerworks/linux/library/l-reent/index.html
Signal Handlers • Two signals cannot be ignored or have their associated action changed: • SIGKILL • SIGSTOP (Don’t confuse with SIGTSTP, which is sent when a user ^z in the shell. The default actions of both signals are similar, but the latter can be modified). • When calling execvp() all signals are set to their default action. The bit that specifies whether to ignore the signal or not is preserved. Why?
Scheme of signal processing User Mode Kernel Mode Normal program flow do_signal() handle_signal() setup_frame() An event which traps to kernel Signal handler system_call() sys_sigreturn() restore_sigcontext() Return code on the stack
Sending Signals • Signals can be sent: • From the keyboard • From the command line via the shell • Using system calls
Keyboard Signals • Ctrl–C – Sends a SIGINT signal . By default this causes the process to terminate. • Ctrl-\ - Sends a SIGQUIT signal. Causes the process to terminate. • Ctrl-Z – Sends a SIGTSTP signal. By default this causes the process to suspend execution. • Note: not all keyboard signals are supported in all shells.
Command line Signals • kill <signal> <PID> – Sends the specified signal to the specified PID. A Negative PID specifies a whole process group. • Kill -9 <PID> sends a SIGKILL which terminates the process. • killall<args> <commands> – can be used to send multiple signals to all processes running specific commands. • Example: killall -9 java • fg –Resumes the execution of a suspended process (sends a SIGCONT).
System call Signals Kill(pid_t pid,int sig) Usage example: #include <unistd.h> /* standard unix functions, like getpid() */ #include <sys/types.h> /* various type definitions, like pid_t */ #include <signal.h> /* signal name macros, and the kill() prototype */ /* first, find my own process ID */ pid_tmy_pid = getpid(); /* now that I got my PID, send myself the STOP signal. */kill(my_pid, SIGSTOP);
Signal Priority • Each pending signal is marked by a bit in a 32 bit word. • Therefore there can only be one signal pending of each type. • A process can’t know which signal came first. • The process executes the signals starting at the lowest numbered signal. • POSIX 2001 also defines a set of Real-Time Signals which behave differently: • Multiple instances may be queued • Provide richer information (may be accompanied by an integer) • Delivered in guaranteed order • Use SIGRTMIN+n up to SIGRTMAX to refer to these signals (32 in Linux)
Manipulation of Signals sighandler_t signal(intsignum, sighandler_thandler) • Registers a new signal handler for the signal with number signum. • The signal handler is set to sighandler which may be a user specified function, or either SIG_IGN or SIG_DFL. • If the corresponding handler is set to SIG_IGN or SIG_DFL, then the signal is ignored or set do default action accordingly. • Return Value: previous value of the signal handler, or SIG_ERR on error. • Deprecated, do not use!
Manipulation of Signals • On some systems (e.g. System V Unix), if the handler is set to a function sighandlerand a signal is received, then first the handler is reset to SIG_DFL, and next sighandler is called. • This may result in portability issues, or unwanted signal handling. • One solution to this problem is demonstrated in the “ouch” signal handler function:void ouch(int sig) { printf(“OUCH! - I got signal %d\n”, sig); signal(SIGINT, ouch); } • What is the problem with this solution?
Manipulation of Signals- sigaction intsigaction(intsignum, const struct sigaction *act, struct sigaction *oldact); • A more sophisticated (and safe) way of manipulating signals. • Doesn’t restore (by default) the signal handler to default when delivering a signal. • signum is the number of the signal. • act is a pointer to a struct containing much information including the new signal handler. • oldact if not null will receive the old signal handler. For more details and another example see: http://www.opengroup.org/onlinepubs/009695399/functions/sigaction.html Example
Manipulation of Signals- sigaction The sigaction structure is defined as something like: structsigaction { void (*sa_handler)(int); sigset_tsa_mask; intsa_flags; void (*sa_restorer)(void); }; • sa_handler specifies the action to be associated with signum and may be SIG_DFL, SIG_IGN, or a pointer to a signal handling function. • sa_maskspecifies a mask of signals which should be blocked during execution of the signal handler. In addition, the signal which triggered the handler will be blocked, unless the SA_NODEFER flag is used. • sa_flags specifies a set of flags which modify the behavior of the signal.
Manipulation of Signals- sigprocmask int sigprocmask(int how, const sigset_t *set,sigset_t *oldset); The sigprocmask call is used to change the list of currently blocked signals. The behaviour of the call is dependent on the value of how, as follows: • SIG_BLOCK The set of blocked signals is the union of the current set and the set argument. • SIG_UNBLOCK The signals in set are removed from the current set of blocked signals. It is legal to attempt to unblock a signal which is not blocked. • SIG_SETMASK The set of blocked signals is set to the argument set.
Manipulation of Signals- sigprocmask • sigset_t is a basic data structure used to represent a signal set. • Initialization of sigset_tshould be done using: sigemptyset, sigfillset, sigaddset, … • A variable of type sigset_t should not be manipulated manually (for portability)! • An example of usage can be found at: http://www.linuxprogrammingblog.com/code-examples/blocking-signals-with-sigprocmask Example
Manipulation of Signals- sigpending intsigpending(sigset_t*set); Returns the set of signals that are pending for delivery to the calling thread (i.e., the signals which have been raised while blocked). The mask of pending signals is returned in set.
Waiting for signals int pause(void); Causesthe calling process (or thread) to sleep until a (any) signal is delivered that either terminates the process or causes the invocation of a signal-catching function. intsigsuspend(constsigset_t *mask); Temporarily replaces the signal mask of the process, and suspends the process until a signal not belonging to the waiting mask arrives. Allows waiting for a particular signal.
The system call alarm unsigned alarm(unsigned seconds); Requests the system to generate a SIGALRMfor the process afterseconds time have elapsed. Processor scheduling delays may prevent the process from handling the signal as soon as it is generated. • If seconds is 0, a pending alarm request, if any, is canceled. • Alarm requests are not stacked; only one SIGALRM generation can be scheduled in this manner. If the SIGALRM signal has not yet been generated, the call shall result in rescheduling the time at which the SIGALRM signal is generated.
Example 1 #include <stdio.h> /* standard I/O functions */ #include <unistd.h> /* standard unix functions, like getpid() */ #include <sys/types.h> /* various type definitions, like pid_t*/ #include <signal.h> /* signal name macros, and the signal() prototype */ /* first, here is the signal handler */ voidcatch_int(intsig_num){ /* reassign the signal handler again to catch_int, for next time */ signal(SIGINT, catch_int); /* and print the message */ printf("Don't do that\n"); } int main(){ /* set the INT (Ctrl-C) signal handler to 'catch_int' */ signal(SIGINT, catch_int); /* now, lets get into an infinite loop of doing nothing */ while (true) { pause(); }} Causes the process to halt execution until it receives any signal.
Example 2 intcpid[5]; // holds the pids of the children intj; // index to cpid // function to activate when a signal is caught intsigCatcher() { signal(SIGINT, sigCatcher); // re-assign the signal catcher printf("PID %d caught one\n", getpid()); if(j > -1) kill(cpid[j], SIGINT); // send signal to next child in cpid }
Example 2-Continued int main() { inti; int zombie; intstatus; intpid; signal(SIGINT, sigCatcher); // sets a handler for INT signal …
Example 2-Continued for(i=0; i<5; i++){ if((pid=fork()) == 0){ // create new child printf("PID %d ready\n", getpid()); j = i-1; pause(); // wait for signal exit(0); // end process (become a zombie) } else// Only father updates the cpid array. cpid[i] = pid; } sleep(2); // allow children time to enter pause kill(cpid[4], SIGINT); // send signal to first child sleep(2); // wait for children to become zombies for(i=0; i<5; i++){ zombie = wait(&status); // collect zombies printf("%d is dead\n", zombie); } exit(0); }
Output PID 22899 ready PID 22900 ready PID 22901 ready PID 22902 ready PID 22903 ready PID 22903 caught one PID 22902 caught one PID 22901 caught one PID 22900 caught one PID 22899 caught one 22903 is dead 22901 is dead 22902 is dead 22899 is dead 22900 is dead
Security Issues • Not all processes can send signals to all processes. • Only the kernel and super user can send signals to all processes. • Normal processes can only send signals to processes owned by the same user.
Process ID • Each process has an ID(pid). • Each process has a group ID (pgid). • One process in the group is the group leaderand all member’s group ID is equal to the leaders pid. • A signal can be sent to a single process or to a process group.
Process Group ID • A process groupis a collection of related processes. • All processes in a process group are assigned the same process-group identifier (pgid). • The process-group identifier is the same as the PID of the process group's initial member. • Used by the shell to control different tasks executed by it.
Process Group ID intgetpid() – return the process’s PID. intgetpgrp()– return the process’s PGID. setpgrp()– set this process’s PGID to be equal to his PID. setpgrp(int pid1, int pid2)– set process’s pid1 PGID to be equal to pid2’s PID.
Overview Assignment 1
Assignment 1 • Divided into four parts: • Get to know xv6, and brushing up your c skills • Implement a signal framework • Implement few simple signals • Write user space programs to test previous sections
Assignment 1Hello xv6 • xv6 is a simplistic educational OS, it is used in universities such as MIT and Yale. • xv6 is a re-implementation of Unix Version 6, but offers only a partial implementation. • We will use QEMU, which is a generic and open source machine emulator and virtualizer to run xv6. • Everything you need is installed on lab computers.
Assignment 1Details • We will (ab)use svn to retrieve the initial xv6 version, and modify that version. • There are two main files that are built by the make file: xv6.img and fs.img, one is the OS and the other is the file system. • In the first task you will modify the shell to display PWD in the prompt. • Then you will implement a signals framework. • In the last task you will add user space programs. Notice that they are different from regular c programs because they use xv6 libraries.
int main(void) { staticcharbuf[100]; int fd; // Assumes three file descriptors open. while((fd = open("console", O_RDWR)) >= 0){ if(fd >= 3){ close(fd); break; } } // Read and run input commands. while(getcmd(buf, sizeof(buf)) >= 0){ if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){ // Clumsy but will have to do for now. // Chdir has no effect on the parent if run in the child. buf[strlen(buf)-1] = 0; // chop \n if(chdir(buf+3) < 0) printf(2, "cannot cd %s\n", buf+3); continue; } if(fork1() == 0) runcmd(parsecmd(buf)); wait(); } exit(); } the shell
/*** sysproc.c ***/ int sys_kill(void) { intpid; if(argint(0, &pid) < 0) return -1; return kill(pid); } /*** syscall.c ***/ staticint (*syscalls[])(void) = { [SYS_chdir] sys_chdir, [SYS_close] sys_close, [SYS_dup] sys_dup, [SYS_exec] sys_exec, [SYS_exit] sys_exit, [SYS_fork] sys_fork, [SYS_fstat] sys_fstat, [SYS_getpid] sys_getpid, [SYS_kill] sys_kill, [SYS_link] sys_link, [SYS_mkdir] sys_mkdir, [SYS_mknod] sys_mknod, [SYS_open] sys_open, [SYS_pipe] sys_pipe, [SYS_read] sys_read, [SYS_sbrk] sys_sbrk, [SYS_sleep] sys_sleep, [SYS_unlink] sys_unlink, [SYS_wait] sys_wait, [SYS_write] sys_write, [SYS_uptime] sys_uptime, }; /*** proc.c ***/ // Kill the process with the given pid. // Process won't exit until it returns // to user space (see trap in trap.c). int kill(intpid) { structproc *p; acquire(&ptable.lock); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->pid == pid){ p->killed = 1; // Wake process from sleep if necessary. if(p->state == SLEEPING) p->state = RUNNABLE; release(&ptable.lock); return 0; } release(&ptable.lock); return -1; } The Kill SYSTEM CALL
Question from midterm 2004 תלמיד קיבל משימה לכתוב תכנית שמטרתה להריץ תכנית נתונה (ברשותו רק הקובץ הבינארי) prompt ע"י שימוש ב-fork ו-execvp. בנוסף נדרש התלמיד למנוע מן המשתמש "להרוג" את התכנית ע"י הקשת ctrl-c (שים לב כי התכנית prompt אינה מסתיימת לעולם). מצורף פתרון שהוצע ע"י התלמיד (my_prog.c) וכן התכנית prompt. • תאר במדויק את פלט התכנית כאשר הקלט הנו: Good luck in the ^c midterm exam. • האם הפתרון המוצע עונה על הגדרת התרגיל? • אם תשובתך ל-ב' היא לא, כיצד היית משנה את התכנית my_prog.c (ניתן להוסיף/ לשנות שורה או שתיים בקוד לכל היותר)?
Question from midterm 2004 my_prog.c #include… voidcntl_c_handler(int dummy){ signal(SIGINT, cntl_c_handler); } main (intargc,char **argv){ int waited; int stat; argv[0] = “prompt”; signal (SIGINT, cntl_c_handler); if (fork() == 0) { // son execvp(“prompt”,argv[0]); } else{ //father waited = wait(&stat); printf(“My son (%d) has terminated \n”,waited); } }
Question from midterm 2004 prompt.c (זכרו כי קוד זה אינו ניתן לשינוי ע"י התלמיד) main(intargc, char** argv){ char buf[20]; while(1){ printf(“Type something: “); gets(buf); printf(“\nYou typed: %s\n”,buf); } }
Sample execution of code • תאר במדויק את פלט התכנית כאשר הקלט הנו: Good luck in the ^c midterm exam. Type something: Good luck You typed: Good luck Type something: in the ^c My son 139 has terminated
Code is incorrect האם הפתרון המוצע עונה על הגדרת התרגיל? • Execvpdoesn’t preserve signal handlers. • Therefore prompt.c doesn’t ignore ^c. • This means that the process can be terminated.
Code correction אם תשובתך ל-ב' היא לא, כיצד היית משנה את התכנית my_prog.c (ניתן להוסיף/לשנות שורה או שתיים בקוד לכל היותר)? • Change signal (SIGINT, cntl_c_handler); in my_prog.c With signal (SIGINT, SIG_IGN); • Add if (fork()==0){ signal (SIGINT, SIG_IGN); execvp(“prompt”,argv[0]);
More Information • http://www.linuxjournal.com/article/3985 • http://www.linux-security.cn/ebooks/ulk3-html/0596005652/understandlk-CHP-11.html • http://cs-pub.bu.edu/fac/richwest/cs591_w1/notes/wk3_pt2.PDF • http://books.google.com/books?id=9yIEji1UheIC&pg=PA156&lpg=PA156&dq=linux+ret_from_intr()&source=bl&ots=JCjEvqiVM-&sig=z8CtaNgkFpa1MPQaCWjJuU5tq4g&hl=en&ei=zf3zSZsvjJOwBs-UxYkB&sa=X&oi=book_result&ct=result&resnum=22#PPA159,M1 • man signal, sigaction… • man kill… • Process groups: http://www.win.tue.nl/~aeb/linux/lk/lk-10.html http://www.informit.com/articles/article.aspx?p=366888&seqNum=8