January 26th, 2015
Scribe notes by Lei Shao, Zhixin Wen, Menyuan Yu, Tianyi Xia for lecture presented by Paul Eggert
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
int status; pid_t p =fork(); if(p > 0) waitpid(p,& status,0);What if child exit before parent calls wait?
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?
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:
int fd = open(“/tmp/temp_sort”,O_RDWR|O_CREATE,0600); //do operations on the file unlink(“/tmp/temp_sort”);problem:
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);
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; }
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);
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 commandThen 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); }
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. }
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); }
pthread_sigmask(SIGBLOCK,& newset,& oldset);
pthread_mask(SIGBLOCK); open(); signal(SIGINT,handle_signal); volatile foo_gz_created = true; //tell handler whether the gz has been created SIGUNBLOCK;
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.
Thread is the placeholder information associated with a single use of a program that can handle multiple concurrent users.
Cons of 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...) |