CS111: Scribe notes for Thursday, April 19, 2012
Written by: Arthur Wolf, Emily Li, and Shuang Xu
(leftover from Tuesday's lecture):
int fd = opentemp(~~~~)
*no filename
- Advantages (of being a syscall):
-
- + The Operating System can decide where to put file
- eg. /dev/shm vs /tmp on SEASnet linux servers
- + The Operating System can free file space automatically when the application exits
- + The Operating System doesn't need to guarantee persistence
-
- -- Persistence means the program could do optimization. There is less "error safe coding", meaning better performance
- Disadvantage:
- - Can approximate by using random file names in /tmp, open O_EXCL, unlink
- (sysadmins can't easily see file)
- - lsof: can show a system administrator all open processes
(end of leftovers)
Why signals? or rare unexpected events
eg. power failure typically:
Ways to address the problem:
- The Operating System can save a snapshot of all current processes, states, etc. into a persistant file while will be restored on reboot
Problems:
- Real-time applications will be confused by the time stop and restart
- Timestamps will "jump"
- Enough disk space needs to be reserved to save everything of RAM
- Temporary files must persist before and after the state is saved
- BIGGEST PROBLEM: Applications may want to warn users of power failure
Applications have no choice of what to do in case of a power failure
- OS kernel notifies each process of pending doom
- This is called an end-to-end approach, in which the developer gets to decide what to do (common approach)
- Forces more reboots => Developers like reboots because it means the OS need not be as reliable
- A hybrid approach between Approach 1 (OS save snapshot) and Approach 2 (OS kernel notification)
- Applications that want Approach 2 can request notifications, and applications that don't care can have Approach 1
/dev/power:
read from /dev/power
you hang
when read returns, power is low
- little CPU time --> no polling, just waiting
How the kernel can notify apps
/dev/power:
cat /dev/power -- outputs decimal integer = $ of seconds until power failure (-1 if unknown)
Signals
signals:processes::interrupts:hardware
- Signals will notify a process of external information (eg. power failure, ^C, etc.)
-----A "trap" is sent to a signal handler to make the precess exit.
-----This can happen at any time.
- Interrupts will notify the kernel of external information (ie. information that is external to the kernel)
-----This can happen at any time in kernel or in the user application
-----Control is transferred to an Interrupt Service Routine (ISR)
int kill(pid_t pid, int sig);
- pid: any of your "processes"
Some exceptions:
$ passwd eggert (setuid root)
- new password ^C
I have controlling terminal
#include <signal.h>
inside, has
#define SIGWR 29
$ kill -s PWR 2716
Example signals:
From outside world:
- SIGWR (power outage)
- SIGINT (^C)
- SIGHUP (hangup)
From hardware or kernel:
- SIGSEGV (segmentation violation)
- SIGFPE (floating point error)
- SIGILL (bad instruction)
- SIGPIPE (write on pipe with no reader)
- SIGTERM (terminate)
- SIGKILL (cannot be caught or ignored)
- SIGALRM (alarm)
- SIGXCPU (exceeded CPU limits; run out of CPU cycles)
- SIGUSR1 (defined by user)
- SIGUSR2 (defined by user)
- SIGXFSZ (exceed file limit size
[can use "ulimit -a" to list limits]
void *malloc(size_t size) {
void *p = htop;
htop += size;
return p;
}
Signals are easier than pipes
Recipients do not need to be informed beforehand
Problems with signals
- There needs to be changes to the abstract machine
(x86 instructions + INT 0x80 (syscalls))
- Between any pair of instructions, the program may:
- Exit
- Run a signal handler
ex. typedef void (*handler) (int);
int is the signal number
#include <signal.h>
handler signal (int, handler);
- returns previous handler
- handler argument is the new handler (address of function OR SIG_IGN (ignore), SIG_DFL (default action)
examples of default actions:
- SIGINT -> exit
- SIGSEGV -> dump core & exit
- SIGCHLD -> do nothing
void handle_power_failure (int sig) {
//save our state
printf("POWER OUT\n");
exit(1);
}
int main (void) {
signal (SIGPWR, handle_power_failure);
//actual work
}
-----> This does not always work (it rarely fails, but if it does, the consequences are very bad)
For the line printf("POWER OUT\n");
Let's say //actual work is:
sort some stuff
printf("size = %d\n", s);
...
-----> Inside the body of printf(), there is some low level pointer manipulation (update buffer pointer, update buffer size)
RACE CONDITION:
- If the SIGNAL happens between the two updates, the line printf("POWER OUT\n"); will use bad pointers
- if in a good case, it will core dump. Or else it could wipe everything
Race Condition (Wikipedia): "A race condition or race hazard is a flaw in an electronic system or process whereby the output or result of the process is unexpectedly and critically dependent on the sequence or timeing of other events." (source)
Blocking signals
int sigprocmask (int how, sigset_t const *restrict set, sigset_t *restrict oset);
- A blocking approach is one that will save CPU cycles by putting the process to sleep until the signal is sent.
- int how: how want to block
- SIG_BLOCK (sets), SIG_SETMASK (specify which), SIG_UNBLOCK
- sigset_t const *restrict set: set of signals
- one bit per signal
- can specify which want to block or unblock
- sigset_t *restrict oset: show current state
- which already blocked
- set & oset must be different storages
- *restrict: ptr to piece of storage
It promises to be the only pointer to that storage for this syscall, which means this is the only one way to access the storage
int main (void) {
signal (SIGPWR, handle_power_failure);
sigset_t oset;
//sort some stuff
sigprocmask (SIG_BLOCK, set, &oset);
//set: arrage for this set to contain PWR
printf("size = %d\n",s);
sigprocmask (SIG_SETMASK, &oset, NULL);
// with sigprocmask(), when call printf, block PWR signal
}
-----> But this method would be annoying
Another fix:
in void handle_power_failure(),
change printf to
write(2, "ouch", 5);
Write is a syscall. It is async-signal-safe, meaning it can run with other functions and won't crash.
Examples of unsafe (library calls):
Examples of safe (low level calls):
EXAMPLE: gzip internals
$gzip foo --> creates foo.gz
^C user wants this "atomic"
internally:
fd1 = open("foo", O_REDONLY);
fd2 = open("foo.gz", O_WRONLY | O_CREAT, 0666);
read/write
(--> ^C would create partial.gz)
close
unlink("foo");
Add signal (SIGINT, cleanup); after fd1
void cleanup (int) {
unlink("foo.gz");
exit(1);
}
-----> But there is a race condition if we have a signal right after the unlink("foo");
Would also need to block/ignore. IE. right before unlink("foo");, put signal (SIGINT, SIGIGN);
But there is still another race condition present, which will be left for students to think about.