Dr. Mark Humphrys

School of Computing. Dublin City University.

Online coding site: Ancient Brain

coders   JavaScript worlds

Search:

Free AI exercises


Memory



Memory protection

Basic idea is that there is such a thing as a "bad" memory access.
User process runs in a different memory space to OS process (and other processes).

On multi-process system (whether single-user or multi-user) each process has its own memory space in which (read-only) code and (read-write) data live. i.e. Each process has defined limits to its memory space:



Q. Even if there are no malicious people, process memory areas need to be protected from each other - Why?


Above, each process has two registers - a base register and a limit register.
e.g. The base register for process 2 has contents 5200000. Its limit register has contents 3200000.
And then every memory reference is checked against these limits.
A simple model would look like this:


When reference is made to memory location x:

if x resolves to between base and (base+limit)
  return pointer to memory location in RAM 
else
  OS error interrupt
 


Q. Load values into base or limit registers are privileged instructions. Why?

As we shall see, in fact memory protection has evolved far beyond this simple model.





Map of memory (Task Manager)



Looking at memory use on Windows.
Here Windows 10 - Task Manager - Performance tab.



Click "Resource Monitor" in the above and you get further breakdowns. See "Memory" tab.
Sort by column. Hover over column names to see definitions.



Apple OS X has Activity Monitor.
OS X is Unix family.
You can also use ps on the Apple Terminal (command-line).
Image from here. Creative Commons.





Compile-time, Load-time and Run-time

The transformations made to the program from the code written by a human to instructions executed by the CPU.

  1. Compiled program (e.g. typical use of C++, Java):
    1. Program typically written "in factory" in HLL, with comments, English-like syntax and variable names, macros and other aids to the programmer.
    2. Program is then compiled "in factory" into an efficient, machine-readable version, with all of the above stripped, optimisations made, and everything at "compile-time" translated and resolved as much as possible, so as little as possible needs to be done when it is run.
    3. At different times, on different machines, and with different other programs already running, the user will "launch" or "run" a copy of this compiled program. Any further translation that could not be done at compile-time will now be done at this "load-time". Then the algorithm itself actually starts to run.
    4. Any change that has to be done while the algorithm has already started to run, is a change at "run-time".

  2. Interpreted program (e.g. typical use of Javascript, Shell):
    1. No compilation step. Source code itself is read at "load-time".



Memory mapping (or binding)

Consider what happens with the memory allocation for a program's global variables.

Programmer defines global variable "x". Refers to "x" throughout his High-Level Language code:

	   do 10 times
		print(x)
		x := x+7
Clearly when the program is running, some memory location will be set aside for x and the actual machine instructions will be:
	   do 10 times
		read contents of memory location 7604 and print them
		read contents of loc 7604, add 7, store result back in loc 7604

How "x" maps to "7604" can happen in many ways:
  1. Source code: The programmer maps x to 7604 in the source code.

  2. Compile-time: If x is mapped to 7604 at this point (or before) then the program can only run in a certain memory location. (e.g. DOS - single user, single process)

  3. Load-time: The compiler might map x to "start of program + 604". Then when the program is launched, the OS examines memory, decides to run the program in a space starting at location 7000, resolves all the addresses to absolute numerical addresses, and starts running.

  4. Run-time: Even after the program has started, we may change the mapping (move the program, or even just bits of it, in memory), so that next time round the loop it is:
    	read contents of memory location 510 and print them
    	read contents of loc 510, add 7, store result back in loc 510
    
    Obviously, OS has to manage this!
    User can't. Programmer can't. Even Compiler designer can't.




Questions

Question - Why is it not practical for the OS to say to a user on a single-user system: "I'm sorry, another program is occupying memory space 7000-8000. Please terminate the other program before running your program"

Question - Why is it not practical for the OS to say to a user on a multi-user system: "I'm sorry, another program is occupying memory space 7000-8000. Please wait until the other program terminates before running your program"

Question - Why can't program writers simply agree on how to divide up the space? Some programs will take memory locations 7000-8000. Other programs will take locations 8000-9000, etc.

Question - The user's program has started. The OS needs to change what memory locations things map to, but binding is done at load-time. Why is it not practical for the OS to say to the user: "Please reload your program so I can re-do the binding"

Question - Why is it not practical to say to a user: "Please recompile your program"




Partial load of programs (desired)

Consider: Does the whole program have to be loaded into memory when it is run?
Many applications contain vast amounts of functionality that is rarely or never used.

e.g. Run web browser for years. Never once click on some menu options.
But was the code behind those menu options loaded into memory every single time, for years?

How can we load only part of a program?



Many applications contain functionality that is rarely or never used.





Pre-load programs and keep old ones in memory (desired)

In apparent contrast to only loading part of a program into RAM before starting execution is the following idea, where we pre-load things (and keep old things) in memory that we may not even need.


Above we saw this typical memory map of a Windows 10 system.
We saw there is not much free memory, but I said that "Standby" is really free memory.
I will now explain this.




Contiguous memory allocation

Memory management
Processes are large. Take up a lot of memory.
Imagine if this has to be contiguous - all in one unbroken chunk from memory location A to location A+n.
End up with a memory map like this:

  
Start address End address Process
0 1000 OS
1000 1200 free
1200 2000 Process 11
2000 2100 Process 12
2100 2400 Process 13
2400 3300 free
3300 3800 Process 3
3800 4500 free

  
Problems with contiguous memory spaces:
  1. Programs starting and ending all the time. End up all over memory. Will have gaps everywhere.
  2. When load program, have to find contiguous space big enough to hold it.
  3. "Holes" develop in memory. Lots of memory free but not contiguous so can't be used.
  4. When process asks for more memory at run-time, might not be any free above it. (See figure above.)
  5. How do we load only part of a process as opposed to whole thing? How do we swap out only part of a process as opposed to whole thing? We need a mechanism for identifying parts of a process.
  6. If you load only part of the program at launch, you still must reserve all possible space ever needed above it.



Paging

The modern OS does Paging.
Non-contiguous memory spaces.
Program broken (automatically by OS) into lots of small chunks that flow like water into every crack and gap in memory, and out to disk (swapping) and back in.
  


Memory mapping with paging

  


Paging means the program can be physically located all over memory.
From here.


  

Example

e.g. Program has logical memory space: page 0, .., page 3. Physical memory frame 0, .., frame 7. Page table:
page -> frame
0 -> 1
1 -> 4
2 -> 3
3 -> 7
Don't have to be contiguous, don't even have to be in order.

Page 0, offset 2,   maps to:   Frame 1, offset 2,
Page 3, offset n,   maps to:   Frame 7, offset n,
etc.

  


Paging advantages

Paging advantages:
  1. Holes and fragmented memory are no problem.
  2. When load program, do not need to search for contiguous space, only enough memory in different locations.
  3. If a bit of memory is available anywhere, program can use it.
  4. Can easily ask for more memory, in any location.
  5. Can easily load only parts of program.
  6. Can easily swap only parts of process to disk.
  7. Makes maximal use of memory. Indeed, we would find modern machines almost unusable without paging.
  8. Separation of process memory from other processes. Process cannot address memory it doesn't own. Has no language to even describe such memory.
  
Paging disadvantages:
  1. Overhead - have to resolve addresses at run-time.




Paging and Swapping (Page faults)

Swapping pages out to disk:
Program addresses a logical space. This logical space maps to pages scattered all over memory and on disk. Much of program can be left on disk if not needed. OS hides this from user and programmer.

Only need: Active processes in memory.
In fact, only need: Active bits of active processes in memory.
OS decides what bits of programs stay in memory at any given time.
If page not in memory it is not an error, OS just fetches it. This is called a Page fault.

  



Major and minor page faults

Minor (soft) page fault - Page is actually in RAM though was not marked as part of the "working set" for this process.
e.g. Page was pre-fetched in case needed.
e.g. Page was brought into RAM for another process.
e.g. Old page marked free but not overwritten.
Can resolve minor page fault without disk read.

Major (hard) page fault - Need a disk read. Takes much longer to return.




Viewing page faults


On Linux we can look at major and minor page faults using ps:


# show min_flt
# and maj_flt

$ ps -A -o user,pid,ppid,min_flt,maj_flt,comm,args



# sorted descending by number of minor page faults:

$ ps -A -o user,pid,ppid,min_flt,maj_flt,comm,args | sort -nr -k 4

USER       PID  PPID  MINFL  MAJFL COMMAND         COMMAND
root      1198     1 20509703    0 xe-daemon       /bin/bash /usr/sbin/xe-daemon  
root      2920     1 10267099    0 sshd            /usr/sbin/sshd  
root      3172     1 6000382     1 xinetd          /usr/sbin/xinetd  
root     11034     1 1176904     0 fail2ban-server /usr/bin/python /usr/bin/fail2ban-server  
root      5293     1 975768     28 nscd            /usr/sbin/nscd
root      3149     1 193727      0 cron            /usr/sbin/cron




On Windows we can look at combined page faults using Windows Task Manager:


Note "Page faults" column on Windows 10 Task Manager.
"Page faults" here combines both hard and soft page faults.





Where to swap to?

Swap to an area of disk dedicated to swapping. Maybe entirely separate to file system.

On UNIX/Linux, swap to a partition that has no file system on it.
User doesn't have to see it. Doesn't have file names. Not addressable by user.

On Windows, swap to the named file   C:\pagefile.sys  
Might move this onto its own partition.




Windows 10 - File Explorer - C drive.
View - Show Hidden items
Then can see C:\pagefile.sys (size here 8 G).




Hibernation

Another form of dumping memory to disk is hibernation. Power down with programs open. Write dump of memory to disk. When power up, read image of memory back from disk.


ancientbrain.com      w2mind.org      humphrysfamilytree.com

On the Internet since 1987.      New 250 G VPS server.

Note: Links on this site to user-generated content like Wikipedia are highlighted in red as possibly unreliable. My view is that such links are highly useful but flawed.