- 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
- SM
Accessing a variable or function in the other process is hard. Why?
- 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
p1
:
x = msgget(75, 0777|IPC_CREAT);
msgsnd(x, &msg, ......);
p2
:
x = msgget(75, 0777);
msgrcv(x, &msg);
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
........
};
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);
}
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.
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.
The example in 4.1..
*y = *y + 1 ==>
mov *y, eax ; *y -> eax
inc eax
mov eax, *y
- 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.
- 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
- method:
- without os, pl
- sw : dekker, peterson
- hw : cli/sti, tsi
- with os
- semaphore
- with pl
- monitor
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
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
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.
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)
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
......
};
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;
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๋ฒ์ ์ฐ์ฐ์ ์ํํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ์ถ๋ ฅํ๋ค.
#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์ด๋ผ ๋ถ๋ฅด๋ฉฐ ์์ ๊ฐ์ ๊ฒฐ๊ณผ์ ์์ธ์ด ๋๋ค.
#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
์ ์ถ๊ฐํด์ฃผ์๋ค.
my_syscall_number_toggle
์ ์ ์ํด์ฃผ์๋ค.
arch/x86/kernel/entry_32.S
:
......
์ด๋ ๊ฒ ํ์ง๋ง pthread_mutex_lock
์ ์์คํ
์ฝ ํธ์ถ์ด ๋ณด์ด์ง ์์๋ค.