1.1 Background
Consider the scenario of reading from a file and transferring the data to another program over the network. This scenario describes the behaviour of many server applications, including Web applications serving static content, FTP servers, mail servers, etc. The core of the operation is in the following two calls:
read(file, user_buffer, len);
write(socket, user_buffer, len);
Figure 1 shows how data is moved from the file to the socket.
Behind these two calls, the data has been copied at least four times, and almost as many user/kernel context switches have been performed. Figure 2 shows the process involved. The top side shows context switches, and the bottom side shows copy operations.
1. The read system call causes a context switch from user mode to kernel mode. The first copy is performed by the DMA (Direct Memory Access) engine, which reads file contents from the disk and stores them into a kernel address space buffer.
2. Data is copied from the kernel buffer into the user buffer ,and the read system call returns. The return from the call causes a context switch from kernel back to user mode. Now the data is stored in the user address space buffer, and it can begin its way down again.
3. The write system call causes a context switch from user mode to kernel mode. A third copy is per- formed to put the data into a kernel address space buffer again. This time, though, the data is put into a different buffer, a buffer that is associated with sockets specifically.
4. The write system call returns, creating our fourth context switch. Return from write call does not guarantee the start of the transmission. It simply means the Ethernet driver had free descriptors in its queue and has accepted our data for transmission. Independently and asynchronously, a fourth copy happens as the DMA engine passes the data from the kernel buffer to the protocol engine. (The forked DMA copy in Figure 2 illustrates the fact that the last copy can be delayed).
As you can see, a lot of data duplication happens in this process. Some of the duplication could be eliminated to decrease overhead and increase performance. To eliminate overhead, we could start by eliminating some of the copying between the kernel and user buffers.

1.2 Overview and Technical Details
Your task in this lab is to implement zero-copy read and write operations that would eliminate the copying between the kernel and user buffers. You will develop a new library with a set of library calls that allow a user to:
• Open a file
• Read from the file without using a user buffer
• Write to the file without using a user buffer
• Reposition within the file
• Close the file
The user directly uses the kernel buffer provided by the library calls to read and write data.
Your implementation should NOT call read and write system calls or other library calls that wrap around read and write system calls. Calling read and write would involve some type of duplication of buffers. You should use the mmap system call in your implementation.

2 Exercises in Lab 4
The goal of this lab assignment is to produce a zero-copy IO library. All function and data structures names are prefixed by zc_. The library uses a data structure called zc_file (defined in zc_io.c) to maintain the information about the opened files and help in the reading and writing operations. You are required to use it and add any information needed to maintain the information about the opened files into this data structure.
For ex1 to ex3, operations on the same file will not be issued concurrently (i.e. you do not need to be concerned about synchronization). We will change this assumption in ex4 and bonus exercise. For all exercises, you may assume that there is no concurrent opening of the same file (the file is opened at most once at the same time, and the file is not modified outside the runner).
The provided runner implements a few testcases on reading and writing a file using the zc_io library. It is not exhaustive but will catch some common errors. If your implementation is correct, the runner will run successfully. Otherwise, it may segmentation fault, or print a “FAIL” message with the reason of the failure. You are also encouraged to implement your own program to test the library.

2.1 Exercise 1A: Zero-copy Read [1% + 1% demo or 2% submission]
You are required to implement four library calls to open/close and perform zero copy read from a file.

- zc_file *zc_open(const char *path)
Opens file specified by path and returns a zc_file pointer on success, or NULL otherwise. Open the file using the O_CREAT and O_RDWR flags.
You can use fstat() to obtain information (if needed) regarding the opened file.
-int zc_close(zc_file *file)
Flushes the information to the file and closes the underlying file descriptor associated with the file. If successful, the function returns 0, otherwise it returns -1. Free any memory that you allocated for the zc_file structure. You can use msync() flush copy of file in virtual memory into file.
-const char *zc_read_start(zc_file *file, size_t *size)
The function returns the pointer to a chunk of *size bytes of data from the file. If the file contains less than *size bytes remaining, then the number of bytes available should be written to *size. The purpose of zc_read_start is to provide the kernel buffer that already contains the data to be read. This avoids the need to copy these data to another buffer as in the case of read system call. Instead, the user can simply use the data from the returned pointer.
Your zc_file structure should help you keep track of a offset in the file. Once size bytes have been requested for reading (or writing), the offset should advance by size and the next time when zc_read_start or zc_write_start is called, the next bytes after offset should be offered.
Note that reading and writing is done using the same offset.
-void zc_read_end(zc_file file)
This function is called when a reading operation on file has ended.
It is always guaranteed that the function is paired with a previous call to zc_read_start. 
2.2 Exercise 1B: Zero-copy Write [1% + 1% demo or 2% submission] 
You are required to implement two library calls that allow writing to file: 

-char *zc_write_start(zc_file *file, size_t size)
The function returns the pointer to a buffer of at least size bytes that can be written. The data written to this buffer would eventually be written to file.
The purpose of zc_write_start is to provide the kernel buffer where information can be written. This avoids the need to copy these data to another buffer as in the case of write system call. The user can simply write data to the returned pointer.
Once size bytes have been requested for writing, the offset should advance by size and the next time when zc_read_start or zc_write_start is called, the next bytes after offset should be written. Note that reading and writing is done using the same offset.
File size might change when information is written to file. Make sure that you handle this case properly. See ftruncate. -void zc_write_end(zc_file *file)
This function is called when a writing operation on file has ended. The function pushes to the file on disk any changes that might have been done in the buffer between zc_write_start and zc_write_end. This means that there is an implicit flush at the end of each zc_write operation. You can check out msync() to help you with flushing.
It is always guaranteed that the function is paired with a previous call to zc_write_start.  Writing to a file using the zc_io library call should have the same semantic behaviour as observed in write system call. 
 2.3 Exercise 2: Repositioning the file offset [1%] 
You are required to implement one library call that allows changing the offset in the file: 
-off_t zc_lseek(zc_file *file, long offset, int whence)
Reposition at a different offset within the file. The new position, measured in bytes, is obtained by adding offset bytes to the position specified by whence.
whence can take 3 values:
• SEEK_SET: offset is relative to the start of the file
• SEEK_CUR: offset is relative to the current position indicator 
• SEEK_END: offset is relative to the end-of-file
The SEEK_SET, SEEK_CUR and SEEK_END values are defined in unistd.h and take the values 0, 1, and 2 respectively.
The zc_lseek() function returns the resulting offset location as measured in bytes from the be- ginningofthefileor(off_t) -1ifanerroroccurs.
zc_lseek() allows the file offset to be set beyond the end of the file (but this does not change the size of the file). If data is later written at this point, subsequent reads of the data in the gap (a “hole”) return null bytes ('\0') until data is actually written into the gap. (Please refer to Appendix B for a simple example on this.) Repositioning the file offset should have the same semantic behaviour as lseek system call. 

2.4 Exercise 3: Zero-copy file transfer [2%] 
You are required to implement the following library call: 
-int zc_copyfile(const char *source, const char *dest)
This function copies the content of source into dest. It will return 0 on success and -1 on failure. You should make use of the function calls you implemented in the previous exercises, and should not use any user buffers to achieve this. Do ftruncate the destination file so they have the same size.c
 2.5 Exercise 4: Readers-writers Synchronization [1%]
Exercises above assumed that the operations on the same file would be issued in sequence. In ex4 we lift this assumption and allow multiple reads and writes to be issued at the same time for the same instance of an open file.
You need to make sure that your zc_read_start, zc_write_start and zc_lseek executed on an open file follow the following rules:
• Multiple zc_read operations can take place at the same time for the same instance of the zc_file. 
• No other operation should take place at the same time with a zc_write or zc_lseek operation. 
• All operation issued while zc_write or zc_lseek is executing would block waiting to start. They
would start only once the zc_write or zc_lseek ends.
In other words, you should solve the readers-writers synchronization problem when multiple operations are issued at the same time for the same instance of an open file. You are not required to ensure that your solution is starvation-free.
While multiple readers can read at the same time, ensure that the offset variable of the file is protected and multiple zc_write_start or especially zc_read_start access and increment the offset variable one at a time. For eg. if two threads read 10 bytes each, with initial offset = 0, one of the threads should read the first 10 bytes, the other the next 10 bytes, and the final value of offset should be 20. 
Fill in the following template:
#include “zc_io.h”

// The zc_file struct is analogous to the FILE struct that you get from fopen.
struct zc_file {
// Insert the fields you need here.


/ Some suggested fields :
- pointer to the virtual memory space
- offset from the start of the virtual memory
- total size of the file
- file descriptor to the opened file
- mutex for access to the memory space and number of readers
*/
};

/*
* Exercise 1 *
/

zc_file zc_open(const char path) {
// To implement
return NULL;
}

int zc_close(zc_file file) {
// To implement
return -1;
}

const char zc_read_start(zc_file file, size_t size) {
// To implement
return NULL;
}

void zc_read_end(zc_file file) {
// To implement
}

char zc_write_start(zc_file file, size_t size) {
// To implement
return NULL;
}

void zc_write_end(zc_file file) {
// To implement
}

/
* Exercise 2 *
/

off_t zc_lseek(zc_file file, long offset, int whence) {
// To implement
return -1;
}

/
* Exercise 3 *
/

int zc_copyfile(const char source, const char dest) {
// To implement
return -1;
}

/
* Bonus Exercise *
*********/

const char zc_read_offset(zc_file file, size_t size, long offset) {
// To implement
return NULL;
}

char zc_write_offset(zc_file file, size_t size, long offset) {
// To implement
return NULL;
}