CS 111 Operating System (Winter 2015)

Orthogonality and trouble - Lecture 6

January 26th, 2015

Scribe notes by Lei Shao, Zhixin Wen, Menyuan Yu, Tianyi Xia for lecture presented by Paul Eggert

Orthogonality & Trouble

fd = open(“file”,O_RDWR);

unlink(“file”);

read(fd,buf,buf_size);

write(fd.buf,buf_size);

If “file” is the last link to the file, $ls in shell will show no file, while this program is running. The file becomes “nameless”, but still takes memory. As long as the file descriptor is still open, the program can read and write the file normally. After the program exits or file descriptor is closed, the memory will be reclaimed.

But if the file is on a removable device:
fd = open(“file”,O_RDWR|O_CREAT,0666);     //remove the device
read(fd,buf,buf_size); //read return -1 and set errno to EIO

Race Condition:

parent process:
int status;

pid_t p =fork();

if(p > 0)

  waitpid(p,& status,0);
What if child exit before parent calls wait?
Process Table Entry:
Process Table Entry

The child process will mark itself as dead, and record its exit status. Now the child process becomes a ZOMBIE process. When parent process calls wait, parent process will check whether the child process is a ZOMBIE process. If so, the parent process will record its exit status and mark the process as available.



What if parent process dies before child?
parent process dies before child

Assume P1 died before C1 and C2. Parent process can only wait for its immediate child, so grandparent cannot wait for C1 or C2. In Linux, the init process (with process id 1) will adopt the orphans (in this case C1 and C2). It will use a code similar to:

  
while(waitpid(-1,& status,0) > 0){   //-1 means wait for all 
    continue;
}

Create a temp file:
Suppose we are creating a temp file for sort to use.

  int fd = open(“/tmp/temp_sort”,O_RDWR|O_CREATE,0600);  //do operations on the file

  unlink(“/tmp/temp_sort”);

problem:
There is a race condition if different sorts runs in parallel. All of the sort processes will write into the same file.

We can fix this by using different file names for different sort processes

char buf[1000];

for(int i = 0; i < 10000000;i++)
 {
    int r = random();

    sprintf(buf,”/tmp/sort%d”,r);

        if(stat(buf,& stat) != 0) //stat will check if the file name has existed

        break;  
}

pid_t fd = open(buf, O_RDWR|O_CREAT|O_TRUNC, 0666);

problem:
We have another race condition here. If we have two processes, and they get the same file name. Before open is called, both of the processes will think the name is not used. And two processes will eventually read and write the same file.

We can fix it by using O_EXCL. This flag means create the file exclusively, and do not create if the file has existed.

char buf[1000];

for(int i = 0; i < 1000000; i++){

    int r = random();

    sprintf(buf,”/tmp/sort%d”,r);

    if(pid_t fd = open(buf, O_RDWR|O_CREAT|O_EXCL, 0666 >= 0)

        break;

}

Signal

Signals are sent to the current process telling it what it needs to do, such as, shutdown, or that it has committed an exception. A process has several signal-handlers which execute code when a relevant signal is encountered. For example, we use ctrl+c to kill the current process in the linux. We can read about the different signals via "man signal".

Given the following command:

$gzip foo   //after completion, foo is gone,
//we only have the foo.gz, which is the compressed version

gzip.c: pid_t fd = open(“foo.gz”.O_WRONLY|O_CREATE|O_TRUNC,0600); // do work //if there is an error in this process, for example, writefailure //unlink(“foo.gz”) //exit(1); close(fd); unlink(“foo”); exit(0);

Problem:

What if we abort the program half way by using ^c ?
We will leave a uncompleted foo.gz!
To fix the problem, we should add the following at the beginning of the program:


int ttyfd = open(“/dev/tty/”,O_RDONLY); //this file record the user’s command

Then we add the following code to different parts of the code to do the cleanup:

    char c;

    every now & then

    read(ttyfd,& c,1);

    if(c == 4){

    unlink(“foo.gz”);

    exit(129);

    }
Note: we cannot add this before unlink(“foo”). Because if we do so, both foo and foo.gz will be deleted, and the user will lose the original data.

However, this method is too tedious to use. Instead we can use signal:

void handle_signal(int sig){

    if (foo_gz_created) //will be useful later
    
    unlink(“foo.gz”);

    _exit(1); //cleans up kernel related data before terminating the process.

}
In gzip.c:

int main() {

    pid_t fd = open(“foo.gz”.O_WRONLY|O_CREATE|O_TRUNC,0600);

    signal(SIGINT,handle_signal);

    //rest of code
    close(fd);

    unlink(“foo”);

    exit(0);

}

Bug:
solution:
use:
 pthread_sigmask(SIGBLOCK,& newset,& oldset);

sudo code for the new algorithm:

    pthread_mask(SIGBLOCK);
    open();
    signal(SIGINT,handle_signal);
    volatile foo_gz_created = true; //tell handler whether the gz has been created  
    SIGUNBLOCK;
This makes sure that signal handler is set up correctly before it will receive and handle the signal.

Reasons for signal:

    1. Uncooperative process       
      ^c                                   SIGINT
    2. Invalid programs:
        illegal instruction                SIGKILL
        illegal floating point execution   SIGFPE
        invalid address                    SIGSEGV, SIGBUS
    3. I/O error                           SIGPIPE/SIGIO
    4. A child died                        SIGCHID
    5. User Signal
     ^z                                    SIGSTP;SIGKILL
    6. User goes away                      SIGHUP
    7. Timer expires                       SIGALARM

Signal handler can be called between any pair of machine instructions which makes program harder to analyze by developers, so keep the handler simple.

Q & A:
Q: Why use _exit instead of exit in signal handler?

A:_exit does not do cleanup, and exit will clean up I/O and memory, which will call malloc.
When we call malloc, the process will look for a trunk of big enough memory and remove it from free list. SIGNAL can come when the process is being removed from the free list. Pointer in heap is a mess at this time, so if we try to change the heap memory, all kinds of bugs will be possible.
For example, data corruption may happen:


 void handle_signal(int sig) 
    { 
        malloc(12); //mess up the heap 

        printf("blah"); //calls malloc 

        exit (1)//calle malloc 
    }

Therefore in signal handler, we can only call async safe functions like: _exit, signal, pthread_sigmask close
Avoid calling the dangerous functions like malloc, printf and exit, which will modify the global status.

Threads

Thread is the placeholder information associated with a single use of a program that can handle multiple concurrent users.

Cons of processes: Threads vs. processes

Process resources: expensive stuff (per process) address space (heap, code), file descriptor, default permmsion, process ID
signal handler table, working directory, unmask uid, gid
**************************************************************************************************************
Thread resources: cheapstuff (per thread) registers, thread ID, stack, signal mask, errorno , state(blocked/ready/zombie...)