Sunday, November 16, 2014

Unix Prog: IPC Shared Memory(2)

1. Example of memory layout
shm.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<sys/shm.h>  
   
 #define ARRAY_SIZE 40000  
 #define MALLOC_SIZE 100000  
 #define SHM_SIZE 100000  
 #define SHM_MODE 0600 // user-read(S_IRUSR 0400), user-write(S_IWUSR 0200), BIT OR => 0600  
   
 char array[ARRAY_SIZE]; // Uninitialized data = bss  
   
 int main(int argc, char* argv[])  
 {  
  int shmid;  
  char *ptr, *shmptr;  
   
  // Print out address range of uninitialied array and local variable  
  printf("array[] from %lx to %lx\n", (unsigned long)&array[0], (unsigned long)&array[ARRAY_SIZE]);  
  printf("stack around %lx\n", (unsigned long)&shmid);  
   
  // Malloc the memory from heap, and print out the address range  
  if((ptr = malloc(MALLOC_SIZE)) == NULL) {  
   printf("malloc error");  
   exit(1);  
  }  
  printf("malloced from %lx to %lx\n", (unsigned long)ptr, (unsigned long)ptr + MALLOC_SIZE);  
   
  // Create the shared memory segment  
  if((shmid = shmget(IPC_PRIVATE, SHM_SIZE, SHM_MODE)) < 0) {  
   printf("shmget error!\n");  
   exit(2);  
  }  
   
  // Attach the local memory to shared memory  
  if((shmptr = shmat(shmid, 0, 0)) == (void*)-1) {  
   printf("shmat error!\n");  
   exit(3);  
  }  
  printf("shared memory attached from %lx to %lx\n", (unsigned long)shmptr, (unsigned long)shmptr+SHM_SIZE);  
   
  // Remove the shared memory segment  
  if(shmctl(shmid, IPC_RMID, 0) < 0) {  
   printf("shmctl error!\n");  
   exit(4);  
  }  
   
  exit(0);  
 }  

shell:
From the following input, we could see the places of different memory in process of current os.
Based on output:
uninitialized data(array) is in lowest address range, heap memory range is right after, and then stack memory, and finally shared memory is in highest memory range.
 ubuntu@ip-172-31-23-227:~$ ./shm.out  
 array[] from 6010a0 to 60ace0  
 stack around 7fffb5fc6b5c  
 malloced from 19b5010 to 19cd6b0  
 shared memory attached from 7f07cdc82000 to 7f07cdc9a6a0  

2. Example of Memory Mapping:
Besides using the shared memory segment, we can also use the mmap to simplify the procedure. mmap can used to share the memory region among multiple processes if they share the common ancestor and MAP_SHARED flag is specified.

mmap.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<fcntl.h>  
 #include<sys/mman.h>  
   
 #define NLOOPS 1000  
 #define SIZE sizeof(long)  
 int pfd1[2], pfd2[2];  
   
 void TELL_WAIT(void)  
 {  
  // Create two pipes here, one for parent -> child  
  // one for child -> parent  
  if(pipe(pfd1) < 0 || pipe(pfd2) < 0) {  
   printf("pipe error!\n");  
   exit(1);  
  }  
 }  
   
 void TELL_PARENT(pid_t pid)  
 {  
  if(write(pfd2[1], "c", 1) != 1) {  
   printf("write error !\n");  
   exit(2);  
  }  
 }  
   
 void WAIT_PARENT(void)  
 {  
  char c;  
   
  if(read(pfd1[0], &c, 1) != 1) {  
   printf("read error!\n");  
   exit(3);  
  }  
   
  if(c != 'p') {  
   printf("wait parent: incorrect data!\n");  
   exit(4);  
  }  
 }  
   
 void TELL_CHILD(pid_t pid)  
 {  
  if(write(pfd1[1], "p", 1) != 1) {  
   printf("write error!\n");  
   exit(2);  
  }  
 }  
   
 void WAIT_CHILD(void)  
 {  
  char c;  
   
  if(read(pfd2[0], &c, 1) != 1) {  
   printf("read error!\n");  
   exit(3);  
  }  
   
  if(c != 'c') {  
   printf("wait child: incorrect data!\n");  
   exit(4);  
  }  
 }  
   
 int update(long* ptr)  
 {  
  return((*ptr)++); // return the value before increment  
 }  
   
 int main(int argc, char* argv[])  
 {  
  int fd, i, counter;  
  pid_t pid;  
  void *area;  
   
  // Open the file descriptor  
  if((fd = open("/dev/zero", O_RDWR)) < 0) {  
   printf("open error!\n");  
   exit(1);  
  }  
   
  // Map the memory to the file descriptor  
  if((area = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {  
   printf("mmap error!\n");  
  }  
  close(fd);  
   
  TELL_WAIT();  
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(5);  
  }  
  else if(pid > 0) { // parent process  
   for(i = 0; i < NLOOPS; i+=2) {  
    if((counter = update((long*)area))!= i) {  
     printf("parent: expected %d, got %d.\n", i, counter);  
     exit(6);  
    }  
   
    TELL_CHILD(pid);  
    WAIT_CHILD();  
   }  
  } else { // child process  
   for(i = 1; i < NLOOPS + 1; i+=2) {  
    WAIT_PARENT();  
    if((counter = update((long*)area) != i)) {  
     printf("child: expected %d, got %d\n", i, counter);  
     exit(6);  
    }  
   
    TELL_PARENT(getppid());  
   }  
  }  
   
  exit(0);  
 }  

shell:
Nothing is outputted.
So the parent and child process operate the mapped memory region pointed to by "area", and value of memory is incremented as expected while both processes touch it one by one.
Actually area in parent and child are different memory region, but they are mapped by using mmap to map it to the external /dev/zero file, which indicates that the external map could be empty.

Note: if we specify the MAP_PRIVATE when doing the mmap, it would not work. since this flag will make the memory to map to a private copy of file instead of shared one, which make another process can't see the change in memory since they are different and not mapped.

Note:
Modern unix system support anonymous memory mapping, which is almost same as above example, the only difference is:
We don't use /dev/zero to do the mapping, we let OS to select one anonymous region for shared mapping, we can change mmap statement to:

if((area = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0)) == MAP_FAILED)

We add one more flag: MAP_ANON to indicate the anonymous mapping, and fd as -1, since we don't need to specify the file path.

And the result is, OS will select one anonymous region for mapping.

No comments:

Post a Comment