리눅스 프로그래밍 - 14주차

Lellow_Mellow·2022년 12월 2일
1
post-thumbnail

🔔 학교 강의를 바탕으로 개인적인 공부를 위해 정리한 글입니다. 혹여나 틀린 부분이 있다면 지적해주시면 감사드리겠습니다.

Semaphore Concept

semaphorepv를 의사 코드로 나타내면 아래와 같다.

p(sem) or wait(sem) {
	if (sem != 0)
		decrement sem by one
	else
		wait until sem becomes non-zero, then decrement
}
----------------------------------------------------
v(sem) or signal(sem) {
	increment sem by one
	if (queue of waiting processes not empty)
		restart first process in wait queue
}

psem이 0보다 크면 1을 감소시키며, 0이면 감소시킬 수 있을때까지 대기한다. vsem을 1 증가시킨다. sem의 값은 vp에 의한 증가 감소에 의해 0보다 작아지면 안된다.

Semaphore

System Vsemaphoresemaphore 요소의 배열 형태로 구성되며, process는 단일 호출로 전체에 대한 작업 수행이 가능하다.

/* <sys/sem.h> */
struct semid_ds {
	struct ipc_perm sem_perm; 	/* permission */
	struct sem *sem_base; 		/* pointer to array of semaphores in set */
	ushort_t sem_nsems 			/* # of semaphores in set */
	time_t sem_otime; 			/* last semop() time */
	time_t sem_ctime; 			/* last change time */
};

semid_ds

  • sem_perm : semaphore permission
  • *sem_base :
  • sem_nsems :
  • sem_otime :
  • sem_ctime :

semaphore는 아래와 같이 구성되어 있다.

struct sem {
	ushort_t semval; 	/* semaphore value, unsigned */
	short sempid; 		/* PID of last successful semop() */
	ushort_t semncnt; 	/* # of awaiting (semval > current value) */
	ushort_t smezcnt; 	/* # of awaiting (semval = 0) */
};

Semaphore element

  • semval : semaphore 값을 나타내는 양의 정수
  • sempid : semaphore을 조작한 마지막 process의 pid
  • semncnt : semaphore가 증가하기를 기다리는 process 수
  • semzcnt : semaphore가 0이 되기를 기다리는 process 수

Semaphore Structure

위와 같이 semaphore element의 array로 이루어진다.

System Call : semget

#include <sys/sem.h>

int semget(key_t key, int nsems, int permflag);

semget을 사용해 전달받은 key와 연결된 semaphore identifier을 얻을 수 있다.

Return

  • 성공 시 return identifier
  • error 발생 시 return -1

Arguments

  • nsems : set의 semaphore element의 수 (nsems == 0일 경우 기존 set 참고)
  • permflag
    - IPC_CREAT : O_CREAT
    - IPC_EXCL : OEXCL

이는 아래와 같이 사용하며, IPC_PRIVATE가 사용될 경우, IPC_CREAT가 있어야 한다. 또한 nsems == 0인 경우에는 기존의 것을 참고하는 의미이기 때문에 IPC_CREATIPC_EXCL와 같이 쓰이면 오류가 발생하기 때문에 주의해야 한다.

Errors

  • EACCES : key에 대한 semaphore가 존재하나, 접근 권한 없음
  • EEXIST : key에 대한 semaphore가 존재하나, IPC_CREAT 혹은 IPC_EXCL이 지정됨
  • EINVAL
    - nsems < 0이거나 system 제한보다 큰 경우
    - key에 대한 semaphore가 존재하나 nsems가 해당 semaphore보다 큰 경우
  • ENOENT : key에 대한 semaphore가 존재하지 않으며 IPC_CREAT가 지정되지 않았음
  • ENOMEM : system 메모리 부족
  • ENOSPC : semaphore 최대 개수 초과

Example : semget

#include <errno.h>
#include <sys/sem.h>
#include <sys/stat.h>

int main(int argc, char **argv) {
	key_t skey;
	int semid;
    
	if (argc != 3) {
		fprintf(stderr, “Usage: %s pathname id\n”, argv[0]);
		return 1;
	}
	if ((skey = ftok(argv[1], atoi(argv[2]))) == (key_t)-1) {
		fprintf(stderr, “failed to derive key from filename %s\n”, argv[1]);
		return 2;
	}
	if ((semid = semget(skey, 2, 0660 | IPC_CREAT)) == -1) {
		fprintf(stderr, “failed to create semaphore with key %d\n”, (int)skey);
		return 3;
	}
	printf(“semid = %d\n”, semid);
	return 0;
}

위 코드는 인자로 받은 pathid를 통해 key를 받아, 해당 key를 이용하여 semaphore identifier를 받아 출력하는 코드이다.

System Call :semctl

#include <sys/sem.h>

int semctl(int semid, int sem_num, int cmd, union semun ctl_arg);

semctlcmd에 따라 semidsem_num에 대해 특정 기능을 수행한다. ctl_argunion이며, structure과는 달리 공간이 하나만 존재하며, 사용을 어떻게 하는지에 따라 값이 달라진다.

Return

  • cmd에 따라 return이 다름

Argument

  • sem_num : semaphore set에서 몇 번째 element인지 의미 (like index)
  • cmd : command type
  • ctl_arg : cmd에 따라 해당 값을 사용
union semun {
	int val; 				/* for SETVAL */
	struct semid_ds *buf; 	/* for IPC_STAT and IPC_SET */
	unsigned short *array; 	/* for GETALL and SETALL */
};

cmd를 정리하면 아래와 같다.

Standard IPC functions

IPC_STATsemid_ds의 정보를 ctl_arg.buf에 복사
IPC_SETctl_arg.buf로 semaphore set의 permission을 set
IPC_RMIDsemid에 해당하는 semaphore을 삭제

Single semaphore operations

GETVAL특정 semaphore의 value를 return
SETVALctl_arg.val로 특정 semaphore의 value를 설정
GETPID마지막으로 semaphore element를 변경한 PID를 return
GETNCNTsemaphore element가 증가하기를 기다리는 process의 개수 return
GETZCNTsemaphore element가 0과 같아지기를 기다리는 process의 개수 return

All semaphore operations

GETALLctl_arg.array에 semaphore의 value를 담아 return
SETALLctl_arg.array로 semaphore의 value를 set

Example : semctl

#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int init_sem (key_t semkey) {
	int status = 0, semid;
    
	if ((semid = semget(semkey, 1, 0600 | IPC_CREAT)) == -1) {
		perror (“init_sem failed”);
		return -1;
	}
	else {
		semun arg;
		arg.val = 1;
		status = semctl(semid, 0, SETVAL, arg);
	}
	return semid;
}

위의 예시코드와 같이 semaphore의 각 element는 사용되기 전에 semctl로 초기화해야 한다.

System Call : semop

#include <sys/sem.h>

int semop(int semid, struct sembuf *op_array, size_t num_ops);

semopsemid에 대한 semaphore set에 증가 혹은 감소 동작을 수행한다.

Return

  • 성공 시 return 0
  • error 발생 시 return -1

Arguments

  • num_op : op_array에 있는 sembuf
  • op_array : semaphore에 대한 연산 배열이며, 아래와 같은 형태를 요소로 가지는 배열
struct sembuf {
	unsigned short sem_num;	/* index number of semaphore in set */
	short sem_op; 			/* see next page */
	short sem_flg;			/* IPC_NOWAIT, SEM_UNDO */
};
  • sem_op : 실질적인 동작을 의미하며 값에 따라 동작이 달라짐
    - < 0 : p(), lock, sem_op만큼 value를 감소시킴
    - or sem_valsem_op의 절댓값보다 크거나 같아질때까지 block
    - > 0 : v(), unlock, sem_op만큼 value를 증가시킴
    - = 0 : semaphore가 0인지를 테스트하며, semval = 0까지 block
  • sem_flag
    - IPC_NOWAIT : sem_op <= 0일때까지 기다리지 않으며, errno = EAGAIN과 함께 return -1
    - SEM_UNDO : process가 죽었을 경우, semaphore을 원래대로 복구하며, 이는 비정상 종료를 방지하고, p 이후 v를 하지 않았을 경우를 해결함
    -> 하나라도 NO_WAIT이 존재하면 나머지도 동일하게 NO_WAIT으로 작동함

Example : Semaphore

/* pv.h -- header for semaphore */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#define SEMPERM 0600
#define TRUE 1
#define FALSE 0
typedef union_semun {
	int val;
	struct semid_ds *buf;
	ushore *array;
} semun;

semaphore 생성 + 초기화

#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int init_sem (key_t semkey) {
int status = 0, semid;

if ((semid = semget(semkey, 1, SEMPERM | IPC_CREAT | IPC_EXCL)) == -1) {
	if (errno == EEXIST)
		semid = semget (semkey, 1, 0);
}
else {
	semun arg;
	arg.val = 1;
	status = semctl(semid, 0, SETVAL, arg);
}
if (semid == -1 || status == -1) {
	perror(“initsem failed”);
	return -1;
}
return semid;

}


> #### p
```c
/* p.c -- p() or lock() */
#include “pv.h”

int p (int semid) {
	struct sembuf p_buf;
    
	p_buf.sem_num = 0;
	p_buf.sem_op = -1;
	p_buf.sem_flg = SEM_UNDO;
    
	if (semop(semid, &p_buf, 1) == -1) {
		perror(p(semid) failed”);
		exit (1);
	}
	return 0;
}

v

/* v.c -- v() or unlock() */
#include “pv.h”

int v (int semid) {
struct sembuf v_buf;

v_buf.sem_num = 0;
v_buf.sem_op = 1;
v_buf.sem_flg = SEM_UNDO;

if (semop(semid, &v_buf, 1) == -1) {
	perror(v(semid) failed”);
	exit (1);
}
return 0;

}


> #### main
```c
/* testsem */
#include “pv.h”

void handlesem (key_t skey) {
	int semid;
	pid_t pid = getpid();
	if ((semid = initsem(skey)) < 0) exit (1);

	printf(“\nprocess %d before critical section\n”, pid);
	p(semid);
	printf(“process %d in critical section\n”, pid);
    
	sleep(10); 		/* do something interesting */
    
	printf(“process %d leaving critical section\n”, pid);
	v(semid);
	printf(“process %d exiting\n”, pid);
	exit (0);
}

int main() {
	key_t semkey = 0x200;
	for (int i = 0; i < 3; i++) {
	if (fork() == 0) handlesem(semkey);
	}
	return 0;
}

위 예시 코드는 semkey를 지정하고 fork를 3번 진행하여, 각 child process는 handlesem을 실행하는 코드이다. 아래의 실행 결과를 보면 확인할 수 있듯이, critical section에 동시에 2개의 process가 들어갈 수는 없음을 확인할 수 있다.

$ ./testsem
process 799 before critical section
process 799 in critical section
process 800 before critical section
process 801 before critical section
process 799 leaving critical section
process 801 in critical section
process 799 exiting
process 801 leaving critical section
process 801 exiting
process 800 in critical section
process 800 leaving critical section
process 800 exiting

Shared Memory

Shared Memory

shared memory는 process들이 동일한 memory segment에 접근하여 읽고 쓸 수 있도록 한다. client와 server 간에 data를 복사할 필요가 없기 때문에 가장 빠른 형태의 IPC이며, server가 data를 shared memory에 배치하는 경우, server가 완료될 때까지 client는 data에 접근을 시도할 수 없다.

semaphore가 shared memory access를 동기화하는 데 사용되기도 한다.

/* <sys/shm.h> */
struct shmid_ds {
	struct ipc_perm shm_perm; 	/* permission */
	size_t shm_segsz; 			/* size of segment in bytes */
	pid_t shm_lpid; 			/* pid of last shmop() */
	pid_t shm_cpid; 			/* pid of creator */
	shmatt_t shm_nattch 		/* number of current attaches */
	time_t shm_atime; 			/* last attach time */
	time_t shm_dtime; 			/* last detach time */
	time_t shm_ctime; 			/* last change time */
	...
};

System Call : shmget

#include <sys/shm.h>

int shmget(key_t key, size_t size, int permflags);

shared memory segmentshmget에 의해 생성된다.

Return

  • 성공 시 return shared memory ID
  • error 발생 시 return -1

Errors

  • EACCES : key에 대한 shmid가 있으나 권한이 없음
  • EEXIST : key에 대한 shmid가 있으나 IPC_CREATIPC_EXCL가 지정됨
  • EINVAL : shared memory segment가 생성되지 않았으나, 크기가 system 제한 혹은 key의 segment 크기와 일치하지 않음
  • ENOENT : key에 대한 shmid가 존재하지 않으며, IPC_CREAT가 지정되지 않음
  • ENOMEM : system memory 부족
  • ENOSPC : shmid의 최대 개수 초과

System Call : shmat

#include <sys/shm.h>

void *shmat(int shmid, const void *daddr, int shmflags);

shmatshmid로 지정된 shared memory segment를 calling process의 address space에 연결하고, shmid에 대한 shm_nattch 값을 증가시킨다. 일반적으로 daddr0을 사용하는 것을 추천하며, 이는 heap이 해당 영역을 침범할 수 있기 때문이다.

Return

  • 성공 시 shared memory segment에 대한 pointer return
  • error 발생 시 return -1

Arguments

  • daddr
    - 0일 경우, segment가 kernel이 선택한 사용 가능한 첫 번째 주소에 연결됨
    - 0이 아니고 SHM_RND가 지정되지 않은 경우, daddr의 주소에 연결됨
    - 0이 아니고 SHM_RND가 지정된 경우 daddrSHMLBA로 나눈 나머지의 주소에 연결됨

System Call : shmctl

#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *shm_stat);

shmctlshmid에 의한 shared memory segment에 대해 cmd에 따른 특정 기능을 수행한다.

Returns

  • 성공 시 return 0
  • error 발생 시 return -1

Arguments

  • shmid : shared memory segment ID
  • cmd
    - IPC_STAT
    - IPC_SET
    - IPC_RMID
    - SHM_LOCK : shared memory segment를 lock
    - SHM_UNLOCK : shared memory segment를 unlock
  • shm_stat : shmid_ds 구조체 포인터

Example : shmcopy

header

/* share_ex.h */
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>

#define SHMKEY1 (key_t) 0x10
#define SHMKEY2 (key_t) 0x15
#define SEMKEY (key_t) 0x20

#define SIZ 5*BUFSIZ

struct databuf {
	int d_nread;
	char d_buf[SIZ];
};

typedef union _semun{
	int val;
	struct semid_ds *buf;
	ushort *array;
} semun;

shared memory와 semaphore get, remove

/* share_init.c */
#include “share_ex.h”

#define IFLAGS (IPC_CREAT | IPC_EXCL)
#define ERR ((struct databuf *) -1)

static int shmid1, shmid2, semid;

void getseg(struct databuf **p1, struct databuf **p2) {
	/* create shared memory segment */
	if ((shmid1 = shmget(SHMKEY1, sizeof(struct databuf),
		0600 | IFLAGS)) == -1)
		fatal(“shmget”);
	if ((shmid2 = shmget(SHMKEY2, sizeof(struct databuf),
		0600 | IFLAGS)) == -1)
		fatal(“shmget”);
        
	/* attach shared memory segment */
	if ((*p1 = (struct databuf *) shmat(shmid1, 0, 0)) == ERR)
		fatal(“shmat”);
	if ((*p2 = (struct databuf *) shmat(shmid2, 0, 0)) == ERR)
		fatal(“shmat”);
}
    
/* get semaphore set */
int getsem(void) { 
	semun x;
	x.val = 0;
       
	if ((semid = semget(SEMKEY, 2, 0600 | IFLAGS)) == -1)
		fatal(“semget”);
           
	if (semctl(semid, 0, SETVAL, x) == -1)
		fatal(“semctl”);
	if (semctl(semid, 1, SETVAL, x) == -1)
		fatal(“semctl”);
	return (semid);
}

/* remove shmids and semid */
void remobj (void) { 
	if (shmctl(shmid1, IPC_RMID, NULL) == -1)
		fatal(“shmctl”);
	if (shmctl(shmid2, IPC_RMID, NULL) == -1)
		fatal(“shmctl”);
	if (semctl(semid, IPC_RMID, NULL) == -1)
		fatal(“semctl”);
}

reader

/* reader.c */
#include “share_ex.h”
struct sembuf p1 = {0, -1, 0}, p2 = {1, -1, 0};
struct sembuf v1 = {0, 1, 0}, v2 = {1, 1, 0};

void reader (int semid, struct databuf *buf1, struct databuf *buf2) {
	for(;;) {
		buf1->d_nread = read(0, buf1->d_buf, SIZ);
		semop(semid, &v1, 1);
		semop(semid, &p2, 1);
		if (buf1->d_nread <= 0) 
			return;
            
		buf2->d_nread = read(0, buf2->d_buf, SIZ);
        
		semop(semid, &v1, 1);
		semop(semid, &p2, 1);
        
		if (buf2->d_nread <= 0)
		return;
	}
}

writer

/* writer.c */
#include “share_ex.h”
extern struct sembuf p1, p2;
extern struct sembuf v1, v2;

void writer (int semid, struct databuf *buf1, struct databuf *buf2) {
	for(;;) {
		semop(semid, &p1, 1);
		semop(semid, &v2, 1);
        
		if (buf1->d_nread <= 0)
			return;
            
		write(1, buf1->d_buf, buf1->d_nread); 
        
		semop(semid, &p1, 1);
		semop(semid, &v2, 1);
        
		if (buf2->d_nread <= 0)
			return;
            
		write(1, buf2->d_buf, buf2->d_nread);
	}
}

main

/* shmcopy.c */
#include “share_ex.h”

int main() {
	int semid;
	pid_t pid;
	struct databuf *buf1, *buf2;
    
	semid = getsem();
	getseg(&buf1, &buf2);
    
	switch (pid=fork()) {
		case -1:
			fatal(“fork”);
		case 0:
			writer(semid, buf1, buf2);
			remobj();
			break;
		default:
			reader(semid, buf1, buf2);
			break;
	}
	return 0;
}

위 코드는 semaphore의 값을 기준으로 fork 기준 child일 경우 write, parent인 경우 read를 반복하며, 실행 과정을 그림으로 나타내면 아래와 같다.

✅ 실습 시간 추가 코드

Semaphore 초기화

union semun arg;
arg.val = 1;
status = semctl(semid, 0, SETVAL, arg);

semid의 0번 semvalarg.val로 초기화한다.

union semun arg;
unsigned short array[3] = {1, 3, 2};
arg.array = array;
status = semctl(semid, 0, SETALL, arg);

semidsem[0], sem[1], sem[2]arg.array로 초기화

profile
festina lenta

0개의 댓글