fork()
copies the PCB, creates a new (duplicate) processexec()
replaces the PCB of the running process with a new one to run a different program, overwrites the code segment of the old process and re-initailizes heap and stackbrk()
or sbrk()
: change the (virtual) end marker of the heap for this process (in essence, allocate more virtual memory to the heap)mmap()
: allocate virtual memory backed either
read()
: faster for random-accessmsync(2)
futex_wait(mutex, v)
puts the thread to sleep and adds it to the queuefutex_wake(mutex)
wakes a sleeping thread in the queue
void lock(int *mutex) {
if (atomic_bit_test_set(mutex, 31) == 0) return;
atomic_increment(mutex);
while true {
if (atomic_bit_test_set(mutex, 31) == 0) {
atomic_decrement(mutex);
return;
}
int v = *mutex;
if (v >= 0) continue;
futex_wait(mutex, v);
}
}
From https://stackoverflow.com/a/4742791/6327671 :
A common implementation approach for a mutex is to use a flag and a queue. The flag indicates whether the mutex is held by anyone (a single-count semaphore would work too), and the queue tracks which threads are in line waiting to acquire the mutex exclusively.
A condition variable is then implemented as another queue bolted onto that mutex. Threads that got in line to wait to acquire the mutex can—usually once they have acquired it—volunteer to get out of the front of the line and get into the condition queue instead. At this point, you have two separate sets of waiters:
- Those waiting to acquire the mutex exclusively
- Those waiting for the condition variable to be signaled
When a thread holding the mutex exclusively signals the condition variable, for which we’ll assume for now that it’s a singular signal (unleashing no more than one waiting thread) and not a broadcast (unleashing all the waiting threads), the first thread in the condition variable queue gets shunted back over into the front (usually) of the mutex queue. Once the thread currently holding the mutex—usually the thread that signaled the condition variable—relinquishes the mutex, the next thread in the mutex queue can acquire it. That next thread in line will have been the one that was at the head of the condition variable queue.
There are many complicated details that come into play, but this sketch should give you a feel for the structures and operations in play.
wait
(decrementing) acquires a mutex and waits on a condition variable if the value is not positivepost
(incrementing) acquires a mutex, increments the value, signals the condition variables, and releases the mutexstruct semaphore {
int value;
mutex m;
condition_variable cv;
};
void init(semaphore *s, int value) {
s->value = value;
init mutex s->m;
init condition variable s->cv;
}
// only decrement when positive
void wait(semaphore *s) {
acquire mutex
while value <= 0:
cv wait
decrement value
release mutex
}
void post(semaphore *s) {
acquire mutex
increment value
signal cv
release mutex
}
condition_variable empty, fill;
mutex m;
size = 0
void consume() {
lock mutex
while size == 0:
wait(fill)
get element from buffer
size -= 1
signal(empty)
unlock mutex
}
void produce() {
lock mutex
while size == FULL:
wait(empty)
put element in buffer
size += 1
signal(fill)
unlock mutex
}
trylock()
: if we fail to acquire the lock then don’t sleep but return early (and presumably release the locks we were holding)open()
, which:
FILE *
in the process’ file table (located in the PCB), returning a file descriptor (the index into this file table)FILE *
points to a file struct, which contains, among others, read/write flags, position in the file, and a pointer to the inodefork()
execve()
exit()
atexit()
exit()
system call which makes the kernel clean up resources and set the exit status in the PCBwait()
open()
struct file
) in the kernel-level open-file table, adds an entry to the per-process file table struct file *
, and returns a file descriptor (the index into this per-process table)close()
read()
write()
read()
pipe()
dup()
dup2()
stat()
and fstat()
mmap()
and munmap()
brk()
sbrk()
brk()
, but increments the current end rather than setting it to a specified valueioctl()
socket()
bind()
listen()
accept()
connect()
select()
select()
returns a value indicating a socket is ready, or none are ready. the ready sockets can then be idenfitied by a structure passed into select()
(which told select()
which sockets we wanted to listen to)poll()
select()
but easier to use: can monitor an unlimited number of socketsepoll()
poll()
that uses events, waits for incoming events rather than scanning all watched socket fdskill()
signal()
sigaction()
signal()
, but provides more controls and optionsA | B
: process is created, a pipe is opened and forked into A
and A'
: before A'
is exec()
‘d into B
, we close the STDOUT of A
and the STDIN of B
and replace them respectively with the write and read ends of the pipe####
constexpr
/consteval
consteval
forces a function to be evaluated at compile timeconstexpr
implies inline, but not necessarily conststruct hashtable {
list<payload> list;
array<payload> table;
}
table[hash]
points to the node indicating the start of its bucket