Sunday, August 17, 2014

Unix Prog: File Struct and Atomic Operation

1. File Structure in Unix



1). Process Table
Each process has one entry at the process table, within the entry, there is one array of file descriptor entries representing all files opened in this process.
For each file descriptor entry: it contains the file descriptor, and file description flags, file pointer pointing to the file table entry.

2). File table
The kernel maintains one file tables for all open files. Each file table entry includes file status flag(read, write, append, sync and nonblocking......), the current file offset, pointer to the v-node table entry.

3). V-node structure
Each open file has a v-node structure that contains information about the type of file and pointers to functions operating on the file. Also it includes i-node structure which indicating the owner of the file, file size and pointer to where the actual data blocks are located on disk.

4) Note:
For different processes opening the same file, they have different file table entry but same vnode structure. The reason is: each process has its own file offset.

5) Behavior of operations:
write operation, the current file offset is incremented by number of bytes written
lseek operation, it only changes the file offset instead of doing the real I/O

2. Atomic Operations
1) read and write atomic operations
fileio.c:
Open the file "test.txt", and set the position to 100, and write "Hello world" from that place.
 #include<unistd.h>  
 #include<fcntl.h>  
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<errno.h>  
   
 int main()  
 {  
  int n;  
  char buff[] = "Hello world!";  
  int fd;  
   
  if((fd = open("test.txt", O_RDWR)) == -1) {  
   printf("open file error! \n");  
   exit(1);  
  }  
   
  if(lseek(fd, 0, SEEK_END) == -1) {  
   printf("seek error! \n");  
   exit(2);  
  }  
   
  if(write(fd, buff, 12) != 12) {  
   printf("write error! \n");  
   exit(3);  
  }  
   
  exit(0);  
 }  

If we have another process, which is same as this piece of code, the only difference is, it setup the position to 100, and start writing from position 150.
If these 2 process run together with following order, it may mess up the final result.

Process A: lseek(fd, 0, SEEK_END)
Process B: lseek(fd, 0, SEEK_END)
Process B: write(fd, buff, 12)
Process A: write(fd, buff, 12)

Process B will start writing from the end of file, then switch to process A. Since A already setup the position to the end of file "BEFORE B WORKING", so, A will overwrite the B's content.

The way to combine "lseek" and "write" together to be an atomic operation is using pread and pwrite.

Atomic Operation: the operation that might be composed of multiple steps, if it is atomic operation, either all steps are performed or none are performed.

shell:
pread and pwrite are defined at unistd.h
 ubuntu@ip-172-31-23-227:~$ less /usr/include/unistd.h
 ......
 /* Read NBYTES into BUF from FD at the given position OFFSET without  
   changing the file pointer. Return the number read, -1 for errors  
   or 0 for EOF.  
   
   This function is a cancellation point and therefore not marked with  
   __THROW. */  
 extern ssize_t pread (int __fd, void *__buf, size_t __nbytes,  
            __off_t __offset) __wur;  
   
 /* Write N bytes of BUF to FD at the given position OFFSET without  
   changing the file pointer. Return the number written, or -1.  
   
   This function is a cancellation point and therefore not marked with  
   __THROW. */  
 extern ssize_t pwrite (int __fd, const void *__buf, size_t __n,  
             __off_t __offset) __wur;  
 ......

3. Using pread and pwrite
fileio.c:
 #include<unistd.h>  
 #include<fcntl.h>  
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<errno.h>  
   
 int main()  
 {  
  int offset;  
  char buff[] = "Hello world!";  
  int fd;  
   
  if((fd = open("test.txt", O_RDWR)) == -1) {  
   printf("open file error! \n");  
   exit(1);  
  }  
   
  if(pwrite(fd, buff, 12, 112) == -1) {  
   printf("write file error! \n");  
   exit(2);  
  }  
   
  close(fd);  
  exit(0);  
 }  

with pwrite, "lseek" and "write" are atomic operations now.
But the offset(112) is fixed in this case, it still doesn't solve our problem, if we want to lseek firstly to get the offset of file end, we will have same problems.

We need a way to make "part of code be atomic" by ourselves.

No comments:

Post a Comment