Herbert G. Mayer, PSU
Status 6/28/2015
1
Process, Thread, Hyperthread
Unix Process
Interrupt
Command ps
Background Process
Command fork()
fork() Sample
Command execve()
execve() Sample
References
2
[Bryant, 16] “A process is the operating system ’s abstraction of a running program.
”
[Silberschatz, 10] “A process can be thought of as a program in execution …”
[Silberschatz, 90] “Informally, a process is a program in execution. … A process is more than the program code. It also includes the current activity, as represented by the value of the program counter and the contents of the processor ’s registers.”
[Tanenbaum, 72] “A process is just an executing program, including the current values of the program counter, registers, and variables.
”
3
[Bryant, 947] “A thread is a logical flow that runs in the context of a process. Each thread has its own thread context, including a unique integer thread ID, stack, stack pointer, program counter, general-purpose registers, and condition codes. All threads running in a process share the entire virtual address space of that process.
”
[Silberschatz, 103]
“A thread , sometimes called a lightweight process , is a basic unit of CPU utilization, and consists of a program counter, a register set, and a stack space. It shares with peer threads its code section, data section, and operating-system resources such as open files and signals.
”
[Tanenbaum, 81] “A thread has a program counter that keeps track of which instruction to execute next. It has registers, which hold its current working variables. It has a stack, which contains the execution history, with one frame for each procedure called, but not yet returned from. ... A thread must execute in some process ”
4
Who makes threads? You, the programmer, or the OS, following your implicit or explicit instructions
Programmer understands which portions of C program are dependent-, and which ones are independent of other parts of the same program –which, when executed, is a process
MS and Intel compiler provide tools to support this analysis
Some C and C++ compilers provide directives
Directives can be hidden as comments, to be transparent to other compilers
See lit ref [9] for Intel ’s Parallel Studio, helping programmer eliminated threading errors in C and C++ programs
Or see Intel
’s [10] [12] to help programmer “to thread” SW
Or Microsoft Multi-Threading support for existing C and C++ source code [11]
5
A processor may be single-core (UP), or have multiple cores, the latter meaning: all processor resources are replicated; e.g. Intel Core 2 Duo
Or a processor may be single-core, hyperthreaded, e.g.
Intel Pentium 4e. Hyperthreaded means that only CPU registers and APIC are replicated , but not ALU units, such as integer unit, floating-point unit, branch unit, caches, etc.
Or a processor may be multi-core, hyperthreaded , in which case each of several real cores has a hyperthread twin (or more), sharing the real core ’s ALU with all hyperthreads, but each hyperthread has own register + APIC; e.g. Intel Core i7
Hyperthreading is an old idea, proposed decades ago by
Digital Equipment Corp. (DEC), implemented first in silicon by Intel in 2002 on Xeon ® server + Pentium ® 4 desktop CPUs
Hyperthread is an overloaded term, referring to the reduced
Silicon core, as well as the active SW thread executing on it
Should be named: Hypothread since it is a threadsub set, but Hyperthread IS THE accepted technical term
6
Ability of processor to run concurrent threads quickly
Some microprocessor hardware replication that creates the illusion to SW of Dual Processor (DP)
Yet such HW with some resource replication is NOT a true dual-core silicon implementation
The execution unit is still shared between multiple threads
Effect of Hyperthreading on Xeon
®
Processor:
Average CPU utilization increases to ~50%, down from ~35% for a typical uni-processor
Up to ~30% performance gain for some applications with the same processor frequency
Hyperthreading Technology Results:
1. More performance with enabled applications
2. Better responsiveness with existing applications
7
Almost two Logical Processors
Architecture state (registers) and
APIC* replicated
On-Die
Caches
Shares execution units, caches, branch prediction, control logic and buses
Architecture State
Adv. Programmable
Interrupt Control
*APIC: Advanced Programmable
Interrupt Controller. Handles interrupts sent to a specified logical processor
Processor
Execution
Resource
Architecture State
Adv. Programmable
Interrupt Control
System Bus
8
Hyperthreaded core replicates in silicon all CPU resources essential to switching fast from one thread to another
Those replicated resources are registers and the APIC. ALU units are not replicated on a hyperthread core
SW hyperthreads are also threads, but execute concurrently on HW hyperthread cores; switch is efficient due to available registers; ALU-sharing is still necessary: Only a single ALU!
This costs ~5% more HW (silicon) than a single core, but can gain up to 30% performance improvement; great ROI!
9
Process is an OS-centric view of a running program. Due to the meaning of “running”, all machine resources are necessary to execute a process
A thread is a subset of a process, not necessarily a proper subset
One purpose for threading a process is to allow continued execution of that one process, even when some part of it has to wait; e.g. one thread waits for an IO operation to finish, but another thread can continue, being independent of the IO result
Purpose for having threads execute a process is to speed up overall execution. Possible, if multiple threads of the same process are sufficiently data-independent to progress concurrently ; never simultaneously on a single core!
A threaded process can –sometimes– execute faster on a single core unit due to reduced waits
10
A hyperthreaded core almost creates illusion of a multicore CPU, though there is only 1 complete CPU
Enables concurrent execution on a uni-processor, when one process thread stalls, but another is ready; switch to the other thread is cheap, since the other register set already has proper state –except the first time around
But hyperthreading (or threading) per se never allows parallel execution ; only a multi-core architecture does
A System Programmer must know: On MP OS not yet tuned for hyperthreading: best disable hyperthread
scheduling; else under the right (i.e.
wrong ) circumstances performance degradation can result:
This is the case, when the “next core to be scheduled” happens to be regularly the hyperthreaded subset-core, while leaving other real cores idle, though a real core could work instead of the hyperthread
11
Under Unix, a process is an instance of running a program. If you execute the ancient editor ed and your colleague does too, then there are 2 --very similar – different processes running
All user a.out
programs and all Unix commands, when running, become processes; commands ll and g++ my_prog.cpp
create 2 different processes
Processes are visible via ps command, and even that command is a process in its own right
Issue the command ps , for process status, and you see your current processes, plus the ps processes
Issue ps –a , and you see a very detailed list, including sleeping processes
Issue the command man ps for your education
12
When a command is issued, Unix starts new process, suspends the current OS process, generally the C-shell , until the new child process completes
Exception: background processes with &
Unix identifies every process by a Process
Identification Number (PID) assigned at initiation
See getpid() function calls below
Unix is a timesharing system, which grants each process time-slices
Allocation of time-slices has to be fair , so that one long process cannot make other, shorter ones, wait for extended periods ( no starvation!
)
Also, time-slicing has to be efficient, lest too much processing time migrates into overhead, like a typical state government
13
Under MS Windows, the process and thread concepts are almost identical to the ones under
Unix; see [14]
But added terms under Windows include:
Job object : group of processes, managed as one unit
Thread pool : a collection of threads
Fiber : execution unit to be scheduled manually by a running application –scheduled explicitly by running SW
UMS : User Mode Scheduling. Thread scheduling managed by application
In CS 201 we focus on Unix process model and Unix terminology
14
Def: Program interrupt is a transparent, non-scheduled change in execution flow with specific cause outside the program, treated by an interrupt handler, ending up at the original program again
Is unpredictable : Programmer does not know that, when, why, where interrupt happens
Is unexpected : Programmer does not know when interrupt happens, or whether it happens
Cannot be pinpointed (i.e. coded) by location: Programmer does not know where such an interrupt happens
Cause is known when interrupt happens, passed to interrupt handler by HW, yet the SW program (i.e. process) does not know a-priori that it will happen; can be some external event like power-outage, or time-slice consumption (timer interrupt), numeric error
15
After handling, execution continues at the place in the code right after the interrupt location
Challenge: if interrupt happens during execution of some very long instruction, say move of a large portion of memory by a byte-move instruction!
Challenge caused by general need of handling interrupt swiftly, more swiftly than the time needed for some long machine instructions!
Interrupt handled in a way that the program never knows it was interrupted: transparent
16
Interrupt has to be fast, after all, the cause ,may be some catastrophic event; e.g. power outrage!
It will not be known that an interrupt has happened, except that execution ends up slower than expected; slower than if the interrupt had not happened
Summary: the SW does not see that, why, when, how often an interrupt has happed; not a programmed event
Note: x86 INT instruction is not an interrupt! Though it is named a “ software interrupt ” instruction; is it totally predictable, locatable!
17
18
Command ps without any option shows all processes with the same controlling terminal and same user id as the invoker of the ps command
Complex command with numerous options!
PID identifies the process, TT the controlling terminal, S the state, and TIME the CPU time consumed for that process
[process] ps
PID TT S TIME COMMAND
8687 pts/33 O 0:00 ps -- O running
19212 pts/33 S 0:00 –csh -- S sleeping
19
The command ps –a prints information about all active processes; can result in a long list, e.g.: ps –a | more
PID TT S TIME COMMAND
12229 Z 0:00
16386 Z 0:00
523 console S 0:00 /usr/lib/saf/ttymon -g -d
/dev/console -l console -m ld
19668 pts/1 S 0:00 -tcsh
19691 pts/1 S 0:00 tcsh
19705 pts/1 S 0:07 pine
22394 pts/2 S 0:00 -bash
22412 pts/2 S 0:06 screen
. . .
20
To abort, AKA kill, any process in Unix whose PID is known, issue the command kill with argument
-9, e.g., the -9 meaning “death” , see below: kill –9 19186
Which in this particular case was the instructor ’s remote shell log-in to PSU ’s computer, and as a result he had to log in again from home
Process status Z means “zombie”, typically a child process that has terminated, but the parent process still needs to know its termination status
Killing a Z process has no effect; good so, else parent would never know status of terminated child; see [8] for detail
21
Some processes may run forever
Others in your environment may run for a limited time but for quite long
What if you do not wish to wait for long process completion before continuing with other work?
Possible in Unix with background processes initiated by the & command modifier, such as: run_big &
Which executes the long running program run_big in the background; making progress whenever CPU cycles are available
Your real interactive work may proceed in parallel
–on a UP implied here: but concurrently!
22
What happens if this program generates output, such as messages to stderr ?
These would be interspersed with other output generated concurrently, causing confusion!
To avoid mixing of messages, stderr can be redirected to a separate file using >& g++ big_program.cpp >& my_errors &
. . . does accomplish that
It redirects via >& all output from stderr to a new file, named: my_errors
. . . and then runs in the background, caused by &
So neither the messages nor the (long running) background process hold you up
23
fork()
Unix fork() creates a new process by cloning the current process issuing the fork() command
Returns 0 to child, and returns child ’s PID to parent
Peculiar about a child processes: it shares all attributes with the forking parent process, except the process id, AKA PID , the parent PID, AKA PPID , locks and a few attributes preventing infinite spawning!
To compile the fork() command in your C program,
#include <unistd.h>
Code and data space of child and parent process are shared for reading only ; when the child needs to write (stack, heap) it receives its own copy with the new modifications ; AKA copy-on-write
Spawning a new process via fork() creates a second exit() action; i.e. both parent and child need to exit eventually
24
Program getpid.cpp
below is trivial, it does:
Inquires about its own main() program process id
Runs in the background, via &
So user has a chance to monitor the process from console
And get that processes id number via Unix ps command
And by the time getpid exits, it also prints its pid number
Just to demonstrate the getpid() function
25
Source Program
// program getpid.cpp
#include <iostream.h>
//#include <unistd.h> no fork() here
// I wish to get the PID of this main() program!
int main()
{ // main int pid = getpid(); int k, i;
// spend execution time, for chance to issue ps command for ( i = 0; i < 1000000; i++ ) { for ( int j = 0; j < 100; j++ ) { k = j;
} //end for
} //end for
// OK time wasted
// fake assignment to i: to fool optimizer!
i = k; printf( "My process id = %d\n", pid );
} //end main
26
Result and Output
[process] a.out &
[1] 24366
[process] ps
PID TT S TIME COMMAND
23847 pts/49 S 0:00 -csh
24366 pts/49 O 0:00 a.out
24375 pts/49 O 0:00 ps
[process] My process id = 24366
^C
[1] Done a.out
27
Now we run several programs that demonstrate the fork() Linux or Unix function
Reminder of fork()
Creates child process identical to parent process
fork() returns pid of child to parent != 0
But returns 0 to child process
If child process wishes to know its own pid, it must call function getpid()
If child wishes to know parent ’s process id, it must call function getppid()
Child process starts executing after the fork() command, NOT repeating the fork() command
28
fork()
#include <unistd.h>
#define DEAD -1
// there are no shared global data, not shared data on heap!
void fork_sample1( void )
{ // fork_sample1 int local = 1; // just some data to track: which process?
pid_t pid = fork(); // from now on: 2 processes switch ( pid ) { case 0:
// this is thread through child process printf( “++local value in child = %d\n", ++local ); break; case DEAD:
// this is en error process, dead, zombie?
printf( “<><> local in dead process = %d\n", local ); default: break;
// clearly thread through parent process printf( ”parent pid = %d, --local = %d\n", pid, --local );
} //end switch
// strange? switch statement has multiple clauses executed! By design!
printf( "Ending process %d\n", pid );
} //end fork_sample1
29
fork()
• Note that local is 2, and not 1 in child process, and it happens to be executed after parent; arbitrarily!
• So you infer: child has its own copy . It does not share local with parent; else it would be 1, since parent decreased local to 0
• Students: Are other outputs possible? How many?
30
fork()
#define DEAD -1
#include <unistd>
// define DEAD as before
// make fork() available
// Bryant & O'Halleron's "Computer Systems", chapter 8 void fork_sample2()
{ // fork_sample2 switch ( fork() ) { case 0:
// child process cout << 'C'; break; case DEAD: cout << " <><> DEAD process?"; break; default:
// parent process cout << 'P';
} //end switch
// in all cases, indicate end by emitting 'E' cout << 'E'; // note: no endl
} //end fork_sample2
31
fork()
[process] a.out
PECE[process] note no carriage return: no endl!
• Would CEPE be possible?
• Would EECP be possible?
• Would CPEE be possible?
• Would PECE be possible?
• Would ECPE be possible?
32
fork()
Challenge: How to modify fork_sample2() into for_sample2() ’ such that:
The end message for the parent process will be pe
And for the child it will be ce ?
The run the modified program fork_sample2() ’
[process] a.out
CPpece
CPcepe
PpeCce
CcePpe or: or: or:
… just few more outputs possible!
33
fork()
void fork_sample2()’
{ // fork_sample2’ pid_t pid; switch ( pid = fork() ) { case 0:
// child process cout << 'C'; break; case DEAD: cout << " <><> DEAD process?"; break; default:
// parent process cout << 'P';
} //end switch
// ok to omit break
// indicate end via string "pe" or "ce" if ( pid ) { cout << "pe";
}else{ cout << "ce";
} //end if
} //end fork_sample2’
// note: no endl
34
// doesn’t catch DEAD!
fork()
#include <iostream.h>
#include <unistd.h> int main( void )
{ // main int number = 0; // Note number on stack if ( 0 == fork() ) { cout << "PID: " << getpid()
<< " child process number = "
<< ++number << endl;
} //end if cout << "PID: " << getpid()
<< " exiting with number = "
<< --number << endl; return 0;
} //end main
// how many output lines, students?
// Which will be the different values for "number"?
35
fork()
[process] a.out
PID: 5587 exiting with number = -1
PID: 5588 child process number = 1
PID: 5588 exiting with number = 0
36
fork()
#include <iostream.h>
#include <unistd.h> int main()
{ // main int number = 100; // Note number on stack if ( 0 == fork() ) { // first child creation cout << "PID: " << getpid()
<< " 1st child, number = "
<< ++number << endl;
} //end if if ( 0 == fork() ) { // second child creation cout << "PID: " << getpid()
<< " 2nd child. PPID: "
<< getppid() << ". ++number = "
<< ++number << endl;
} //end if cout << "PID: " << getpid()
<< " exiting with number = "
<< --number << endl; return 0;
} //end main
37
fork()
[process] a.out
PID: 4432 exiting with number = 99
PID: 4433 1st child, number = 101
PID: 4434 2nd child. PPID: 4432. ++number = 101
PID: 4434 exiting with number = 100
PID: 4433 exiting with number = 100
[process] PID: 4435 2nd child. PPID: 4433. ++number = 102
PID: 4435 exiting with number = 101
had to enter CR-LF
[process]
38
fork()
#include <iostream.h>
#include <unistd.h> int main()
{ // main int number = 100; // Note number on stack if ( 0 == fork() ) { // first child creation cout << "PID: " << getpid()
<< " 1st child, number = "
<< ++number << endl;
} //end if if ( 0 == fork() ) { // second child creation cout << "PID: " << getpid()
<< " 2nd child. PPID: "
<< getppid() << ". ++number = "
<< ++number << endl; return 0 ; // Which program output now?
} //end if cout << "PID: " << getpid()
<< " exiting with number = "
<< --number << endl; return 0;
} //end main
39
fork()
#include <iostream.h>
#include <unistd.h> void fork_sample()
{ // fork_sample pid_t pid1 = fork() ; // pid1 not same as getpid() pid_t pid2 = fork() ; // pid2 not same as getpid() pid_t pid3 = fork() ; // pid3 not same as getpid() cout << " pid1 = " << pid1
<< " pid2 = " << pid2
<< " pid3 = " << pid3
<< endl;
} //end fork_sample int main()
{ // main fork_sample(); exit( 0 );
} //end main
40
fork()
[process] a.out
pid1 = 24583 pid2 = 24584 pid3 = 24585 pid1 = 24583 pid2 = 24584 pid3 = 0 pid1 = 24583 pid2 = 0 pid3 = 24587 pid1 = 0 pid2 = 24586 pid3 = 24588
[process] pid1 = 24583 pid2 = 0 pid3 = 0 pid1 = 0 pid2 = 24586 pid3 = 0 pid1 = 0 pid2 = 0 pid3 = 0 pid1 = 0 pid2 = 0 pid3 = 24589
had to enter CR-LF, came out early!
[process]
Why are there 8 lines of output?
Why do you see only 7 different pid numbers?
Which is the original parent ’s pid number?
Which line above is printed by original parent process?
41
fork()
Why are there 8 lines of output?
8 processes:
1 parent
3 child processes from parent: c1, c2, c3
2 child processes from c1: c12, c13
1 child process from c12: c13
1 child process from c2: c23
Why do you see only 7 different pid numbers?
original parent never acquires via getpid(), its own pid, so never prints it
Which is the original parent ’s pid number?
not known here; not programmed in!
Which line above is printed by original parent process?
the one printing 3 non-zero pid numbers, happens to be first line during another execution, this could be a different output line number
42
fork()
#include <iostream.h>
#include <unistd.h> void fork_sample()
{ // fork_sample pid_t pid1 = fork(); // 1 pid_t pid2 = fork(); // 2 pid_t pid3 = fork(); // 3 pid_t pid4 = fork(); // 4 printf( "I am %5d, parent= %5d, pid1= %5d, pid2= %5d, pid3= %5d, pid4= %5d\n”, getpid(), getppid(), pid1, pid2, pid3, pid4 );
} //end fork_sample int main()
{ // main fork_sample(); exit ( 0 );
} //end main
43
fork()
I am 22255, parent= 21528, pid1= 22256, pid2= 22257, pid3= 22258, pid4= 22260
I am 22260, parent= 22255, pid1= 22256, pid2= 22257, pid3= 22258, pid4= 0
I am 22256, parent= 22255, pid1= 0, pid2= 22259, pid3= 22261, pid4= 22264
I am 22258, parent= 22255, pid1= 22256, pid2= 22257, pid3= 0, pid4= 22265
I am 22264, parent= 22256, pid1= 0, pid2= 22259, pid3= 22261, pid4= 0
I am 22257, parent= 22255, pid1= 22256, pid2= 0, pid3= 22263, pid4= 22268
I am 22261, parent= 22256, pid1= 0, pid2= 22259, pid3= 0, pid4= 22266
I am 22265, parent= 22258, pid1= 22256, pid2= 22257, pid3= 0, pid4= 0
I am 22259, parent= 22256, pid1= 0, pid2= 0, pid3= 22262, pid4= 22267
I am 22266, parent= 22261, pid1= 0, pid2= 22259, pid3= 0, pid4= 0
I am 22263, parent= 22257, pid1= 22256, pid2= 0, pid3= 0, pid4= 22270
I am 22268, parent= 22257, pid1= 22256, pid2= 0, pid3= 22263, pid4= 0
I am 22269, parent= 22262, pid1= 0, pid2= 0, pid3= 0, pid4= 0
I am 22270, parent= 22263, pid1= 22256, pid2= 0, pid3= 0, pid4= 0
I am 22267, parent= 22259, pid1= 0, pid2= 0, pid3= 22262, pid4= 0
I am 22262, parent= 22259, pid1= 0, pid2= 0, pid3= 0, pid4= 22269
44
fork()
28
Shell Process
4 children of Parent
60
61 59
56
55
58
Parent Process
57
64
65 68 63
70
67 62
66
69
45
execve()
So why does the fork() command exist, if all it does is: make a copy of the current process?
fork() is just the first step of creating new processes , including the execution of any Unix commands, but also user programs, such as a.out
Therefore, code and data space have to be replaced to spawn off a new, separate process
Unix command execve() accomplishes this
A clever resource saving: pages in memory are not duplicated for new process; only modified pages are: AKA copy-on-write
Side-effect of any fork() command is eventual execution of two returns or exits, namely of parent and child process; correspondingly, execve() command creates no new return or exit
46
execve()
void fork_exec_sample( char * argv[], char * envp[] )
{ // fork_exec_sample pid_t pid = fork (); // parent + child exist now if ( 0 == pid ) {
// child process
// strange order of: 0 == . . .
cout << "run 'herb.o' program." << endl;
// must yield complete path; giving relative path also OK if ( execve ( "/u/herb/progs/process/herb.o", argv, envp ) < 0 ) { cout << "Aborting " << endl; exit( 0 );
} //end if
} //end if cout << "Created process " << pid << endl;
} //end fork_exec_sample int main( int argc, char * argv[], char * envp[] )
{ // main fork_exec_sample( argv, envp ); return 0;
} //end main
47
execve()
herb.o
// new program = process to be initiated by execve()
// source named: herb.cpp
#include <stdio.h> int main()
{ // main printf( "<> SUCCESS <> You executed Herb\n" ); return 0; // exits main()
} //end main
1.
Compile this, and rename: a.out
--> herb.o
2.
Move herb.o
into directory /u/herb/progs/process
[process] a.out
Created process 21010 students: why printed only once?
run 'herb.o' program.
[process] <> SUCCESS <> You executed Herb
48
1.
IBM literature about Unix 8: http://www.ibm.com/developerworks/aix/library/au-speakingunix8/
2.
Computer Systems, a Programmer ’s Perspective , Bryant and O ’Halleron , Prentice Hall © 2011, 2 nd ed.
3.
Modern Operating Systems , Andrew S. Tanenbaum , Prentice Hall © 2011, 2. ed.
4.
Operating System Concepts , Silberschatz and Galvin, Addison Wesley © 1998, 5. ed.
5.
Posix Threa d: https://computing.llnl.gov/tutorials/pthreads/#Thread
6.
Thread, Hyperthread, Process : http://decipherinfosys.com/HyperthreadedDualCore.pdf
7.
Intel Hyperthreading : http://www.intel.com/technology/itj/2002/volume06issue01/vol6iss1_hyper_threading_technology.
8.
Zombie process : http://en.wikipedia.org/wiki/Zombie_process
9.
Eliminate threading errors : http://download-software.intel.com/en-us/sites/default/files/eliminatethreading-errors_studioxe-evalguide.pdf
10.
Intel Timebase Utility for multi-threading and concurrency: http://software.intel.com/enus/forums/topic/304224
11.
MS multi-threading : http://msdn.microsoft.com/en-us/library/vstudio/172d2hhw.aspx
12.
Threading help : http://software.intel.com/en-us/articles/automatic-parallelization-with-intelcompilers
13.
Threading instructions from Intel: http://software.intel.com/en-us/articles/intel-guide-fordeveloping-multithreaded-applications
14.
MW Windows process and thread concept: https://msdn.microsoft.com/enus/library/windows/desktop/ms684841(v=vs.85).aspx
49