Chapter 7 Linking Topics Static linking Object files
Chapter 7: Linking ¢ Topics § § § Static linking Object files Static libraries Loading Dynamic linking of shared libraries
C program: foo. c Steps to Starting a Program (translation) Compiler Assembly program: foo. s Assembler Object (mach lang module): foo. o Linker Executable (mach lang pgm): foo. exe Loader Memory lib. o
Assembler ¢ ¢ ¢ Input: Assembly Language Code (e. g. , foo. s) Output: Object Code, information tables (e. g. , foo. o) Reads and Uses Directives Produce Machine Language Creates Object File
Assembler Directives (Appendix) ¢ Give directions to assembler, but do not produce machine instructions. text: Subsequent items put in user text segment (machine code). data: Subsequent items put in user data segment (binary rep of data in source file). symtab: declares sym global and can be referenced from other files
Example C Program main. c swap. c int buf[2] = {1, 2}; extern int buf[]; int main() { swap(); return 0; } static int *bufp 0 = &buf[0]; static int *bufp 1; void swap() { int temp; } bufp 1 = &buf[1]; temp = *bufp 0; *bufp 0 = *bufp 1; *bufp 1 = temp;
Static Linking ¢ Programs are translated and linked using a compiler driver: unix> gcc -O 2 -g -o p main. c swap. c unix>. /p main. c swap. c Translators (cpp, cc 1, as) Source files Translators (cpp, cc 1, as) main. o swap. o Separately compiled relocatable object files Linker (ld) p Fully linked executable object file (contains code and data for all functions defined in main. c and swap. c
Why Linkers? Modularity! ¢ ¢ Program can be written as a collection of smaller source files, rather than one monolithic mass. Can build libraries of common functions (more on this later) § e. g. , Math library, standard C library
Why Linkers? Efficiency! ¢ Time: Separate Compilation § Change one source file, compile, and then relink. § No need to recompile other source files. ¢ Space: Libraries § Common functions can be aggregated into a single file. . . § Yet executable files and running memory images contain only code for the functions they actually use.
Three Kinds of Object Files (Modules) ¢ Relocatable object file (. o file) § Contains code and data in a form that can be combined with other relocatable object files to form executable object file. § Each. o file is produced from exactly one source (. c) file ¢ Executable object file § Contains code and data in a form that can be copied directly into memory and then executed. ¢ Shared object file (. so file) § Special type of relocatable object file that can be loaded into memory and linked dynamically, at either load time or run-time. § Called Dynamic Link Libraries (DLLs) by Windows
Executable and Linkable Format (ELF) ¢ ¢ Standard binary format for object files Originally proposed by AT&T System V Unix § Later adopted by BSD Unix variants and Linux ¢ One unified format for § Relocatable object files (. o), § Executable object files § Shared object files (. so) ¢ Generic name: ELF binaries
ELF Object File Format ¢ Elf header § Word size, byte ordering, file type (. o, exec, . so), machine type, etc. ¢ Segment header table § Page size, virtual addresses for memory segments (sections), segment sizes. ¢ . text section § Code ¢ . rodata section ¢ § Read only data: jump tables, format strings, . . data section § Initialized global variables ¢ . bss section § Uninitialized global variables § “Block Started by Symbol” § “Better Save Space” § Has section header but occupies no space ELF header Segment header table (required for executables). text section. rodata section. bss section. symtab section. rel. txt section. rel. data section. debug section Section header table 0
ELF Object File Format (cont. ) ¢ ¢ . symtab section § Symbol table § Procedure and static variable names § Section names and locations. rel. text section § Relocation info for. text section § Addresses of instructions that will need to be modified in the executable § Instructions for modifying ¢ . rel. data section § Relocation info for. data section § Addresses of pointer data that will need to be modified in the merged executable ¢ ¢ ELF header Segment header table (required for executables). text section. rodata section. bss section. symtab section. rel. txt section . debug section § Info for symbolic debugging (gcc -g) . rel. data section Section header table § Offsets and sizes of each section Section header table . debug section 0
Linker Symbols ¢ Global symbols § Symbols defined by module m that can be referenced by other modules. § E. g. : non-static C functions and non-static global variables. ¢ External symbols § Global symbols that are referenced by module m but defined by some other module. ¢ Local symbols § Symbols that are defined and referenced exclusively by module m. § E. g. : C functions and variables defined with the static attribute. § Local linker symbols are not local program variables
What Do Linkers Do? ¢ Step 1: Symbol resolution § Programs define and reference symbols (variables and functions): void swap() {…} swap(); int *xp = &x; /* define symbol swap */ /* reference symbol swap */ /* define xp, reference x */ § Symbol definitions are stored (by compiler) in symbol table. Symbol table is an array of structs § Each entry includes name, type, size, and location of symbol. § § Linker associates each symbol reference with exactly one symbol definition.
Resolving Symbols Global External Local int buf[2] = {1, 2}; extern int buf[]; int main() { swap(); return 0; } static int *bufp 0 = &buf[0]; static int *bufp 1; External main. c void swap() { int temp; Linker knows nothing of temp } Global bufp 1 = &buf[1]; temp = *bufp 0; *bufp 0 = *bufp 1; *bufp 1 = temp; swap. c
What Do Linkers Do? (cont. ) ¢ Step 2: Relocation § Merges separate code and data sections into single sections § Relocates symbols from their relative locations in the. o files to their final absolute memory locations in the executable. § Updates all references to these symbols to reflect their new positions.
Relocating Code and Data Relocatable Object Files System code . text System data Executable Object File 0 Headers System code main() main. o swap() main() . text int buf[2]={1, 2} . data More system code . text System data int buf[2]={1, 2} int *bufp 0=&buf[0] Uninitialized data. symtab. debug swap. o swap() int *bufp 0=&buf[0]. data int *bufp 1. bss . text . data. bss
Relocation Info (main) main. c int buf[2] = {1, 2}; int main() { swap(); return 0; } main. o 0000000 <main>: 0: 55 1: 89 e 5 3: 83 ec 08 6: e 8 fc ff ff ff b: d: f: 10: 31 c 0 89 ec 5 d c 3 push %ebp mov %esp, %ebp sub $0 x 8, %esp call 7 <main+0 x 7> 7: R_386_PC 32 swap xor %eax, %eax mov %ebp, %esp pop %ebp ret Disassembly of section. data: 0000 <buf>: 0: 01 00 00 00 02 00 00 00 Relocation information from. rel. text Source: objdump (objdump displays within disassembled code for convenience)
Relocation Info (swap, . text) swap. c swap. o extern int buf[]; Disassembly of section. text: static int *bufp 0 = &buf[0]; static int *bufp 1; 0000 <swap>: 0: 55 1: 8 b 15 00 00 void swap() { int temp; } bufp 1 = &buf[1]; temp = *bufp 0; *bufp 0 = *bufp 1; *bufp 1 = temp; 7: c: e: 15: 18: 1 a: 1 c: 1 e: 23: 25: 26: push %ebp mov 0 x 0, %edx 3: R_386_32 bufp 0 a 1 00 00 mov 0 x 0, %eax 8: R_386_32 buf 89 e 5 mov %esp, %ebp c 7 05 00 00 04 movl $0 x 4, 0 x 0 00 00 00 10: R_386_32 bufp 1 14: R_386_32 buf 89 ec mov %ebp, %esp 8 b 0 a mov (%edx), %ecx 89 02 mov %eax, (%edx) a 1 00 00 mov 0 x 0, %eax 1 f: R_386_32 bufp 1 89 08 mov %ecx, (%eax) 5 d pop %ebp c 3 ret
Relocation Info (swap, . data) swap. c extern int buf[]; Disassembly of section. data: static int *bufp 0 = &buf[0]; static int *bufp 1; 0000 <bufp 0>: 0: 00 00 void swap() { int temp; } bufp 1 = &buf[1]; temp = *bufp 0; *bufp 0 = *bufp 1; *bufp 1 = temp; 0: R_386_32 buf
Executable After Relocation (. text) 080483 b 4 <main>: 80483 b 4: 55 80483 b 5: 89 80483 b 7: 83 80483 ba: e 8 80483 bf: 31 80483 c 1: 89 80483 c 3: 5 d 80483 c 4: c 3 080483 c 8 <swap>: 80483 c 8: 55 80483 c 9: 8 b 80483 cf: a 1 80483 d 4: 89 80483 d 6: c 7 80483 dd: 94 80483 e 0: 89 80483 e 2: 8 b 80483 e 4: 89 80483 e 6: a 1 80483 eb: 89 80483 ed: 5 d 80483 ee: c 3 e 5 offset to ec 08 swap() 09 00 00 00 c 0 ec 15 58 e 5 05 04 ec 0 a 02 48 08 bufp 0 5 c 94 04 08 buf[] bufp 1 48 95 04 08 58 08 buf[1] 95 04 08 bufp 1 push mov sub call xor mov pop ret %ebp %esp, %ebp $0 x 8, %esp 80483 c 8 <swap> %eax, %eax %ebp, %esp %ebp push mov movl %ebp 0 x 804945 c, %edx 0 x 8049458, %eax %esp, %ebp $0 x 8049458, 0 x 8049548 mov mov mov pop ret %ebp, %esp (%edx), %ecx %eax, (%edx) 0 x 8049548, %eax %ecx, (%eax) %ebp
Executable After Relocation (. data) Disassembly of section. data: 08049454 <buf>: 8049454: 01 00 00 00 02 00 00 00 0804945 c <bufp 0>: 804945 c: 54 94 04 08
Strong and Weak Symbols ¢ Program symbols are either strong or weak § Strong: procedures and initialized globals § Weak: uninitialized globals p 1. c p 2. c strong int foo=5; int foo; weak strong p 1() { } p 2() { } strong
Linker’s Symbol Rules ¢ Rule 1: Multiple strong symbols are not allowed § Each item can be defined only once § Otherwise: Linker error ¢ Rule 2: Given a strong symbol and multiple weak symbol, choose the strong symbol § References to the weak symbol resolve to the strong symbol ¢ Rule 3: If multiple weak symbols exist, pick an arbitrary one § Can override this with gcc –fno-common
Linker Puzzles int x; p 1() {} Link time error: two strong symbols (p 1) int x; p 1() {} int x; p 2() {} References to x will refer to the same uninitialized int. Is this what you really want? int x; int y; p 1() {} double x; p 2() {} Writes to x in p 2 might overwrite y! Evil! int x=7; int y=5; p 1() {} double x; p 2() {} Writes to x in p 2 will overwrite y! Nasty! int x=7; p 1() {} int x; p 2() {} References to x will refer to the same initialized variable. Nightmare scenario: two identical weak structs, compiled by different compilers with different alignment rules.
Example /* file 1. c */ #include <stdio. h> int x=0; int y; int p 1() { x = 7; } main() { p 1(); y = x>>1; printf("x: %d, y: %dn", x, y); p 2(); printf("x: %d, y: %dn", x, y); } /* file 2. c */ double x; int p 2() { x = 21913. 57895; } paprika>gcc -O 2 file 1. c file 2. c /usr/bin/ld: Warning: alignment 4 of symbol `x' in /tmp/cc. Mj. CLY 9. o is smaller than 8 in /tmp/cc. CBTGnd. o paprika>a. out x: 7, y: 3 x: 226774273, y: 1087727205 paprika> generated 20 March 2009 n n n The good news: ld gave a warning The bad news: code compiled, ran, and overwrote y Lessons: l Pay attention to compiler and linker warnings l Use “static” to limit scope of variable and avoid name conflicts l System dependent: doesn’t overwrite y under Mac. OS; memory layout differs
Global Variables ¢ Avoid if you can ¢ Otherwise § Use static if you can § Initialize if you define a global variable § Use extern if you use external global variable
Packaging Commonly Used Functions ¢ How to package functions commonly used by programmers? § Math, I/O, memory management, string manipulation, etc. ¢ Awkward, given the linker framework so far: § Option 1: Put all functions into a single source file Programmers link big object file into their programs § Space and time inefficient § Option 2: Put each function in a separate source file § Programmers explicitly link appropriate binaries into their programs § More efficient, but burdensome on the programmer §
Solution: Static Libraries ¢ Static libraries (. a archive files) § Concatenate related relocatable object files into a single file with an index (called an archive). § Enhance linker so that it tries to resolve unresolved external references by looking for the symbols in one or more archives. § If an archive member file resolves reference, link into executable.
Creating Static Libraries atoi. c printf. c Translator atoi. o printf. o Archiver (ar) libc. a ¢ ¢ random. c. . . Translator random. o unix> ar rs libc. a atoi. o printf. o … random. o C standard library Archiver allows incremental updates Recompile function that changes and replace. o file in archive.
Commonly Used Libraries libc. a (the C standard library) § 8 MB archive of 900 object files. § I/O, memory allocation, signal handling, string handling, data and time, random numbers, integer math libm. a (the C math library) § 1 MB archive of 226 object files. § floating point math (sin, cos, tan, log, exp, sqrt, …) % ar -t /usr/libc. a | sort … fork. o … fprintf. o fpu_control. o fputc. o freopen. o fscanf. o fseek. o fstab. o … % ar -t /usr/libm. a | sort … e_acos. o e_acosf. o e_acoshf. o e_acoshl. o e_acosl. o e_asinf. o e_asinl. o …
Linking with Static Libraries addvec. o multvec. o main 2. c vector. h Translators (cpp, cc 1, as) Relocatable object files main 2. o Archiver (ar) libvector. a addvec. o libc. a printf. o and any other modules called by printf. o Linker (ld) p 2 Static libraries Fully linked executable object file
Using Static Libraries ¢ Linker’s algorithm for resolving external references: § Scan. o files and. a files in the command line order. § During the scan, keep a list of the current unresolved references. § As each new. o or. a file, obj, is encountered, try to resolve each unresolved reference in the list against the symbols defined in obj. § If any entries in the unresolved list at end of scan, then error. ¢ Problem: § Command line order matters! § Moral: put libraries at the end of the command line. unix> gcc -L. libtest. o -lmine unix> gcc -L. -lmine libtest. o: In function `main': libtest. o(. text+0 x 4): undefined reference to `libfun' -L: add directory to search list for libraries -lmine: searchive“mine. a”
Loading Executable Object Files Executable Object File ELF header 0 Kernel virtual memory 0 xc 0000000 Program header table (required for executables) User stack (created at runtime) . init section. text section. rodata section Memory invisible to user code %esp (stack pointer) Memory-mapped region for shared libraries 0 x 40000000 . data section. bss section Run-time heap (created by malloc) . symtab. debug Read/write segment (. data, . bss) . line. strtab Section header table (required for relocatables) Read-only segment (. init, . text, . rodata) 0 x 08048000 0 Unused brk Loaded from the executable file
Shared Libraries ¢ Static libraries have the following disadvantages: § Duplication in the stored executables (e. g. many call printf) § Duplication in the running executables § Minor bug fixes in system libraries require each application to explicitly relink ¢ Modern solution: shared libraries § Object files that contain code and data that are loaded and linked into an application dynamically, at either load-time or run-time § Also called: dynamic link libraries (DLLs), . so files
Shared Libraries (cont. ) ¢ Dynamic linking can occur when executable is first loaded and run (load-time linking). § Common case for Linux, handled automatically by the dynamic linker (ld-linux. so). § Standard C library (libc. so) usually dynamically linked. ¢ Dynamic linking can also occur after program has begun (run-time linking). § In Unix, this is done by calls to the dlopen() interface. § ¢ Used in high-performance web servers Shared library routines can be shared by multiple processes. § More on this when we learn about virtual memory
Dynamic Linking at Load-time main 2. c vector. h Translators (cpp, cc 1, as) Relocatable object file unix> gcc -shared -o libvector. so addvec. c multvec. c libc. so libvector. so Relocation and symbol table info only – no code or data from program main 2. o Linker (ld) Partially linked executable object file p 2 Loader (execve) libc. so libvector. so Code and data Fully linked executable in memory Dynamic linker (ld-linux. so)
Dynamic Linking at Runtime: Example #include <stdio. h> #include <dlfcn. h> int x[2] = {1, 2}; int y[2] = {3, 4}; int z[2]; int main() { void *handle; void (*addvec)(int *, int); char *error; /* dynamically load the shared lib that contains addvec() */ handle = dlopen(". /libvector. so", RTLD_LAZY); if (!handle { fprintf(stderr, "%sn", dlerror()); exit(1); }. . .
Dynamic Linking at Run-time. . . /* get a pointer to the addvec() function we just loaded */ addvec = dlsym(handle, "addvec"); if ((error = dlerror()) != NULL) { fprintf(stderr, "%sn", error); exit(1); } /* Now we can call addvec() it just like any other function */ addvec(x, y, z, 2); printf("z = [%d %d]n", z[0], z[1]); /* unload the shared library */ if (dlclose(handle) < 0) { fprintf(stderr, "%sn", dlerror()); exit(1); } return 0; }
Dynamically linked libraries ¢ Space/time issues § + Storing a program requires less disk space. Why? § + Sending a program requires less time. Why? § + Executing two programs requires less memory (if they share a library). Why? § – At runtime, there’s time overhead to do link. Why? ¢ Upgrades § + Replacing one file (lib. XYZ. so) upgrades every program that uses library “XYZ” § – Having the executable isn’t enough anymore Overall, dynamic linking adds quite a bit of complexity to the compiler, linker, and operating system. However, it provides many benefits that often outweigh these.
Peer Instruction 1. 2. 3. Assembler knows where a module’s data & instructions are in relation to other modules. 0: Assembler will ignore the instruction 1: Loop: nop because it does nothing. 2: 3: Java designers used a translator AND interpreter (rather than just a translator) mainly 4: 5: because of (at least 1 of): ease of writing, 6: better error msgs, smaller object code. 7: ABC FFF FFT FTF FTT TFF TFT TTF TTT
Peer Instruction Answer 1. Assembler only sees one compiled program at a time, that’s why it has to make a symbol & relocation table. It’s the job of the linker to link them all together…F! 2. Assembler keeps track of all labels in symbol table…F! 3. Java designers used an interpreter mainly because of code portability…F! 1. Assembler knows where a module’s data & instructions are in relation to other modules. 2. Assembler will ignore the instruction Loop: nop because it does nothing. 3. Java designers used a translator AND interpreter (rather than just a translator) mainly because of (at least 1 of): ease of writing, better error msgs, smaller object code. 0: 1: 2: 3: 4: 5: 6: 7: ABC FFF FFT FTF FTT TFF TFT TTF TTT
Chapter 8: Exceptional Flow Control ¢ Topics § § § § Exceptions Process context switches Creating and destroying processes Linux process hierarchy Shells Signals Nonlocal jumps
Control Flow ¢ Processors do only one thing: § From startup to shutdown, a CPU simply reads and executes (interprets) a sequence of instructions, one at a time § This sequence is the CPU’s control flow (or flow of control) Physical control flow Time <startup> inst 1 inst 2 inst 3 … instn <shutdown>
Altering the Control Flow ¢ Up to now: two mechanisms for changing control flow: § Jumps and branches § Call and return Both react to changes in program state ¢ Insufficient for a useful system: Difficult to react to changes in system state § § ¢ Data arrives from a disk or a network adapter Instruction divides by zero User hits Ctrl-C at the keyboard System timer expires System needs mechanisms for “exceptional control flow”
Exceptional Control Flow ¢ ¢ Exists at all levels of a computer system Low level mechanisms § Exceptions/Interrupts Change in control flow in response to a system event (i. e. , change in system state) § Implementation: combination of hardware and OS software § ¢ Higher level mechanisms § § Process context switch Signals Nonlocal jumps: setjmp()/longjmp() Implementation: software § operating system (context switch and signals) § C runtime library (nonlocal jumps)
Exceptions ¢ An exception is a transfer of control to the OS in response to some event (i. e. , change in processor state) User Process event I_current I_next OS exception processing by exception handler • return to I_current • return to I_next • abort ¢ Event examples: div by 0, arithmetic overflow, page fault, I/O request completes, Ctrl-C
Interrupt Vectors Exception numbers code for exception handler 0 Exception Table 0 1 2 n-1 . . . code for exception handler 1 ¢ ¢ code for exception handler 2 . . . code for exception handler n-1 ¢ Each type of event has a unique exception number k k = index into exception table (a. k. a. interrupt vector) Handler k is called each time exception k occurs
Asynchronous Exceptions (Interrupts) ¢ Caused by events external to the processor § Indicated by asserting the processor’s interrupt pin § Handler returns to “next” instruction ¢ Examples: § I/O interrupts hitting Ctrl-C at the keyboard § arrival of a packet from a network § arrival of data from a disk § Hard reset interrupt § hitting the reset button § Soft reset interrupt § hitting Ctrl-Alt-Delete on a PC §
Synchronous Exceptions ¢ Caused by events that occur as a result of executing an instruction: § Traps Intentional § Examples: system calls, breakpoint traps, special instructions § Returns control to “next” instruction § Faults § Unintentional but possibly recoverable § Examples: page faults (recoverable), protection faults (unrecoverable), floating point exceptions § Either re-executes faulting (“current”) instruction or aborts § Aborts § Unintentional and unrecoverable § Examples: parity error, machine check § Aborts current program §
Trap Example: Opening File ¢ ¢ User calls: open(filename, options) Function open executes system call instruction int 0804 d 070 <__libc_open>: . . . 804 d 082: cd 80 804 d 084: 5 b. . . User Process int pop $0 x 80 %ebx OS exception open file Why not use a simple function call? returns ¢ ¢ OS must find or create file, get it ready for reading or writing Returns integer file descriptor
User and Kernel Modes ¢ Kernel or supervisor mode § Any instruction can be executed § Any memory location can be accessed ¢ User mode § Execution limited to non-privileged instructions Privileged: halt CPU, change mode bit, start I/O operation, etc. § Memory access limited to user sections in address space § Can’t access kernel code or data (above 0 xc 0000000) § ¢ Restrictions required for airtight process abstraction § Only way to change from user to kernel: exception Includes interrupts, faults, traps § Change from kernel to user: special return instruction § On x 86: iret (interrupt return) §
Kernel Process
Fault Example: Page Fault ¢ ¢ User writes to memory location That portion (page) of user’s memory is currently on disk 80483 b 7: c 7 05 10 9 d 04 08 0 d User Process movl ¢ ¢ movl OS exception: page fault returns ¢ int a[1000]; main () { a[500] = 13; } Create page and load into memory Page handler must load page into physical memory Returns to faulting instruction Successful on second try $0 xd, 0 x 8049 d 10
Fault Example: Invalid Memory Reference int a[1000]; main () { a[5000] = 13; } 80483 b 7: c 7 05 60 e 3 04 08 0 d User Process movl $0 xd, 0 x 804 e 360 OS exception: page fault detect invalid address signal process ¢ ¢ ¢ Page handler detects invalid address Sends SIGSEGV signal to user process User process exits with “segmentation fault”
IA 32 Exceptions: Examples Exception Number Description Exception Class 0 Divide error Fault 13 General protection fault Fault 14 Page fault Fault 18 Machine check Abort 32 -127 OS-defined Interrupt or trap 128 (0 x 80) System call Trap 129 -255 OS-defined Interrupt or trap See pp. 183, http: //download. intel. com/design/processor/manuals/253665. pdf
Linux/IA 32 System Calls ¢ ¢ Put system call number in %eax Put arguments in other registers (call dependent) Execute “int $0 x 80” Examples (from /usr/include/syscall. h) No. Name Description 1 2 3 4 5 6 7. . . exit fork read write open close waitpid Terminate process Create new process Read file Write file Open file Close File Wait for child to terminate
Making System Calls ## Helper Function: prints string to stdout putstring: . type @function pushl %ebp movl %esp, %ebp movl 8(%ebp), %ecx # address of string xorl %edx, %edx count_chars: movb (%ecx, %edx, 1), %al testb %al, %al jz done_count_chars incl %edx # length jmp count_chars done_count_chars: movl $4, %eax # system call #4 xorl %ebx, %ebx incl %ebx int $0 x 80 # file descriptor #1 movl %ebp, %esp popl %ebp ret
Processes ¢ Definition: A process is an instance of a running program. § One of the most profound ideas in computer science § Not the same as “program” or “processor” ¢ Process provides each program with two key abstractions: § Logical control flow Each program seems to have exclusive use of the CPU § Private virtual address space § Each program seems to have exclusive use of main memory § ¢ How are these Illusions maintained? § Process executions interleaved (multitasking) § Address spaces managed by virtual memory system
Concurrent Processes ¢ ¢ ¢ Two processes run concurrently (are concurrent) if their flows overlap in time Otherwise, they are sequential Examples: § Concurrent: A & B, A & C § Sequential: B & C Process A Time Process B Process C
User View of Concurrent Processes ¢ ¢ Control flows for concurrent processes are physically disjoint in time However, we can think of concurrent processes are running in parallel with each other Process A Time Process B Process C
Context Switching ¢ Processes are managed by a shared chunk of OS code called the kernel § Important: the kernel is not a separate process; it runs as part of some user process ¢ Control flow passes from one process to another via a context switch Process A Process B user code kernel code Time context switch user code kernel code user code context switch
BONUS SLIDES
fork: Creating New Processes ¢ int fork(void) § creates a new process (child) that is identical to the calling process (parent) § returns 0 to the child process § returns child’s pid to the parent process pid_t pid = fork(); if (pid == 0) { printf("hello from childn"); } else { printf("hello from parentn"); } ¢ Fork is interesting (and often confusing) because it is called once but returns twice
Understanding fork Process n Child Process m pid_t pid = fork(); if (pid == 0) { printf("hello from childn"); } else { printf("hello from parentn"); } pid_t pid = fork(); if (pid == 0) { pid = m printf("hello from childn"); } else { printf("hello from parentn"); } pid_t pid = fork(); if (pid == 0) { pid = 0 printf("hello from childn"); } else { printf("hello from parentn"); } pid_t pid = fork(); if (pid == 0) { printf("hello from childn"); } else { printf("hello from parentn"); } hello from parent Which one is first? hello from child
Fork Example #1 ¢ Parent and child both run same code § Distinguish parent from child by return value from fork ¢ Start with same state, but each has private copy § Including shared output file descriptor § Relative ordering of their printf statements undefined void fork 1() { int x = 1; pid_t pid = fork(); if (pid == 0) { printf("Child has x = %dn", ++x); } else { printf("Parent has x = %dn", --x); } printf("Bye from process %d with x = %dn", getpid(), x); }
Fork Example #2 ¢ Both parent and child can continue forking void fork 2() { printf("L 0n"); fork(); printf("L 1n"); fork(); printf("Byen"); } L 0 L 1 Bye Bye
Fork Example #3 ¢ Both parent and child can continue forking void fork 3() { printf("L 0n"); fork(); printf("L 1n"); fork(); printf("L 2n"); fork(); printf("Byen"); } L 1 L 0 L 1 L 2 Bye Bye
Fork Example #4 ¢ Both parent and child can continue forking void fork 4() { printf("L 0n"); if (fork() != 0) { printf("L 1n"); if (fork() != 0) { printf("L 2n"); fork(); } } printf("Byen"); } Bye L 0 L 1 L 2 Bye
Fork Example #5 ¢ Both parent and child can continue forking void fork 5() { printf("L 0n"); if (fork() == 0) { printf("L 1n"); if (fork() == 0) { printf("L 2n"); fork(); } } printf("Byen"); } Bye L 2 L 1 L 0 Bye Bye
exit: Ending a process ¢ void exit(int status) § exits a process Normally return with status 0 § atexit() registers functions to be executed upon exit § void cleanup(void) { printf("cleaning upn"); } void fork 6() { atexit(cleanup); fork(); exit(0); }
Zombies ¢ Idea § When process terminates, still consumes system resources Various tables maintained by OS § Called a “zombie” § Living corpse, half alive and half dead § ¢ Reaping § Performed by parent on terminated child § Parent is given exit status information § Kernel then discards process ¢ What if parent doesn’t reap? § If any parent terminates without reaping a child, then child will be reaped by init process § So, only need explicit reaping in long-running processes § e. g. , shells and servers
Zombie Example void fork 7() { if (fork() == 0) { /* Child */ printf("Terminating Child, PID = %dn", getpid()); exit(0); } else { printf("Running Parent, PID = %dn", linux>. /forks 7 & getpid()); while (1) [1] 6639 ; /* Infinite loop */ Running Parent, PID = 6639 } Terminating Child, PID = 6640 } linux> ps PID TTY TIME 6585 ttyp 9 00: 00 6639 ttyp 9 00: 03 6640 ttyp 9 00: 00 6641 ttyp 9 00: 00 linux> kill 6639 [1] Terminated linux> ps PID TTY TIME 6585 ttyp 9 00: 00 6642 ttyp 9 00: 00 CMD tcsh forks <defunct> ps ¢ ¢ CMD tcsh ps ps shows child process as “defunct” Killing parent allows child to be reaped by init
Nonterminating Child Example void fork 8() { if (fork() == 0) { /* Child */ printf("Running Child, PID = %dn", getpid()); while (1) ; /* Infinite loop */ } else { printf("Terminating Parent, PID = %dn", getpid()); exit(0); } } linux>. /forks 8 Terminating Parent, PID = 6675 Running Child, PID = 6676 linux> ps PID TTY TIME CMD 6585 ttyp 9 00: 00 tcsh 6676 ttyp 9 00: 06 forks 6677 ttyp 9 00: 00 ps linux> kill 6676 linux> ps PID TTY TIME CMD 6585 ttyp 9 00: 00 tcsh 6678 ttyp 9 00: 00 ps ¢ ¢ Child process still active even though parent has terminated Must kill explicitly, or else will keep running indefinitely
wait: Synchronizing with Children ¢ int wait(int *child_status) § Suspends current process until one of its children terminates § Return value is the pid of the child process that terminated § If child_status != NULL, then the object it points to will be set to a status indicating why the child process terminated
wait: Synchronizing with Children void fork 9() { int child_status; if (fork() == 0) { printf("HC: hello from childn"); } else { printf("HP: hello from parentn"); wait(&child_status); printf("CT: child has terminatedn"); } printf("Byen"); exit(); } HC Bye HP CT Bye
wait() Example ¢ ¢ If multiple children completed, will take in arbitrary order Can use macros WIFEXITED and WEXITSTATUS to get information about exit status (and thereby reap child processes) void fork 10() { pid_t pid[N]; int i; int child_status; for (i = 0; i < N; i++) if ((pid[i] = fork()) == 0) exit(100+i); /* Child */ for (i = 0; i < N; i++) { pid_t wpid = wait(&child_status); if (WIFEXITED(child_status)) printf("Child %d terminated with exit status %dn", wpid, WEXITSTATUS(child_status)); else printf("Child %d terminate abnormallyn", wpid); } }
waitpid(): Waiting for a Specific Process ¢ waitpid(pid, &status, options) § Suspends current process until specific process terminates § Various options (that we won’t talk about) void fork 11() { pid_t pid[N]; int i; int child_status; for (i = 0; i < N; i++) if ((pid[i] = fork()) == 0) exit(100+i); /* Child */ for (i = 0; i < N; i++) { pid_t wpid = waitpid(pid[i], &child_status, 0); if (WIFEXITED(child_status)) printf("Child %d terminated with exit status %dn", wpid, WEXITSTATUS(child_status)); else printf("Child %d terminated abnormallyn", wpid); }
Output: Wait/Waitpid Examples Using wait (fork 10) Child Child. . . 3565 3564 3563 3562 3566 terminated terminated with with exit exit status status 103 102 101 100 104 Using waitpid (fork 11) Child Child. . . 3568 3569 3570 3571 3572 terminated terminated with with exit exit status status 100 101 102 103 104
execve: Loading and Running Programs 0 xbfffffff ¢ ¢ int execve( char *filename, char *argv[], char *envp ) Loads and runs § Executable filename § With argument list argv § And environment variable list envp ¢ ¢ ¢ Does not return (unless error) Overwrites process, keeps pid Environment variables: § “name=value” strings Stack Null-terminated environment variable strings Null-terminated commandline arg strings unused envp[n] = NULL envp[n-1] … envp[0] argv[argc] = NULL argv[argc-1] … argv[0] Linker vars envp argv argc
#include <stdio. h> argc, argv, envp main(int argc, char *argv[], char *envp[]) { int a; printf("The command name (argv[0]) is %sn", argv[0]); printf("There are %d arguments: n", argc-1); for (a=1; a<argc; a++) printf("targument %2 d: t%sn", a, argv[a]); printf("The environment is as follows: n"); a = 0; while (envp[a] != NULL) printf("t%sn", envp[a++]); } prompt>: gcc envp. c prompt>: a. out The command name (argv[0]) is a. out There are 0 arguments: The environment is as follows: BIBINPUTS=. //: /Users/jamesarchibald/jka/facstaff/tex/bib/: TERM_PROGRAM=Apple_Terminal TERM=xterm-color SHELL=/bin/bash TMPDIR=/var/folders/Zv/Zv. E 4 YYCHHGCXt. Pobzsm 7 RE+++TI/-Tmp-/ PERL 5 LIB=/sw/lib/perl 5: /sw/lib/perl 5/darwin Apple_Pub. Sub_Socket_Render=/tmp/launch-Wn. Uuta/Render TERM_PROGRAM_VERSION=272. . .
execl and exec Family ¢ int execl(char *path, char *arg 0, char *arg 1, …, 0) ¢ Loads and runs executable at path with args arg 0, arg 1, … § § § ¢ ¢ path is the complete path of an executable object file By convention, arg 0 is the name of the executable object file “Real” arguments to the program start with arg 1, etc. List of args is terminated by a (char *)0 argument Environment taken from char **environ, which points to an array of “name=value” strings: § USER=jka § LOGNAME=jka § HOME=/ee/user/jka Returns -1 if error, otherwise doesn’t return! Family of functions includes execv, execve (base function), execvp, execle, and execlp
exec: Loading and Running Programs main() { if (fork() == 0) { execl("/usr/bin/cp", "foo", "bar", 0); } wait(NULL); printf("copy completedn"); exit(); }
Programmer’s Model of Multitasking ¢ Basic functions § fork() spawns new process Called once, returns twice § exit() terminates calling process § Called once, never returns § Can turn process into zombie (if parent still running) § wait() and waitpid() wait for and reap terminated children § execl() and execve() run a new program in an existing process § Called once, never return (unless error finding or running file) § ¢ Programming challenge § Understanding the nonstandard semantics of the functions § Avoiding improper use of system resources § E. g. , “fork bombs” can disable a system.
Unix Process Hierarchy [0] init [1] Daemon e. g. httpd Login shell Child Grandchild
Unix Startup: Step 1 1. Pushing reset button loads the PC with the address of a small bootstrap program. 2. Bootstrap program loads the boot block (disk block 0). 3. Boot block program loads kernel binary (e. g. , /boot/vmlinux) 4. Boot block program passes control to kernel. 5. Kernel handcrafts the data structures for process 0. [0] Process 0: handcrafted kernel process Process 0 forks child process 1 init [1] Child process 1 execs /sbin/init
Unix Startup: Step 2 [0] /etc/inittab Daemons e. g. , ftpd, httpd init [1] getty init forks and execs daemons per /etc/inittab, and forks and execs a getty program for the console
Unix Startup: Step 3 [0] init [1] login The getty process execs a login program
Unix Startup: Step 4 [0] init [1] tcsh login reads login and passwd. if OK, it execs a shell. if not OK, it execs another getty
The Run-Time Picture [0] init [1] Daemon e. g. httpd Login shell Child Grandchild
Shell Programs ¢ A shell is an application program that runs programs on behalf of the user. § § sh csh tcsh bash Original Unix Bourne Shell BSD Unix C Shell Enhanced C Shell Bourne-Again Shell int main() { char cmdline[MAXLINE]; while (1) { /* read */ printf("> "); Fgets(cmdline, MAXLINE, stdin); if (feof(stdin)) exit(0); } } /* evaluate */ eval(cmdline); A simple example Execution is a sequence of read/evaluate steps
Simple Shell eval Function void eval(char *cmdline) { char *argv[MAXARGS]; /* argv for execve() */ int bg; /* should the job run in bg or fg? */ pid_t pid; /* process id */ bg = parseline(cmdline, argv); if (!builtin_command(argv)) { if ((pid = Fork()) == 0) { /* child runs user job */ if (execve(argv[0], argv, environ) < 0) { printf("%s: Command not found. n", argv[0]); exit(0); } } if (!bg) { /* parent waits for fg job to terminate */ int status; if (waitpid(pid, &status, 0) < 0) unix_error("waitfg: waitpid error"); } else /* otherwise, don’t wait for bg job */ printf("%d %s", pid, cmdline);
What Is a “Background Job”? ¢ Users generally run one command at a time § Type command, read output, type another command ¢ Some programs run “for a long time” § Example: “delete this file in two hours” % sleep 7200; rm /tmp/junk ¢ # shell stuck for 2 hours A “background” job is a process we don't want to wait for % (sleep 7200 ; rm /tmp/junk) & [1] 907 % # ready for next command
Problem with Simple Shell Example ¢ Shell correctly waits for and reaps foreground jobs ¢ But what about background jobs? § Will become zombies when they terminate § Will never be reaped because shell (typically) will not terminate § Will create a memory leak that could theoretically run the kernel out of memory § Modern Unix: once you exceed your process quota, your shell can't run any new commands for you: fork() returns -1 % limit maxproc 3574 $ ulimit -u 3574 # csh syntax # bash syntax
ECF to the Rescue! ¢ Problem § The shell doesn't know when a background job will finish § By nature, it could happen at any time § The shell's regular control flow can't reap exited background processes in a timely fashion § Regular control flow is “wait until running job completes, then reap it” ¢ Solution: Exceptional control flow § The kernel will interrupt regular processing to alert us when a background process completes § In Unix, the alert mechanism is called a signal
Signals ¢ A signal is a small message that notifies a process that an event of some type has occurred in the system § Akin to exceptions and interrupts § Sent by the kernel (sometimes at request of another process) to a process § Signal type is identified by small integer ID’s (1 -30) § Only information in a signal is its ID and the fact that it arrived ID Name Default Action Corresponding Event 2 SIGINT Terminate Interrupt (e. g. , ctl-c from keyboard) 9 SIGKILL Terminate Kill program (cannot override or ignore) 11 SIGSEG V Terminate & Dump Segmentation violation 14 SIGALR M Terminate Timer signal 17 SIGCHL Ignore Child stopped or terminated
Sending a Signal ¢ ¢ Kernel sends (delivers) a signal to a destination process by updating some state in the context of the destination process Kernel sends a signal for one of the following reasons: § Kernel has detected a system event such as divide-by-zero (SIGFPE) or the termination of a child process (SIGCHLD) § Another process has invoked the kill system call to explicitly request the kernel to send a signal to the destination process
Receiving a Signal ¢ ¢ A destination process receives a signal when it is forced by the kernel to react in some way to the delivery of the signal Three possible ways to react: § Ignore the signal (do nothing) § Terminate the process (with optional core dump) § Catch the signal by executing a user-level function called signal handler § Akin to a hardware exception handler being called in response to an asynchronous interrupt
Signal Concepts (continued) ¢ A signal is pending if sent but not yet received § There can be at most one pending signal of any particular type § Important: Signals are not queued § ¢ If a process has a pending signal of type k, then subsequent signals of type k that are sent to that process are discarded A process can block the receipt of certain signals § Blocked signals can be delivered, but will not be received until the signal is unblocked ¢ A pending signal is received at most once
Signal Concepts ¢ Kernel maintains pending and blocked bit vectors in the context of each process § pending: represents the set of pending signals Kernel sets bit k in pending when a signal of type k is delivered § Kernel clears bit k in pending when a signal of type k is received § § blocked: represents the set of blocked signals § Can be set and cleared by using the sigprocmask function
Process Groups ¢ Every process belongs to exactly one process group pid=10 pgid=10 pid=20 pgid=20 Background job #1 Foreground job Child pid=21 pgid=20 pid=22 pgid=20 Foreground process group 20 Shell pid=32 pgid=32 Background process group 32 Background job #2 pid=40 pgid=40 Background process group 40 getpgrp() Return process group of current process setpgid() Change process group of a process
Sending Signals with kill Program ¢ kill program sends arbitrary signal to a process or process group linux>. /forks 16 linux> Child 1: pid=24818 pgrp=24817 Child 2: pid=24819 pgrp=24817 linux> ps ¢ Examples PID TTY TIME CMD 24788 pts/2 00: 00 tcsh § kill – 9 24818 pts/2 00: 02 forks Send SIGKILL to process 24818 24819 pts/2 00: 02 forks 24820 pts/2 00: 00 ps linux> kill -9 -24817 § kill – 9 – 24817 linux> ps Send SIGKILL to every process in PID TTY TIME CMD process group 24817 24788 pts/2 00: 00 tcsh 24823 pts/2 00: 00 ps linux>
Sending Signals with kill Function void fork 12() { pid_t pid[N]; int i, child_status; for (i = 0; i < N; i++) if ((pid[i] = fork()) == 0) while(1); /* Child infinite loop */ /* Parent terminates the child processes */ for (i = 0; i < N; i++) { printf("Killing process %dn", pid[i]); kill(pid[i], SIGINT); } /* Parent reaps terminated children */ for (i = 0; i < N; i++) { pid_t wpid = wait(&child_status); if (WIFEXITED(child_status)) printf("Child %d terminated with exit status %dn", wpid, WEXITSTATUS(child_status)); else printf("Child %d terminated abnormallyn", wpid); } }
Receiving Signals ¢ ¢ Suppose kernel is returning from an exception handler and is ready to pass control to process p Kernel computes pnb = pending & ~blocked § The set of pending nonblocked signals for process p ¢ If (pnb == 0) § Pass control to next instruction in the logical flow for p ¢ Else § Choose least nonzero bit k in pnb and force process p to receive signal k § The receipt of the signal triggers some action by p § Repeat for all nonzero k in pnb § Pass control to next instruction in logical flow for p
Default Actions ¢ Each signal type has a predefined default action, which is one of: § § The process terminates and dumps core The process stops until restarted by a SIGCONT signal The process ignores the signal
Installing Signal Handlers ¢ The signal function modifies the default action associated with the receipt of signal signum: handler_t *signal(int signum, handler_t *handler) ¢ Different values for handler: § SIG_IGN: ignore signals of type signum § SIG_DFL: revert to the default action on receipt of signals of type signum § Otherwise, handler is the address of a signal handler Called when process receives signal of type signum § Referred to as “installing” the handler § Executing handler is called “catching” or “handling” the signal § When the handler returns, control passes back to instruction in the control flow of the process that was interrupted by receipt of the signal §
Signal Handling Example void int_handler(int sig) { printf("Process %d received signal %dn", getpid(), sig); exit(0); } void fork 13() { pid_t pid[N]; int i, child_status; signal(SIGINT, int_handler); . . . } Parent forks 5 child processes, sends each a SIGINT (~ctrl-c), then reaps and prints exit status of each linux>. /forks 13 Killing process 24974 Killing process 24975 Killing process 24976 Killing process 24977 Process 24977 received Child 24977 terminated Process 24976 received Child 24976 terminated Process 24975 received Child 24975 terminated Process 24974 received Child 24974 terminated Process 24973 received Child 24973 terminated linux> signal 2 with exit signal 2 with exit status 0 status 0
Signals Handlers as Concurrent Flows ¢ A signal handler is a separate logical flow (not process) that runs concurrently with the main program § “Concurrently” in the “not sequential” sense Time Process A while (1) ; handler(){ … } Process B
Another View of Signal Handlers as Concurrent Flows Process A Signal delivered Icurr Process B user code (main) kernel code context switch user code (main) kernel code Signal received user code (handler) kernel code Inext user code (main) context switch
Nonlocal Jumps: setjmp/longjmp ¢ Powerful (but dangerous) user-level mechanism for transferring control to an arbitrary location § Controlled way to break the procedure call / return discipline § Useful for error recovery and signal handling ¢ int setjmp(jmp_buf j) § Must be called before longjmp § Identifies a return site for a subsequent longjmp § Called once, returns one or more times ¢ Implementation: § Remember where you are by storing the current register context, stack pointer, and PC value in jmp_buf § Return 0
setjmp/longjmp (cont) ¢ void longjmp(jmp_buf j, int i) § Meaning: return from the setjmp remembered by jump buffer j again. . . § … this time returning i instead of 0 § Called after setjmp § Called once, but never returns § ¢ longjmp Implementation: § Restore register context (stack pointer, base pointer, PC value) from jump buffer j § Set %eax (the return value) to i § Jump to the location indicated by the PC stored in jump buf j
setjmp/longjmp Example #include <setjmp. h> jmp_buf buf; main() { if (setjmp(buf) != 0) { printf("back in main due to an errorn"); else printf("first time throughn"); p 1(); /* p 1 calls p 2, which calls p 3 */ }. . . p 3() { <error checking code> if (error) longjmp(buf, 1) }
Limitations of Nonlocal Jumps ¢ Works within stack discipline § Can only long jump to environment of function that has been called but not yet completed jmp_buf env; P 1() { if (setjmp(env)) { /* Long Jump to here */ } else { P 2(); } } P 2() {. . . P 2(); . . . P 3(); } P 3() { longjmp(env, 1); } env Before longjmp P 1 P 2 P 2 P 3 After longjmp P 1
Limitations of Long Jumps (cont. ) ¢ Works within stack discipline § Can only long jump to environment of function that has been called but not yet completed P 1 jmp_buf env; P 1() { P 2(); P 3(); } P 2() { if (setjmp(env)) { /* Long Jump to here */ } } P 3() { longjmp(env, 1); } env P 2 At setjmp P 1 env X P 2 returns P 1 env X P 3 At longjmp
Putting It All Together: A Program That Restarts Itself When ctrl-c’d #include <stdio. h> #include <signal. h> #include <setjmp. h> sigjmp_buf buf; void handler(int sig) { siglongjmp(buf, 1); } main() { signal(SIGINT, handler); if (!sigsetjmp(buf, 1)) printf("startingn"); else printf("restartingn"); while(1) { sleep(1); printf("processing. . . n"); } } bass> a. out starting processing. . . restarting processing. . . Ctrl-c
Summary ¢ Signals provide process-level exception handling § Can generate from user programs § Can define effect by declaring signal handler ¢ Some caveats § Very high overhead >10, 000 clock cycles § Only use for exceptional conditions § Don’t have queues § Just one bit for each pending signal type § ¢ Nonlocal jumps provide exceptional control flow within process § Within constraints of stack discipline
Sending Signals from the Keyboard ¢ Typing ctrl-c (ctrl-z) sends a SIGINT (SIGTSTP) to every job in the foreground process group. § SIGINT – default action is to terminate each process § SIGTSTP – default action is to stop (suspend) each process pid=10 pgid=10 pid=20 pgid=20 Background job #1 Foreground job Child pid=21 pgid=20 pid=22 pgid=20 Foreground process group 20 Shell pid=32 pgid=32 Background process group 32 Background job #2 pid=40 pgid=40 Background process group 40
Example of ctrl-c and ctrl-z bluefish>. /forks 17 Child: pid=28108 pgrp=28107 Parent: pid=28107 pgrp=28107 <types ctrl-z> Suspended bluefish> ps w PID TTY STAT TIME COMMAND 27699 pts/8 Ss 0: 00 -tcsh 28107 pts/8 T 0: 01. /forks 17 28108 pts/8 T 0: 01. /forks 17 28109 pts/8 R+ 0: 00 ps w bluefish> fg. /forks 17 <types ctrl-c> bluefish> ps w PID TTY STAT TIME COMMAND 27699 pts/8 Ss 0: 00 -tcsh 28110 pts/8 R+ 0: 00 ps w STAT (process state) Legend: First letter: S: sleeping T: stopped R: running Second letter: s: session leader +: foreground proc group See “man ps” for more details
Signal Handler Funkiness int ccount = 0; void child_handler(int sig) { int child_status; pid_t pid = wait(&child_status); ccount--; printf("Received signal %d from process %dn", sig, pid); } void fork 14() { pid_t pid[N]; int i, child_status; ccount = N; signal(SIGCHLD, child_handler); for (i = 0; i < N; i++) if ((pid[i] = fork()) == 0) { sleep(1); /* deschedule child */ exit(0); /* Child: Exit */ } while (ccount > 0) pause(); /* Suspend until signal occurs */ } ¢ Pending signals are not queued § For each signal type, just have single bit indicating whether or not signal is pending § Even if multiple processes have sent this signal
Living With Nonqueuing Signals ¢ Must check for all terminated jobs § Typically loop with wait void child_handler 2(int sig) { int child_status; pid_t pid; while ((pid = waitpid(-1, &child_status, WNOHANG)) > 0) { ccount--; printf("Received signal %d from process %dn", sig, pid); } } void fork 15() {. . . signal(SIGCHLD, child_handler 2); . . . }
Signal Handler Funkiness (Cont. ) ¢ ¢ Signal arrival during long system calls (say a read) Signal handler interrupts read() call § Linux: upon return from signal handler, the read() call is restarted automatically § Some other flavors of Unix can cause the read() call to fail with an EINTER error number (errno) in this case, the application program can restart the slow system call ¢ Subtle differences like these complicate the writing of portable code that uses signals
A Program That Reacts to Externally Generated Events (Ctrl-c) #include <stdlib. h> #include <stdio. h> #include <signal. h> void handler(int sig) { printf("You think hitting ctrl-c will stop the bomb? n"); sleep(2); printf("Well. . . "); fflush(stdout); sleep(1); printf("OKn"); exit(0); } main() { signal(SIGINT, handler); /* installs ctl-c handler */ while(1) { } }
A Program That Reacts to Internally Generated Events #include <stdio. h> #include <signal. h> int beeps = 0; /* SIGALRM handler */ void handler(int sig) { printf("BEEPn"); fflush(stdout); if (++beeps < 5) alarm(1); else { printf("BOOM!n"); exit(0); } } main() { signal(SIGALRM, handler); alarm(1); /* send SIGALRM in 1 second */ while (1) { /* handler returns here */ } } linux> a. out BEEP BEEP BOOM! bass>
- Slides: 123