Disposition of a signal: (1) ignore (2) default action (3) catch w/ per-process signal handler
SIGKILL
/SIGSTOP
: always default action
fork()
; reset to default thru exec()
signal()
sets the disposition of signum
to handler
:pause()
suspends a process until a signal is delivered (i.e. waits for a signal to be caught)EINTR
)SA_RESTART
flag ( 6) is set on a signal handlerioctl()
, read()
, wait()
, …), which can be disabled per-signalThe signal handler does not know where the process is executing as the signal was caught.
malloc()
/free()
(per-process) defines the set of signals currently blocked
sig[empty/fill]set()
: initializationsig[add/del]set()
sigismember()
: test if a signal is in the setsigprocmask()
: modify the signal mask
sigprocmask(-, NULL, &set)
sigpending()
: returns currently pending signalskill(pid, signo)
: send signal to a group of processes:
ESRCH
if not exist), not atomicraise()
: send signal to selfalarm(seconds)
: sets a timer that generates SIGALRM
(only 1 timer per process)
SIGALARM
: terminating the processseconds == 0
: cancel all pending alarmseg. implement a
sleep1()
func w/alarm
&pause
Happens when a signal is received & handled before pause()
is called
abort()
: unblocks SIGABRT
and raiseSIGABRT
for the calling process
SIGABRT
is ignored or caught by a handler, abort()
will restore the default disposition for it and raise the signal againsigaction()
to examine/modify the action on a particular signal.
signal()
( 1) can be implemented using sigaction()
sa_mask
: specifies the additional signals to block during the execution of the signal handler,Before invoking a signal handler,
sa_mask
are added to the signal mask.
sa_sigaction
:
sa_handler
w/ SA_SIGINFO
onsiginfo_t
contains info about why the signal was generatedSA_
)properties | fork() |
exec() |
---|---|---|
signal mask | Inherited from the parent | unchanged |
pending signals/alarms | emptied | unchanged |
signal action | Inherited from the parent | not ignored -> default; ignored -> ignored |
(1) error-handling in deeply nested function calls
(2) signal handlers can goto a specific point
In C, goto a label in another function is not valid. Nonlocal jumps (aka nonlocal gotos) enable program control transfer to an arbitrary location.
setjmp(env)
: dynamically set the target to which control will be transfered
env
0
when called directlyval
when returning from a call to longjump()
(fake return)longjmp(env, val)
: performs the transfer
env
Storage Location | Memory | CPU or float-point registers |
---|---|---|
Type | global, volatile, static | compiled with optimization: local(auto), register |
Value | remains the same as of the time of longjmp() |
restored to when setjump() was called |
We can only longjmp()
to the env of a func that has been called but not yet completed!
Or else its stack frame is reclaimed and not in the call stack
(1) from I.5
(2)read()
w/ a timeout byalarm()
can both be solved w/ nonlocal jumps
Problem 1:
setjmp()
&longjmp()
does not save/restore the signal mask (not specified in POSIX)
sigsetjmp()
& siglongjmp()
: support saving & restoring signal masks to env
(of type sigjmp_buf
)Problem 2: updating signal mask and then
pause()
is not atomic
sigsuspend()
: updates the sig mask and pauses the process in a atomic operation
sigsuspend()
Stack | Heap |
---|---|
local variables / function args | gloabal & static variables |
per-thread | per-process |
User threads: managed w/o OS kernel support
Kernel threads: supported & managed directly by kernel
pthread_self()
: TID
pthread_equal(TID1, TID2)
: compare TIDs (not ==
!)
pthread_t
start_rtn
is the start routine function that a new thread starts running at, and arg
is passed to it.Termintating a process and all of its threads:
exit()
, _Exit()
, _exit()
main()
in the main threadTermintating a specific thread:
pthread_exit()
itselfpthread_cancel()
pthread_exit(void *rval_ptr)
returns a exit status rval_ptr
to another thread that calls pthread_join()
The resources of a process are not released until the last thread of it terminates
joinable thread
can be reaped and killed by others; resources are not freed until it is reaped
pthread_join()
: caller reaps exit status (blocking)
pthread_detach()
: kernel reaps resources & exit status
pthread_cancel(tid)
(==pthread_exit(PTHREAD_CANCELED)
): just a request; does not block
The thread being canceled can ignore or control the cancellation by:
pthread_setcancelstate()
pthread_setcanceltype()
pthread_testcancel()
: immediately creates a cancellation point and respond to currently pending cancel requests; no effect if CANCEL_DISABLE
or no request pending
pthread_attr_[init/destroy]()
: initialize/disinitialize the structure pthread_attr_t
pthread_attr_[set/get]detachstate()
: set/check if the thread should start in detached state or joinablepthread_cleanup_[push/pop]()
: register a cleanup handler (recorded in stack)pthread_exit()
pthread_cancel()
requestpthread_cleaup_pop(execute)
w/ nonzero execute
return
doesn't trigger clean-up handlers!
Process | Thread |
---|---|
fork |
pthread_create |
exit |
pthread_exit |
waitpid |
pthread_join |
atexit |
pthread_cleanup_push |
getpid |
pthread_self |
abort |
pthread_cancel |
non-atomic operation will have inconsistent result if it is made at the same time by many threads
Goal: Sequential Consistency
pthread_mutex_[init/destroy]()
pthread_mutex_init(mutex, attr)
mutex = PTHREAD_MUTEX_INITIALIZER
_lock/trylock/unlock()
lock
will block if mutex is locked; trylock
returns EBUSY
immediatelyunlock
some lock the caller does not hold is undefinedpthread_mutexattr_[init/destroy]()
_[get/set]pshared()
: set/check if the mutex is shared between processesPTHREAD_MUTEX_ERRORCHECK
pthread_mutex_trylock
in a polling fashioncoarse-grained <––––-> fine-grained
bad concurrency <––––-> high complexity
concurrent read & exclusive write
shared read lock & exclusive write lock
pthread_rwlock_[init/destroy]()
pthread_rwlock_init(rwlock, attr)
rwlock= PTHREAD_RWLOCK_INITIALIZER
_[rd/wr]lock()
, _unlock()
, _try[rd/wr]lock()
The functions are basically the same as mutex.
Threads blocks, atomically acquire a mutexblock, and then change/evaluate a condition (event) until the condition is satisfied
pthread_cond_[init/destroy]()
pthread_cond_init(cond, attr)
cond = PTHREAD_COND_INITIALIZER
_wait(cond, mutex)
: blocks caller, wait by specified cond. variable to be signaled or broadcast
_timedwait(cond, mutex, timespec)
: specifies a wait time and returns an error if time is up before wait finishes_signal/broadcast(cond)
: effective only when there's any thread blocked on cond
cond
_wait()
first for predictable scheduling behaviourpthread_condattr_init/destroy()
_get/setpshared()
: set/check if the mutex is shared between processesWhen a thread is waken up from signaled cond variable but the condition it is waiting for is not satisfied. Possible reasons:
Hence the iteratively checking.
fork
After fork()
, only the caller thread exists in the child. Child inherits every mutex, rw lock and cond var.
pthread_atfork()
: registers fork handlers, eg. to unlock mutexes locked by other threadsSignal mask: per-thread (inherited from the creator)
pthread_sigmask()
sigwait(set, signo)
: atomatically unblocks (so must blocks beforehand, or else undefined) & wait for 1 or more signals. Signal mask is restored before return.
sigwait()
ing the same signal only 1 one of them will return at deliverySignal dispositions/actions: per-process, and any thread can modify them
pthread_kill(thread, signo)
: send a signal signo
to speicified threadSince file descriptors are shared between threads, cocurrent access to the same fd cause race condition use p[read/write]()
Thread-safe: can be safely called by multiple threads concurrently
Reentrant: can be recursively called
A busy-waiting lock alternative. (Acquiring mutex is blocked by sleeping)
Address Space (per-process): an array of contiguous byte-size VAs.
Memory Mapping:
MAX_ADDR
]) are either mapped/unmappedfork()
exec()
Memory Allocation: malloc()
, realloc()
, free()
-> (de)allocates memory from the heap making NO system call
sbrk()
sys call: to expand/contract the heapmmap()
: creates a new memory region (i.e. a new mapping) in the address space of caller
fork()
A memory-mapped file: a memory buffer created by
mmap()
that maps to a file, from/to which reading/writing corresponds to the same bytes of the file
MAP_FAILED
addr
: specify the starting address
len
> 0; off
is offsetfd
is fd of the file/device; closing it does not unmap the regionprot
: specify the memory protection (PROT_[READ/WRITE/EXEC/NONE]
); SIGSEGV
generated if invalid accesses
mprotect()
can modify the permissions laterflags
:
MAP_SHARED
: a shared mapping; updates to this mapping is visible to other processes and carried through to the respective fileMAP_PRIVATE
: a private COW mapping; updates to this mapping is invisible to other processes and not carried through to the respective fileMAP_FIXED
: create exactly at addr
, or else failedMAP_ANONYMOUS
: mapping is backed by memory and fd
should be -1; contents of the mapping are init to 0.
/dev/zero
munmap()
: unmap memory-mapped region. The contents of the MMR will not be write back to the file on the disk.source program/code -> machine instructions/code and data, packaged into an object file
Format (linux): ELF (Executable and Linkable Format)
.symtab
: a symbol table about functions and global vars
.rel.text
: relocation entries for code. A list of locations in .text
that need to be modified during linking, eg. calling an external function, referencing global vars.rel.data
: relocation entries for data. Info for any global vars that are referenced or defined by the OFTypes
Pros: Code maintainance, splitting big programs into smaller modules.
.a
ar
can be used to create a static libLinker associates each symbol ref with one def by searching in the symbol table, and copies from static libraries (only the OFs that are referenced by program).
.rel
and updates the symbol ref in program to correct runtime addrCons of Static Linking:
- Static libraries requires maintenance and update -> relink every time it is updated!!
- Libraries are copied to executables -> bigger size
.so
, i.e. shared OFs)Linking | Dynamic | Static |
---|---|---|
Pros | Size efficient, simple maintenance | Less runtime overhead |
_start()
, which calls main()
laterexec()
invokes kernel to load respective program into PAS