Operating Systems: Signals, Scheduling, and Threads
Note: Superscripts refer to code in the Code Appendix at the bottom of this page. Numbers correspond.
(leftover from last lecture: Temporary Files)
int fd = opentemp() Benefits:
- OS can decide where to put the file
- OS can free file space automatically when app exists.
- No need for an unlink operation later.
- Since OS doesn't need to guarantee persistence, it can do a lot of optimizations and increases performance
- Persistence: having the files actually on disk
- creating a random file name in /temp
- Open in O_EXCL
- Unlink the file
- Downside:
- Sysadmin/users cannot easily see the files.
Today's Lecture
Handling unexpected events (Power failure, etc)
-
Ways to address this problem:
-
Entirely in the OS
-
OS saves a snapshot of all current processes into a persistent file
- Restores after reboot, as if nothing had happened
-
This creates problems for:
- Realtime dependant applications
- Timestamp dependent apps
- Ensuring that temp files persist
- Apps may want to warn users about events
- Kernel notifies each process of the upcoming shutdown
- Benefits
- Allows the application developer to decide how to handle the power outage
- More Reboots (So that the O.S. need not be as reliable)
- Benefits
-
OS saves a snapshot of all current processes into a persistent file
-
Entirely in the OS
How the kernel can notify applications
cat/dev/power outputs decimal integer = the number of seconds until power failure (-1 if unknown)
-
App can use polling
-
Reads /dev/power repeatedly as needed
-
Benefits:
- It works
-
Problems:
- Must modify all applications to use /dev/power
- CPU time wasted on polling, the applications periodically asking if there is a power outage
- Buggy applications (even with one loop that doesn't poll) may not know about losing power
- These bugs are common/likely to occur
-
Benefits:
-
Reads from /dev/power and does work only until the power is low
-
Benefits:
- Also works
- Polling doesn't use up so much CPU time
-
Problems:
- Must modify all applications to use /dev/power
- Buggy applications (even with one loop that doesn't poll) may not know about losing power
-
Benefits:
-
Reads /dev/power repeatedly as needed
About Signals:
- Asynchronous interrupts to processes
- signal:process :: interrupts:hardware
-
Low volume signal, pipes are used for greater volumes of data
- Pipes are more efficient
- Signals are used because they are easier to work with.
-
Signals can happen at any time
- "trap" to a signal handler
- to make process exit
- Interrupts can happen at any time in kernel or in user app
- Control transferred to an I.S.R. (Interrupt Service Routine)
-
Example for creation of a signal:
- int kill(pid_t pid, int sig);
- pid can be any of your processes
- Kill creates the SIGKILL signal
-
Exceptions:
- $_passwd eggert
- New password
- ^C
-
Example Signals:
- SIGPWER (power outage)
- SIGINT (^C)
- SIGHUP (hangup, can be caused by User Logout)
-
SIGSEGV (segmentation violation)
- Segmentation violations are due to the hardware or kernel, instead of the outside world
- This signal is a modeled version of the original interrupt
Segfaults occur when the stack pushes through the forbidden zone & into the heap.
void *malloc(size_t size){ void *p = htop; htop+=size; return p; }
- SIGFPE (floating point error)
- SIGILL (instruction)
- SIGPIPE (writing to a pipe that has no reader)
- SIGTERM (software termination signal)
- SIGKILL (similar to sigterm, but it cannot be caught or ignored)
- SIGALARM (the alarm has gone off)
- SIGXCPU (you"ve gone over your CPU cycles (but have a grace period))
- SIGFSZ (file size over limit)
- SIGUSR1 (user defined)
- SIGUSR2 (user defined)
-
Problems with Signals
-
Forces change to abstract machine, more specifically:
- x86 insns
- more system calls for int 0x80
-
Between any pair of instructions, the program may:
- Exit
- Run a signal handler¹
- Blocking Signals to prevent faults due to signals²
-
Problem: this would not be an efficient, easy way to prevent the race condition in hadle_power_failure
- More efficient solution: have a slow version of printf (i.e.: write), which has this solution implemented in it.
- This slow version can be used in the application signal checks.
-
In general:
-
Safe:
- Read/write
- Close
- _exit
-
Unsafe:
- Printf
- Malloc/fne
- Exit
-
Safe:
-
Forces change to abstract machine, more specifically:
Example: GZIP
$gzip foo
/*
* creates foo.gz
* The user wants this to be atomic, i.e. it will leave either foo or foo.gz but not both
* these internals would create problems if the user presses ^c. Both files will be present, but fd2 may be partially complete
*/
gzip internals{
fd1 = open("foo", O_RDONLY);
fd2 = open("foo.gz", O_WRONLY|O_CREAT, 0666);
read /write/ close
unlink
}
signal(SIGINT, cleanup);
//To fix the error create a signal handler
void cleanup(int){
unlink("foo.gz");
_exit(1);
}
Code Appendix
1)
//pointer to a function that takes an integer as a parameter
//int is the signal number
typedef void (*handler)(int);
#include <signal.h>
handler signal(int, handler);
old handler new handler Takes default action
or SIG_IGN
SIGDFL
int main (void){
//when there's a power failure, execute function handle_power_failure
signal(SIGPWER, handle_power_failure);
...
}
void handle_power_failure(int sig){
//save state
/*the printf command can cause errors, due to the the low level pointer manipulation of printf. Printf will fail if in between the commands "update buffer pointer" and "update buffer size". To fix this problem, block signals (different from ignoring) */
printf("power out! \n");
exit(1);
}
2)
int main (void){
//when there's a power failure, execute function handle_power_failure
signal(SIGPWER, handle_power_failure);
/*
how takes in signals:
SIGBLOCK
SIG_SEMASK
SIG_UNBLOCK
sigset_t takes one bit per signal
set is a restricted pointer to a constant sigset_t
restricted: only way to get to that object from the current system call. Places a constraint on the caller. I.e. don't use aliases for this.
*/
int sigprocmask(int how, sigset_t const *restrict set, sigset_t *restrict oset);
//saving the old signal set, preventing the race condition from the previous example (handle_power_failure)
sigset_t oset;
//sort some stuff
sigprocmask(SIG_BLOCK, set, &oset)
printf("size=%d\n", s);
sigprocmask(SIG_SETMASK, &oset, NULL);
}