Systems software design Processes, threads and operating system resources
Who are we? Krzysztof Kąkol Software Developer Jarosław Świniarski Software Developer Presentation based on materials prepared by Andrzej Ciarkowski, M.Sc., Eng. 2
Outline Processes Process properties Multitasking Scheduling Forking Threads Thread properties Threads vs. processes Synchronization Operating system resources Operating systems role in managing resources Unix everything is file philosophy OS services and resources file access APIs memory management
What is a process? An instance of computer program being executed A program is a collection of the instructions, a process is actual execution of these instructions There may be multiple process instances for the same program, each executing different code path
Process resources Each process owns its resources, like: Image of the executable machine code (may be shared by other program instances, but in rare cases where image is mutable, it is being copied), contained within Identifier (process id, PID) An isolated region of virtual memory Operating system resources (e.g. file handles, timers, synchronization primitives, device handles ) Security attributes (id of process owner, set of permissions) Current state of the processor (execution context) set of registers, mapping of virtual memory to physical addresses Priority needed for scheduling
Process memory Each process runs within isolated address space from the others (AKA sandbox) Process address space includes process-specific data structures call stacks of all process threads a heap A stack is a fragment of memory which stores information about the active subroutines, specific to each thread A heap (free store) is a memory region shared between threads used for dynamic memory allocation
Multitasking Most modern operating systems appear to run multiple processes (tasks) simultaneously, even when run on single-core (singlethreaded) CPU This is achieved by time-sharing each task (process/thread) receives is given a small period of processor s time, after which it is preempted and a context switch is made to another task With rapid context switches and small enough time slots the tasks seem to the user to be running in parallel
Scheduler A part of operating system kernel Algorithm which decides which task should be run next based on task priorities, execution history, I/O state and many other factors The scheduler may be tuned for best Throughput Latency Fairness Deadlines
Creating a new process Windows Linux/POSIX See man 3 exec Actually, exec() replaces existing process address space with a new one So you ll need fork() too
Forking Unique feature to Unix/POSIX systems is a fork() system call See man 2 fork fork() creates a new process by duplicating the calling process The child process is identical to the parent, including most of the open system resources whose handles are inherited The inheritance of the system resources & memory contents makes for esp. easy IPC between parent and child process, which historically was the reason Unix systems lacked multithreading and used forking for the same purpose
How it works?
Outline Processes Threads Thread properties Threads vs. processes Synchronization Operating system resources
Multithreading Modern operating systems support the notion of multithreading multiple concurrent (simultaneous) execution paths within a single process Threads share the parent process resources (memory, descriptors/handles), but have their own execution stacks (and sometimes security attributes) Since threads share common address space, they can easily communicate with each other using ordinary programming language constructs
Thread properties thread of execution running path of code instructions (smallest sequence of instructions that can be managed independently by a scheduler) Each process has at least 1 thread (main thread, program loop) Additional threads may be created on demand to perform some specific tasks (worker threads) Libraries & system may create additional threads which are invisible to the user but make the application multithreaded
Threads vs. processes Processes are independent, threads exist as subsets of a process Processes have more state configuration than threads, threads share process state, memory and resources Processes have separate address space, threads share address space Processes interact through systemprovided IPC mechanisms Context switches between threads in same process is faster than between processes
Multiple threads vs multiple processes Threads Threads are considered lightweight processes - the amount of information required to process when creating or destroying a thread within existing process is much smaller than it is when creating a new process Their ability to work on the same shared data makes them generally cheaper with regards to system resources than processes Inter-thread communication is easier than inter-process communication Processes Faulty thread can crash entire process, processes are more robust There are techniques which allow to share a region of memory between processes the same (but it s not as natural as with threads) There are techniques which allow creating new processes as cheaply as threads (fork, copy-on-write)
Threading what for? Responsiveness perform long, blocking operations in the worker threads so that the application remains responsive to user input and doesn t look frozen Non-blocking I/O may achieve the same without multiple threads but is more error-prone, harder & less natural in use Performance on multicore systems multiple threads allow to achieve the result faster by partitioning the work into parts executed in parallel on separate cores Throughput multithreaded application can better utilize the system by performing work in some threads when other are blocked waiting for I/O to complete
Multithreading - dangers Synchronization multiple threads may modify the same data concurrently, leading to unexpected behavior Race condition software depends on the sequence or timing of threads to operate correctly. Without proper synchronization of threads, the timing may be nondeterministic Deadlock improper use of synchronization objects may lead to a state when thread A acquired resource a and waits for thread B to release resource b, while thread B acquired resource b and waits for thread A to release resource a Stability faulty thread crashes the entire process
Race conditions Expected Possible These situations cause very difficult to diagnose bugs (Heisenbugs) Solution is to use mutual exclusion (or other synchronization objects, eg. Monitor) or atomic operations
Deadlock You need to maintain the same order of resources being acquired in all threads or use safe patterns eg. monitor
Synchronization primitives Mutex Semaphore Condition variable Monitor (actually not a primitive) Barrier Read/Write Lock Event (Windows)
Mutex MUTual EXclusion object Basic tool for creating critical sections Only one thread at a time may acquire ownership of a mutex Other threads trying to acquire ownership will wait (or try waiting) until first thread releases ownership Operations: acquire (lock) release (unlock) try acquire (try lock) returns boolean value whether ownership was actually acquired
Mutex
Scoped lock idiom (C++) Using standard lock()/unlock() API is unsafe/uncomfortable in presence of exceptions It s best to use additional scoped lock class which will acquire the lock in constructor and release it in destructor, so even when exception is thrown, the lock will be released preventing the deadlock
Semaphore A synchronization object which maintains its lock count (and may optionally have maximum lock count) Increase lock count with signal operation (raise the semaphore) Decrease lock count with wait operation; waiting on semaphore with zero lock count will block until semaphore is raised There s no notion of semaphore owner any thread can signal or wait on semaphore Mutex is a special case of semaphore which maximum lock count of 1 and restriction that only the thread which acquired lock (succeeded in wait) may signal it Semaphores are not as easy to understand & use correctly as mutexes, so try to avoid them until you know what you re doing ;)
Barrier Barrier (or rendezvous point) is a place in code where threads in a group are blocked and can not proceed until all of them reached the barrier Barrier enforces synchronization of threads useful in high-performance computing & number crunching
Read/Write lock Also called shared mutex A special kind of mutex which allows many threads to simultaneously perform read operation but exclusive write access May provide performance gain in a scenario where the write operation occurs rarely but read operations are common Natively supported on POSIX, not on Windows How it works Writing will cause to block all other writers and readers Writer will wait until all readers have finished Special operation: upgrading rwlock from read mode to write mode Lots of readers may lead to starvation of writers!
Outline Processes Threads Operating system resources operating systems role in managing resources Unix everything is file philosophy OS services and resources file access APIs memory management
Role of the OS in managing system resources OS is the environment for running and controlling user s tasks It takes care of managing computer s resources planning and scheduling CPU time allocation allocating memory to the tasks provides IPC mechanisms controls the hardware and provides interference-free access to concurrent tasks manages filesystems
Role of the OS in managing system resources Allocation of resources Creating and deleting resource descriptors Realization of requests for allocation and release of resources Synchronizing access to resources (prevention of interference) Authorization of access to resources (through system privileges) Recovery of released resources Accounting (collection of statistical data on the use of resources)
Managed resources processes CPU time synchronization objects and IPC operating memory memory allocation for processes memory protection files create and delete files and directories read and write operations input / output devices storage media
Unix philosophy everything is file Almost all Unix system resources are represented by the files existing in the file system This allows to consistently give permissions to users and processes to these resources and manage their access The same set of tools can be used with many types of resources Access to resources can be achieved by opening the file descriptor representing the device in the file system Files representing devices can exist in the physical file system (on a disk) or in a virtual file systems mounted to the actual disk location (eg. /proc)
everything is file resources which are represented by files the actual files and directories sockets and streams I/O devices (device drivers): /dev filesystem character devices (unbuffered access) block devices (buffered access) pseudo-device (eg. /dev/null, /dev/random - the random number generator) synchronization objects (semaphores, mutexes,...) shared memory (shmfs) CPU, physical memory, processes, timers,... (procfs filesystem) many system calls are possible to achieve as a read/write of the specific file
File access APIs Operating systems provide an abstraction layer, which allows access to the files in various file systems (local, network, virtual, portable, removable...) to take place in the same way The differences in the implementation of the operations of various file systems are operated by the driver in a manner transparent to the user, using a single set of system calls The operating system provides a unified, standardized programming interface (API)
Typical file operations Opening and closing files (create file descriptors or handles for the objects in the file system) file descriptor - abstract identifier (number), usually an index into an array of descriptors managed by the OS for a given process Read, write and file position File metadata management (time of last modification, tags, etc) Directory management Defining the principles of sharing open files between processes, blocking File permissions Encryption
File API layers operating systems, programming languages and libraries provide a layered model of files access, providing abstraction of hardware, platform, programming language UNIX Windows C++ standard library C++97 iostream library (fstream class) C++97 iostream library (fstream class) C standard library C89 stdio library (FILE pointer) C89 stdio library (FILE pointer) emulated POSIX file API (int file descriptor) Native OS API Unix/POSIX file API (int file descriptor) Win32 file API (HANDLE)
Low-level file access APIs POSIX native API: open()/creat() - return the file descriptor, table of descriptors managed by the OS low-level file operations lack of library-level caching - each operation on the descriptor is a separate system call buffering on the OS cache level Windows native API: CreateFile() open()/creat() - implemented in the standard library based on the native API, table of descriptors is managed by the library 1:1 mapping between the emulated POSIX APIs and Win32 system callse more control over the permissions and file locking using Win32 API POSIX API emulation for compatibility with C89 standard library
Buffered I/O API layer Standard C89 Library builds buffered I/O layer on top of low-level POSIX API, based on FILE structure buffering to avoid (costly) system call for each basic I/O operation data is read/written in larger blocks reading/writing individual characters does not involve frequent system calls - performance gains FILE structure contains the information (book keeping) on the properties of an open file descriptor It is possible to obtain a file descriptor from a FILE (function fileno()), but the position of the "descriptor" is different than in the FILE* stream
Comparing operations - open operation type characteristics open()/creat() int (file descriptor) native POSIX API, unbuffered, low-level I/O CreateFile() HANDLE native Win32 API, unbuffered, low-level I/O fopen(), _wfopen(), freopen() FILE* buffered stream API provided by the C stdio library based on the low-level API fdopen() FILE* create buffered I/O based on open file descriptor
Basic I/O operation type characteristics read()/write() int (file descriptor) native POSIX API ReadFile()/WriteFile() HANDLE native API Win32 ReadFileEx()/ WriteFileEx() HANDLE fread(),fwrite() FILE* buffered C stdio API native API Win32 allows for asynchronous I/O (in the background) fflush() FILE* buffered C stdio API commiting file buffers to the disk (synchronization)
Character-based I/O operation type characteristics getc() FILE* buffered C stdio API read next char from the stream putc() FILE* buffered C stdio API write single char to the stream ungetc() FILE* buffered C stdio API undo read of last char
Creating and deleting directories and files operation type characteristics CreateDirectory()/ CreateDirectoryEx() RemoveDirectory() DeleteFile() mkdir() rmdir() unlink() remove() creates directory at specified path (Win32) deletes existing (empty) directory deletes file creates directory at specified path (POSIX) deletes existing (empty) directory deletes file name from the filesystem, the actual file will be removed after its last open descriptor is closed deletes file or directory calls unlink() for files, rmdir() for directories
Memory management process when creating receives from the OS an area of "free memory", which is commonly called the "arena" arena is managed by the process memory allocator, which is implemented by the programming language/standard library process allocator is used to allocate memory for: stacks of threads of the process objects and structures created in the free memory ("on the heap") the size of the arena can be enlarged with the growing needs of the process this is done in a manner transparent to the user process allocator calls the operating system functions for increasing the arena
Process memory allocator Interface of the process allocator provided by the C standard library consists of: malloc() - allocate a continuous memory region with a given size from the heap calloc() - allocate contiguous memory for an N-element vector of objects of a given size realloc() - changes the size of the previously allocated memory block, perhaps moving it to another location free() - frees up memory allocated by malloc(), calloc() or realloc() On the basis of the above APIs are created more advanced memory allocation mechanisms - Garbage collectors, C ++ allocator (new / delete)
Process allocator behavior Memory allocated by means of an the process allocator remains occupied until you call free() If memory is not released, "leak occurs block of memory is lost until the end of the process Allocation of many small blocks leads to memory fragmentation, which reduces the efficiency of allocator with the course of the program
Questions? 10.04.2017 46
Krzysiek Jarek kkakol@pgs-soft.com jswiniarski@pgs-soft.com www.pgs-soft.com