Книга: Programming with POSIX® Threads

6.1.1 Fork handlers

6.1.1 Fork handlers

int pthread_atfork (void (*prepare)(void),

void (*parent)(void), void (*child)(void));

Pthreads added the pthread_atfork "fork handler" mechanism to allow your code to protect data invariants across fork. This is somewhat analogous to atexit, which allows a program to perform cleanup when a process terminates. With pthread_atfork you supply three separate handler addresses. The prepare fork handler is called before the fork takes place in the parent process. The parent fork handler is called after the fork in the parent process, and the child fork handler is called after the fork in the child process.

|If you write a subsystem that uses mutexes and does not establish fork handlers,then that subsystem will not function correctly in a child process after a fork.

Normally a prepare fork handler locks all mutexes used by the associated code (for a library or an application) in the correct order to prevent deadlocks. The thread calling fork will block in the prepare fork handler until it has locked all the mutexes. That ensures that no other threads can have the mutexes locked or be modifying data that the child might need. The parent fork handler need only unlock all of those mutexes, allowing the parent process and all threads to continue normally.

The child fork handler may often be the same as the parent fork handler; but sometimes you'll need to reset the program or library state. For example, if you use "daemon" threads to perform functions in the background you'll need to either record the fact that those threads no longer exist or create new threads to perform the same function in the child. You may need to reset counters, free heap memory, and so forth.

Your fork handlers are only as good as everyone else's fork handlers.

The system will run all prepare fork handlers declared in the process when any thread calls fork. If you code your prepare and child fork handlers correctly then, in principle, you will be able to continue operating in the child process. But what if someone else didn't supply fork handlers or didn't do it right? The ANSI C library on a threaded system, for example, must use a set ofmutexes to synchronize internal data, such as stdio file streams.

If you use an ANSI C library that doesn't supply fork handlers to prepare those mutexes properly for a fork, for example, then, sometimes, you may find that

your child process hangs when it calls printf, because another thread in the parent process had the mutex locked when your thread called fork. There's often nothing you can do about this type of problem except to file a problem report against the system. These mutexes are usually private to the library, and aren't visible to your code — you can't lock them in your prepare handler or before calling fork.

The program atfork.c shows the use of fork handlers. When run with no argument, or with a nonzero argument, the program will install fork handlers. When run with a zero argument, such as atfork 0, it will not.

With fork handlers installed, the result will be two output lines reporting the result of the fork call and, in parentheses, the pid of the current process. Without fork handlers, the child process will be created while the initial thread owns the mutex. Because the initial thread does not exist in the child, the mutex cannot be unlocked, and the child process will hang — only the parent process will print its message.

13-25 Function fork_prepare is the prepare handler. This will be called by fork, in the parent process, before creating the child process. Any state changed by this function, in particular, mutexes that are locked, will be copied into the child process. The fork_prepare function locks the program's mutex.

31-42 Function fork_parent is the parent handler. This will be called by fork, in the parent process, after creating the child process. In general, a parent handler should undo whatever was done in the prepare handler, so that the parent process can continue normally. The fork_parent function unlocks the mutex that was locked by fork_prepare.

48-60 Function fork_child is the child handler. This will be called by fork, in the child process. In most cases, the child handler will need to do whatever was done in the fork_parent handler to "unlock" the state so that the child can continue. It may also need to perform additional cleanup, for example, fork_child sets the self_pid variable to the child process's pid as well as unlocking the process mutex.

65-91 After creating a child process, which will continue executing the thread_ routine code, the thread_routine function locks the mutex. When run with fork handlers, the fork call will be blocked (when the prepare handler locks the mutex) until the mutex is available. Without fork handlers, the thread will fork before main unlocks the mutex, and the thread will hang in the child at this point. 99-106 The main program declares fork handlers unless the program is run with an argument of 0.

108-123 The main program locks the mutex before creating the thread that will fork. It then sleeps for several seconds, to ensure that the thread will be able to call fork while the mutex is locked, and then unlocks the mutex. The thread running thread_routine will always succeed in the parent process, because it will simply block until main releases the lock.

However, without the fork handlers, the child process will be created while the mutex is locked. The thread (main) that locked the mutex does not exist in the child, and cannot unlock the mutex in the child process. Mutexes can be unlocked

in the child only if they were locked by the thread that called fork — and fork handlers provide the best way to ensure that.

? atfork.c

1 #include <sys/types.h>
2 #include <pthread.h>
3 #include <sys/wait.h>
4 #include "errors.h"

5
6 pid_t self_pid; /* pid of current process */
7 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

8
9 /*
10 * This routine will be called prior to executing the fork,
11 * within the parent process.
12 */
13 void fork_prepare (void)
14 {
15 int status;

16
17 /*
18 * Lock the mutex in the parent before creating the child,
19 * to ensure that no other thread can lock it (or change any
20 * associated shared state) until after the fork completes.
21 */
22 status = pthread_mutex_lock (&mutex);
23 if (status != 0)
24 err_abort (status, "Lock in prepare handler");
25 }

26
27 /*
28 * This routine will be called after executing the fork, within
29 * the parent process
30 */
31 void fork_parent (void)
32 {
33 int status;

34
35 /*
36 * Unlock the mutex in the parent after the child has been
37 * created.
38 */
39 status = pthread_mutex_unlock (&mutex);
40 if (status != 0)
41  err_abort (status, "Unlock in parent handler");
42 }

43
44 /*
45 * This routine will be called after executing the fork, within
46 * the child process.
47 */
48 void fork_child (void)
49 {
50 int status; 51
52 /*
53 * Update the file scope "self_pid" within the child process, and
54 * unlock the mutex.
55 */
56 self_pid = getpid ();
57 status = pthread_mutex_unlock (&mutex);
58 if (status != 0)
59 err_abort (status, "Unlock in child handler");
60 }

61
62 /*
63 * Thread start routine, which will fork a new child process.
64 */
65 void *thread_routine (void *arg)
66 {
67 pid_t child_pid;
68 int status;

69
70 child_pid = fork ( );
71 if (child_pid == (pid_t)-l)
72 errno_abort ("Fork");
73
74 /*
75 * Lock the mutex — without the atfork handlers, the mutex will
76 * remain locked in the child process and this lock attempt will
77 * hang (or fail with EDEADLK) in the child.
78 */
79 status = pthread_mutex_lock (&mutex);
80 if (status != 0)
81  err_abort (status, "Lock in child");
82 status = pthread_mutex_unlock (&mutex);
83 if (status != 0)
84  err_abort (status, "Unlock in child");
85 printf ("After fork: %d (%d)n", child_pid, self_pid);
86 if (child_pid != 0) {
87 if ((pid_t)-l == waitpid (child_pid, (int*)0, 0))
88 errno_abort ("Wait for child");
89 }
90 return NULL;
91 }

92
93 int main (int argc, char *argv[])
94 {
95 pthread_t fork_thread;
96 int atfork_flag = 1;
97 int status;
98

99 if (argc > 1)
100 atfork_flag = atoi (argv[l]);
101 if (atfork_flag) {
102 status = pthread_atfork (
103 fork_prepare, fork_parent, fork_child);
104 if (status != 0)
105 err_abort (status, "Register fork handlers");
106 }
107 self_pid = getpid ();
108 status = pthread_mutex_lock (&mutex);
109 if (status != 0)
110 err_abort (status, "Lock mutex");
111 /*
112 * Create a thread while the mutex is locked. It will fork a
113 * process, which (without atfork handlers) will run with the
114 * mutex locked.
115 */
116 status = pthread_create (
117 &fork_thread, NULL, thread_routine, NULL);
118 if (status != 0)
119 err_abort (status, "Create thread");
120 sleep (5);
121 status = pthread_mutex_unlock (&mutex);
122 if (status != 0)
123 err_abort (status, "Unlock mutex");
124 status = pthread_join (fork_thread, NULL);
125 if (status != 0)
126 err_abort (status, "Join thread");
127 return 0;
128 }

? atfork.c

Now, imagine you are writing a library that manages network server connections, and you create a thread for each network connection that listens for service requests. In your prepare fork handler you lock all of the library's mutexes to make sure the child's state is consistent and recoverable. In your parent fork handler you unlock those mutexes and return. When designing the child fork handler, you need to decide exactly what a fork means to your library. If you want to retain all network connections in the child, then you would create a new listener thread for each connection and record their identifiers in the appropriate data structures before releasing the mutexes. If you want the child to begin with no open connections, then you would locate the existing parent connection data structures and free them, closing the associated files that were propagated by fork.

Оглавление книги

Оглавление статьи/книги

Генерация: 1.228. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз