When a thread calls "fork", a copy of the entire process address space is made for the child. By inheriting a copy of the address space, the child also inherits the state of every mutex, reader-writer lock, and condition variable from the parent process. If the parent consists of more than one thread, the child will need to clean up the lock state if it isn't going to call exec immediately after fork returns.
Inside the child process, only one thread exists, which is a copy of the thread that called fork in the parent. If locks are held in parent process, they are still held at child process, but since child only has one thread, there is no way to know who hold the lock. So we need to clean up the lock state while doing the fork.
System Definition:
ubuntu@ip-172-31-23-227:~$ less /usr/include/pthread.h
......
/* Install handlers to be called when a new process is created with FORK.
The PREPARE handler is called in the parent process just before performing
FORK. The PARENT handler is called in the parent process just after FORK.
The CHILD handler is called in the child process. Each of the three
handlers can be NULL, meaning that no handler needs to be called at that
point.
PTHREAD_ATFORK can be called several times, in which case the PREPARE
handlers are called in LIFO order (last added with PTHREAD_ATFORK,
first called before FORK), and the PARENT and CHILD handlers are called
in FIFO (first added, first called). */
extern int pthread_atfork (void (*__prepare) (void),
void (*__parent) (void),
void (*__child) (void)) __THROW;
......
Before doing the fork, prepare is called to lock all mutexes. After doing the fork we have 2 copies of locked mutexes in both parent and child process. "Parent" handler release the lock at parent process, and "child" handler release the lock at child process.
2. Example:
atfork.c:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;
void prepare(void)
{
// Acquire all locks in this handler, but this handler aims to
// wait for these mutexes getting unlocked firstly, otherwise
// it will block here forever
printf("preparing locks...\n");
pthread_mutex_lock(&lock1);
pthread_mutex_lock(&lock2);
}
void parent(void)
{
printf("parent unlocking locks...\n");
pthread_mutex_unlock(&lock1);
pthread_mutex_unlock(&lock2);
}
void child(void)
{
printf("child unlocking locks...\n");
pthread_mutex_unlock(&lock1);
pthread_mutex_unlock(&lock2);
}
void* thr_fn(void* arg)
{
printf("thread started...\n");
pthread_mutex_lock(&lock1);
pthread_mutex_lock(&lock2);
sleep(2);
// We have to unlock the mutex, otherwise, prepare handler will be
// blocked forever.
pthread_mutex_unlock(&lock1);
pthread_mutex_unlock(&lock2);
return 0;
}
int main(int argc, char* argv[])
{
int err;
pid_t pid;
pthread_t tid;
// Establish the at fork handlers
if((err = pthread_atfork(prepare, parent, child)) != 0) {
printf("pthread_atfork error!\n");
exit(1);
}
// Launch the new thread
if((err = pthread_create(&tid, NULL, thr_fn, 0)) != 0) {
printf("pthread_create error!\n");
exit(2);
}
// fork the child process
sleep(1);
printf("parent about to fork...\n");
if((pid = fork()) < 0) {
printf("fork failed!\n");
}
else if(pid == 0) /* child */ {
printf("child returned from fork\n");
}
else {
printf("parent returned from fork\n");
}
exit(0);
}
shell:
ubuntu@ip-172-31-23-227:~$ ./atfork.out
thread started...
parent about to fork...
preparing locks...
parent unlocking locks...
parent returned from fork
ubuntu@ip-172-31-23-227:~$ child unlocking locks...
child returned from fork
3. Threads I/O
System Definition:
ubuntu@ip-172-31-23-227:~$ less /usr/include/unistd.h
......
/* Read NBYTES into BUF from FD at the given position OFFSET without
changing the file pointer. Return the number read, -1 for errors
or 0 for EOF.
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t pread (int __fd, void *__buf, size_t __nbytes,
__off_t __offset) __wur;
/* Write N bytes of BUF to FD at the given position OFFSET without
changing the file pointer. Return the number written, or -1.
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t pwrite (int __fd, const void *__buf, size_t __n,
__off_t __offset) __wur;
......
pread is equal to "lseek" + "read"
pwrite is equal to "lseek" + "write"
By combining two function together to be atomic, it can help solve many I/O synchronization problem among threads.
No comments:
Post a Comment