diff options
author | Denis Vlasenko <vda.linux@googlemail.com> | 2007-12-04 09:48:40 +0000 |
---|---|---|
committer | Denis Vlasenko <vda.linux@googlemail.com> | 2007-12-04 09:48:40 +0000 |
commit | d0bbbdcd6eefd249637f153f9d29b37c7f545e33 (patch) | |
tree | d2a4453c90ae1d7fc1090a907ad8fe3aa21429fe /docs | |
parent | 7221c8c22dd527700204eb5cc4d0651af4273f4f (diff) | |
download | busybox-w32-d0bbbdcd6eefd249637f153f9d29b37c7f545e33.tar.gz busybox-w32-d0bbbdcd6eefd249637f153f9d29b37c7f545e33.tar.bz2 busybox-w32-d0bbbdcd6eefd249637f153f9d29b37c7f545e33.zip |
getty: don't accept ancient '#' and '@' as backspace/kill line,
it only confuses people.
(Alexander Griesser <alexander.griesser@lkh-vil.or.at> (LKH Villach))
various other cleanups.
function old new delta
getty_main 2526 2546 +20
static.baud_index 4 - -4
parse_speeds 91 - -91
------------------------------------------------------------------------------
(add/remove: 0/2 grow/shrink: 1/0 up/down: 20/-95) Total: -75 bytes
text data bss dec hex filename
773152 1086 9008 783246 bf38e busybox_old
773081 1086 9008 783175 bf347 busybox_unstripped
Diffstat (limited to 'docs')
-rw-r--r-- | docs/ctty.htm | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/docs/ctty.htm b/docs/ctty.htm new file mode 100644 index 000000000..26d2c7956 --- /dev/null +++ b/docs/ctty.htm | |||
@@ -0,0 +1,474 @@ | |||
1 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> | ||
2 | <html><head> | ||
3 | <!-- saved from http://www.win.tue.nl/~aeb/linux/lk/lk-10.html --> | ||
4 | <meta name="GENERATOR" content="SGML-Tools 1.0.9"><title>The Linux kernel: Processes</title> | ||
5 | </head> | ||
6 | <body> | ||
7 | <hr> | ||
8 | <h2><a name="s10">10. Processes</a></h2> | ||
9 | |||
10 | <p>Before looking at the Linux implementation, first a general Unix | ||
11 | description of threads, processes, process groups and sessions. | ||
12 | </p><p>A session contains a number of process groups, and a process group | ||
13 | contains a number of processes, and a process contains a number | ||
14 | of threads. | ||
15 | </p><p>A session can have a controlling tty. | ||
16 | At most one process group in a session can be a foreground process group. | ||
17 | An interrupt character typed on a tty ("Teletype", i.e., terminal) | ||
18 | causes a signal to be sent to all members of the foreground process group | ||
19 | in the session (if any) that has that tty as controlling tty. | ||
20 | </p><p>All these objects have numbers, and we have thread IDs, process IDs, | ||
21 | process group IDs and session IDs. | ||
22 | </p><p> | ||
23 | </p><h2><a name="ss10.1">10.1 Processes</a> | ||
24 | </h2> | ||
25 | |||
26 | <p> | ||
27 | </p><h3>Creation</h3> | ||
28 | |||
29 | <p>A new process is traditionally started using the <code>fork()</code> | ||
30 | system call: | ||
31 | </p><blockquote> | ||
32 | <pre>pid_t p; | ||
33 | |||
34 | p = fork(); | ||
35 | if (p == (pid_t) -1) | ||
36 | /* ERROR */ | ||
37 | else if (p == 0) | ||
38 | /* CHILD */ | ||
39 | else | ||
40 | /* PARENT */ | ||
41 | </pre> | ||
42 | </blockquote> | ||
43 | <p>This creates a child as a duplicate of its parent. | ||
44 | Parent and child are identical in almost all respects. | ||
45 | In the code they are distinguished by the fact that the parent | ||
46 | learns the process ID of its child, while <code>fork()</code> | ||
47 | returns 0 in the child. (It can find the process ID of its | ||
48 | parent using the <code>getppid()</code> system call.) | ||
49 | </p><p> | ||
50 | </p><h3>Termination</h3> | ||
51 | |||
52 | <p>Normal termination is when the process does | ||
53 | </p><blockquote> | ||
54 | <pre>exit(n); | ||
55 | </pre> | ||
56 | </blockquote> | ||
57 | |||
58 | or | ||
59 | <blockquote> | ||
60 | <pre>return n; | ||
61 | </pre> | ||
62 | </blockquote> | ||
63 | |||
64 | from its <code>main()</code> procedure. It returns the single byte <code>n</code> | ||
65 | to its parent. | ||
66 | <p>Abnormal termination is usually caused by a signal. | ||
67 | </p><p> | ||
68 | </p><h3>Collecting the exit code. Zombies</h3> | ||
69 | |||
70 | <p>The parent does | ||
71 | </p><blockquote> | ||
72 | <pre>pid_t p; | ||
73 | int status; | ||
74 | |||
75 | p = wait(&status); | ||
76 | </pre> | ||
77 | </blockquote> | ||
78 | |||
79 | and collects two bytes: | ||
80 | <p> | ||
81 | <figure> | ||
82 | <eps file="absent"> | ||
83 | <img src="ctty_files/exit_status.png"> | ||
84 | </eps> | ||
85 | </figure></p><p>A process that has terminated but has not yet been waited for | ||
86 | is a <i>zombie</i>. It need only store these two bytes: | ||
87 | exit code and reason for termination. | ||
88 | </p><p>On the other hand, if the parent dies first, <code>init</code> (process 1) | ||
89 | inherits the child and becomes its parent. | ||
90 | </p><p> | ||
91 | </p><h3>Signals</h3> | ||
92 | |||
93 | <p> | ||
94 | </p><h3>Stopping</h3> | ||
95 | |||
96 | <p>Some signals cause a process to stop: | ||
97 | <code>SIGSTOP</code> (stop!), | ||
98 | <code>SIGTSTP</code> (stop from tty: probably ^Z was typed), | ||
99 | <code>SIGTTIN</code> (tty input asked by background process), | ||
100 | <code>SIGTTOU</code> (tty output sent by background process, and this was | ||
101 | disallowed by <code>stty tostop</code>). | ||
102 | </p><p>Apart from ^Z there also is ^Y. The former stops the process | ||
103 | when it is typed, the latter stops it when it is read. | ||
104 | </p><p>Signals generated by typing the corresponding character on some tty | ||
105 | are sent to all processes that are in the foreground process group | ||
106 | of the session that has that tty as controlling tty. (Details below.) | ||
107 | </p><p>If a process is being traced, every signal will stop it. | ||
108 | </p><p> | ||
109 | </p><h3>Continuing</h3> | ||
110 | |||
111 | <p><code>SIGCONT</code>: continue a stopped process. | ||
112 | </p><p> | ||
113 | </p><h3>Terminating</h3> | ||
114 | |||
115 | <p><code>SIGKILL</code> (die! now!), | ||
116 | <code>SIGTERM</code> (please, go away), | ||
117 | <code>SIGHUP</code> (modem hangup), | ||
118 | <code>SIGINT</code> (^C), | ||
119 | <code>SIGQUIT</code> (^\), etc. | ||
120 | Many signals have as default action to kill the target. | ||
121 | (Sometimes with an additional core dump, when such is | ||
122 | allowed by rlimit.) | ||
123 | The signals <code>SIGCHLD</code> and <code>SIGWINCH</code> | ||
124 | are ignored by default. | ||
125 | All except <code>SIGKILL</code> and <code>SIGSTOP</code> can be | ||
126 | caught or ignored or blocked. | ||
127 | For details, see <code>signal(7)</code>. | ||
128 | </p><p> | ||
129 | </p><h2><a name="ss10.2">10.2 Process groups</a> | ||
130 | </h2> | ||
131 | |||
132 | <p>Every process is member of a unique <i>process group</i>, | ||
133 | identified by its <i>process group ID</i>. | ||
134 | (When the process is created, it becomes a member of the process group | ||
135 | of its parent.) | ||
136 | By convention, the process group ID of a process group | ||
137 | equals the process ID of the first member of the process group, | ||
138 | called the <i>process group leader</i>. | ||
139 | A process finds the ID of its process group using the system call | ||
140 | <code>getpgrp()</code>, or, equivalently, <code>getpgid(0)</code>. | ||
141 | One finds the process group ID of process <code>p</code> using | ||
142 | <code>getpgid(p)</code>. | ||
143 | </p><p>One may use the command <code>ps j</code> to see PPID (parent process ID), | ||
144 | PID (process ID), PGID (process group ID) and SID (session ID) | ||
145 | of processes. With a shell that does not know about job control, | ||
146 | like <code>ash</code>, each of its children will be in the same session | ||
147 | and have the same process group as the shell. With a shell that knows | ||
148 | about job control, like <code>bash</code>, the processes of one pipeline. like | ||
149 | </p><blockquote> | ||
150 | <pre>% cat paper | ideal | pic | tbl | eqn | ditroff > out | ||
151 | </pre> | ||
152 | </blockquote> | ||
153 | |||
154 | form a single process group. | ||
155 | <p> | ||
156 | </p><h3>Creation</h3> | ||
157 | |||
158 | <p>A process <code>pid</code> is put into the process group <code>pgid</code> by | ||
159 | </p><blockquote> | ||
160 | <pre>setpgid(pid, pgid); | ||
161 | </pre> | ||
162 | </blockquote> | ||
163 | |||
164 | If <code>pgid == pid</code> or <code>pgid == 0</code> then this creates | ||
165 | a new process group with process group leader <code>pid</code>. | ||
166 | Otherwise, this puts <code>pid</code> into the already existing | ||
167 | process group <code>pgid</code>. | ||
168 | A zero <code>pid</code> refers to the current process. | ||
169 | The call <code>setpgrp()</code> is equivalent to <code>setpgid(0,0)</code>. | ||
170 | <p> | ||
171 | </p><h3>Restrictions on setpgid()</h3> | ||
172 | |||
173 | <p>The calling process must be <code>pid</code> itself, or its parent, | ||
174 | and the parent can only do this before <code>pid</code> has done | ||
175 | <code>exec()</code>, and only when both belong to the same session. | ||
176 | It is an error if process <code>pid</code> is a session leader | ||
177 | (and this call would change its <code>pgid</code>). | ||
178 | </p><p> | ||
179 | </p><h3>Typical sequence</h3> | ||
180 | |||
181 | <p> | ||
182 | </p><blockquote> | ||
183 | <pre>p = fork(); | ||
184 | if (p == (pid_t) -1) { | ||
185 | /* ERROR */ | ||
186 | } else if (p == 0) { /* CHILD */ | ||
187 | setpgid(0, pgid); | ||
188 | ... | ||
189 | } else { /* PARENT */ | ||
190 | setpgid(p, pgid); | ||
191 | ... | ||
192 | } | ||
193 | </pre> | ||
194 | </blockquote> | ||
195 | |||
196 | This ensures that regardless of whether parent or child is scheduled | ||
197 | first, the process group setting is as expected by both. | ||
198 | <p> | ||
199 | </p><h3>Signalling and waiting</h3> | ||
200 | |||
201 | <p>One can signal all members of a process group: | ||
202 | </p><blockquote> | ||
203 | <pre>killpg(pgrp, sig); | ||
204 | </pre> | ||
205 | </blockquote> | ||
206 | <p>One can wait for children in ones own process group: | ||
207 | </p><blockquote> | ||
208 | <pre>waitpid(0, &status, ...); | ||
209 | </pre> | ||
210 | </blockquote> | ||
211 | |||
212 | or in a specified process group: | ||
213 | <blockquote> | ||
214 | <pre>waitpid(-pgrp, &status, ...); | ||
215 | </pre> | ||
216 | </blockquote> | ||
217 | <p> | ||
218 | </p><h3>Foreground process group</h3> | ||
219 | |||
220 | <p>Among the process groups in a session at most one can be | ||
221 | the <i>foreground process group</i> of that session. | ||
222 | The tty input and tty signals (signals generated by ^C, ^Z, etc.) | ||
223 | go to processes in this foreground process group. | ||
224 | </p><p>A process can determine the foreground process group in its session | ||
225 | using <code>tcgetpgrp(fd)</code>, where <code>fd</code> refers to its | ||
226 | controlling tty. If there is none, this returns a random value | ||
227 | larger than 1 that is not a process group ID. | ||
228 | </p><p>A process can set the foreground process group in its session | ||
229 | using <code>tcsetpgrp(fd,pgrp)</code>, where <code>fd</code> refers to its | ||
230 | controlling tty, and <code>pgrp</code> is a process group in the | ||
231 | its session, and this session still is associated to the controlling | ||
232 | tty of the calling process. | ||
233 | </p><p>How does one get <code>fd</code>? By definition, <code>/dev/tty</code> | ||
234 | refers to the controlling tty, entirely independent of redirects | ||
235 | of standard input and output. (There is also the function | ||
236 | <code>ctermid()</code> to get the name of the controlling terminal. | ||
237 | On a POSIX standard system it will return <code>/dev/tty</code>.) | ||
238 | Opening the name of the | ||
239 | controlling tty gives a file descriptor <code>fd</code>. | ||
240 | </p><p> | ||
241 | </p><h3>Background process groups</h3> | ||
242 | |||
243 | <p>All process groups in a session that are not foreground | ||
244 | process group are <i>background process groups</i>. | ||
245 | Since the user at the keyboard is interacting with foreground | ||
246 | processes, background processes should stay away from it. | ||
247 | When a background process reads from the terminal it gets | ||
248 | a SIGTTIN signal. Normally, that will stop it, the job control shell | ||
249 | notices and tells the user, who can say <code>fg</code> to continue | ||
250 | this background process as a foreground process, and then this | ||
251 | process can read from the terminal. But if the background process | ||
252 | ignores or blocks the SIGTTIN signal, or if its process group | ||
253 | is orphaned (see below), then the read() returns an EIO error, | ||
254 | and no signal is sent. (Indeed, the idea is to tell the process | ||
255 | that reading from the terminal is not allowed right now. | ||
256 | If it wouldn't see the signal, then it will see the error return.) | ||
257 | </p><p>When a background process writes to the terminal, it may get | ||
258 | a SIGTTOU signal. May: namely, when the flag that this must happen | ||
259 | is set (it is off by default). One can set the flag by | ||
260 | </p><blockquote> | ||
261 | <pre>% stty tostop | ||
262 | </pre> | ||
263 | </blockquote> | ||
264 | |||
265 | and clear it again by | ||
266 | <blockquote> | ||
267 | <pre>% stty -tostop | ||
268 | </pre> | ||
269 | </blockquote> | ||
270 | |||
271 | and inspect it by | ||
272 | <blockquote> | ||
273 | <pre>% stty -a | ||
274 | </pre> | ||
275 | </blockquote> | ||
276 | |||
277 | Again, if TOSTOP is set but the background process ignores or blocks | ||
278 | the SIGTTOU signal, or if its process group is orphaned (see below), | ||
279 | then the write() returns an EIO error, and no signal is sent. | ||
280 | <p> | ||
281 | </p><h3>Orphaned process groups</h3> | ||
282 | |||
283 | <p>The process group leader is the first member of the process group. | ||
284 | It may terminate before the others, and then the process group is | ||
285 | without leader. | ||
286 | </p><p>A process group is called <i>orphaned</i> when <i>the | ||
287 | parent of every member is either in the process group | ||
288 | or outside the session</i>. | ||
289 | In particular, the process group of the session leader | ||
290 | is always orphaned. | ||
291 | </p><p>If termination of a process causes a process group to become | ||
292 | orphaned, and some member is stopped, then all are sent first SIGHUP | ||
293 | and then SIGCONT. | ||
294 | </p><p>The idea is that perhaps the parent of the process group leader | ||
295 | is a job control shell. (In the same session but a different | ||
296 | process group.) As long as this parent is alive, it can | ||
297 | handle the stopping and starting of members in the process group. | ||
298 | When it dies, there may be nobody to continue stopped processes. | ||
299 | Therefore, these stopped processes are sent SIGHUP, so that they | ||
300 | die unless they catch or ignore it, and then SIGCONT to continue them. | ||
301 | </p><p>Note that the process group of the session leader is already | ||
302 | orphaned, so no signals are sent when the session leader dies. | ||
303 | </p><p>Note also that a process group can become orphaned in two ways | ||
304 | by termination of a process: either it was a parent and not itself | ||
305 | in the process group, or it was the last element of the process group | ||
306 | with a parent outside but in the same session. | ||
307 | Furthermore, that a process group can become orphaned | ||
308 | other than by termination of a process, namely when some | ||
309 | member is moved to a different process group. | ||
310 | </p><p> | ||
311 | </p><h2><a name="ss10.3">10.3 Sessions</a> | ||
312 | </h2> | ||
313 | |||
314 | <p>Every process group is in a unique <i>session</i>. | ||
315 | (When the process is created, it becomes a member of the session | ||
316 | of its parent.) | ||
317 | By convention, the session ID of a session | ||
318 | equals the process ID of the first member of the session, | ||
319 | called the <i>session leader</i>. | ||
320 | A process finds the ID of its session using the system call | ||
321 | <code>getsid()</code>. | ||
322 | </p><p>Every session may have a <i>controlling tty</i>, | ||
323 | that then also is called the controlling tty of each of | ||
324 | its member processes. | ||
325 | A file descriptor for the controlling tty is obtained by | ||
326 | opening <code>/dev/tty</code>. (And when that fails, there was no | ||
327 | controlling tty.) Given a file descriptor for the controlling tty, | ||
328 | one may obtain the SID using <code>tcgetsid(fd)</code>. | ||
329 | </p><p>A session is often set up by a login process. The terminal | ||
330 | on which one is logged in then becomes the controlling tty | ||
331 | of the session. All processes that are descendants of the | ||
332 | login process will in general be members of the session. | ||
333 | </p><p> | ||
334 | </p><h3>Creation</h3> | ||
335 | |||
336 | <p>A new session is created by | ||
337 | </p><blockquote> | ||
338 | <pre>pid = setsid(); | ||
339 | </pre> | ||
340 | </blockquote> | ||
341 | |||
342 | This is allowed only when the current process is not a process group leader. | ||
343 | In order to be sure of that we fork first: | ||
344 | <blockquote> | ||
345 | <pre>p = fork(); | ||
346 | if (p) exit(0); | ||
347 | pid = setsid(); | ||
348 | </pre> | ||
349 | </blockquote> | ||
350 | |||
351 | The result is that the current process (with process ID <code>pid</code>) | ||
352 | becomes session leader of a new session with session ID <code>pid</code>. | ||
353 | Moreover, it becomes process group leader of a new process group. | ||
354 | Both session and process group contain only the single process <code>pid</code>. | ||
355 | Furthermore, this process has no controlling tty. | ||
356 | <p>The restriction that the current process must not be a process group leader | ||
357 | is needed: otherwise its PID serves as PGID of some existing process group | ||
358 | and cannot be used as the PGID of a new process group. | ||
359 | </p><p> | ||
360 | </p><h3>Getting a controlling tty</h3> | ||
361 | |||
362 | <p>How does one get a controlling terminal? Nobody knows, | ||
363 | this is a great mystery. | ||
364 | </p><p>The System V approach is that the first tty opened by the process | ||
365 | becomes its controlling tty. | ||
366 | </p><p>The BSD approach is that one has to explicitly call | ||
367 | </p><blockquote> | ||
368 | <pre>ioctl(fd, TIOCSCTTY, ...); | ||
369 | </pre> | ||
370 | </blockquote> | ||
371 | |||
372 | to get a controlling tty. | ||
373 | <p>Linux tries to be compatible with both, as always, and this | ||
374 | results in a very obscure complex of conditions. Roughly: | ||
375 | </p><p>The <code>TIOCSCTTY</code> ioctl will give us a controlling tty, | ||
376 | provided that (i) the current process is a session leader, | ||
377 | and (ii) it does not yet have a controlling tty, and | ||
378 | (iii) maybe the tty should not already control some other session; | ||
379 | if it does it is an error if we aren't root, or we steal the tty | ||
380 | if we are all-powerful. | ||
381 | </p><p>Opening some terminal will give us a controlling tty, | ||
382 | provided that (i) the current process is a session leader, and | ||
383 | (ii) it does not yet have a controlling tty, and | ||
384 | (iii) the tty does not already control some other session, and | ||
385 | (iv) the open did not have the <code>O_NOCTTY</code> flag, and | ||
386 | (v) the tty is not the foreground VT, and | ||
387 | (vi) the tty is not the console, and | ||
388 | (vii) maybe the tty should not be master or slave pty. | ||
389 | </p><p> | ||
390 | </p><h3>Getting rid of a controlling tty</h3> | ||
391 | |||
392 | <p>If a process wants to continue as a daemon, it must detach itself | ||
393 | from its controlling tty. Above we saw that <code>setsid()</code> | ||
394 | will remove the controlling tty. Also the ioctl TIOCNOTTY does this. | ||
395 | Moreover, in order not to get a controlling tty again as soon as it | ||
396 | opens a tty, the process has to fork once more, to assure that it | ||
397 | is not a session leader. Typical code fragment: | ||
398 | </p><p> | ||
399 | </p><pre> if ((fork()) != 0) | ||
400 | exit(0); | ||
401 | setsid(); | ||
402 | if ((fork()) != 0) | ||
403 | exit(0); | ||
404 | </pre> | ||
405 | <p>See also <code>daemon(3)</code>. | ||
406 | </p><p> | ||
407 | </p><h3>Disconnect</h3> | ||
408 | |||
409 | <p>If the terminal goes away by modem hangup, and the line was not local, | ||
410 | then a SIGHUP is sent to the session leader. | ||
411 | Any further reads from the gone terminal return EOF. | ||
412 | (Or possibly -1 with <code>errno</code> set to EIO.) | ||
413 | </p><p>If the terminal is the slave side of a pseudotty, and the master side | ||
414 | is closed (for the last time), then a SIGHUP is sent to the foreground | ||
415 | process group of the slave side. | ||
416 | </p><p>When the session leader dies, a SIGHUP is sent to all processes | ||
417 | in the foreground process group. Moreover, the terminal stops being | ||
418 | the controlling terminal of this session (so that it can become | ||
419 | the controlling terminal of another session). | ||
420 | </p><p>Thus, if the terminal goes away and the session leader is | ||
421 | a job control shell, then it can handle things for its descendants, | ||
422 | e.g. by sending them again a SIGHUP. | ||
423 | If on the other hand the session leader is an innocent process | ||
424 | that does not catch SIGHUP, it will die, and all foreground processes | ||
425 | get a SIGHUP. | ||
426 | </p><p> | ||
427 | </p><h2><a name="ss10.4">10.4 Threads</a> | ||
428 | </h2> | ||
429 | |||
430 | <p>A process can have several threads. New threads (with the same PID | ||
431 | as the parent thread) are started using the <code>clone</code> system | ||
432 | call using the <code>CLONE_THREAD</code> flag. Threads are distinguished | ||
433 | by a <i>thread ID</i> (TID). An ordinary process has a single thread | ||
434 | with TID equal to PID. The system call <code>gettid()</code> returns the | ||
435 | TID. The system call <code>tkill()</code> sends a signal to a single thread. | ||
436 | </p><p>Example: a process with two threads. Both only print PID and TID and exit. | ||
437 | (Linux 2.4.19 or later.) | ||
438 | </p><pre>% cat << EOF > gettid-demo.c | ||
439 | #include <unistd.h> | ||
440 | #include <sys/types.h> | ||
441 | #define CLONE_SIGHAND 0x00000800 | ||
442 | #define CLONE_THREAD 0x00010000 | ||
443 | #include <linux/unistd.h> | ||
444 | #include <errno.h> | ||
445 | _syscall0(pid_t,gettid) | ||
446 | |||
447 | int thread(void *p) { | ||
448 | printf("thread: %d %d\n", gettid(), getpid()); | ||
449 | } | ||
450 | |||
451 | main() { | ||
452 | unsigned char stack[4096]; | ||
453 | int i; | ||
454 | |||
455 | i = clone(thread, stack+2048, CLONE_THREAD | CLONE_SIGHAND, NULL); | ||
456 | if (i == -1) | ||
457 | perror("clone"); | ||
458 | else | ||
459 | printf("clone returns %d\n", i); | ||
460 | printf("parent: %d %d\n", gettid(), getpid()); | ||
461 | } | ||
462 | EOF | ||
463 | % cc -o gettid-demo gettid-demo.c | ||
464 | % ./gettid-demo | ||
465 | clone returns 21826 | ||
466 | parent: 21825 21825 | ||
467 | thread: 21826 21825 | ||
468 | % | ||
469 | </pre> | ||
470 | <p> | ||
471 | </p><p> | ||
472 | </p><hr> | ||
473 | |||
474 | </body></html> | ||