popen() 함수는 단방향 파이프를 생성하고 fork 후 쉘을 호출하여 프로세스를 open 한다.
type 인수는 fopen의 mode 인수와 비슷하나 단방향이므로 읽기("r") 또는 쓰기("w")만 지정할 수 있다.
popen()으로 생성한 파일 포인터는 pclose()로 닫아야 한다.
fclose가 아님에 유의
command 인수는 널로 종료되는 문자열이며 -c 플래그를 사용하여 /bin/sh에 전달된다.
popen()은 정상 종료 시 파일 포인터를 반환하며, 실패 시 NULL 반환 후 errno를 설정하고,
pclose()는 정상 종료 시 command의 exit 상태 값을 반환하며, 실패 시 -1 반환 후 errno 설정한다.
예시
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp;
int status;
char buff[1024];
fp = popen("pwd", "r");
if (fp == NULL) {
fprintf(stderr, "popen error: %m\n");
exit(1);
}
printf("Current Path is ");
while (fgets(buff, sizeof(buff), fp) != NULL) {
printf("%s", buff);
}
status = pclose(fp);
if (status == -1) {
fprintf(stderr, "pclose error: %m\n");
exit(1);
}
return 0;
}
<br><br>
## 지명 파이프 (FIFO)
- 명명된 파이프(named pipe) 혹은 지명 파이프는 일반 파이프를 확장한 것으로, 프로세스 간 통신 기법 중 하나이다.
<img width="540" alt="image" src="https://user-images.githubusercontent.com/86760744/182021293-36836911-7cac-4f92-8145-92a02f6a66de.png">
<img width="412" alt="image" src="https://user-images.githubusercontent.com/86760744/182021310-f89928da-dcf7-49cd-ab9a-b96e0ab2bfa3.png">
- 생성된 fifo 파일은 파이프 역할을 수행하며 해당 파일로 데이터를 보낼 수도 있고, (ex. cat file > fifo_pipe)
- 일반 파일처럼 삭제할 수도 있다. (ex. rm fifo_pipe)
<br>
``` c
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
#include <fcntl.h> /* Definition of AT_* constants */
#include <sys/stat.h>
int mkfifoat(int dirfd, const char *pathname, mode_t mode);
mkfifo()는 이름이 pathname 인 FIFO 특수 파일을 생성한다.
mode는 FIFO의 권한을 지정한다.
FIFO 특수 파일을 만든 후에는 일반 파일과 같은 방식으로 모든 프로세스에서 파일 읽거나 쓸 수 있다.
FIFO로부터 읽어 들이는 수신 측은 O_RDONLY로 파일을 열고, 송신 측은 O_WRONLY로 파일을 열어야 한다.
일반적으로 수신 측이 FIFO를 먼저 open 하는데 송신 측이 open할 때까지 블록된다.
read, write를 통해서 읽고 쓰는 도중에 수신 측에서 FIFO를 close하면,
송신 측 write는 SIGPIPE 시그널 수신, 송신 측에서 FIFO를 close하면 수신 측 read는 0을 반환한다.
mkfifoat()은 파일 디스크립터 dirfd가 가리키는 디렉토리 위치에서 상대경로로 파일을 열어주는 것 외에는 mkfifo()와 동일하다.
성공 시 0 반환하고, 실패 시 -1 반환 후 errno를 설정한다.
FIFO를 이용해 두 프로세스가 채팅하듯이 통신하는 프로그램을 실습해보자.
FIFO 파일이 존재하지 않으면 생성한다.
한 프로그램은 FIFO를 읽은 후 입력 받은 문자열을 FIFO에 쓰고, (write_fifo)
다른 프로그램은 FIFO에 입력 받은 문자열을 쓴 후 FIFO를 읽는다. (read_fifo)
<br>
- 실행 결과
![image](https://user-images.githubusercontent.com/86760744/183564590-41cfcd83-ce18-42cf-8287-2bc6c22503a0.png)
![image](https://user-images.githubusercontent.com/86760744/183564599-a9cb52bc-a723-4437-a44b-94bf462eaf87.png)
<br><br>
## 세마포어
### 개요
- 개념
* 세마포어는 다익스트라가 고안한, 두 개의 원자적 함수로 조작되는 정수 변수로서, 멀티 프로그래밍 환경에서 공유 자원에 대한 접근을 제한하는 방법(상호배제)으로 사용된다.
- 원리
* 세마포어 는 정수값을 가지는 변수이며, 다음과 같이 P와 V라는 명령에 의해서만 접근할 수 있다.
* P와 V는 각각 try와 increment를 뜻하는 네덜란드어의 첫글자를 딴 것이다.
* P는 임계 영역에 들어가기 전에 수행되고, V는 임계 영역에서 나올 때 수행된다.
* 이때 변수 값을 수정하는 연산은 모두 원자성을 만족해야 한다.
* 즉, 한 프로세스에서 세마포어 값을 변경하는 동안 다른 프로세스가 동시에 이 값을 변경해서는 안된다.
- 종류
* 계수 세마포어(Counting Semaphore) : 다수개의 자원에 대해 카운팅이 가능한 세마포어 (0 ~ n)
* 이진 세마포어(Binary Semaphore) : 1개의 자원에 대해 카운팅, 즉 세마포어 값은 0 또는 1을 가진다.
- 설명
* 계수 세마포어의 경우 3개의 자원이 있다고 가정할 때 (S = 3) 여러 프로세스가 이 자원에 접근을 시도할 것이다.
* 자원에 접근할 때마다 세마포어를 하나씩 감소시키고(P), 세마포어 값이 0이 되면 자원에 접근을 시도하는 다른 프로세스는 대기하게 된다.
* 자원에 접근했던 프로세스가 접근을 해제하면 세마포어를 다시 하나씩 증가시키고(V),
* 대기하던 프로세스는 다시 자원에 접근이 가능하게 된다.
* 이진 세마포어는 계수 세마포어의 자원 값이 1개인 경우(S = 1)를 의미한다. (뮤텍스와 유사하다.)
<Br>
### SysV 세마포어
- SysV 세마포어의 주요 함수
* semget : 세마포어의 IPC ID를 생성하거나 가져온다.
* semctl : 세마포어 조작 (초기화, 정복획득, 제거)
* semop : 세마포어 증가/감수
* semtimedop : 타임아웃 기능이 추가된 semop
- 흐름
* 1. semget()을 통해 ID 획득
* 2. 새로 생성된 경우 semctl()을 통해 세마포어 초기화
* 3. semop()를 이용해 세마포어 감소(P)
* 4. 처리
* 5. semop()를 이용해 세마포어 증가(V)
* 6. semctl()을 통해 세마포어 정리
<br>
``` c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
semget() 시스템 호출은 인수 키와 연관된 System V 세마포어 세트 ID를 리턴한다.
키에 IPC_PRIVATE 값이 있거나 키와 연관된 기존 세마포어 세트가 없고 semflg에 IPC_CREAT가 지정된 경우,
새로운 nsems 개의 세마포어 세트가 작성된다. (shmget과 동일)
semflg에 IPC_CREAT와 IPC_EXCL을 모두 지정하고 키와 연관된 세마포어 세트가 이미 존재하는 경우,
결과는 실패하고 errno는 EEXIST로 설정된다.
open (2)의 O_CREAT | O_EXCL 조합의 효과와 유사하다.
semflg 인수의 최하위 9 비트는 세마포어 세트에 대한 권한 (소유자, 그룹, 기타에 대한 권한)을 정읜한다.
이러한 비트는 open (2)의 모드 인수와 동일한 형식 및 의미를 갖는다.
실행 권한은 의미가 없다. 쓰기 권한은 세마포어 값을 변경할 수 있는 권한을 의미한다.
성공 시 양수 세마포어 세트 식별자를 반환하고, 실패 시 -1을 반환 후 errno 설정
ex) 키 값으로 1234를 사용하고 접근권한 666으로 세마포어를 새롭게 생성하는 경우
int sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
semctl()은 semid로 식별 된 System V 세마포어 세트의 semnum번째 세마포어에서 cmd로 지정된 제어 조작을 수행한다.
세트의 세마포어는 0부터 시작하여 번호가 매겨진다.
cmd의 종류에 따라 뒤에 semun 공용체 인자가 추가될 수 있다.
성공 시 cmd에 따라 반환 값이 달라지며, 실패 시 -1 반환 후 errno 설정
cmd 값
IPC_STAT(*) : IPC 자원 정보 획득 (생성자, 생성시간, 접근권한 등)
IPC_SET(*) : IPC 자원 정보 설정
IPC_RMID : IPC 자원 제거
IPC_INFO(*) : IPC 자원 시스템 설정값 획득 (리눅스 전용)
SEM_INFO (*) : IPC_INFO와 유사, 시스템 전체 세마포어 수 획득 가능 (리눅스 전용)
SEM_STAT(*) : IPC_STAT과 유사, 시스템 전체 세마포어 배열 인덱스 획득 (리눅스 전용)
int sem_use;
int semid;
char shared_buf[] = "Hello, World!\n";
int shared_index = 0;
void print_one(void arg)
{
int tidx = (int )arg;
bool b = true;
char c;
while (b) {
if (sem_use) SEM_P(semid);
if (shared_index >= strlen(shared_buf)) {
b = false;
} else {
if (tidx)
c = toupper(shared_buf[shared_index++]);
else
c = tolower(shared_buf[shared_index++]);
sleep(rand()%2);
putchar(c);
fflush(stdout);
}
if (sem_use) SEM_V(semid);
}
pthread_exit((void *)0);
}
int main(int argc, char *argv[])
{
key_t key;
union semun u;
int pid;
pthread_t thread_t;
int i, ret;
int tidx[2] = {0, 1};
- 실행 결과
![image](https://user-images.githubusercontent.com/86760744/183567721-a327f12e-2d49-41ec-82cc-b6d34ccfc940.png)
<br><br>
## POSIX 세마포어
- POSIX 세마포어를 사용하면 프로세스와 스레드가 작업을 동기화할 수 있다.
- POSIX 세마포어의 연산
* sem_wait (3) : 세마포어 값을 1 감소 시킨다. (P), 세마포어의 값이 0이면 0보다 커질 때 까지 차단.
* sem_post (3) : 세마포어 값을 1 증가 시킨다. (V)
- POSIX 세마포어는 두 가지 형태가 있다.
* 명명된 세마포어
* 익명 세마포어
<br>
### 명명된 세마포어 (named semaphore)
- /somename 형식의 이름으로 식별된다.
- 즉, 초기 슬래시로 구성되는 최대 NAME_MAX-4 (즉, 251) 개의 null로 끝나는 문자열
- 동일한 이름을 sem_open (3)에 전달하여 두 개의 프로세스가 동일한 이름의 세마포어에서 작동할 수 있다.
- sem_open(3) 함수는 새로 명명된 새마포어를 생성하거나 기존의 명명된 세마포어를 연다.
- 세마포어를 연 후에는 sem_post (3) 및 sem_wait (3)을 사용하여 작동할 수 있다.
- 프로세스가 세마포어 사용을 마치면 sem_close (3)를 사용해 세마포어를 닫을 수 있다.
- 모든 프로세스가 세마포어 사용을 마치면 sem_unlink (3)를 사용해 시스템에서 제거할 수 있다.
<br>
### 익명 세마포어 (nameless semaphore)
- 이름이 없는 대신 여러 스레드 (스레드 공유 세마포어) 또는 프로세스 (프로세스 공유 세마포어)간에 공유되는 메모리 영역에 배치된다.
- 스레드 공유 세마포어는 프로세스의 스레드(예 : 전역 변수) 간에 공유되는 메모리 영역에 배치된다.
- 프로세스 공유 세마포어는 공유 메모리 영역(예 : shmget (2)을 사용해 작성된 System V 공유메모리 세그먼트 또는 shm_open (3)을 사용해 작성된 POSIX 공유메모리 오브젝트)에 배치해야 한다.
- 생성된 프로세스가 종료되면 세마포어가 없어지는 임시 세마포어이다.
- 사용하기 전에 sem_init (3)을 사용해 초기화해야 한다.
- 그런 다음 sem_post (3) 및 sem_wait (3)을 사용해 작동할 수 있다.
- 세마포어가 더 이상 필요하지 않은 경우, 세마포어가 있는 메모리를 할당 해제하기 전에 sem_destroy (3)를 사용해 세마포어를 제거해야 한다.
<br>
- POSIX 세마포어 **주요 함수**
* sem_open : 명명된 세마포어를 생성하거나 오픈
* sem_close : 명명된 세마포어 닫기
* sem_unlink : 명명된 세마포어 제거
* sem_init : 익명 세마포어 생성 및 초기화
* sem_destroy : 익명 세마포어 제거
* sem_wait : P 연산
* sem_post : V 연산
* 주의 : 컴파일 시 Link with -pthread
<br><br>
``` c
#include <fcntl.h> /* For O_ * constants */
#include <sys/stat.h> /* For mode constatns */
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
int sem_close(sem_t *sem);
int sem_unlink(const char *name);
예
sem_t *sem;
sem = sem_open("/test_sem", O_CREAT|O_EXCL, 0600, 1);
if (sem == SEM_FAILED) {
if (errno != EEXIST) {
perror("sem_open");
exit(1);
}
sem = sem_open("/test_sem", 0);
}
...
sem_close(sem);
sem_unlink("/test_sem");
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
익명 세마포어를 생성하는 sem_init()은 다음 인자를 받는다.
sem : 초기화 후 반환받을 세마포어 객체
pshared : 여러 프로세스에서 공유할 것인지 결정하는 플래그
0일 경우 현재 프로세스만, 0이 아닐 경우 공유
value : 세마포어 초기값
익명 세마포어를 pshared를 통해 공유하려는 경우 sem 인수가 프로세스의 로컬이라면 공유는 안되므로
익명 세마포어를 공유하려면 sem 인수로 넘어오는 객체가 공유 메모리 공간에 위치해야 한다.
<Br>
``` c
#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
int sem_post(sem_t *sem);
sem_wait : P 연산
sem_trywait : sem_wait에 넌블락킹 기능 추가 (EAGAIN)
sem_timedwait : sem_wait에 타임아웃 기능 추가 (ETIMEOUT)
sem_trywait이나 sem_timedwait을 이용하면 sem_wait이 무한대기에 빠질 가능성을 해소할 수 있다.
예
struct timespec ts;
ts.tv_sec = time(NULL) + 5;
ts.tv_nsec = 0;
if (sem_timedwait(sem, &ts) == -1) {
if (errno == ETIMEOUT) {
/* 5초 타임 아웃 */
} else {
/* 에러 처리 */
}
}
15. IPC
IPC 개요
익명 파이프 (Pipe)
파이프란?
|
) 문자를 사용하여,int main() { FILE *fp; int status; char buff[1024];
}
int main() { int fd; char *fifofile = "/tmp/fifo_test"; char inputbuf[1024]; char readbuf[1024];
}
공유메모리 개요
공유메모리란?
SysV 공유메모리
예제
int main() { key_t key; int shmid; char *str;
}
예제
union semun { int val; struct semid_ds buf; unsigned short array; };
struct sembuf p = { 0, -1, SEM_UNDO }; struct sembuf v = { 0, +1, SEM_UNDO };
define SEM_P(ID) ({ \
})
define SEM_V(ID) ({ \
})
int sem_use; int semid; char shared_buf[] = "Hello, World!\n"; int shared_index = 0;
void print_one(void arg) { int tidx = (int )arg; bool b = true; char c;
}
int main(int argc, char *argv[]) { key_t key; union semun u; int pid; pthread_t thread_t; int i, ret; int tidx[2] = {0, 1};
}
익명 세마포어를 생성하는 sem_init()은 다음 인자를 받는다.
익명 세마포어를 pshared를 통해 공유하려는 경우 sem 인수가 프로세스의 로컬이라면 공유는 안되므로
익명 세마포어를 공유하려면 sem 인수로 넘어오는 객체가 공유 메모리 공간에 위치해야 한다.
예
if (sem_init(&sem, 0, 1) == -1) { perror("sem_init"); exit(1); } ... sem_destroy(sem);
예제
sem_t sem;
void thread_t1(void arg) { int tidx = (int )arg; sem_wait(&sem); printf("[%d] Start...\n", tidx); / critical section / sleep(3); printf("[%d] End!\n\n", tidx); sem_post(&sem); }
void thread_t2(void arg) {
try: ret = sem_trywait(&sem); if (ret == -1) { if (errno == EAGAIN) { printf("[%d] retry get semaphoe...\n", tidx); sleep(1); goto try; } perror("sem_trywait"); exit(1); } else if (ret == 0) { printf("[%d] Start...\n", tidx); / critical section / sleep(3); printf("[%d] End!\n\n", tidx); sem_post(&sem); } }
int main() { int ret; pthread_t t1, t2, t3; int tidx[] = {0, 1, 2};
}