$ cd c_intro
$ cmake .
-- The C compiler identification is GNU 9.3.0
-- The CXX compiler identification is GNU 9.3.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
>> Linux
-- Configuring done
-- Generating done
-- Build files have been written to: /home/gkt/Work/systems-code-examples/c_intro
Run make to crate the executable:
$ make
Scanning dependencies of target c-intro-demo
[ 20%] Building CXX object CMakeFiles/c-intro-demo.dir/main.cc.o
[ 40%] Building CXX object CMakeFiles/c-intro-demo.dir/debug.cc.o
[ 60%] Building CXX object CMakeFiles/c-intro-demo.dir/list.cc.o
[ 80%] Building CXX object CMakeFiles/c-intro-demo.dir/tests.cc.o
[100%] Linking CXX executable bin/c-intro-demo
[100%] Built target c-intro-demo
Note that for all of our examples, the output executable appears in the bin subdirectory.
Note also that almost all of our examples use cmake and make as shown here.
Run the layout shell script, which shows the size of the text, data, and bss in bytes:
loader allocates memory for executable’s text, data, bss, heap, and
stack. and loads program’s image into memory
loader gets information from OS where shared libraries are already
allocated in memory and loads the ones that are not already loaded.
each shared library has its own text, data, and bss
loader goes through executable and adjusts the list of external
symbols to point to the correct spots in memory (to shared libraries)
try the nm command to see the symbols in a compiled object/executable.
once program is ready, loader invokes _start() method
_start() calls _init() for each shared library
_start() initializes static constructors of objects defined as
global variables
Memory references in shared libraries are position independent (GCC
-fpic or -fPIC flagsA. Newer GCC makes PIC defualt. Use -fno-pic
if you notice this.
Linker must resolve all of these position independent memory accesses
to local accesses. This is accomplished by writing the GOT for each
linked process.
The need for the memory addresses to be position independent is
because the offset that a shared library will be loaded will differ
between executions of the same program and among other programs.
Also, the same loaded shared library will be shared with other
processes without reloading. So, the same library may have different
offsets for different programs
Generate position-independent code (PIC) suitable for use in a shared
library, if supported for the target machine. Such code accesses all
constant addresses through a global offset table (GOT). The dynamic
loader resolves the GOT entries when the program starts (the dynamic
loader is not part of GCC; it is part of the operating system). If the
GOT size for the linked executable exceeds a machine-specific maximum
size, you get an error message from the linker indicating that -fpic
does not work; in that case, recompile with -fPIC instead. (These
maximums are 8k on the SPARC and 32k on the m68k and RS/6000. The 386
has no such limit.)
Reduced memory footprint. If two programs load the same shared
library, the .text segment is reused across processes thanks to the
GOT
Weaknesses
Requires a more advanced virtual memory implementation in the
operating system. Sometimes not practical for simple or embedded
systems
Requires more advanced compiler code generators. Different processors
have special features regarding memory offset registers or function
table size limitations.
When deploying software, dependencies are less of a concern (e.g.
missing dependencies, incorrectly upgraded dependencies, custom
patches and alterations to shared code)
Versioning and path problems are less of a concern
Code obfuscation can obfuscate across object files
Compiler optimizers can optimize across object files
In modern operating systems with virtual memory and privileged
separation the following protections are afforded:
One process cannot read the memory of another process (except when
explicitly permitted)
A process can fully manage the memory that it can access - garbage
collection, explicit allocation/deallocation, method call and
parameter passing standards, stack management, etc.
A crash, exception, resource starvation, deadlock, or other fault in
one process does not directly affect other processes
While mapped to the same address space, the process cannot modify
kernel memory or memory otherwise protected by the operating system
(such as text pages).
fork() creates a new process by duplicating the calling process.
The new process, referred to as the child, is an exact duplicate of the calling process,
referred to as the parent, except for the following points:
the child has its own unique process id (PID)
the child’s parent PID is the same as the parent’s PID
the parent’s threads are not recreated on the child
interesting point: in Linux, fork()!=fork(); fork() calls clone()
See systems-code-examples/wait if you want to run this program.
1#include<sys/types.h> 2#include<sys/wait.h> 3#include<stdio.h> 4#include<stdlib.h> 5#include<unistd.h> 6 7 8intmain(intargc,char*argv[]) 9{10pid_tpid=fork();11if(pid==0)12{13abort();//child process exits14}15intstatus;16wait(&status);// wait for child to exit17if(WIFEXITED(status))18{19printf("normal exit. exit code = %d\n",WEXITSTATUS(status));20}21elseif(WIFSIGNALED(status))22{23printf("abnormal termination, signal number = %d\n",WTERMSIG(status));24}25elseif(WIFSTOPPED(status))26{27printf("child stopped, signal number = %d\n",WSTOPSIG(status));28}29}3031