CS 111 Lecture 6 Note

Designed by Yao Zhang



Orthogonality & Trouble:


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 base


parent:
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)

If a child process become an orphan, its parent will be linked to process number 1.

("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:

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
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.

Threads:

We need threads because:
  1. Processes are heavy weight
  2. fork() is slow
  3. IPC is slow
The problem here is that processes are to isolated!
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.