Exercises:Synchronisation: Difference between revisions
pc>Yuron No edit summary |
Yuron [PHRhYmxlIGNsYXNzPSJ0d3BvcHVwIj48dHI+PHRkIGNsYXNzPSJ0d3BvcHVwLWVudHJ5dGl0bGUiPkdyb3Vwczo8L3RkPjx0ZD51c2VyPGJyIC8+YnVyZWF1Y3JhdDxiciAvPmludGVyZmFjZS1hZG1pbjxiciAvPnN5c29wPGJyIC8+PC90ZD48L3RyPjwvdGFibGU+] (talk | contribs) m (1 revision imported) |
(No difference)
|
Revision as of 12:46, 26 July 2019
On path: Exercises | 0: Exercises • 1: Pointer Exercise • 2: Arguments Exercise • 3: Malloc Exercise • 4: Structs Exercise • 5: Processes Exercise • 6: Shared memory Exercise • 7: Pipes Exercise • 8: Exceptions Exercise • 9: Synchronisation Exercise • 10: Files Exercise • 11: Threads Exercise • 12: Unix proc Exercise |
---|
Depends on | Unix Signals • Sleep |
---|
The purposes of this exercise are:
- to appreciate PIDs better
- to look further at signalling and synchronisation of processes
Before properly starting this exercise, a quick illustration of process operation. The exact results you get may vary slightly according to the system, but in a Unix shell (terminal) type: ps
which should print a list of the processes running there. One will be the shell (such as sh
, ksh
, bash
etc.); another should be ps
which is doing the printing. (There may be others if you have run background jobs from that shell.) Alongside there should be the PID (Process IDentifier), a unique process number. (The process name is inadequate as you can run multiple copies of one program. Try ps -A
.)
Now try ps
again in the same shell. The shell will have the same
PID but ps
will be different because it is a new process. It will
probably have a similar but higher number allocated.
This reveals something of the operation of executing an application from a shell: the shell creates a new (largely independent) process, then waits for it to finish, then continues.
If you don’t want the shell to wait, add &
on the end of the command
line and it won’t – but with applications like ps
it will then
tangle any ‘command prompt’ with the application’s output.
To illustrate, these exercises run a ‘parent’ process (under the shell) which starts its own family of child processes.
Part 1
Compile and execute synchronisation.c
As a bonus, this prints its own and its parent PID. Processes know who created them: there is only ever one parent.
You should have already seen that a parent receives its child’s PID
when it forks: one parent can have many child processes. That happens
again here and the processes are directed down different code
sequences by the if
. It is not deterministic which will run first
but pretty much certain that the sleep()
call will make the children
terminate later. (The sleep()
might represent some time-consuming
processing in another application.)
Have a few goes: you will probably see some differences in scheduling.
This is merely messy here because the parent terminating will wake up the shell which will print the prompt before the child says “Goodbye”. In other circumstances it may be more important to ensure there is ordering.
Uncomment the waitpid()
call; you can ignore the syntax, at least
for now. This call waits for a change in state of the selected child
process – in this case its termination. If you run this version you
will see that the selected child always terminates before its
parent. Similarly, the parent process termination signals to a
similar wait in the shell which puts the prompt back again. The shell
can be scheduled any time after the parent’s termination.
Note: the
sleep()
call suspends a process for a time; This is another scheduling issue and is examined in more detail elsewhere.
Part 2
Change the argument of the sleep from ‘1’ to something more
interesting like i
or (FAMILY - i)
. This will show up the
causality better. Try it (a couple of times).
Now replace the waitpid()
call with the code from barrier.c
.
This introduces a synchronisation
barrier in the parent process. In this case the wait()
is non-specific: it responds to a SIGCHLD
signal from any child process. The signal source is given as the return value and the variable status
(passed by reference) is used to indicate any reason the process went wrong. An exit(0);
will return a NULL
status; anything else will return a non-NULL code – which is not distinguished in this code fragment.
Here, the barrier is implemented by collecting the same number of
SIGCHLD
signals – in any order – as there were processes created before proceeding. This is a bit crude – it would be better to check
off separate, legitimate terminations for each child – but is serves
to illustrate the principle.
Part 3
“Prolicide” is the act of killing one’s own children.
Compile and run prolicide.c
. This has a badly behaved child, which
does not terminate. As the parent is waiting, it can’t terminate
either.
If you ran that you’d better try ^C.
Uncomment the /*
*/
line with the kill()
call, and try again. This time everything should terminate correctly and in a well defined order because the parent still waits for the child’s termination.
Although appropriate in this case, kill()
is really a call which
sends a ‘signal’ of any form. The signal here is
SIGTERM
– the same (default) signal sent by ^C from a shell – and
the result is the same. This example traps that exception to print a
final, accusatory message.
If you want to experiment further, try kill
ing using SIGKILL
instead; that signal is not – and indeed cannot – be intercepted.
(This is used, for example, by kill -9
in a command line.)
If/when you have to employ these techniques prefer signals such
SIGTERM
if possible as it allows interception and the orderly release of any resources the process might have.SIGKILL
can bypass this and may thus leave things in an untidy state. For example, if there is a shared memory it does not specifically belong to the process so the operating system can’t free it if that would be appropriate and it can therefore remain ‘forever’.
Submission
Make up a text file from the amalgamated outputs from runs of the
various parts, above. You should have more than one run of each to show some differences in ordering (as the process scheduler is liable to have made different choices) and include something to identify the various parts. Leave any shell prompts in place, to show where that process runs too. Call this
[ex9.txt
].
Here’s a partial example suggestion:
----------------------------------------
Part 1:
./synchronisation
Hello from ./synchronisation with PID 11468 whose parent is 11462
I am 11469, child 0 of 11468
I am 11470, child 1 of 11468
I am 11471, child 2 of 11468
I am 11473, child 4 of 11468
I am 11472, child 3 of 11468
I am 11474, child 5 of 11468
I am 11475, child 6 of 11468
I am 11476, child 7 of 11468
I am 11477, child 8 of 11468
I am 11478, child 9 of 11468
[11468]: I now have 10 children:
11469
11470
11471
11472
11473
11474
11475
11476
11477
11478
[11468]: Goodbye from parent process.
jdg-synchronisation-)
Goodbye from child 0 with PID 11469
Goodbye from child 1 with PID 11470
Goodbye from child 2 with PID 11471
... etc ...