Signals are sent either from processes to other processes, or as a result of the violation of some conditions (e.g. illegal instruction). Sending signals share some behaviour with interrupts:
The kernel, very frequently (sometimes immediately, otherwise each time it switches from kernel mode to user mode, at least almost every timer interrupt), checks if there are signals to deliver to processes. If there are pending signals, the kernel:
Once created, a signal is pending.
Qhen pending signals are given the target process and an action taken, they are delivered.
If delivering makes a signal handler run, the signals is caught.
Signals that are handled without the use of a handler are accepted.
ok- what is the usefulness of EINTR? it cannot be used to kill processes. Well, EINTR is the correct way to let a program know that a system call aborted. It is just a pity that it is not possible to use it because of lack of persistence in signals
- there is a need to have a look to the other patterns and see what races are in them and see if persistence in signals would solve them
- how was sigsafe able to test races? it uses probably ptrace, creates a child and makes it advance single step testing at each its behaviour if a signal arrives at that point
ok- use write() instead of printf in signal handlers? Yes, but there should be no need to do i/o on signal handlers, except for debugging
- reimplement the examples using write() instead of printf() in handlers
- understand well the use of signals with a single thread, and then with a threaded process. Some solutions use threads, though.
- have a look to the first examples down here
ok- cost of signal functions:
| pthread_sigmask takes | 0.159088 μs | 110.3 units |
| sigemptyset takes | 0.015451 μs | 10.7 units |
| sigaddset takes | 0.006815 μs | 4.7 units |
| sigaction takes | 0.153278 μs | 106.3 units |
| setjmp takes | 0.008640 μs | 6.0 units |
| sigsetjmp takes | 0.113223 μs | 79.1 units |
| longjmp takes | 0.016844 μs | 11.8 units |
| siglongjmp takes | 0.158201 μs | 110.5 units |
| pthread_testcancel takes | 0.004869 μs | 3.4 units |
| pthread_setcancelstate enable/disable takes | 0.034077 μs | 23.5 units |
| pthread_setspecific | 0.011772 μs | 8.8 units |
| pthread_getspecific | 0.009972 μs | 7.5 units |
(a "unit" is the time taken by a memory-to-memory copy)
- the problem I noted when sending signals to processes, that would have the desired >effect only if the receiver was in a given state is noted also on page 308 of the Advanced programming in the unix environment: there is a need to make atomic many more system calls besides pause(), i.e. all the ones that suspend a process. It seems that >also here we need some sort of supervised block statement. Perhaps using a longjmp could solve the problem also there? Probably yes.
- then there are similar problems with java? java has try blocks, and a way to cause exceptions, so, there should be fewer problems. It seems that java threads have an interrupted status >which is like a permanent signal, and that makes blocking methods abort if on entry the >status is interrupted or during execution they are interrupted
ok- perhaps it is not much important, but signals are more asynchronous than interrupts. The >program that receives interrupts has enabled a device to assert them. They occur at a point in time that lies in the interval from the enabling instant onwards (and often within some defined time). A kill signal is more unexpected, even if a program that wants to treat it >has defined a handler for it. An event that makes a program terminate abruptly (e.g. a CPU failure) is even worse because it cannot be handled and can lead to leave data (e.g. disk >files) inconsistent. To handle the latter, a stronger form of atomicity must be used, e.g. >the writing of a block, that is likely to occur or not to occur even in case of failure. Operations that are made atomic by using locks can be interrupted by a failure.
- sigset(), sigvec()? these are probably old system calls
ok- retrieve the man signals of ubuntu 9.04, there is the list of slow system calls
ok- a function that is not reentrant can be made so by protecting it with a mutex, providing that it does not call itself (however, a truly reentrant function can be recursive, while one protected with a mutex cannot)
ok- it seems strange that gnu does not tell if the library functions are reentrant.
Since it is not stated, there is a need to build a library at least to manipulate strings
- sending messages to a dedicated thread that is devoted to wait for them should have the >same power as sending signals, probably even better. The thread can raise kill flags for others, and if it really needs to make another that is in a wait go ahead, it can send an internal signal to it
.. all the more because kill can be done without signals
.. but the other ipc need to create an ipc object for each process or application, and to destroy it when it is not running, while signals can be sent to any process without a need for system objects
- allocating a resource and remembering it atomically seems difficult. It must be protected with a mutex to make it atomic, but the mutex must be released immediately before waiting so as to let others enter and test if the resource is allocated. The Hoare's monitor with the internal queue solves this. The problem is that it is not always possible to enclose a sequence of statements in a block that is executed atomically keeping the block as it is. This is exactly the problem of composition. In order to achieve atomicity in some cases we must move the p(sem) contained in the block body at the beginning of the block, but when the body contains a wait on some other conditions the solution is more complex (see monitor). I have the impression that we must use non-canonicaly synchronizations such as polling to solve the problem. There is a need to find out a general solution (elegant or not as it is). However, remember that signals can interrupt threads also when they are inside a critical region.
- when there is a need for atomic operations in kernel? It seems when there are more than one operations to be done, and they must not be interrupted by another thread or handler. E.g. semaphore, when we need a solution that does not use spinning (and that applies to any number of processes); timeout (if a time supervision is done without the kernel, the thread can get descheduled, and time expiry, but not while waiting), sigwait (waiting for a signal requires to have it blocked, and unblock it immediately before start waiting), etc.
ok- note that critical regions are not atomic. They behave like atomic actions in the world of processes, but not between processes and handlers
ok- Hoare monitors:
monitor
semaphore mutex = 1;
semaphore urgent = 0;
int urgentcount = 0;
semaphore condsem = 0;
int condcount = 0;
procedure(){
P(mutex); // entry in procedure
cond.wait():
condcount++;
if (urgentcount > 0) V(urgent) else V(mutex);
P(condsem);
coundcount--;
cond.signal();
urgentcount++;
if (condcount > 0){V(condsem); P(urgent);};
urgentcount--;
if (urgentcount > 0) V(urgent) else V(mutex); // exit from procedure
}
end
If every cond.signal is the last operation:
monitor
semaphore mutex = 1;
semaphore condsem = 0;
int condcount = 0;
procedure(){
P(mutex); // entry in procedure
cond.wait():
condcount++;
V(mutex);
P(condsem);
coundcount--;
cond.signal();
if (condcount > 0) V(condsem) else V(mutex); // combined signal + exit
V(mutex); // exit from procedure
}
end
The monitor without simplifications guarantees that processes that are waiting for conditions take precedence over the newcomers, even without fifo semaphore policy. The producer does not release the monitor lock, but the condition one, thereby passing it to the consumer, and preventing any other to enter the monitor. The monitor with the simplification above guarantees the same.