Sie sind auf Seite 1von 20

BIRLA INSTITUTE OF TECHNOLOGY AND

SCIENCE

Subject: Advanced Operation System

Topic: Implement Write-invalidate protocol to


demonstrate the memory coherency
services

Group: Nagarjuna E
Puneeth SM
Sonal HP
Sangeeta Patil
1 Introduction

In a shared memory multiprocessor with a separate cache memory for


each processor , it is possible to have many copies of any one
instruction operand (one copy in the main memory and one in
each cache memory).

When one copy of an operand is changed, the other copies of the


operand must be changed also.Cache-coherence takes care

When a specific data is shared by several caches and a processor


modifies the value of the shared data, the change must be
propagated to all the other caches which have the same copy of the
data. Otherwise, it may violate a cache coherency. The notification of
data change can be done by bus snooping. All the snoopers monitor
every transaction on a bus. If a transaction modifying a shared cache
block appears on a bus, all the snoopers check whether their caches
have the same copy of the shared block. If a cache has the copy of
the shared block, the corresponding snooper performs an action to
ensure cache coherency. The action can be a flush or an invalidation
of the cache block. It also involves a change of cache block status
depending on the cache coherence protocol.
Snooping

First introduced in 1983,snooping is a process where the individual


caches monitor address lines for accesses to memory locations that
they have cached.The write invalidate protocols and write update
protocols make use of this mechanism.

For the snooping mechanism, a snoop filter reduces the snooping


traffic by maintaining a plurality of entries, each representing a cache
line that may be owned by one or more nodes. When replacement of
one of the entries is required, the snoop filter selects for replacement
the entry representing the cache line or lines owned by the fewest
nodes, as determined from a presence vector in each of the entries. A
temporal or other type of algorithm is used to refine the selection if
more than one cache line is owned by the fewest number of nodes.

Coherence protocols

Coherence Protocols apply cache coherence in multiprocessor


systems.The intention is that two clients must never see different values
of the same shared data.

The protocol must implement the basic requirements for coherence. It


can be tailor made for the target system/application.
Protocols can also be classified as Snooping(Snoopy/Broadcast) or
Directory based. Typically, early systems used directory based protocols
where a directory would keep a track of the data being shared and the
sharers. In Snoopy protocols , the transaction request.
(read/write/upgrade) are sent out to all processors. All processors snoop
the request and respond appropriately.

Write Propagation in Snoopy protocols can be implemented by either of


the following :

Write Invalidate : When a processor writes on a shared cache block, all


the shared copies in the other caches are invalidated through bus
snooping. This method ensures that only a copy of a data can be
exclusively read and written by a processor. All the other copies in other
caches are invalidated.

Write Update : when a write operation is observed to a location that a


cache has a copy of, the cache controller updates its own copy of the
snooped memory location with the new data.

If the protocol design states that whenever any copy of the shared data
is changed, all the other copies must be "updated" to reflect the change,
then it is a write update protocol. If the design states that on a write to a
cached copy by any processor requires other processors to
discard/invalidate their cached copies, then it is a write invalidate
protocol.

Various models and protocols have been devised for maintaining


coherence, such as MSI, MESI (aka Illinois), MOSI, MOESI, MERSI, MESIF,
write-once, and Synapse, Berkeley, Firefly and Dragon protocol.

Write-Invalidate and Write-Through

In this simple protocol the memory is always consistent with the most
recently updated cache copy. Multiple processors can read block copies
from main memory safely until one processor updates its copy. At this
time, all cache copies are invalidated and the memory is updated to
remain consistent. The block statesand protocol are summarized in Table
Example: Consider a bus-based shared memory with two processors P
and Q as shown in Figure 4.6. Let us see how the cache coherence is
maintained using Write- Invalidate Write-Through protocol. Assume that
that X in memory was originally set to 5 and the following operations
were performed in the order given:

(1)P reads X; (2) Q reads X; (3) Q updates X; (4) Q reads X; (5) Q


updates X; (6) P updates X; (7) Q reads X.

Write-Invalidate and Write-Back


In this protocol a valid block can be owned by memory and shared in multiple caches
that can contain only the shared copies of the block. Multiple processors

can safely read these blocks from their caches until one processor updates its copy. At
this time, the writer becomes the only owner of the valid block and all other copies are
invalidated. The block states and protocol are summarized in Table

Example

Consider the shared memory system of Figure 4.6 and the following

operations: (1) P reads X; (2) Q reads X; (3) Q updates X; (4) Q reads X; (5) Q updates X;
(6) P updates X; (7) Q reads X. Table 4.6 shows the contents of memory and the two
caches after the execution of each operation when Write-Invalidate Write-Back was used
for cache coherence. The table also shows the state of the block containing X in Ps
cache and Qs cache.
Implementation :

One of the possible implementations is as follows:

The cache would have three extra bits:

V valid
D dirty bit, signifies that data in the cache is not the same as in
memory

S shared

Each cache line is in one of the following states: "dirty" (has been
updated by local processor), "valid", "invalid" or "shared". A cache line
contains a value, and it can be read or written. Writing on a cache line
changes the value. Each value is either in main memory (which is very
slow to access), or in one or more local caches (which is fast). When a
block is first loaded into the cache, it is marked as "valid".

On a read miss to the local cache, the read request is broadcast on the
bus. All cache controllers monitor the bus. If one has cached that address
and it is in the state "dirty", it changes the state to "valid" and sends the
copy to requesting node. The "valid" state means that the cache line is
current. On a local write miss (an attempt to write that value is made,
but it's not in the cache), bus snooping ensures that any copies in other
caches are set to "invalid". "Invalid" means that a copy used to exist in
the cache, but it is no longer current.

For example, an initial state might look like this:

Tag | ID | V | D | S
---------------------
1111 | 00 | 1 | 0 | 0
0000 | 01 | 0 | 0 | 0
0000 | 10 | 1 | 0 | 1
0000 | 11 | 0 | 0 | 0
After a write of address 1111 00, it would change into this:

Tag | ID | V | D | S
---------------------
1111 | 00 | 1 | 1 | 0
0000 | 01 | 0 | 0 | 0
0000 | 10 | 1 | 0 | 1
0000 | 11 | 0 | 0 | 0

The caching logic monitors the bus and detects if any cached memory is
requested. If the cache is dirty and shared and the bus requests that
memory, the snooping elements will supply the value from the cache
then notify every unit that needs that memory, that the memory was
updated. When the other units are notified of the updated cache, they
will turn off the valid bit for their cache of that variable. Thus the original
cache will be marked as exclusive (S bit would be zero)

When invalidating an address marked as dirty (i.e. one cache would have
a dirty address and the other cache is writing) then the cache will ignore
that request. The new cache will be marked as dirty, valid and exclusive
and that cache will now take responsibility for the address.

MSI Protocol
In computing, the MSI protocol - a basic cache-coherence protocol -
operates in multiprocessor systems. As with other cache coherency
protocols, the letters of the protocol name identify the possible states in
which a cache line can be.

In MSI, each block contained inside a cache can have one of three possible states:

Modified: The block has been modified in the cache. The data in the cache is then
inconsistent with the backing store (e.g. memory). A cache with a block in the "M" state has
the responsibility to write the block to the backing store when it is evicted.

Shared: This block is unmodified and exists in read-only state in at least one cache. The
cache can evict the data without writing it to the backing store.

Invalid: This block is either not present in the current cache or has been invalidated by a
bus request, and must be fetched from memory or another cache if the block is to be stored
in this cache.[1]
Design and Implementation
of MESI Coherency Protocols
using JAVA

APPENDIX

Code for the Program of Cache Simulator in Java:

package sim;

import
java.io.BufferedWriter;
import java.io.File;
import
java.io.FileWriter;
import
java.io.IOException;
import
java.text.DateFormat;
import
java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Timer;
import
java.util.TimerTask;

import sim.controller.CacheController;
import
sim.controller.MESICacheController;
import sim.enumerators.CacheProtocol;
import sim.enumerators.Level;
import
sim.interconnect.SnoopyBus;
import sim.processor.Cache;
import sim.processor.Core;

/**
* Main class of the MESI cache simulator.
*/
public class CacheSimulator {
/**
* list of controllers. Used for printing test statistics
*/
static List<CacheController> cntrlrs = new ArrayList<CacheController>();

/**
* main method of the cache simulator. This method takes the number of cores as command
* line argument and creates that many number of threads. Each thread represents a processor core.
* {@link Core} sends out random read and write requests to its private cache. The cache controller
* can be either {@link MESICacheController} depending on the
* {@link CacheProtocol} selected. The test statistics such as total requests generated, read hits,
* write hits, read miss and write miss are printed and saved to a log file. The time for which
* simulator must run can also be specified in this method.
* @param args - args[0] - the number of cores to be created.
*/
public static void main(String[] args) {
if(args.length !=1){
System.out.println("Invalid number of arguments!");
return;
}

//the number of cores to be created.


final int numCores = Integer.parseInt(args[0]);

//l1 cache size.


int l1CacheSize = 1000;

//the cache protocol. Possible values are MESI.


final CacheProtocol protocol = CacheProtocol.MESI;

//milliseconds after which the application must stop.


int endInMilis = 1*60*1000;

final int mins = endInMilis/(60*1000);

System.out.println("Running the application for protocol "+protocol+"...." +


"Will end in "+mins+" minutes");

//create a bus object which handles the traffic between caches. Any communication
//in between caches is synchronised so as to avoid data races.
SnoopyBus bus = new SnoopyBus();

//create a cache object representing the L2 cache.


Cache l2Cache = new Cache(-1,Level.L2);

//connect L2 cache to the bus


bus.setL2Cache(l2Cache);

//create a number of cores and start sending random read and write requests.
for(int i=0;i<numCores;i++){

//create a core object


Core core = new Core(bus,protocol,l1CacheSize);
cntrlrs.add(core.getCacheController());

//start the runnable Core in a new thread.


new Thread(core).start();
}

//create a new timer to stop execution after specified time.


Timer timer = new Timer();

//schedule a task to stop execution after specified time and print test statistics.
timer.schedule(new TimerTask() {

@Override
public void run()
{ System.out.println("Exiting...."
);
DateFormat formatter = new SimpleDateFormat("yyyy_MM_dd_hh_mm");
String filename = formatter.format(new Date())+"_"+protocol+"_"+mins+"_"+numCores;
try {
//create a writer for a new log file
BufferedWriter writer = new BufferedWriter(new FileWriter(new File(filename)));
long readHits = 0;
long writeHits = 0;
long readMiss = 0;
long writeMiss = 0;
long totalRequests = 0;
long l2AccessCount = 0;

//write the test statistics to the log file.


for (CacheController ctrlr : cntrlrs)
{ readHits =
readHits+ctrlr.getReadHits();
writeHits = writeHits+ctrlr.getWriteHits();
readMiss = readMiss +ctrlr.getReadMiss();
writeMiss = writeMiss+ctrlr.getWriteMiss();
totalRequests = totalRequests +ctrlr.getTotalRequests();
l2AccessCount = l2AccessCount+ctrlr.getL2AccessCount();
}
String total = "Requests "+totalRequests+" Read Hits: "+readHits
+" Read Miss: "+readMiss+" Write Hits: "+writeHits
+" Write Miss: "+writeMiss+" L2 Accesses: "+l2AccessCount;
System.out.println(total);
writer.write("------Total----");
writer.write("\r\n");
writer.write(total);
writer.write("\r\n");
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
System.exit(1);
}
}, endInMilis);
}
}

(tag);

//return the value stored in the selected cache line at the specified index.
CacheLine cacheLine = cache.getCacheLines().get(tag);
return cacheLine.getValue(index);
}

/**
* The cache controller implementing MESI protocol

*/
public class MESICacheController extends CacheController{

/**
* Constructor.
*/
public MESICacheController(Cache cache,SnoopyBus bus) {
this.cache = cache;
this.bus = bus;
}

/**
* load the cache line from other L1caches or L2Cache.
* Update status to Shared if loaded from another L1,
* exclusive if loaded from L2
* @param tag - the address to be loaded to cache
*/
protected void load(long tag) {

//create a new cache line


CacheLine newCacheLine = new CacheLine();
newCacheLine.setTag(tag);

//create object for loaded cache line


CacheLine loadedCacheLine = null;

synchronized (bus) {
//broadcast load request to other l1 caches
List<CacheLine> cacheLines = bus.getCacheLineCopiesFromOtherL1Caches(this.cache, tag);
//if there are no copies in other L1 caches, load the cache line from L2 cache.
if (cacheLines.isEmpty()) {

//increment the l2 access count.


l2AccessCount++;

//load the cache line from l2 cache


loadedCacheLine = bus.getCacheLineFromL2(tag);

//as the cache line is loaded from L2, it is exclusive to this cache
//and can be marked as Exclusive.
loadedCacheLine.setCacheState(CacheState.EXCLUSIVE);
newCacheLine.setCacheState(CacheState.EXCLUSIVE);
} else {

//take the first copy in the copies returned from other L1 caches.
loadedCacheLine = cacheLines.get(0);

//if the copy is already in MODIFIED state, write it to L2 before it is loaded


if (loadedCacheLine.getCacheState() == CacheState.MODIFIED)
{ store(tag);
}

//update the status to Shared for all other L1 caches


//& L2 Cache holding the same address
bus.markCacheLineShared(tag);
}
//update the values in the new cache line with values from the loaded cache line.
newCacheLine.setValues(loadedCacheLine.getValues());
}
//add the new cache line to the cache.
cache.getCacheLines().put(tag, newCacheLine);
}

/**
* bus that acts as interconnect between L1 and L2 caches and maintains cache coherency

*
*/
public class SnoopyBus {

/**
* the shared L2 cache
*/
private Cache l2Cache = null;

/**
* the list of private L1 caches present in the system
*/
private List<Cache> l1Caches = new ArrayList<Cache>();

/**
* adds new L1 cache
* @param cache
*/
public void addNewL1Cache(Cache cache) {
l1Caches.add(cache);
}

/**
* gets all L1 caches
* @return list of L1 caches
*/
public List<Cache> getL1Caches() {
return l1Caches;
}

/**
* get the L2 cache
* @return L2 cache
*/
public Cache getL2Cache() {
return l2Cache;
}

/**
* sets the L2 cache connection
*/
public void setL2Cache(Cache l2Cache) {
this.l2Cache = l2Cache;
}

/**
* invalidate all cache lines which are modified
*/
public synchronized void invalidateCacheLine(long tag) {
for(Cache l1Cache: l1Caches){
CacheLine cacheLine = l1Cache.getCacheLines().get(tag);
if(cacheLine != null)
{ cacheLine.setCacheState(CacheState.INVALID);
}
}
}

/**
* mark a cache line as shared in all the caches holding it.
* @param tag - the address tag
*/
public void markCacheLineShared(long tag) {
for(Cache l1Cache: l1Caches){
CacheLine cacheLine = l1Cache.getCacheLines().get(tag);
if(cacheLine != null)
{ cacheLine.setCacheState(CacheState.SHARED);
}
}
}

/**
* get the copies of the same cache line from other processors.
* @param cache - the cache requesting other copies
* @param tag - the tag of cache line
* @return the list of cache line copies.
*/
public List<CacheLine> getCacheLineCopiesFromOtherL1Caches(Cache cache,long tag) {
List<CacheLine> cacheLines = new ArrayList<CacheLine>();
for (Cache l1Cache :l1Caches) {
if(l1Cache == cache)continue;
CacheLine cacheLine = l1Cache.getCacheLine(tag);
if(cacheLine != null){
if(cacheLine.getCacheState() != CacheState.INVALID)
{ cacheLines.add(cacheLine);
}
}
}
return cacheLines;
}

/**
* get a cache line from L2 cache.
* @param tag - the tag of the cache line.
* @return the retrieved cache line
*/
public CacheLine getCacheLineFromL2(long tag) {
CacheLine loadedCacheLine;

//if the core is requesting the cache line for the first time
//create a new cache line in L2, fill it with random integer
//values and return it
if (l2Cache.getCacheLine(tag) == null) {

//create an array of random integers.


long [] values = new long [8];
for(int i = 0;i<8;i++){
values[i] = new Random().nextInt(10000) + 10000;
}

//create a new cache line in L2


CacheLine cacheLine = new CacheLine();
cacheLine.setCacheState(CacheState.EXCLUSIVE);
cacheLine.setTag(tag);
cacheLine.setValues(values);
l2Cache.getCacheLines().put(tag, cacheLine);
}
//return the cache line retrieved from L2.
loadedCacheLine = l2Cache.getCacheLine(tag);
return loadedCacheLine;
}
}
/**
* The class representing a cache.

*
*/
public class Cache {

/**
* a map holding the address tag and cache lines
*/
private Map<Long, CacheLine> cacheLines;
/**
* maximum size of the cache.
*/
private int cacheSize;

/**
* the level of the cache.In this implementation the possible values are L1 and L2.
*/
private Level level;

/**
* constructor.
* @param size - cache size
* @param level- cache level.
*/
public Cache(int size, Level level) {
cacheLines = new HashMap<Long, CacheLine>();
this.cacheSize = size;
this.level = level;
}

/**
* get the cache line for a given tag
* @param tag - the address tag
* @return the cache line
*/
public CacheLine getCacheLine(long tag) {
return cacheLines.get(tag);
}

/**
* get all cache lines in the cache.
* @return cacheLines
*/
public Map<Long, CacheLine> getCacheLines() {
return cacheLines;
}

/**
* get the maximum size of the cache.
* @return cache size
*/
public int getCacheSize() {
return cacheSize;
}

/**
* get the cache level
* @return the cache level.
*/
public Level getLevel() {
return level;
}
}

/**
* The class representing a cache line.

*
*/
public class CacheLine {

//the address tag


private long tag;

//cache line is 64 bytes. So it can hold 8 long values(long = 8 bytes)


private long[] values = new long[8];

//the state of the cache line.


private CacheState cacheState;

/**
* @return the tag
*/
public long getTag() {
return tag;
}

/**
* @param tag the tag to set
*/
public void setTag(long tag)
{ this.tag = tag;
}

/**
* @return the value
*/
public long getValue(int index) { return
values[index];
}

/**
* @param value the value to set
*/
public void setValues(long[] values)
{ this.values = values;
}

/**
* @return the cacheState
*/
public CacheState getCacheState()
{ return cacheState;
}

/**
* @param cacheState the cacheState to set
*/
public void setCacheState(CacheState cacheState)
{ this.cacheState = cacheState;
}

/***
* returns a string representation of the object
*/
@Override
public String toString() {
return tag+" , "+values+" , "+cacheState;
}

/**
* returns all values in the selected cache line as an array.
* @return array of integers
*/
public long[] getValues() {
return values;
}

Das könnte Ihnen auch gefallen