CS 111 Lecture 6 Note
Designed by Yao Zhang
Orthogonality & Trouble:
If a child process become an orphan, its parent will be linked to process number 1.One process: fd = open("file", O_RDWR); <---unlink("file");//remove the file a similar situation is to unplug USB read(fd...); write(fd...); (Then call read again and write and so on) suppose read(fd) fails, return -1 and set errno When exits, space is reclaimed $ls $du//file base $fu//file system baseparent: int status; pid_t p = fork(); if (p > 0) waitpid(p, &status, 0);//here p must be your child if child calls _exit(37) Then suppose p is 19, we will have the process status of 19 with exit 37 and dead 1 (zombie process)
("init")//part of system
while (waitpid(-1, ) > 0)
  continue;
sort.c
int fd = open(sorttmp, O_RDWR | O_CREAT | O_EXCL, 0600);
//O_EXCL means exclusive We can use (0000) instead of exclusive (won't work correctly under root access)
write(fd...);
read(fd...);
close(fd);
unlink(sorttmp);
race condition: a | sort | b | sort | c
char sorttmp[100];
for (int c = 0; i < 1000000; i++){
  int r = random();
  sprint(sorttmp, "/tmp/sort%d", r);
  if (int fd = open(sorttmp, O_RDWR | O_CREAT | O_EXCL, 0600))
    break;
}
if (i == 1000000)
  error();
$gzip foo
gzip.c: <---int ttyfd = open("/dev/tty", O_RDONLY); fcntl(fd, ...);
fd = open("foo.gz", O_WRONLY | O_CREAT | O_TRUNC, 0600);
[do work] <---on error (eg. write failure) unlink("foo.gz"); exit(1);
close(fd);
unlink("foo");
exit(0);
//foo is gone and foo.gz is compressed version
What if we have a Control-C in middle of gzip? 
We want a version that compressed or not (all or nothing, never partially done)
void check_for_interrupt(void) {
  char c;
  every now and then
  read(ttyfd, &c, 1);<---return -1
  if (c == 4)
  {
    unlink("foo.gz");
    exit(29);
  }
}
Signals:
Here is our implementation of a signal handler:
What can be used in handle_signal(int sig)?
We can use: _exit, signal, pthread_sigmask, close and etc.
We can not use: malloc, printf(which calls malloc), exit and etc.
void 
handle_signal(int sig) {
  unlink("foo.gz");
  _exit(1);
}
Here is gzip.c:
int
main (int argc, char **argv) {
  if (open("foo", O_RDWR) < 0), set errno and exit;
  signal(SIGINT, handle_signal);
  [rest of main];
  open("foo.gz", O_RDWR);
  [other stuff];  <---signal(SIGINT, SIG_IGN);
  unlink("foo");
  exit(0);
}
again in gzip:
bool volatile foo_gz_created;
signal(SIGINT, handle_signal);
...
open("foo.gz", O_WRONLY | O_CREAT | O_TRUNC, 0600); --->pthread_sigmask(SIGBLOCK, &old, &new); 
foo_gz_created = true;                                     (signal masks containing SIGINT)
(critical section)
Then SIGUNBLOCK
Handler becomes: 
void 
handle_signal(int sig) {
  if (foo_gz_created) {
    unlink("foo.gz");
    _exit(1);
  }
}
foo_gz_created should be declared as volatile since: for example,
int x;
x = 12;
if (x > 0)//Will be optimized out
  print("ok");//Will print forever
gcc -O2(Signal do not "trash" variables)
Signals have changed our abstract machine between any pair of machine instructions a signal handler can be called, makes programs harder to analyze.
Keep your handlers simple! Use "volatile" to discourage optimization.
Reasons for signals:
| Uncooperative processes (control-c) | SIGINT | 
| Invalid programs Illegal instructions | SIGILL | 
| floating point exception | SIGFPE | 
| Invalid address | SIGSEGV, SIGBUS | 
| I/O Error | SIGPIPE SIGIO | 
| A child died | SIGCHLD | 
| User signals | SIGTSTP SIGKILL | 
| hang up | SIGHUP | 
| Timer expires | SIGALRM | 
We can use: _exit, signal, pthread_sigmask, close and etc.
We can not use: malloc, printf(which calls malloc), exit and etc.
Threads:
We need threads because: 
- Processes are heavy weight
 - fork() is slow
 - IPC is slow
 
| Expensive stuff  (per process)  | 
address space(heap, code); file descriptors; signal handler table; working directory; umask; process ID; uid/gid. | 
| Cheap stuff (per thread) | registers; stack; signal mask; errno; thread ID; state. |