CS444 Class 8  Processes

Handout: Chapter 1 Important Figures

 

process = program in execution, running in a virtual machine provided by the OS.

 

Last time: looked at a simple program that used the UNIX fork system call to get its whole address space copied, ending up with two executions running on the system together with completely separate memory images, but identical at the moment of the fork.  So both resulting processes can see the old memory values, but not each other’s changes to them.

 

Execution of a program across a call to fork()—

 

<picture of user address space, kernel in cloud>

 

At fork, second address space shows up, a copy of first:

 

<picture of two user address spaces, kernel in cloud>

 

After the fork, parent and child are completely separate in terms of available memory—they can’t spy on each other.  This is true between any two user processes.

 

The mystery of the addresses in user spaces: the copied program uses exactly the same addresses as the original, but they point to different values in memory after one of the processes has change a value. How can this work?

 

Answer: we will cover this carefully in the memory management topic. The secret is in memory mapping: we aren’t really accessing memory location 0x40000 or whatever, but rather some mapped location in physical memory.  The two processes have different memory maps and are using separate pages in physical memory, i.e., separate memory chips.

 

Question: can UNIX processes ever actively share memory?

Answer: yes, via special “shared memory” system calls, considered advanced programming.  The memory involved would be in that big area above the heap and below the stack, where the DLLs hang out.  DLLs themselves are largely shared between processes (all the code).

 

Question: fork() produces another process, what about threads?

Answer: threads share the same process image, so thread programming is very tricky—it’s easy for one thread to damage data needed by another thread.  The processes are “bottled up” from one another each in their own address space.  Java supports threads and the synchronization features needed. More on this later.

 

 

 

UNIX Exec system call

 

Execve, or exec for short, replaces the current process image with one constructed from the contents of an executable file, i.e. it runs a program in the current process.  This means the old program in the process is wiped out, replaced by the one specified as an argument to the exec system call. A simple (UNIX) program would be:

 

int main(int argc, char *argv[])

{

    int status;

 

    if (fork()) {

        waitpid(-1, &status, 0);  /* parent waits for child */

        printf("status=%d\n",status);

    } else {  /* run date in the child */

        execve("/usr/bin/date", argv, 0);

        /* never get here, unless execve fails */

    }

}

Here we are passing argv along from our old process to be the argv of the new image, since execve wants a non-trivial argvWaitpid(-1, ...) waits for any child to terminate, so the parent waits for date to run, print out the date, and exit, and then prints out “status = 0”, the success status.  Note that the stdout, stdin, and stderr fd’s pass live through the exec, so date has a good stdout to use and we see the output of date on the screen.

 

Parent processes are expected to pick up the child exit status—it’s their “parental responsibility”. One way to do this is to wait for any-child termination by calling system call waitpid, as shown above.  The parent can do real work before calling waitpid.  If the child exits while the parent is still working, that’s no real problem. It becomes a “zombie”, a runt process that just holds the exit status. The zombie disappears entirely when the parent picks up its exit status.

 

We realize that the parent and child are running completely separately (in terms of accessible memory) after the fork. The child doesn’t “live at home” under the direct supervision of the parent. Instead it takes off immediately and rattles around on its own, and when it dies, it’s as if the system sends a postcard to the parent about its exit status. The parent is expected to be still alive when the child exits, and pick up the exit status. If the parent creates a lot of children and then ignores them, the system can get clogged up with thousands of zombies.

 

Stripped-down shell: look at code in Tan., pg. 54.  Similar to above with loop around it and general command instead of just “date.”

 

Tanenbaum’s address space pictures—addresses run from bottom to top, unlike my pictures on the board that run from left to right--

Pg. 51: Highest address is FFFFFFFF: 32 bit system

Pg. 56: Highest address is FFFF: 16-bit system.

 

We have covered UNIX process management system calls: fork, exec, waitpid, exit.  Windows has no fork or exec system calls, only the combination CreateProcess system call that takes the executable file as one of many parameters, and creates a new process running that program.  We could write a “wshell”, something like the UNIX shell with CreateProcess instead of fork and exec, and WaitForSingleObject (using the process handle returned by CreateProcess) after the CreateProcess to make the parent wait for termination of the program, itself done by ExitProcess.

 

Look at the system call conversion table, pg. 61:

 

Summary of First Part of Course

 

The OS provides a virtual machine for programs

UNIX and Windows have similar virtual machine setups—

 

User programs execute in CPU user mode, interrupts enabled. They can’t do the privileged instructions. They are “bottled up” in their virtual machines.

 

System call instruction: each instruction set has a special instruction, generic TRAP in Tanenbaum, INT n/SYSENTER/SYSCALL for x86, SWI for ARM. 

·         changes the CPU from user mode to kernel/supervisor mode, enabling the privileged instructions such as cli

·         saves the old CPU state, so the old user execution can be resumed at the next instruction in user code (by IRET on x86)

·         works like an interrupt cycle, except it doesn’t change IF. Gets the handler address from IDT and puts it in EIP, causing the execution of the system call handler in the kernel.

 

Below the OS, the hardware, so the OS code needs to run the hardware, including of course the interrupt system.  

 

Also, don’t forget 2 ways to look at system call—function prototype, part of system API, and single special instruction in user program image.

 

Idea of starting new job, finding out they are using a special OS, say for embedded programming.  What do you ask?  Does it provide a flat address space?  timesharing? automatic stack? and what are the system calls?

 

 

Tanenbaum Chap. 2: Processes and Threads, on a uniprocessor (see pg. 85)

 

sequential process = process, with emphasis on its single program counter showing where the CPU is in the code—the CPU just “follows its nose” through the program code

multiprogramming = multitasking = OS method of switching one CPU from process to process, so that for each process seems to have its own (slower) CPU.  Almost synonymous with “timesharing”, except that it is more specifically single-CPU-oriented.  Of course a system could have several CPUs each doing multiprogramming.

 

How Timesharing works

Each process gets to use the CPU for a while, maybe 50 ms (milliseconds), then the OS reassigns it to another process to use.  Thus multiple processes can make progress in their programs alongside each other on the system.

 

Look at Fig. 2-1(c).  Shows 4 processes executing in “round-robin” fashion—over and over in some pattern.  Of course the pattern is usually more irregular.  Each little interval of running is typically about 50 ms long, the “quantum” of CPU for the process.  These four processes are all running code that computes constantly (maybe “ray tracing”), so they all want to use the CPU as much as possible.

 

Processes vs. programs.  Programs are algorithms embodied in machine code and usually stored in an executable file.  Processes are alive on the system and are using memory, CPU, etc.

 

Example of program that forks.  End up with two processes each running the same program.  For Windows, could run the same program twice and get the same situation.

 

Process Creation and Termination

 

All processes are born by the process-create syscall (fork, CreateProcess), except the very first.  On bootup, the kernel is running as a standalone program, and morphs itself into a proper process.

 

Process Termination:  usually by (exit, ExitProcess).  These both have a parameter for an error/success code.  (In UNIX, this code is reported to the parent in waitpid, and then forgotten by the kernel.  In Win2K, it is reported via the process handle—I’m not sure how it is ever garbage-collected)

 

Other ways processes terminate:

 

Zombies.  If a process exits in UNIX, and its parent does not pick up its exit status, it stays as a “zombie” in the system.  It no longer has a virtual machine, so it isn’t using resources, but you can see it in “ps –a” output with a Z  in the “S” (process state) column.  It will stay there until the system is rebooted.  Like a proper zombie, you can’t kill it by normal methods.  To avoid zombies, make your parent processes do a wait or waitpid to pick up the status.