Skip to content

Latest commit

ย 

History

History

05. IPC(Interprocess Communication)

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
ย 
ย 
ย 
ย 

IPC(Interprocess Communication)

  • Communication between two processes
  • SM(Shared Memory), MP(Message Passing)
    • SM
      • two processes share the same memory area
      • one of them writes data in it
      • the other reads from it
      • SM is faster but can cause "race condition"
      • e.g., thread
    • MP
      • Operating system provides system calls to relay messages
      • between processes

1. Communication between two processes

Accessing a variable or function in the other process is hard. Why?

2. Two approaches: MP, SM

  • MP(Message Passing): ask OS to transfer the data to the other process
  • SM(Shared Memory): access the variable in the other process directly with the help of OS

3. messge passing

3.1 Usage

p1:

x = msgget(75, 0777|IPC_CREAT);
msgsnd(x, &msg, ......);

p2:

x = msgget(75, 0777);
msgrcv(x, &msg);

3.2 data structure

msgget returns struct msg_queue.

struct msg_queue{
   struct kern_ipc_perm  q_perm;
   .........
   struct list_head  q_messages;
};

struct kern_ipc_perm{
   .........
   int  key;
   unsigned short  mode;  // permission bit mask
   ........
};

4. Shared memory

4.1) usage

p1 :

x = shmget(75, 4, 0777|IPC_CREAT);
y= (int *)shmat(x, ......);

p2 :

x = shmget(75, 4, 0777);
y = (int *)shmat(x, ....);

example :

x = shmget(75, 4, 0777|IPC_CREAT);
if ((fork())==0){ // child
   y= (int *) shmat(x, 0, 0);
   for(j=0;j<100;j++)
      for(i=0;i<10000;i++)
         *y = *y + 1;
   printf("child: the result is %d\n", *y);
}
else{ // parent
   y= (int *) shmat(x, 0, 0);
   for(j=0;j<100;j++)
      for(i=0;i<10000;i++)
         *y = *y + 1;
   printf("parent: the result is %d\n", *y);
}

4.2) data structure

shmget() returns struct shmid_kernel.

struct shmid_kernel{
   struct kern_ipc_perm  shm_perm;
   struct file * shm_file; // this file represents the shared memory
                          // this file exists in the physical memory only except
                          // during swapped-out state. The physical location of
                          // this file is the physical location of the shared memory
   .........
};

shmat() allocates a space in the virtual address space and map this address to the physical location of the shared memory through the page table.

5. Race condition

Shared memory is fast but can cause "race condition". When the outcome of a computation depends on how two or more processes are scheduled, the code is incorrect. We say that there is a race condition.

5.1) Example

The example in 4.1..

5.2) Why it happens?

*y = *y + 1 ==>
mov *y, eax ; *y -> eax
inc eax
mov eax, *y

5.3) When it happens?

  • Critical section: the code area where a shared memory is accessed.
  • Race condition can happen when two or more processes enter the CS at the same time.

5.4) How to solve?

  • Basic idea: mutual exclusion.
  • Mutual exclusion: Make sure only one process enters the CS.
  • How can we guarantee the mutual exclusion?
    • method:
      • All processes check before entering CS if there is another process in the CS.
      • If there is one
        • wait until it comes out
      • Else
        • enter

5.5) Implementing mutual exclusion

  • without os, pl
    • sw : dekker, peterson
    • hw : cli/sti, tsi
  • with os
    • semaphore
  • with pl
    • monitor

5.6) sw

P1, P2:

lp1: mov r0, lock
     cmp r0, 1
     je lp
     mov lock, 1
     -- CS --
     mov lock, 0
  • lock=0 initially
  • enter if lock=0

5.7) hw

P1,P2:

    cli
    --CS--
    sti

Or

    lp: tsl ro, lock
    cmp r0, 1
    je lp
    --CS--
    mov lock, 0

tsl ro, lock is same as the following operations all executed atomically

    ro = lock
    if (r0==0)
    lock=1

5.8) semaphore

P1,P2:

wait(s);
--CS--
signal(s);

wait(s):

   s = s - 1;
   if (s < 0)
      someone is already in CS. wait here. (insert into waiting list on s)
   else
      enter CS

signal(s):

   s = s + 1;
   if (s <=0)
      someone is in the waiting list. wake one up.

5.9) using semaphore

int semid;
struct sembuf psembuf={0,-1,SEM_UNDO};
struct sembuf vsembuf={0,1,SEM_UNDO};
main(){
.............
semid = semget(75, 1, 0777|IPC_CREAT); // get a semaphore
sem_union.val=1;
semctl(semid, 0, SETVAL, sem_union); // initial value is 1
semop(semid, &psembuf, 1); // wait(s)
--CS--
semop(semid, &vsembuf, 1); // signal(s)

5.10) data structure for semaphore

semget() returns struct sem_array.

struct sem_array{
   struct  kern_ipc_perm  sem_perm;
   struct  sem *            sem_base; // link list of sem structure
   .........
};
struct sem{
   unsigned long semval;  // value of this semaphore
   ......
};

5.11) Deadlock

Using semaphore correctly not easy. Example: producer-consumer problem.

[producer] produce item and push to the stk

lp: get_item(&item);
if (top==MAX)
   sleep();
top=top+1;
stk[top]=item
if (top==1)
   wakeup(์†Œ๋น„์ž);
goto lp;

[consumer] pop item from stk and consume

lp: if (top==0)
   sleep();
item = stk[top];
top = top-1;
if (top==MAX-1)
   wakeup(์ƒ์‚ฐ์ž);
consume(item);
goto lp;

mutex : intialize with 1. sema for mutual exclusion holes : initial val=MAX. sema for holes items : initial val=0. sema for items produced so far

[producer]

lp: get_item(&item);
wait(holes);
wait(mutex);
top=top+1;
stk[top]=item
signal(mutex);
signal(items);
goto lp;

[consumer]

lp:
wait(items);
wait(mutex);
item = stk[top];
top = top-1;
signal(mutex);
signal(holes);
consume(item);
goto lp;

6. Exercise

1) Try below (ex1.c) and explain the result.

ex1.c :

#include <stdio.h>

unsigned long long sum = 0;

int main(){
    int x = fork();
    if (x == 0) { // child
        int i, j;
        for (i=0; i<20000; i++)
            for(j=0; j<2000; j++)
                sum++;
        printf("child sum: %llu\n", sum);
    } else { // parent
        int i, j;
        for(i=0; i<20000; i++)
            for(j=0; j<2000; j++)
                sum++;
        printf("parent sum: %llu\n", sum);
    }
    return 0;
}
$ gcc -o ex1 ex1.c
$ ./ex1

fork๋กœ ์ƒˆ๋กœ์šด ํ”„๋กœ์„ธ์Šค๋ฅผ ๋งŒ๋“ค๋ฉด ํ”„๋กœ์„ธ์Šค ๊ฐ„์˜ ๋ณ€์ˆ˜๋ฅผ ๊ณต์œ ํ•˜์ง€ ์•Š๊ณ  ์ƒˆ๋กœ์šด ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ• ๋‹น๋ฐ›๋Š”๋‹ค. ๊ทธ ๊ฒฐ๊ณผ, Race Condition์ด ์ผ์–ด๋‚˜์ง€ ์•Š๊ณ  ๋‘ ํ”„๋กœ์„ธ์Šค ๋ชจ๋‘ 40000000๋ฒˆ์˜ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค.

2) Try below (th.c) and explain the result.

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

pthread_t t1, t2;  // thread 1, thread 2
unsigned long long sum=0;

void * foo1(void *arg){
   int i,j;
   for(i=0;i<20000;i++){
      for(j=0;j<2000;j++)
         sum += 1;
   }
   printf("thread 1 sum:%llu\n", sum);
   return NULL;
}
void * foo2(void *arg){
   int i,j;
   for(i=0;i<20000;i++){
      for(j=0;j<2000;j++)
         sum += 1;
   }
   printf("thread 2 sum:%llu\n", sum);
   return NULL;
}

int main(void){
    pthread_create(&t1, NULL, &foo1, NULL);
    pthread_create(&t2, NULL, &foo2, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    return 0;
}

undefined reference to 'pthread_create' ์—๋Ÿฌ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด, -lpthread ์˜ต์…˜์„ ์ฃผ์–ด th.c๋ฅผ ์ปดํŒŒ์ผํ•˜์˜€๋‹ค.

$ gcc -o th -lpthread th.c
$ ./th
....
$ ./th
....
$ ./th
....

  • (1) ์‹คํ–‰ ๊ฒฐ๊ณผ๊ฐ€ ๋งค๋ฒˆ ๋‹ฌ๋ž๊ณ  thread2์˜ sum์€ ๊ธฐ๋Œ“๊ฐ’์ธ 80000000์— ๋ฏธ์น˜์ง€ ๋ชปํ–ˆ๋‹ค.
  • (2) ํ”„๋กœ๊ทธ๋žจ์ด ์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋๋‹ค๋ฉด foo1๊ณผ foo2๊ฐ€ ์ „์—ญ๋ณ€์ˆ˜ sum์„ ๊ณต์œ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— thread2์˜ sum์€ 80000000์ด ๋˜์–ด์•ผํ•œ๋‹ค.
  • (3) ๊ฐ thread์˜ ์—ฐ์‚ฐ์ด ๊ธธ๋‹ค๋ณด๋‹ˆ ์—ฐ์‚ฐ ์ค‘๊ฐ„์— timeout์ด ๋ฐœ์ƒํ•ด ๋‹ค๋ฅธ thread๊ฐ€ schedule ๋˜๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•œ๋‹ค.
  • (4) sum += 1 . ์ฝ”๋“œ๋Š” ์–ด์…ˆ๋ธ”๋ฆฌ ์ฝ”๋“œ๋กœ ๋‚˜ํƒ€๋‚ด๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค
    mov eax, sum    // eax โ† sum`
    inc eax         // eax = eax + 1
    mov sum, eax    // sum โ† eax
  • (5) ๋งŒ์•ฝ ์—ฌ๊ธฐ์„œ thread1์ด mov eax, sum์ด๋‚˜ inc eax๊นŒ์ง€ ์‹คํ–‰ ํ›„ timeout๋˜๊ณ , thread2๋ฅผ ๊ฑฐ์นœ ํ›„ ๋‹ค์‹œ thread1์ด schedule ๋  ๋•Œ๋ฅผ ๊ฐ€์ •ํ•˜๋ฉด, thread2์—์„œ ๊ณ„์‚ฐํ–ˆ๋˜ sum์ด thread1์˜ eax์— ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š”๋‹ค. thread2 ์ž…์žฅ์—์„œ๋„ ๋˜‘๊ฐ™์ด ์ƒ๊ฐํ•ด๋ณผ ์ˆ˜ ์žˆ๋‹ค.
    ์ด๋Ÿฌํ•œ ์ƒํ™ฉ์„ race condition์ด๋ผ ๋ถ€๋ฅด๋ฉฐ ์œ„์™€ ๊ฐ™์€ ๊ฒฐ๊ณผ์˜ ์›์ธ์ด ๋œ๋‹ค.

3) Try below(th2.c) and explain the result.

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

pthread_t t1, t2;  // thread 1, thread 2
pthread_mutex_t lock;  // semaphore
unsigned long long sum=0;

void * foo1(void *arg){
    int i,j;
    for(i=0;i<20000;i++){
        pthread_mutex_lock(&lock);
        for(j=0;j<2000;j++)
            sum += 1;
        pthread_mutex_unlock(&lock);
    }
    printf("thread 1 sum:%llu\n", sum);
    return NULL;
}
void * foo2(void *arg){
    int i,j;
    for(i=0;i<20000;i++){
        pthread_mutex_lock(&lock);
        for(j=0;j<2000;j++)
            sum += 1;
        pthread_mutex_unlock(&lock);
    }
   printf("thread 2 sum:%llu\n", sum);
   return NULL;
}

int main(void){
    pthread_mutex_init(&lock, NULL);
    pthread_create(&t1, NULL, &foo1, NULL);
    pthread_create(&t2, NULL, &foo2, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&lock);
    return 0;
}
$ gcc -o th2 -lpthread th2.c
$ ./th2
...
$ ./th2
...
$ ./th2
.....

pthread_mutex_t lock;(semaphore)์„ ์ด์šฉํ•ด ์—ฌ๋Ÿฌ thread๊ฐ€ ๋™์‹œ์— ํ•œ ๋ณ€์ˆ˜์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๊ฒŒ ํ–ˆ๋‹ค.
๊ทธ ๊ฒฐ๊ณผ, 2๋ฒˆ์ฒ˜๋Ÿผ ์ตœ์ข… ๊ณ„์‚ฐ ๊ฐ’์ด ์œ ์‹ค๋˜์ง€ ์•Š๊ณ  80000000์ด ํ•ญ์ƒ ๋‚˜์˜จ๋‹ค.

thread1์˜ sum์ด 80000000์ด ์•„๋‹Œ ์ด์œ ๋Š” thread2๊ฐ€ ๋๋‚˜๊ธฐ ์ „์— ๋จผ์ € ์ข…๋ฃŒ๋˜๊ณ  thread2์—์„œ 80000000์ด ๊ณ„์‚ฐ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

4) (Deadlock) Try below(th3.c) and explain the result. Modify the code so that it won't have a deadlock.

th3.c :

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

pthread_t t1, t2;  // thread 1, thread 2
pthread_mutex_t lock1;  // semaphore 1 for sum 1
pthread_mutex_t lock2;  // semaphore 2 for sum 2

unsigned long long sum1=0;
unsigned long long sum2=0;

void * foo1(void *arg){
    int i,j;
    for(i=0;i<20000;i++){
        pthread_mutex_lock(&lock1);
        pthread_mutex_lock(&lock2);
        for(j=0;j<2000;j++)
            sum1 += 1;
        pthread_mutex_unlock(&lock1);
        for(j=0;j<2000;j++)
            sum2 += 1;
        pthread_mutex_unlock(&lock2);
    }
    printf("thread 1 sum1:%llu\n", sum1);
    printf("thread 1 sum2:%llu\n", sum2);

    return NULL;
}

void * foo2(void *arg){
    int i,j;
    for(i=0;i<20000;i++){
        pthread_mutex_lock(&lock2);
        pthread_mutex_lock(&lock1);
        for(j=0;j<2000;j++)
            sum1 += 1;
        pthread_mutex_unlock(&lock1);
        for(j=0;j<2000;j++)
            sum2 += 1;
        pthread_mutex_unlock(&lock2);
    }
    printf("thread 2 sum1:%llu\n", sum1);
    printf("thread 2 sum2:%llu\n", sum2);

    return NULL;
}

int main(void){
    pthread_mutex_init(&lock1, NULL);
    pthread_mutex_init(&lock2, NULL);
    pthread_create(&t1, NULL, &foo1, NULL);
    pthread_create(&t2, NULL, &foo2, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&lock1);
    pthread_mutex_destroy(&lock2);

    return 0;
}
$ gcc -o th3 -lpthread th3.c
$ ./th3
# deadlock

์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ  ์‹คํ–‰ํ•˜์˜€๋”๋‹ˆ, ๋‘ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์„œ๋กœ ์ƒ๋Œ€๋ฐฉ์˜ semaphore๊ฐ€ ํ’€๋ฆฌ๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ƒํƒœ๊ฐ€ ๋˜์–ด ํ”„๋กœ๊ทธ๋žจ์ด ๋” ์ด์ƒ ์ง„ํ–‰๋˜์ง€ ์•Š์•˜๋‹ค. ์ด๋Ÿฌํ•œ ์ƒํ™ฉ์„ deadlock์ด๋ผ๊ณ  ํ•œ๋‹ค.

์ด๋Š” lock์ด๋‚˜ unlock์˜ ์ˆœ์„œ๋ฅผ ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค. ์œ„ ์ฝ”๋“œ์—์„œ thread1์ด pthread_mutex_lock(&lock1);๋ฅผ ์‹คํ–‰ํ•œ ์งํ›„ timeout์œผ๋กœ thread2๊ฐ€ schedule๋˜๋ฉด, thread2๋Š” pthread_mutex_lock(&lock2);๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๊ทธ ๋‹ค์Œ ์ฝ”๋“œ์ธ pthread_mutex_lock(&lock1);๋ฅผ ๋งˆ์ฃผ์น˜๋ฉด thread1์— ์˜ํ•ด ์•„๋ž˜์˜ CS๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์—†๊ฒŒ ๋œ๋‹ค.
์ด ์ƒํƒœ์—์„œ ๋‹ค์‹œ thread1์ด schedule๋˜์–ด foo1์˜ ๋‹ค์Œ ์ฝ”๋“œ์ธ pthread_mutex_lock(&lock2);๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์ด ์—ญ์‹œ lock2์˜ CS๊ฐ€ thread2์— ์˜ํ•ด block ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋” ์ด์ƒ ์ฝ”๋“œ๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์—†๊ฒŒ ๋œ๋‹ค.

deadlock์„ ๋ฐฉ์ง€ํ•˜๊ณ ์ž, foo1๊ณผ foo2์˜ lock ์ˆœ์„œ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•˜์˜€๋‹ค.

th3-modified.c :

void * foo2(void *arg){
    int i,j;
    for(i=0;i<20000;i++){
        pthread_mutex_lock(&lock1);
        pthread_mutex_lock(&lock2);
        for(j=0;j<2000;j++)
            sum1 += 1;
        pthread_mutex_unlock(&lock1);
        for(j=0;j<2000;j++)
            sum2 += 1;
        pthread_mutex_unlock(&lock2);
    }
    printf("thread 2 sum1:%llu\n", sum1);
    printf("thread 2 sum2:%llu\n", sum2);

    return NULL;
}
$ gcc -o th3 -lpthread th3.c
$ ./th3

5). Find out the ISR2 function for pthread_mutex_lock() and trace the code.
You can do kernel tracing also in the following site: https://elixir.bootlin.com/linux/latest/ident/.
Select the right version (v2.6.25.10) and type the ISR2 name in the search box.

arch/x86/kernel/syscall_table_32.S :

32๋ฒˆ ์ž๋ฆฌ์— my_syscall_number_toggle์„ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค.


fs/read_write.c :

my_syscall_number_toggle์„ ์ •์˜ํ•ด์ฃผ์—ˆ๋‹ค.

arch/x86/kernel/entry_32.S :

......

์ด๋ ‡๊ฒŒ ํ–ˆ์ง€๋งŒ pthread_mutex_lock์˜ ์‹œ์Šคํ…œ ์ฝœ ํ˜ธ์ถœ์ด ๋ณด์ด์ง€ ์•Š์•˜๋‹ค.