IPC

sesame·2022년 2월 7일
0

교육

목록 보기
27/46

Interprocess Communication

IPC는 프로세스간 데이터 이동이다.
ex) 웹브라우저가 웹서버에서 웹페이지를 요청할 때 HTML 데이터를 전송한다.
이 데이터 이동은 대부분 소켓을 이용한다.
ex) $ ls | lpr
셸은 | 기호로 표현되는 파이프로 두 개를 연결하는 ls 프로세스와 별도의 lpr 프로세스를 생성한다.파이프는 두 개의 관련 프로세스 사이의 단방향 통신을 허용힌다. ls 프로세스는 파이프에 데이터를 쓴다. lpr 프로세스는 파이프에서 데이터를 읽습니다.

  1. 공유 메모리는 지정된 메모리 위치에 읽기 및 쓰기만으로 프로세스가 통신할 수 있도록 합니다.
  2. 매핑된 메모리는 파일 시스템의 파일과 연결되어 있다는 점을 제외하고는 공유 메모리와 유사합니다.
  3. 파이프는 한 프로세스에서 관련 프로세스로의 순차적 통신을 허용합니다.
  4. FIFO는 파이프가 파일 시스템에 이름이 주어지기 때문관련 없는 프로세스가 통신할 수 있다는 점을 제외하면 파이프와 유사하다.
  5. 소켓은 서로 다른 컴퓨터에서도 관련 없는 프로세스 간의 통신을 지원합니다.

이러한 유형의 IPC는 다음 기준에 따라 다름

  • 관련 프로세스(공통 상위 프로세스)로 통신을 제한할지, 동일한 파일 시스템을 공유하는 관련 없는 프로세스로 통신을 제한할지 또는 Networ에 연결된 컴퓨터로 제한할지 여부
  • 통신 프로세스가 데이터 쓰기 또는 데이터 읽기만으로 제한되는지 여부
  • 통신이 허용된 프로세스 수
  • 통신 프로세스가 IPC에 의해 동기화되는지 여부(ex: 데이터를 읽을 수 있을 때까지 읽기 프로세스가 중지됨)

Shared Memory

가장 간단한 프로세스 간 통신 방법 중 하나는 공유 메모리를 사용하는 것입니다.
공유 메모리는 둘 이상의 프로세스들이 같은 메모리에 접근할 수 있게 한다.
한 프로세스가 메모리를 변경하면 다른 모든 프로세스에서도 수정 사항이 확인됩니다.

Fast Local Communication

공유 메모리는 모든 프로세스가 동일한 메모리를 공유하기 때문에 프로세스 간 통신에서 가장 빠른 형태

  • 이 공유 메모리에 대한 접근은 프로세스의 공유되지 않은 메모리에 접근하는 것만큼 빠름
  • 커널에 대한 시스템 호출이나 입력이 필요하지 않음
  • 불필요한 데이터 복사를 방지

커널은 공유 메모리에 대한 액세스를 동기화하지 않으므로 사용자가 직접 동기화해야함
ex) 프로세스는 데이터가 기록되기 전까지 메모리에서 읽으면 안 되며, 두 프로세스는 동시에 동일한 메모리 위치에 쓰면 안 된다.
이러한 경주 조건을 피하기 위한 일반적인 전략은 다음 섹션에서 논의하는 세마포어를 사용하는 것이다. 그러나 우리의 예시 프로그램은 공유 메모리 메커니즘에 초점을 맞추고 동기화 논리로 샘플 코드를 혼란스럽게 하지 않기 위해 메모리에 액세스하는 단일 프로세스만 보여준다.

The Memory Model

Linux 메모리 모델을 이해하면 할당 및 첨부 프로세스를 설명하는 데 도움이 됩니다. 리눅스에서 각 프로세스의 가상 메모리는 여러 페이지로 분할됩니다. 각 프로세스는 메모리 주소에서 실제 데이터가 포함된 가상 메모리 페이지로의 매핑을 유지합니다. 각 프로세스가 자체 주소를 가지고 있더라도 여러 프로세스의 매핑은 메모리 공유를 허용하면서 동일한 페이지를 가리킬 수 있습니다. 메모리 페이지는 8장 "리눅스 시스템 호출"의 섹션 8.8 "mlock 제품군: 물리적 메모리 잠금"에서 더 자세히 논의된다.

새 공유 메모리 세그먼트를 할당하면 가상 메모리 페이지가 생성됩니다. 모든 프로세스가 동일한 공유 세그먼트에 액세스하기를 원하기 때문에 하나의 프로세스만 새 공유 세그먼트를 할당해야 합니다.기존 세그먼트를 할당하면 생성되지 않습니다.
새 페이지이지만 기존 페이지의 식별자를 반환합니다.프로세스가 공유 메모리 세그먼트를 사용하도록 허용하기 위해 프로세스가 이를 첨부하여 가상 메모리에서 세그먼트의 공유 페이지로 매핑되는 항목을 추가합니다.세그먼트가 완료되면 이러한 매핑 항목이 제거됩니다.더 이상 공유 메모리 세그먼트에 액세스하려는 프로세스가 없으면 정확히 한 프로세스가 가상 메모리 페이지의 할당을 취소해야 합니다.

세그멘테이션은 프로세스를 세그먼트의 집합으로 생각한다.
하나의 프로세스가 동작하려면 기본적으로 코드, 데이터, 스택 세 가지의 세그먼트는 항상 가지고 있다.
세그멘테이션은 물리적인 크기의 단위가 아닌 논리적 내용의 단위로 자르기 때문에 세그먼들의 크기는 일반적으로 같지 않다.
장점

  • 페이징은 프로세스를 같은 단위로 자르게 되므로 중요한 부분과 중요하지 않은 부분이 같은 페이지 안으로 잘라질 수 있다
  • 세그멘테이션 방법으로 자르게 되면 코드 영역은 코드 영역으로 잘리게 되고, 중요한 세그먼트, 중요하지 않은 세그먼트를 논리적인 내용 측면으로 자를 수 있다.
  • 그렇기 때문에 보호와 공유의 기능을 수행하기 쉬워짐
    단점
  • 세그먼트는 크기가 고정되어있지 않고 가변적
  • 크기가 다른 각 세그먼트를 메모리에 두려면 동적 메모리 할당해야함
  • 외부 단편화(메모리 낭비) 발생 가능

Allocation

shmget

인자로 전달된 key의 값으로 공유메모리를 얻고 shared memory segment의 id를 반환

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

key: 공유메모리를 할당할때 사용하는 고유 key값
size: 메모리의 최소 size를 의미, 새로운 공유메모리를 할당받는다면 size를 명시하고 이미 존재하는 메모리면 0

shmflg:

  • IPC_CREAT: 새로운 메모리 세그먼트는 만듬, 이 flag를 사용하지 않는다면 shmget은 명시된 key와 연관된 것 찾고 접근할 수 있는 권한이 있는지 확인

  • IPC_EXCL: IPC_CREAT과 함께쓰는 플래그로 만약 메모리 세그먼트가 존재하면 shmget은 실패하게됨
    IPC_EXCL을 사용하는 경우는 우선 공유메모리가 있는지 확인 후 없으면 IPC_CREAT을 통해 할당받으라는 뜻, 이렇게 공유메모리가 오염되는 것을 방지가능

  • Mode flags: ex) S_IWUSER : 부여된 권한을 나타내는 9비트
    user, group 및 other를 사용하여 세그먼트에 대한 액세스를 제어, 실행 비트는 무시

    S_IRUSR 및 S_IWUSR은 공유 메모리 세그먼트 소유자의 읽기 및 쓰기 권한
    S_IROTH 및 S_IWOTH는 다른 사용자의 읽기 및 쓰기 권한

Attachment and Detachment

shmat(2)

공유메모리를 얻었으면 메모리의 위치에 이 프로세스를 묶는(attach) 시스템 콜

void *shmat(int shmid, const void *shmaddr, int shmflg);

shmid: 공유메모리의 id를 의미합니다. shmget을 통해 얻어올 수 있습니다.
shmaddr:
-NULL(0)일 경우: 커널에서 적절한 주소를 반환하여 줌
-NULL이 아닐 경우: shmflg로 SHM_RND일때, 그 주소와 attach할 가장 가까운 주소를 반환
shmflg:

  • SHM_RND: 두 번째 매개변수에 대해 지정된 주소를 페이지 크기의 배수로 내림해야 함을 나타냄
    이 플래그를 지정하지 않으면 두 번째 인수를 페이지 정렬하여 사용자 자신을 shmatch해야 함
  • SHM_RDONLY:세그먼트가 읽기만 되고 쓰기가 되지 않음을 나타냅니다.

return: 성공시 적절한 포인터, 실패시 (void)-1(공유 메모리에 들어가있는 데이터가 정수형인지 구조체인지 모르니까 무엇이든 받을 수 있는 void 넘겨줄테니 알아서 반환해라)

shmdt(2)

공유메모리를 이 프로세스와 떼어냄, 이는 공유메모리를 제거하는 것이 아님에 주의

int shmdt(const void *shmaddr);

shmaddr : shmat에서 전달받은 그 포인터를 전달하면 됩니다.
return: 성공 시 0, 실패시 -1을 반환합니다.

Controlling and Deallocating Shared Memory

shmctl(2)

공유메모리를 제어하기 위해 사용
ex) 공유메모리의 정보를 얻거나 어떤 값을 쓰거나 공유메모리를 삭제하는 등의 조작이 있다.

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

shmid: 공유메모리 id
cmd: 제어할 일종의 command

  • IPC_STAT: 공유 메모리 세그먼트에 대한 정보를 조회하여 buf에 저장
  • IPC_RMID: shmid를 생성한 shared memory의 key와 관련된 shared memory를 system에서 삭제, argument buf에 설정된 값은 무시되니 NULL을 설정
  • IPC_SET : shmget(2)으로 생성된 shared memory의 shm_perm.uid, shm_perm.gid, shm_perm.mode 정보를 변경

buf: shmid_ds라는 구조체

struct shmid_ds {
	struct  ipc_perm shm_perm;    // 소유권과 권한
    size_t shm_segsz;   // 세그먼트의 크기 (bytes)
    time_t shm_atime;   // Last attach time
    time_t shm_dtime;   // Last detach time
    time_t shm_ctime;   // Last change time
    pid_t shm_cpid;    // 최초로 만들어낸 PID
    pid_t shm_lpid;    // 최근 shmat, shmdt를 수행한 PID
    shmatt_t shm_nattch;  // No. of current attaches
    ...
};
//
//shmid_ds 구조체의 첫번째 구조체(ipc_perm)
struct ipc_perm {
	key_t __key;    // Key supplied to shmget(2)
    uid_t uid;      // Effective UID of owner
    gid_t gid;      // Effective GID of owner
    uid_t cuid;     // Effective UID of creator
    gid_t cgid;     // Effective GID of creator
    unsigned short mode;     // Permissions + SHM_DEST and SHM_LOCKED flags
    unsigned short __seq;    // Sequence number
};

return: 성공시 -1이 아님, 실패시 -1/errno

각 공유 메모리 세그먼트는 시스템 전체 공유 메모리 세그먼트 수에 대한 제한을 위반하지 않도록 작업이 끝나면 shmctl을 사용하여 명시적으로 할당 해제해야 합니다. 종료 및 Exec을 호출하면 메모리 세그먼트가 분리되지만 할당 해제되지는 않습니다.

shm.c

#include <stdio.h>
#include <sys/shm.h>
#include <sys/stat.h>
int main (){
        int segment_id;
        char* shared_memory;
        struct shmid_ds shmbuffer;
        int segment_size;
        const int shared_segment_size = 0x6400;

        /* Allocate a shared memory segment. */
        segment_id = shmget(IPC_PRIVATE, shared_segment_size, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);

        /* Attach the shared memory segment. */
        //두번째 인자 0인 경우 적절한 주소 반환
        shared_memory = (char*) shmat(segment_id, 0, 0);
        printf("shared memory attached at address %p\n", shared_memory);

        /* Determine the segment’s size. */
        //공유 메모리 정보 조회하여 shmbuffer 에 저장
        shmctl(segment_id, IPC_STAT, &shmbuffer);
        segment_size = shmbuffer.shm_segsz;
        printf("segment size: %d\n", segment_size);

        /* Write a string to the shared memory segment. */
        //sprintf()는 출력값을 문자열에 저장하는 함수
        sprintf(shared_memory, "Hello, world.");

        /* Detach the shared memory segment. */
        shmdt(shared_memory);

        /* Reattach the shared memory segment, at a different address. */
        shared_memory = (char*) shmat(segment_id, (void*) 0x5000000, 0);
        printf("shared memory reattached at address %p\n", shared_memory);

        /* Print out the string from shared memory. */
        printf("%s\n", shared_memory);

        /* Detach the shared memory segment. */
        shmdt(shared_memory);

        /* Deallocate the shared memory segment. */
        shmctl(segment_id, IPC_RMID, 0);

        return 0;
}

shared memory attached at address 0x7fb65aec2000
segment size: 25600
shared memory reattached at address 0x5000000
Hello, world.

Debugging

$ ipcs -m

------ Shared Memory Segments --------
key        shmid  owner perms bytes  nattch status
0x00000000 1627649 user  640  25600     0

-m: 공유 메모리
:: 1627649 shmid인 공유 메모리 세그먼트가 사용중임을 나타냄

장점 단점

  • 공유 메모리 세그먼트는 여러 프로세스 간에 빠른 양방향 통신을 허용
  • 각 사용자는 읽고 쓸 수 있지만, 프로그램은 읽기 전에 정보를 덮어쓰기와 같은 레이스 조건을 방지하기 위한 프로토콜을 설정하고 따라야 함
  • 유감스럽게도 IPC_PRIVATE로 새 공유 세그먼트를 생성하는 경우에도 Linux는 배타적 액세스를 엄격하게 보장하지 않음
  • 또한 여러 프로세스가 공유 세그먼트를 사용하려면 동일한 키를 사용하도록 설정해야 함

Processes Semaphores

semaphores: shared data의 개수
두 개 이상의 프로세스가 동시에 공유 메모리와 같은 공유 자원을 접근할때 동기화를 걸어주는 것이 목표(한번에 여러 프로세스가 접근하여 데이터를 동시에 변경하는 것을 막기 위한 장치)

  • binary semaphore: 0또는 1의 값만 갖는 세마포어
  • counting semaphore: 0, 1, 2, 3, 4 등의 값들 또한 가질 수 있는, 즉 도메인이 제한 없는 세마포어
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

Allocation and Deallocation

semget(2)

세마포어 식별자를 얻는데 쓰이는 시스템콜
세마포어는 집합이라 같은 집합에 속하여 index로 구분된다.
보통은 한 집합에 1개의 세마포어를 사용

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

key: 세마포어를 식별하는 키
nsems: 세마포어 자원의 갯수
semflg: 세마포어 동작옵션

  • IPC_CREAT: 새로운 세마포어를 만듬
  • IPC_EXCL : IPC_CREAT과 같이 사용하는데, 이미 세마포어가 존재할 경우 Error를 반환

return 성공시 세마포어 식별자

semctl(2)

세마포어를 제어할 수 있는 시스템콜

int semctl(int semid, int semnum, int cmd, ...);
  • semid: 세마포어의 식별자

  • semnum: semaphore 집합에서 표현되는 일종의 인덱스

  • cmd: 세마포어를 제어할 수 있는 command(이것에 따라 semctl이 3개의 인자를 갖느냐, 4개의 인자를 갖느냐가 결정)

    cmd내용
    GETVAL세마포어의 현재 값을 구한다.
    GETPID세마포어에 가장 최근에 접근했던 프로세스의 프로세스 ID를 구한다.
    GETNCNT세마포어 값이 증가하기를 기다리는 프로세스의 개수
    GETZCNT세마포어 값이 0 이 되기를 기다리는 프로세스의 개수
    GETALL세마포어 집합의 모든 세마포어 값을 구한다.
    SETVAL세마포어 값을 설정
    SETALL세마퍼어 집합의 모든 세마포어 값을 설정
    IPC_STAT세마포어의 정보를 구한다.
    IPC_SET세마포어의 소유권과 접근 허가를 설정
    IPC_RMID세마포어 집합을 삭제
  • union semun: cmd에 의해 4번째 인자가 쓰일때 여러분이 작성하는 프로그램에서는 아래의 union을 정의해주어야함

union semun {
	int val;    // Value for SETVAL
    struct semid_ds *buf;    // Buffer for IPC_STAT, IPC_SET
    unsigned short *array;  // Array for GETALL, SETALL
    struct seminfo *__buf;  // Buffer for IPC_INFO (Linux-specific)
};
//
//semun에 멤버 semid_ds 구조체
struct semid_ds {
	struct ipc_perm sem_perm;  // Ownership and permissions
	time_t sem_otime; // Last semop() time
	time_t sem_ctime; // Last change time
	unsigned long   sem_nsems; // No. of semaphores in set
};

return 성공시 0, 실패시 -1

sem_all_deall.c

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

/* We must define union semun ourselves. */
union semun {
        int val;
        struct semid_ds *buf;
        unsigned short int *array;
        struct seminfo *__buf;
};

/* Obtain a binary semaphore's ID, allocating if necessary. */
int binary_semaphore_allocation(key_t key, int sem_flags){
        return semget(key, 1, sem_flags);
}

/* Deallocate a binary semaphore. All users must have finished their use. Returns -1 on failure. */
//명시적으로 할당 해제해야함
int binary_semaphore_deallocate(int semid){
        union semun ignored_argument;
        return semctl(semid, 1, IPC_RMID, ignored_argument);
}
int main(){
        int semid;

        semid = binary_semaphore_allocation(IPC_PRIVATE, IPC_CREAT|0660);
        if(semid == -1)
                printf("semget error\n");

        int del = binary_semaphore_deallocate(semid);
        if(del == -1)
                printf("semctl error\n");
        else if(del == 0)
                printf("semctl success\n");
}

Initializing Semaphores

세마포어를 할당하고 초기화하는 것은 두 개의 별개의 작업이다.

바이너리 세마포어 초기화

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

// We must define union semun ourselves.
union semun {
        int val;
        struct semid_ds *buf;
        unsigned short int *array;
        struct seminfo *__buf;
};
// 값이 1인 이진 세마포를 초기화
int binary_semaphore_initialize (int semid){
        union semun argument;
        unsigned short values[1];
        values[0] = 1;
        argument.array = values;
        return semctl(semid, 0, SETALL, argument);
        //int semctl(int semid, int semnum, int cmd, ...);
}
//네 번째 인수의 경우 합집합 반달 객체를 작성하고 해당 배열 필드를 부호 없는 짧은 값의 배열로 지정

Wait and Post Operations

semop(2)

세마포어의 값을 증가, 감소시킴으로써 크리티컬 섹션 전 후에 사용

int semop(int semid, struct sembuf *spos, size_t nsops);
  • semid: semid
  • spos: sembuf라는 구조체 포인터
struct sembuf{
 	unsigned short sem_num;  //sem_num은 작업이 수행되는 세마포어 집합의 세마포어 번호
 	short sem_op;   // 세마포어 작업을 지정하는 정수
    	//양수이면 해당 숫자는 즉시 세마포어 값에 추가
    	//음수이면, 그 숫자의 절대값은 세마포어 값에서 차감
    	//만약 이것이 세마포어 값을 음의 값으로 만든다면, 세마포어 값이 세마포어의 절대값만큼 커질 때까지 호출은 차단
    	//0이면 세마포어 값이 0이 될 때까지 작업이 차단
 	short sem_flg;  // operation flags
 };
  • IPC_NOWAIT: 는 작업이 차단되지 않도록 함
    작업이 차단되었으면 semop에 대한 호출이 대신 실패
  • SEM_UNDO: 프로세스가 종료될 때 리눅스는 자동으로 세마포어의 작업을 실행 취소

nsops: 변경하려는 세마포어 개수로 변경하려는 세마포어 개수가 여러 개일 때 사용
n개의 세마포어 옵션들

ex) Wait and Post Operations for a Binary Semaphore

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

// 세마포어 값이 양수가 될 때까지 차단한 다음 1만큼 줄임
int binary_semaphore_wait (int semid){
        struct sembuf operations[1];

        //0이면 세마포어 값이 0이 될 때까지 작업이 차단
        operations[0].sem_num = 0;

        //음수이면, 그 숫자의 절대값은 세마포어 값에서 차감
        operations[0].sem_op = -1;

        //프로세스가 종료될 때 리눅스는 자동으로 세마포어의 작업을 실행 취소
        operations[0].sem_flg = SEM_UNDO;

        return semop(semid, operations, 1);
}
//이진 세마포어의 값을 1씩 증가시킴, 즉시 반환
int binary_semaphore_post (int semid){
        struct sembuf operations[1];

        //0이면 세마포어 값이 0이 될 때까지 작업이 차단
        operations[0].sem_num = 0;

        //양수이면 해당 숫자는 즉시 세마포어 값에 추가
        operations[0].sem_op = 1;

        //프로세스가 종료될 때 리눅스는 자동으로 세마포어의 작업을 실행 취소
        operations[0].sem_flg = SEM_UNDO;

        return semop(semid, operations, 1);
}

Debugging Semaphores

$ ipcrm sem 5790517
식별자가 5790517인 세마포어 집합을 제거하려면 이 명령어 사용

Mapped Memory

매핑된 메모리는 파일과 프로세스의 메모리 사이의 연결을 형성합니다. 리눅스는 파일을 페이지 크기로 나눈 다음 가상 메모리 페이지로 복사하여 프로세스의 주소 공간에서 사용할 수 있도록 한다.따라서 프로세스는 일반적인 메모리 액세스로 파일의 내용을 읽을 수 있습니다. 메모리에 기록하여 파일 내용을 수정할 수도 있습니다.이렇게 하면 파일에 빠르게 액세스할 수 있습니다.

매핑된 메모리는 버퍼를 할당하여 파일의 전체 내용을 보관한 다음 파일을 버퍼로 읽어들이고 버퍼가 수정된 경우 버퍼를 나중에 파일에 다시 쓰는 것이라고 생각할 수 있습니다. Linux는 파일 읽기 및 쓰기를 처리합니다.

Mapping an Ordinary File

일반 파일을 프로세스의 메모리에 매핑하려면 mmap("메모리 MAPped", "em-map") 호출을 사용합니다.첫 번째 인수는 리눅스가 프로세스의 주소 공간에 파일을 매핑할 주소입니다. NULL 값을 사용하면 리눅스가 사용 가능한 시작 주소를 선택할 수 있습니다.두 번째 인수는 맵의 길이(바이트)입니다.세 번째 인수는 매핑된 주소 범위에 대한 보호를 지정합니다.보호는 각각 읽기, 쓰기 및 실행 권한에 해당하는 PROT_READ, PROT_WRITE 및 PROTECT_EXEC의 비트 "또는"로 구성됩니다.네 번째 논쟁은
추가 옵션을 지정하는 플래그 값입니다.다섯 번째 인수는 매핑될 파일에 열려 있는 파일 설명자이다.마지막 인수는 맵을 시작할 파일의 시작부터의 오프셋입니다.파일 전체 또는 일부를 메모리에 매핑할 수 있습니다.
시작 간격띄우기 및 길이를 적절하게 선택함으로써.

#include <sys/mmap.h>

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

start: 파일을 매핑할 주소
length: 매핑할 길이
prot: 실행 권한, PROT_READ, PROT_WRITE, PROT_EXEC, PROT_NONE
flags:

  • MAP_FIXED: 매핑할 주소를 정확히 지정한다. 해당 메모리 영역에 매핑된 파일이 있다면 이파일의 매핑은 해제되고 새파일이 매핑된다. 이 플래그는 시스템이 메모리를 효율적으로 사용할 수 없게 하므로 사용을 권장하지 않는다.
  • MAP_PRIVATE: 데이터의 변경 내용을 공유하지 않는다. 이 플래그가 설정되어 있으면 최초의 쓰기 동작은 매핑된 메모리의 내용을 변경한다.
  • MAP_SHARED: 다른 사용자와 데이터의 변경 내용을 공유한다. 이 플래그가 설정되어 있으면 쓰기 동작은 매핑된 메모리의 내용을 변경한다.
    fd: file descriptor
    offset: file offset

Write a Random Number to a Memory-Mapped File

mmap-write 프로그램은 파일을 열어 이전에 존재하지 않았던 파일을 만듭니다.그
열 세 번째 인수는 읽기 및 쓰기를 위해 파일이 열려 있음을 지정합니다. 파일의 길이를 모르기 때문에 lsek를 사용하여 파일이 정수를 저장할 수 있을 만큼 충분히 큰지 확인한 다음 파일 위치를 다시 시작 부분으로 이동합니다. 더 이상 필요하지 않기 때문에 프로그램은 파일을 매핑한 다음 파일 설명자를 닫습니다.프로그램은 매핑된 메모리에 임의의 정수를 쓰고, 따라서 파일을 쓰고, 메모리를 언매칭한다.프로그램이 종료될 때 리눅스가 자동으로 파일의 매핑을 해제하므로 문맵 호출은 불필요합니다.

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#define FILE_LENGTH 0x100

/* Return a uniformly random number in the range [low,high]. */
int random_range(unsigned const low, unsigned const high){
        unsigned const range = high - low + 1;
                return low + (int) (((double) range) * rand () / (RAND_MAX + 1.0));
}

int main(int argc, char* const argv[]){
        int fd;
        void* file_memory;

        //void srand(unsigned int) 형태이다. 리턴타입은 없으며,
        //전달인자로 주는 unsigned int 값을 seed라 부른다.
        //즉 랜덤한 난수를 생성하려는 seed값을 결정해주면 된다.
        //time에 null을 넣어주면 1970 ~ 부터의 경과시간을 돌려준다.
        //따라서 항상 다른 값이 나옴
        srand(time(NULL));

        //이전에 존재하지 않던 파일 생성
        //부호 없는 정수를 저장할 수 있을 정도의 크기인지 확인
        fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
        //lseek로 충분히 큰지 확인
        lseek(fd, FILE_LENGTH+1, SEEK_SET);
        write(fd, "", 1);
        //파일 위치를 다시 시작부분으로 이동
        lseek(fd, 0, SEEK_SET);

        /* Create the memory mapping. */
        file_memory = mmap(0, FILE_LENGTH, PROT_WRITE, MAP_SHARED, fd, 0);
        close (fd);

        //매핑된 메모리에 임의의 정수 write
        sprintf((char*) file_memory, "%d\n", random_range (-100, 100));

        //프로그램이 종료될 때 리눅스가 자동으로 파일의 매핑을 해제하므로 문맵 호출은 불필요
        munmap (file_memory, FILE_LENGTH);

        return 0;
}

Read an Integer from a Memory-Mapped File, and Double It

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#define FILE_LENGTH 0x100

int main (int argc, char* const argv[]){
        int fd;
        void* file_memory;
        int integer;

        /* Open the file. */
        fd = open(argv[1], O_RDWR, S_IRUSR | S_IWUSR);

        /* Create the memory mapping. */
        file_memory = mmap(0, FILE_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        close(fd);

        //파일의 숫자를 읽은 다음 두배의 값을 파일에 작성
        sscanf(file_memory, "%d", &integer);
        printf("value: %d\n", integer);
        sprintf((char*) file_memory, "%d\n", 2 * integer);

        //프로그램이 종료될 때 리눅스가 자동으로 파일의 매핑을 해제하므로 문맵 호출은 불필요
        munmap(file_memory, FILE_LENGTH);

        return 0;
}

Shared Access to a File

msync(2)

메모리 맵에 데이터를 갱신해도 바로 파일과 동기화가 이루어지는 것은 아니다. 커널이 여유있을 때 동기화 수행
그렇기 때문에 개발자가 직접 동기화를 보장하고 싶을 때 사용

int msync(void *start, size_t length, int flags);

start: mmap을 통해 리턴 받은 메모리 맵의 시작 주소
length: 동기화할 길이
flags:

  • MS_ASYNC: 비동기 방식. 동기화(Memory->File)하라는 명령만 내리고 결과에 관계 없이 바로 리턴. 그렇기 때문에 동기화가 된 건지 알수 없다.
  • MS_SYNC: 동기 방식. 동기화(Memory->File)가 될때까지 블럭 상태로 대기한다.
  • MS_INVALIDATE: 현재 메모리 맵을 무효화하고 파일의 데이터로 갱신. 즉 File->Memory

공유 메모리 세그먼트와 마찬가지로 메모리 매핑 영역은 둘 이상의 프로세스가 한번에 매핑된 메모리에 접근하는 것을 방지하기 위해 세마포어 또는 fcntl 사용될 수 있다.

Private Mappings

MAP_Private를 mmap로 지정하면 쓰기 시 복사 영역이 생성된다. 영역에 대한 모든 쓰기는 이 프로세스의 메모리에만 반영되며, 동일한 파일을 매핑하는 다른 프로세스에서는 변경 사항을 볼 수 없다. 모든 프로세스가 공유하는 페이지에 직접 쓰는 대신 이 페이지의 개인 복사본에 쓴다.이후 프로세스에 의한 모든 읽기 및 쓰기는 이 페이지를 사용

통신 이외 목적 mmap

  • 한 가지 일반적인 용도는 읽기와 쓰기를 대체하는 것
    일부 프로그램의 경우 이 방법이 명시적 파일 I/O 작업보다 더 편리하고 빠르게 실행될 수 있음

  • 메모리 매핑된 파일에 데이터 구조(예를 들어, 일반 구조 인스턴스)를 구축하는 것
    이후 호출 시 프로그램은 파일을 메모리에 매핑하고 데이터 구조는 이전 상태로 복원됨
    그러나 이러한 데이터 구조의 포인터들은 그들이 모두 메모리의 매핑된 동일한 영역 내에서 가리키고 파일이 원래 점유했던 동일한 주소 영역으로 다시 매핑되도록 주의를 기울이지 않는 한 유효하지 않다.

  • 특수 /dev/zero 파일을 메모리에 매핑하는 것입니다. 0바이트의 소스가 필요한 프로그램은 /dev/zero 파일을 mmap할 수 있습니다.

Pipes

Creating Pipes

int pipe_fds[2];
int read_fd;
int write_fd;

pipe(pipe_fds);
read_fd = pipe_fds[0];
write_fd = pipe_fds[1];

Communication Between Parent and Child Processes

파이프 호출은 해당 프로세스와 하위 프로세스 내에서만 유효한 fd 만듬
프로세스의 fd는 관련 없는 프로세스로 전달할 수 없지만 fork를 호출할 때 fd는 child process에 복사됨

Using a Pipe to Communicate with a Child Process

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

//각각 1초씩 중지
void writer(const char* message, int count, FILE* stream){
        for(; count > 0; --count) {
                //stream에 message 작성
                fprintf(stream, "%s\n", message);
                fflush(stream);

                sleep(1);
        }
}

/* Read random strings from the stream as long as possible. */
//stream에서 임의의 문자열을 가능한 오래 읽음
void reader (FILE* stream){
        char buffer[1024];

        //stream이 끝날 때까지 읽음, 줄바꿈이나 EOF까지
        while (!feof (stream) && !ferror (stream) && fgets (buffer, sizeof (buffer), stream) != NULL)
        fputs (buffer, stdout);
}

int main (){
        int fds[2];
        pid_t pid;

        //pipe 생성
        pipe(fds);

        /* Fork a child process. */
        pid = fork();
        if (pid == (pid_t) 0) {
                FILE* stream;

                //write fd 닫음
                close (fds[1]);

                /* Convert the read file descriptor to a FILE object, and read from it. */
                //read fd에서 읽음
                stream = fdopen (fds[0], "r");
                reader (stream);
                close (fds[0]);
        }
        else {
        /* This is the parent process. */
                FILE* stream;

                //read fd 닫음
                close (fds[0]);

                //fd에 write함
                stream = fdopen(fds[1], "w");
                writer("Hello, world.", 5, stream);
                close (fds[1]);
        }
        return 0;
}

Redirecting the Standard Input, Output, and Error Streams

dup, dup2

int dup(int fd);

int dup2(int fd1, int fd2);

fd를 복제하는 함수
dup() 호출 성공시 가장 낮은 fd 반환, 실패시 -1
dup2() 호출 성공시 fd2를 새 fd값으로 지정, 실패시 -1

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(){
        int fds[2];
        pid_t pid;

        //creat pipe
        pipe(fds);

        // Fork a child process
        pid = fork ();
        if(pid == (pid_t) 0) {
                //write fd close
                close(fds[1]);

                //read fd connect stdin
                //dup2는 fds[0] 서술자 값을 두번째 인자로 설정
                dup2(fds[0], STDIN_FILENO);

                //파일 내부 텍스트 정렬함, default 알파벳 오름차순
                execlp("sort", "sort", 0);
        }
        else {
                /* This is the parent process. */
                FILE* stream;

                //read fd close
                close(fds[0]);

                /* Convert the write file descriptor to a FILE object, and write to it. */
                //write fd 에 작성
                stream = fdopen(fds[1], "w");
                fprintf(stream, "This is a test.\n");
                fprintf(stream, "Hello, world.\n");
                fprintf(stream, "My dog has fleas.\n");
                fprintf(stream, "This program is great.\n");
                fprintf(stream, "One fish, two fish.\n");
                fflush(stream);
                close (fds[1]);

                //pid인 child process 종료까지 대기
                waitpid(pid, NULL, 0);
        }
        return 0;
}

popen and pclose

파이프의 일반적인 용도는 하위 프로세스에서 실행 중인 프로그램으로 데이터를 보내거나 받는 것
popen 및 pclose 함수는 pipe(), fork(), dup2(), exec(), fdopen()을 호출할 필요가 없음

FILE *popen(const char *command, const char *type);

command: 실행할 명령어
type:
"r": 읽기용
"w": 쓰기용
return 성공시 FILE 포인터, 실패시 NULL

int pclose(FILE *stream);

return 성공시 -1아닌값, 실패시 -1

popen.c

#include <stdio.h>
#include <unistd.h>
int main (){
        FILE* stream = popen("sort", "w");

        fprintf(stream, "This is a test.\n");
        fprintf(stream, "Hello, world.\n");
        fprintf(stream, "My dog has fleas.\n");
        fprintf(stream, "This program is great.\n");
        fprintf(stream, "One fish, two fish.\n");

        return pclose (stream);
}

FIFOs

0개의 댓글