Redirection

Posted by 시경이 Study/Operation system : 2009/10/14 23:13
Redirection 이란, 표준 입출력에서 입력 혹은 출력을 다른 것으로 대체하는데 쓰입니다.

그래서 가장 일반적으로 많이 알고 있는 것은,

cat file1 > file2

와 같은 명령문인데,
위 명령은, cat file1 의 출력위치를 stdout 에서 file2 로 대체한 것입니다.
그러므로 file1 의 내용이 file2 에 저장됩니다.

사실상 redirection의 정확한 의미는,
프로그램이 실행되기 전에 '파일디스크립터'를 변경한다는 것입니다.

man page 에는 다음과 같은 예제가 나옵니다.

ls > dirlist 2>&1
ls 2>&1 > dirlist

위의 명령문을 이해하기 위해서는 우선 파일 디스크립터 라는 개념을 이해해야 합니다.

파일 디스크립터에서,

0 : stdin
1 : stdout
2 : stderr

로 지정되어 있고, 3~9번까지는 별도의 디스크립터가 있습니다.
이 각각의 0~9번호가 파일을 핸들하는 일종의 변수(variable)라고 생각하면 됩니다.
그럼 stdin, stdout, stderr 가 일종의 파일로서 0~2 번의 디스크립터에 저장되어 있다고 보면 됩니다.
이점을 숙지한 후 다시 위의 예제로 돌아가 봅시다.

redirection 는 기본적으로 다음과 같은 form 을 갖습니다.

[n] < word    (default n = 0)
[n] > word    (default n = 1)

윗줄의 명령문은 출력 redirection 으로 n 이 생략된 경우 n=0 입니다.
아랫줄의 명령문은 입력 redirection 으로 n이 생략된 경우 n=1 입니다.

ls > dirlist 에서 ">dirlist"의 의미는 n이 생략된 것으로 "1>dirlist" 의미와 같습니다.
즉, 출력파일 디스크립터를 dirlist 로 대체하겠다는 의미입니다.

여기서, ls > dirlist 를 하게 되면 출력결과를 dirlist로 보낸다라고 설명하는것은 그다지 정확한 것은 아닙니다.
1번파일 디스크립터를 dirlist 로 바꾼 후에 ls 를 실행한다고 설명하는게 좀 더 정확합니다.

실행되면 ls 는 1번 파일 디스크립터로 값을 보낼꺼고, 1번은 stdout에서 dirlist 로 바뀌어 있으므로
dirlist 에 ls 의 결과값이 저장되어 있는 것입니다.

그리고 2>&1 의 의미는, 2번 파일 디스크립터를 &1이 나타내는 것으로 &1 은 앞에서 dirlist 로 바꾸어 놓았기 때문에
2번 디스크립터가 dirlist 로 대체되었다고 할 수 있습니다.

그래서 결론적으로 dirlist 는 1번, 2번 디스크립터를 통해 받는 stdout 과 stderr 에 해당하는 값들을 다 받게 됩니다.



참고 : http://www.postech.ac.kr/plus
태그 : redirection

메세지 큐(message queue)

Posted by 시경이 Study/Operation system : 2007/11/21 18:39
1 메세지 큐 란?

메시지큐는 메시지를 queue 데이타 구조 형태로 관리한다. 는 선입선출(먼저 들어간게 먼저 나오는) 데이타 구조를 말하며, 보통의 은행창구 혹은 일반적인 줄서기를 생각하면 된다. 이것은 흔히 FIFO(First in First Out)라고 불리운다(IPC 의 FIFO 설비와 혼동하지 말자). 이것과 반대되는 데이타 구조를 stack 이라고 하며, 큐와 반대로 가장 나중에 들어온게(가장 최근데이타) 먼저 나오는 형태를 가진다.

일반적인 배열을 접근방법에 따라 특수하게 분류한것이라고 생각하기 바란다.

메시지큐는 커널에서 전역적으로 관리되며(이를테면 커널 전역변수형태로), 모든 프로세스에서 접근가능하도록 되어있으므로, 하나의 메시지큐 서버가 커널에 요청해서 메시지큐를 작성하게 되면, 메시지큐의 접근자(식별자)를 아는 모든 프로세서는 동일한 메시지큐에 접근함으로써, 데이타를 공유할수 있게 된다.

메시지큐의 IPC로써의 특징은 다른 공유방식에 비해서 사용방법이 매우 직관적이고 간단하다라는데 있다. 다른 코드의 수정없이 단지 몇줄만의 코드를 추가시킴으로써 간단하게 메시지큐에 접근할수 있다.

또한 뒤에서 자세히 다루겠지만 메시지의 type 에 의해서 여러종류의 메시지를 효과적으로 다룰수 있는 장점을 가지고 있다. 여러개의 프로세스가 하나의 메시지큐를 엑세스 할때, 각 메시지에 type 를 줌으로써 각 프로세스에게 필요로하는 메시지만을 가져가게 할수 있는 상당히 편리한 기능을 제공한다.

또한 구조체를 몽땅 넘길수 있고, 이는 데이타의 사용을 매우 편하게 만들어준다.


2 메시지큐의 생성, 사용, 제어

메시지큐를 생성하고, 이를 이용 및 조작하기 위해서 Unix 시스템은 다음과 같은 함수들을 제공한다. 앞으로의 설명은 아래의 함수들을 기준으로 이루어지게 될것이다.

#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/msg.h> 
 
int msgget (key_t key, int msgflg) 
int msgsnd (int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg) 
ssize_t msgrcv (int msqid, struct  msgbuf  *msgp,  size_t msgsz,  
                long msgtyp, int msgflg)  
 
Unix 커널은 메시지큐 정보를 유지하기 위해서 msqid_ds 라는 별도의 구조체를 유지한다. msqid_ds 구조체는 /usr/include/bits/msq.h 에 선언되어 있으며 대충 다음과 같은 구조를 가진다

이건 linux os 기준이며, Unix 에 따라 약간씩 다를수 있다
struct msqid_ds 
{ 
    struct ipc_perm msg_perm; /* structure describing operation permission */ 
    __time_t msg_stime;       /* time of last msgsnd command */ 
    unsigned long int __unused1; 
    __time_t msg_rtime;       /* time of last msgrcv command */ 
    unsigned long int __unused2; 
    __time_t msg_ctime;       /* time of last change */ 
    unsigned long int __unused3; 
    unsigned long int __msg_cbytes; /* current number of bytes on queue */ 
    msgqnum_t msg_qnum;       /* number of messages currently on queue */ 
    msglen_t msg_qbytes;      /* max number of bytes allowed on queue */ 
    __pid_t msg_lspid;        /* pid of last msgsnd() */ 
    __pid_t msg_lrpid;        /* pid of last msgrcv() */ 
    unsigned long int __unused4; 
    unsigned long int __unused5; 
}; 
 
최초 msgget 를 이용해서 커널에 메시지큐를 요청하면, 커널은 해당 메시지큐를 위해 메모리를 할당하고, 메모리 관리와 그밖의 정보 관리를 위해 위의 구조체를 세팅하게 된다.


3 메시지큐 생성

메시지큐의 생성과 기존에 있던 메시지큐의 참조는 msgget(2) 를 이용해서 이루어진다. 첫번째 아규먼트인 key 는 kernel 에서 유일한 메시지큐를 만들고 참조하기 위해서 사용하는 식별번호이며, msgflg 는 메시지큐를 어떻게 생성하고 참조할지 행동양 식을 정해주기 위한 아규먼트이다. key 는 적당하게 유일한 int 형의 숫자를 정해주면 된다. msgflg 에는 IPC_CREAT와 IPC_EXCL등의 시작동작을 정해줄수 있으며, 퍼미션을 지정해 줄수도 있다.
IPC_CREAT
메시지큐를 새로 생성하기 위해서 사용한다. 만약 기존에 key 로 생선된 메시지큐가 있다면 해당 메시지큐의 식별자를 되돌려준다.
IPC_EXCL
IPC_CREAT 와 함께 쓰이며, IPC_EXCL이 지정되어 있을경우 이미 key 로 존재하는 메시지큐가 있다면, -1 을 리턴하고 errno 를 세팅한다.

msgget 는 성공할경우 메시지큐에 접근할수 있는 int 형의 메시지큐 식별자를 되돌려주며, 이후로는 이 메시지큐 식별자를 통해서 필요한 작업을 하게 된다.


4 메시지큐에 데이타 쓰기

메시지를 보내기 위해서는 msgsnd(2) 를 사용한다. 첫번째 아규먼트는 msgget 를 통해서 얻어온 메시지큐 식별자이며, 2번째 아규먼트는 메시지큐에 넘기고자하는 구조체, 3번째 아규먼트는 2번째 아규먼트인 구조체의 크기, 마지막 아규먼트는 메시지전달 옵션으로 봉쇄할것인지 아니면 비봉쇄로 메시지를 결정하기 위해서 사용된다.

2번째 아규먼트가 메시지큐로 전달할 메시지라고 했는데, 이것은 구조체로 전달 되며, 다음과 같은 모습을 가지게 된다.
struct msgbuf 
{ 
    long mtype; 
    char mtext[255]; 
} 
 
위의 모습은 메시지 구조체의 매우 전형적인 모습으로 사실멤버변수는 필요에 따라서 얼마든지 변경될수 있다. 다만 long mtype 만이 필수요소이다.

mtype 는 메시지의 타입으로 반드시 0보다 더큰 정수이여야 한다. 우리는 이 mtype 를 각각 다르게 줌으로써, 특정 프로세스에서 특정 메시지를 참조할수 있도록 만들수 있다. 예를 들어 A 라는 프로세스가 A 라는 메시 타입을 참조해야 하고 B 는 B 로 참조하도록 만들어야 한다면, msgbuf 를 만들때, mtype 에 A 은 1 B 은 2 이런식으로 메시지 타입을 정의 하고 A 는 mtype 가 1인것을 B는 mtype 이 2인것을 가지고 가도록 만들면 된다.

Upload new Attachment "queue.png"

위의 그림에서 처럼 mtype 을 이용해서 자신이 원하는 메시지에만 선택 적으로 접근이 가능하다. 이특성을 잘 이용하면 매우 유용하게 사용할수 있을것이다.

msgsz 은 구조체의 크기이니 그냥 넘어가고, msgflg 에 대해서 설명하겠다. msgflg 에는 IPC_NOWAIT를 설정할수 있으며 이 값을 이용해서 봉쇄형으로 할것인지 비봉쇄형으로 할것인지 결정할수 있다. IPC_NOWAIT를 설정하면, 메시지가 성공적으로 보내지게 될때까지 해당영역에서 봉쇄(block)되며, 설정하지 않을경우 에는 바로 return 하게 된다.


5 메시지큐의 데이타 가져오기

데이타는 msgrcv(2) 함수를 이용해서 가져올수 있다. 1번째 아규먼트는 메시지큐 식별자이며, 2번째가 가져올 데이타가 저장될 구조체, 3번째는 구조체의 크기, 4번째는 가져올 메시지 타입, 5번째는 세부 조종 옵션이다.

다른것들은 굳이 설명할 필요가 없는 간단한 것들이고, 다만 4번째 메시지 타입인 msgtyp에 대해서 상세히 설명하고, msgflg 를 간단히 설명하는 정도로 넘어가도록 하겠다. 우리는 메시지를 보낼 구조체를 만들때 mtype 라는것을 정의 해서, 메시지를 분류해서 접근할수 있도록 한다는것을 알고 있다. 메시지를 가져올때는 바로 msgtyp 를 통해서 자기가 원하는 msgtyp 의 메시지 구조체에 접근할수 있게 된다.
msgtyp == 0
메시지 큐의 첫번째 데이타를 돌려준다.
msgtyp > 0
메시지의 mtype 가 msgtyp 와 같은 첫번째 데이타를 돌려준다.
msgtyp < 0
메시지의 mtype 이 msgtyp 의 절대값보다 작거나 같은 첫번째 데이타를 돌려준다.

msgflg 는 msgrcv 의 메시지 가져오는 형태를 봉쇄로 할것인지 비 봉쇄로 할것인지 지정하기 위해서 사용한다. IPC_NOWAIT 를 설정할경우 가져올 메시지가 없더라도 해당 영역에서 봉쇄되지 않고 바로 error 코드를 넘겨주고 리턴한다.


6 예제를 통해 알아본 메시지큐

여기에는 총 2개의 예제프로그램이 만들어진다. 하나는 메시지큐 생산자로써, 메시지큐를 생성하고 메시지를 만들어서 메시지큐에 보내는(msgsnd) 일을 하고 다른 하나는 소비자로써 메시지큐에 있는 데이타를 받아오는 일을 한다. 다음은 메시지큐 생산자 이다.
#include <sys/types.h>  
#include <sys/ipc.h>  
#include <sys/msg.h>  
#include <sys/stat.h>  
 
struct msgbuf 
{ 
    long msgtype; 
    char mtext[256]; 
    char myname[16]; 
    int  seq; 
}; 
 
int main() 
{ 
    key_t key_id; 
    int i; 
    struct msgbuf mybuf, rcvbuf; 
 
    key_id = msgget((key_t)1234, IPC_CREAT|0666); 
    if (key_id == -1) 
    { 
        perror("msgget error : "); 
        exit(0); 
    } 
 
    printf("Key is %d\n", key_id); 
 
    memset(mybuf.mtext, 0x00, 256);  
    memset(mybuf.myname, 0x00, 16);  
    memcpy(mybuf.mtext, "hello world 4", 13); 
    memcpy(mybuf.myname, "yundream", 8); 
    mybuf.seq = 0; 
    i = 0; 
 
    while(1) 
    { 
        // 짝수일경우 메시지 타입이 4 
        // 홀수일경우에는 메시지 타입이 3 
        if (i % 2 == 0) 
            mybuf.msgtype = 4; 
        else  
            mybuf.msgtype = 3; 
        mybuf.seq = i; 
 
        // 메시지를 전송한다.  
        if (msgsnd( key_id, (void *)&mybuf, sizeof(struct msgbuf), IPC_NOWAIT) == -1) 
        { 
            perror("msgsnd error : "); 
            exit(0); 
        }  
        printf("send %d\n", i); 
        i++; 
        sleep(1); 
    } 
 
    printf("%d \n", rcvbuf.msgtype); 
    printf("%s \n", rcvbuf.mtext); 
    printf("%s \n", rcvbuf.myname); 
    exit(0); 
} 
 
[ 다운로드 : msgsnd.c ]

프로그램은 간단하다. mybuf 란 구조체를 만들어서 메시지를 전송하는데, 이때 메시지 타입을 i % 2 가 0일경우 4로 그렇지 않을경우 3으로 해서 전송을 하도록 만들었다.

#include <sys/types.h>  
#include <sys/ipc.h>  
#include <sys/msg.h>  
#include <sys/stat.h>  
struct msgbuf 
{ 
    long msgtype; 
    char mtext[256]; 
    char myname[16]; 
    int  seq; 
}; 
 
int main(int argc, char **argv) 
{ 
    key_t key_id; 
    struct msgbuf mybuf; 
    int msgtype; 
 
    // 아규먼트가 있을경우 msgtype 가 3인 메시지를 받아오고(홀수)  
    // 아규먼트가 없을경우 msgtype 가 4인 메시지를 받아온다(짝수)   
    if (argc == 2) 
        msgtype = 3; 
    else  
        msgtype = 4; 
 
    key_id = msgget(1234, IPC_CREAT|0666); 
    if (key_id < 0) 
    { 
        perror("msgget error : "); 
        exit(0); 
    } 
    while(1) 
    { 
        if (msgrcv( key_id, (void *)&mybuf, sizeof(struct msgbuf), msgtype, 0) == -1) 
        { 
            perror("msgrcv error : "); 
            exit(0);     
        } 
        printf("%d\n", mybuf.seq); 
    } 
    exit(0); 
} 
 
[ 다운로드 : msgrcv.c ]

이 예제는 더 간단하다. 아규먼트가 있으면 메시지타입이 3인 메시지를 아규먼트가 없으면 메시지 타입이 4인 메시지를 가져오도록 한다.

프로그램을 컴파일후 테스트를 해보면 ./msgrcv 1을 (아규먼트를 주고 실행) 실행시키면 msgtype 가 4인 메시지를 받아오고 그렇지 않을경우 msgtype 가 3인 메시지를 받아옴을 알수 있을것이다. ./msgsnd, ./msgrcv, ./msgrcv 1 을 동시에 띄워서 테스트하면 된다.


7 메시지큐의 제어

msgctl(2)함수를 이용한다. 첫번째 아규먼트인 msqid 는 메시지 식별자이며, 2번째 아규먼트인 cmd 는 해당 작동명령, 그리고 마지막 아규먼트는 msqid_ds 구조체 이다. 우리는 cmd 를 통해서 해당 메시지식별자가 가르키는 메시지큐를 제어할수 있다. cmd 에는 아래와 같은 종류의 명령을 사용할수 있다.

IPC_STAT
메시지큐의 정보를 원할때 사용한다. 해당 메시지큐의 정보는 3번째 아규먼트인 msqid_ds 구조체를 통해 넘어오게 된다.

IPC_SET
msqid_ds 구조체 정보를 변경하고자 할때 사용한다. 주로 퍼미션 정보를 바꾸기 위해서 사용한다.
IPCRMID
현재 메시지큐를 제거한다.


8 정리


이상 메시지큐에 대해서 간단히 알아보았다. 지금까지 설명에서 처럼 메시지큐는 내부 프로세스간 통신을 위한 상당히 유연한 방법을 제공하고 있음을 알수 있다. 반면 단점이 있는데, 제어하기가 상당히 까다롭다는 점이다.

우선 메시지큐에 들어갈수 있는 데이타의 수가 고정되어 있는데, 메시지큐가 어떤 이유로 꽉찼을 경우 이를 알수 있는 방법이 애매하다. 위의 예제에서 ./msgrcv 와 ./msgrcv 1 이 메시지를 계속적으로 소비하도록 되어 있는데, 만약 둘중 하나가 이상작동을 해서 메시지를 받아오지 못할경우 결국 메시지큐가 꽉 차버리게 되고, 더이상 정상적인 작동을 못하게 될것이다. 또한 커널의 영향을 많이 받으며, 잘못된 메시지큐의 사용은 전체 시스템에 영향을 미칠수도 있게 만든다. 이는 전체 시스템에서 사용할수 있는 메시지큐의 수와 크기에 제한이 있기 때문으로, 메시지큐를 사용하기 위해서는 조심해서 사용해야될 필요성이 있다.

또한 커널은 몇개의 프로세스가 현재 메시지큐를 참조하는지를 알려주는 참조계수를 제공하지 않는다. 그러므로 프로세스에 어떤 문제가 생겼을때, 해당 프로세스에 정확하게 어떤 문제가 발생했는지 알아내는게 상당히 까다롭다.

그러므로 메시지큐를 짧은 시간에 다량의 정보를 전달하기 위한 목적으로 사용하는 데에는 적당치 않다. 그리 많지 않은 정보를 프로세스간 교환하기 위한 용도로 사용하기에 적당한 IPC 설비이다.


태그 : MessageQueue

공유메모리 (shared memory)

Posted by 시경이 Study/Operation system : 2007/11/21 15:00
공유메모리(shared memory) 보통 프로세스에서 사용되는 메모리영역은 해당 프로세스만이 사용할수 있다.
하지만 때때로 여러개의 프로세스가 특정 메모리영역을 사용했으면 하는때가 있을것이다.
System V IPC 설비중의 하나인 "공유메모리"를 통해서 이러한일을 할수있다.

개요

모든 프로세스는 자신의 업무를 수행하기 위해서 필요한 자료를 저장하기 위한 메모리 공간을 가지게 된다. 이러한 메모리공간에는 CPU에 의해 수행되는 명령어들, 프로그램 시작시 정의되고 초기화된 데이타, 프로그램 시작시 정의되었지만 초기화 되지 않은 데이타, 함수호출에 필요한 정보, 동적할당이 이루어지는 데이타등 이 들어가게 된다.
프로세스는 시작시 혹은 실행중에 이러한 데이타를 저장하고 사용하기 위한 메모리 공간을 커널에 요구하여서 할당받아 사용하게 되는데, 이러한 메모리공간은 기본적으로 메모리를 요청한 프로세스만이 접근가능하도록 되어있다.
하지만 가끔은 여러개의 프로세스가 특정 메모리 공간을 동시에 접근해야할 필요성을 가질때가 있을것이다.
공유메모리는 이러한 작업을 위한 효율적인 방법을 제공한다.

공유메모리는 여러 IPC 중에서 가장 빠른 수행속도를 보여준다.
그이유는 하나의 메모리를 공유해서 접근하게 되므로, 데이타 복사와 같은 불필요한 오버헤드가 발생하지 않기 때문으로, 빠른 데이타의 이용이 가능하다.
그러나 하나의 프로세스가 메모리에 접근중에 있을때, 또다른 프로세스가 메모리에 접근하는 일이 발생하면 자칫 데이타가 홰손될수 있을것이므로, 한번에 하나의 프로세스가 메모리에 접근하고 있다는걸 보증해줄수 있어야 할것이다.
이러한 작업을 위해서 Unix 에서는 Semaphore 라는 또다른 공유자원을 제어할수 있도록 해주는 도구를 제공해준다.
이번 문서에서는 Semaphore 를 다루지는 않을것이다. 이것은 다른 문서에서 다루도록 하고 여기에서는 단지 공유메모리에 대해서만 다루도록 할것이다.

다음은 공유메모리에 관련된 함수들의 모음이다.
#include <sys/types.h>
#include <sys/shm.h>

int shmget(key_t key, int size, int shmflg)
void *shmat( int shmid, const void *shmaddr, int shmflg )
int shmdt( const void *shmaddr)
int shmctl(int shmid, int cmd, struct shmid_ds *buf)

공유메모리는 어떻게 할당되는가

위의 함수들을 설명하기 전에 우선 공유메모리가 어떻게 할당되고, 어떤 과정을 통해서 접근가능한지에 대해서 우선 알아보도록 하겠다.

공유메모리의 생성요청은 최초 공유메모리 영역을 만드는 프로세스가 커널에 공유메모리 공간의 할당을 요청함으로써 이루어지며, 만들어진 공유메모리는 커널에 의해서 관리 되게 된다.

이런 이유로 한번만들어진 공유메모리는 운영체제를 리부팅하거나, 직접 공유메모리 공간을 삭제시켜주지 않은한, 공유메모리를 사용하는 모든 프로세스가 없어졌다고 하더라도, 계속적으로 유지되게 된다.

프로세스가 커널에게 공유메모리 공간을 요청하게 되면, 커널은 공유메모리 공간을 할당시켜주고 이들 공유메모리공간을 관리하기 위한 내부자료구조를 통하여, 이들 공유메모리를 관리하게 된다.
이 자료는 shmid_ds 라는 구조체에 의해서 관리되며 <shm.h> 에 정의되어 있다.
struct shmid_ds
{
    struct         ipc_perm shm_perm;    // 퍼미션
    int            shm_segsz;            // 메모리 공간의 크기
    time_t         shm_dtime;            // 마지막 attach 시간 
    time_t         shm_dtime;            // 마지막 detach 시간 
    time_t         shm_ctime;            // 마지막 변경 시간
    unsigned short shm_cpid;             // 생성프로세스의 pid
    unsigned short shm_lpid;             // 마지막으로 작동한 프로세스의 pid
    short          shm_nattch;           // 현재 접근한 프로세스의 수
};
Unix 버젼에 따라서 멤버변수들이 약간씩 차이를 보일수 있다.
shm_perm
공유메모리는 여러개의 프로세스가 동시에 접근 가능하므로, 파일과 같이 그 접근권한을 분명히 명시해줘야 한다.
shm_segsz
할당된 메모리의 byte 크기이다
shm_atime
가장최근의 프로세스가 세그먼트를 attach한 시간
shm_dtime
가장최근의 프로세스가 세그먼트를 detach한 시간
shm_ctime
마지막으로 이 구조체가 변경된 시간
shm_cpid
이 구조체를 생성한 프로세스의 pid
shm_lpid
마지막으로 작동을 수행한 프로세스의 pid
shm_nattch
현재 접근중인 프로세스의 수
이러한 공유메모리에 접근을 하기 위해서는 고유의 공유메모리 key 를 통해서 접근가능해지며, 이 key값을 통해서 다른 여러개의 공유메모리들과 구분되어 질수 있다.

shmget

shmget 은 커널에 공유메모리 공간을 요청하기 위해 호출하는 시스템 호출 함수이다. key 는 바로 위에서 설명했듯이 고유의 공유메모리임을 알려주기 위해서 사용된다. shmget 을 이용해서 새로운 공유메모리 영역을 생성하거나 기존에 만들어져있던 공유메모리 영역을 참조할수 있다.

첫번째 아규먼트는 여러개의 공유메모리중 원하는 공유메모리에 접근하기 위한 Key 값이다. 이 Key 값은 커널에 의해서 관리되며, Key 값을 통해서 선택적인 공유메모리에의 접근이 가능하다. 두번째 아규먼트는 공유메모리 의 최소크기 이다. 새로운 공유메모리를 생성하고자 한다면 크기를 명시해주어야 한다. 존재하는 메모리를 참조한다면 크기는 0으로 명시한다.

3번째 아규먼트는 공유메모리의 접근권한과, 생성방식을 명시하기 위해서 사용한다.
아규먼트의 생성방식을 지정하기 위해서 IPC_CREAT 와 IPC_EXCL 을 사용할수 있다. 아래 이들에 대해서 설명을 해두었다.

IPC_CREAT
key 를 이용 새로운 공유메모리 공간을 만든다.
IPC_EXCL
IPC_CREAT와 같이 사용되며, 공유메모리 공간이 이미 존재할경우 error 를 되돌려준다.
만약 IPC_CREAT 만 사용된다면 shmget()은 새로 생성되는 공유메모리공간을 지시하는 공유메모리공간 "식별자" 되돌려준다. 만약 입력된 key 값이 지시하는 공유메모리 공간이 이미 존재하고 있다면 존재하는 공유메모리 공간의 "식별자"를 되돌려준다. IPC_EXCL 과 IPC_CREAT 를 같이 사용할경우, 공유메모리 공간이 존재하지 않으면 새로 생성시켜주며, 존재할경우에 error 를 되돌려준다.

3번째 아규먼트는 이외에도 권한을 지정해줄수도 있다. 권한은 파일권한과 동일하게, 유저, 그룹, Other 에 대한 읽기/쓰기 권한을 지정할수 있다. 단 실행권한은 줄수 없도록 되어 있다.
아래와 같이 사용가능하다.
int shmid;
key_t keyval;

keyval = 1234;
shmid = shmget(keyval, sizeof(keyval), IPC_CREAT | 0666)); 
if (shmid == -1)
{
    return -1;
}

shmat

일단 공유메모리 공간을 생성했으면, 우리는 공유메모리에 접근할수 있는 int 형의 "식별자" 를 얻게 된다. 우리는 이 식별자를 shmat 를 이용해서 지금의 프로세스가 공유메모리를 사용가능하도록 "덧붙임" 작업을 해주어야 한다.

첫번째 아규먼트는 shmget을 이용해서 얻어낸 식별자 번호이며, 2번째 아규먼트는 메모리가 붙을 주소를 명시하기 위해 사용하는데, 0을 사용할경우 커널이 메모리가 붙을 주소를 명시하게 된다. 특별한 사항이 없다면 0을 사용하도록 한다.
3번째 아규먼트를 이용해서, 우리는 해당 공유메모리에 대한 "읽기전용", "읽기/쓰기가능" 모드로 열수 있는데, SHM_RDONLY를 지정할경우 읽기 전용으로, 아무값도 지정하지 않을경우 "읽기/쓰기 가능" 모드로 열리게 된다.

shmdt

프로세스가 더이상 공유메모리를 사용할필요가 없을경우 프로세스와 공유메모리를 분리 하기 위해서 사용한다. 이 함수를 호출할 경우 단지 현재 프로세스와 공유메모리를 분리시킬뿐이지, 공유메모리 내용을 삭제하지는 않는다는 점을 기억해야 한다.
공유메모리를 커널상에서 삭제 시키길 원한다면 shmctl 같은 함수를 이용해야 한다.

shmdt 가 성공적으로 수행되면 커널은 shmid_ds 의 내용을 갱신한다.
즉 shm_dtime, shm_lpid, shm_nattch 등의 내용을 갱신하는데, shm_dtime 는 가장 최근에 dettach (즉 shmdt 를 사용한)된 시간, shm_lpid 는 호출한 프로세세의 PID, shm_nattch 는 현재 공유메모리를 사용하는 (shmat 를 이용해서 공유메모리에 붙어있는) 프로세스의 수를 돌려준다. shmdt 를 사용하게 되면 shm_nattch 는 1 감소하게 될것이며, shm_nattch 가 0 즉 더이상 붙어있는 프로세스가 없다라는 뜻이 될것이다. shm_nattch 가 0이 되어있을때 만약 이 공유메모리가 shm_ctl 등에 의해 삭제표시 가 되어 있다면, 이 공유메모리는 삭제되게 된다.

shmctl

이것은 공유메모리를 제어하기 위해서 사용한다.
즉 shmid_ds 를 직접 제어함으로써, 해당 공유메모리에 대한 소유자, 그룹 등의 허가권을 변경하거나, 공유메모리를 삭제혹은, 공유메모리의 잠금을 설정하거나 해제하는 등의 작업을 한다.

2번째 아규먼트를 이용해서 shmid 가 가르키는 공유메모리를 제어하며, cmd 를 이용해서 원하는 제어를 할수 있다. cmd 를 이용해 내릴수 있는 명령에는 다음과 같은 것들이 있다.

IPC_STAT
공유메모리 공간에 관한 정보를 가져오기 위해서 사용된다. 정보는 buf 에 저장된다.
IPC_SET
공유메모리 공간에 대한 사용자권한 변경을 위해서 사용된다. 사용자 권한 변경을 위해서는 슈퍼유저 혹은 사용자권한을 가지고 있어야 한다.
IPC_RMID
공유메모리 공간을 삭제하기 위해서 사용된다. 이 명령을 사용한다고 해서 곧바로 사용되는건 아니며, 더이상 공유메모리 공간을 사용하는 프로세스가 없을때, 즉 shm_nattch 가 0일때 까지 기다렸다가 삭제된다. 즉 해당 공유메모리 공간에 대해서 삭제표시를 하는거라고 생각하면 된다.
다음은 실제로 공유메모리를 사용하는 방법에 대한 가장간단한 예제이다. 자식과 부모프로세스간에 어떻게 메모리가 공유되는지 보여준다.
예제 : shm.c
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <string.h> 
#include <unistd.h> 


int main()
{
    int shmid;
    int pid;

    int *cal_num;
    void *shared_memory = (void *)0;


    // 공유메모리 공간을 만든다.
    shmid = shmget((key_t)1234, sizeof(int), 0666|IPC_CREAT);

    if (shmid == -1)
    {
        perror("shmget failed : ");
        exit(0);
    }

    // 공유메모리를 사용하기 위해 프로세스메모리에 붙인다. 
    shared_memory = shmat(shmid, (void *)0, 0);
    if (shared_memory == (void *)-1)
    {
        perror("shmat failed : ");
        exit(0);
    }

    cal_num = (int *)shared_memory;
    pid = fork();
    if (pid == 0)
    {
        shmid = shmget((key_t)1234, sizeof(int), 0);
        if (shmid == -1)
        {
            perror("shmget failed : ");
            exit(0);
        }
        shared_memory = shmat(shmid, (void *)0, 0666|IPC_CREAT);
        if (shared_memory == (void *)-1)
        {
            perror("shmat failed : ");
            exit(0);
        }
        cal_num = (int *)shared_memory;
        *cal_num = 1;

        while(1)
        {
            *cal_num = *cal_num + 1;
            printf("child %d\n", *cal_num); 
            sleep(1);
        }
    }

    // 부모 프로세스로 공유메모리의 내용을 보여준다. 
    else if(pid > 0)
    {
        while(1)
        {
            sleep(1);
            printf("%d\n", *cal_num);
        }
    }
}
에제가 하는 일은 간단하다. int 형의 공유메모리 공간을 할당한다음. 자식프로세스에서 여기에 1씩을 더하고 부모프로세스에서는 공유메모리 내용을 출력하는 일을한다.

쉘 코멘드로 공유메모리 제어하기

쉘에서 공유메모리의 상황을 보여주기 위해서"ipcs"란 도구를 제공한다. ipcs 를 사용하면 공유메모리 뿐만 아닌, Semaphore, Message Queue 등 소위 sytem V IPC 설비에 대한 내용을 보여준다.
그리고 ipcrm 도구를 이용해서 필요없는 공유메모리, Message Queue, Semaphore 등을 지워줄수 있다.
위의 예제코드를 컴파일 시켜서 실행시킨다음 ipcs 를 이용해서 확인을 해보면 공유메모리 자원이 어떤식으로 관리되는지 좀더 이해를 쉽게 할수 있을것이다.


출처 : http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/system_programing/IPC/SharedMemory
태그 : shared memory

운영체제 프로젝트 제안서

Posted by 시경이 Study/Operation system : 2007/11/07 18:39

<프로젝트 요약>


1. 프로젝트의 개요


1. 메모리 페이지 교체 알고리즘.


2. CPU 프로세스 스케줄링 알고리즘.


3. DISK의 구조와 파일의 생성, 삭제, 수정 시 DISK의 상태변화.


1, 2, 3에 대한 성능 비교.


2. 제안 프로젝트의 필요성


1. 운영체제 자원관리에 대한 학습.


2. 자원관리 알고리즘의 성능 비교.


3. 운영체제에서 사용하는 자원관리 알고리즘 학습에 대한 자료 제공.


3. 프로젝트 개발의 목표


1. 메모리 페이지 교체 성능 비교 시뮬레이터 개발.


2. CPU 프로세스 스케줄링 성능 비교 시뮬레이터 개발.


3. DISK의 구조와 파일의 생성, 삭제, 수정 시 DISK의 상태변화

시뮬레이터 개발.

4. 개발 방법

1. 온오프라인을 이용한 대화를 통한 프로젝트 수행.

2. 각 팀원별로 특정 부분 담당.

3. Linux에서 C로 구현.

4. GDB(디버거)를 사용.




[ 다운 로드 ]

프로스세 동기화 기법

Posted by 시경이 Study/Operation system : 2007/11/07 18:05
문 제 :

1.  행렬  곱  프로그램  (C  =  A  *  B)
2.  Pthread  사용
3.  A,  B  행렬은  rand()  (%  100)  함수를  사용하여  만들어서  파일에  저장할  것(2048  *  2048)
4.  A,  B,  C  행렬  및  Thread  개수는  명령어  라인에서  명시해야  함
          $  mat  A  B  C  8
5.  thread는  공유  변수인  next_row와  next_col이  지정하는  C  원소를  계산함
        Next_row와  next_col은  각각  0으로  초기화됨
6.  Thread는  자신이  어떤  원소를  계산할지를  알아내고  다른  thread를  위해  next_row와  next_col  값을  적절히  변경함 
7.  Bakery,  semaphore,  mutex,  sem을  사용하여  thread가  1,  2,  4,  8,  16,  32일때의  실행시간  측정

보고서 밑 코드 :

[ 다 운 로 드 ]

베이커리(bakery) 알고리즘

Posted by 시경이 Study/Operation system : 2007/11/06 03:14

Bakery 알고리즘

======================================================================

do

{

   choosing[i] = true;

   number[i] = max(number[0], number[1], ... , number[n-1]) + 1;

  choosing[i] = false;

  for (j = 0;j <n;j++) {

      while (choosing[j]); 

      while ((number[j] != 0) &&((number[j], j) <(number[i], i)));

   }

      critical section

   number[i] = 0;

      remainder section

} while(1)
======================================================================

CS : Critical Section

먼저, 여러분들이 헷갈리는것을 미연에 방지하기 위하여 모든 프로세스들은 본 알고리즘을 자신의
내부에 내장하고 있다고 전제하며, 각각의 프로세스들이 주체가 되어 알고리즘을 이용해서 자신이 CS에 들어가야할지, 아니면 더 기다려야 할지를 결정한다는 것이다.

일단, CS에 들어갈 준비가 된 순간 프로세스 i는 i번째 번호표를 부여받는다.(이때 i는 프로세스 자신의 번호). 바로 number[i]에 부여받은 번호가 들어가게 되는데, 잘보시면 끝에 "+1"이 있다. 현재 대기중인 가장 큰 번호보다 한개가 더 큰 번호가 번호표에 기재된다. 또 보면 알겠지만 CS돌입 준비가 끝나고 번호를 받는 순간 choosing[i]은 참값이 된다는 사실에서, 다른 프로세스(CS 돌입 준비를 끝내고 대기중인)들 역시 for 루프내에서 모종의 음모를 꾸미다가 어떤 프로세스가 CS돌입을 위한 번호표를 받을때면 모든것을 엄숙하게 중단하고 번호표를 받는 프로세스를 주시하게 된다. 새로운 프로세스의 번호표에 번호가 기재됬음을 확인한 다음에야 비로서 다시 for 루프는 돌아간다. 이유를 예를 들어 설명하면,
Px가 번호표를 받는 중에 ( max값을 확인하고 +1을 하기전에...) Pi 역시 번호표를 할당받고 for루프안으로 먼저 들어왔다면  number[i]와 number[x]가 같아질 수 (즉 같은 번호표를 받을 수)있기 때문이다. 따라서 이러한 경우를 감안해서 기다려주는 것이다.

for 루프 내부를 한번 보자. 먼저 for 루프는 0부터 n-1개 까지, 즉 총 n개의 번호표 number[]를 차례로 훑어 보면서 각 번호표에 번호가 새겨져있는지를 확인하게 된다. 0은 아무 번호도 기재되어있지 않다는 뜻이다. 이 프로세스들은 CS 돌입에 대한 의사가 없는 프로세스들이다. 그래서 모두 0이면 for loop를 유유자적하게 빠져나와 CS에 돌입하게 된다. 허나, 누군가의 번호표가 0이 아님과 동시에(누군가 CS돌입을 신청) 이것이 자신의 번호표보다 작은 값인지를 확인한다. 만일 작은 값이거나 값이 같더라도 프로세스 번호가(번호표의 번호와 프로세스 번호는 다르다는것을 주지하시길) 자신보다 작다면 그냥 루프는 돌아간다는 사실에서 아직 자신은 CS에 들어갈때가 아니라는 것을 알게 된다.

만일 위의 경우가 아니라면 자신의 번호표 번호가 가장 작거나 번호표 번호가 같더라도 프로세스 번호가 자신이 가장 작은 경우이므로 그냥~ 루프에서 벗어나면서 CS로 쏙~ 들어가서 기득권을 얻게 된다.

이 알고리즘에서 번호표의 번호와 프로세스 번호를 각각 고려하는 이유는 보시면 알겠지만 공통 데이터 스트럭쳐인 배열들(바로 choosing[]과 number[]) 자체도 CS문제를 안고 있기에 동시에 두 프로세스가 CS돌입 준비를 하면서 번호표에 똑같은 번호를 받는 경우가 발생할수 있다.(+1을 한참 하려는 와중에 또 다른 프로세스가 CS돌입을 준비 해버리면 업데이트 되기도 전에 번호표를 같이 부여받는 경우) 그래서 그 경우에 대한 해결책으로써 만일 두 번호표의 내용이 같다면!? 그때는 프로세스의 고유 번호를 비교하게 되고, 결국 프로세스 번호가 낮은 녀석이 기득권을 쥐게 된다.

CS에서 일을 해결하고 나오면서 자신이 부여받은 번호표의 내용을 0으로 다시 초기화 시킴으로써 다른 사람들이 프로세스들이 CS에 돌입할수 있도록 배려를 해주게 된다. 그리고 나서 여유를 가지고 RS에서 자기 하고픈일 계속 하게 된다. 베이커리 알고리즘은 위에 프로세스의 3가지 조건을 모두 만족한다.

Mutual Exclusion

 먼저 Pi가 Critical Section에 이미 가 있고 Pk가 번호표를 이미 가지고 있다고 가정하자.
(즉 Pk는 CS에 들어가려고 시도한다고 가정)

  만약 Pi와 Pk모두 Pi가 Critical Section에 들어가야 겠다는 결정을 하기 전에 둘 모두 번호표를 가지고 있었다면   알고리즘상 number[i]와 number[k]가 비교되었을 것이고 따라서 (number[i],i)<(number[k],k)라는 조건이 만족되었음을 알 수 있다.  그렇지 않았다면 Pi는 Critical Section에 진입하지 못했을
것이다.
Pi가 Critical Section에 진입한 후 Pj가 번호표를 할당받았다면 번호표를 부여하는 방식의 특성상 Pk가 받은 번호표는 Pi의 것보다 큰 것이 확실하다.

따라서 Pk는 Pi가 CS에서 나올 때까지 CS에 접근할 수 없다. Mutual Exclusion 조건이 만족된다는 이야기다.

나머지 두개의 규칙 .ProgressBounded Waiting은 당연히 만족한다.

나중에 번호표를 받은 프로세스들은 먼저 번호표를 받은 프로세스보다 우선순위가 작고, 번호표의 우선순위가 같은 경우 프로세스 번호를 이용하여 우선순위를 결정하게 되므로 하나의 프로세스가 영원히 기다리게 될 우려는 없다.

[ 예제 소스 다운로드 ]

참고 : http://blog.naver.com/leaders3?Redirect=Log&logNo=80019356655
          http://blog.naver.com/femalesheep?Redirect=Log&logNo=60009236314        

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <pthread.h>

#define N 2048

int **a;
int **b;
int **c;

int row_block;

void *cal_matrix(void *arg);

int main(int argc, char* argv[])
{
 FILE *fp1, *fp2, *fp3;
 
 time_t start, end;

 int i, j;

 // thread 갯수 int 형으로...
 int thread_i = atoi(argv[4]);

 // thread ID 부여받기 위한 변수
 pthread_t *tcb = (pthread_t *)calloc(thread_i, sizeof(pthread_t));

 // thread 에 argument 넘겨주기 위함
 int *arg = (int *)calloc(thread_i, sizeof(int));

 //하나의 thread 에 실행될 row 갯수
 row_block = N / thread_i;


 /*****************************/
 /* argument 검사                    */
 /*****************************/
 if (argc != 5)
 {
  printf("Argument is not enough! \n");
  exit(1);
 }

 /*****************************/
 /* FILE 열기                          */
 /*****************************/
 fp1 = fopen(argv[1], "w+");
 fp2 = fopen(argv[2], "w+");
 fp3 = fopen(argv[3], "w+");
 
 /*****************************/
 /* 배열 동적 할당                   */
 /*****************************/
 a = (int **)calloc(N, sizeof(int));
 b = (int **)calloc(N, sizeof(int));
 c = (int **)calloc(N, sizeof(int));

 for (i = 0; i < N; i++)
 {
  a[i] = (int *)calloc(N, sizeof(int));
  b[i] = (int *)calloc(N, sizeof(int));
  c[i] = (int *)calloc(N, sizeof(int));
 }

 /*****************************/
 /* 배열에 랜덤값 입력              */
 /*****************************/
 srand(time(NULL));
 for (i = 0; i < N; i++ )
 {
  for (j = 0; j < N ; j++ )
  {
   a[i][j] = rand() % 100;
   fprintf(fp1, "%d ", a[i][j]);
   b[i][j] = rand() % 100;
   fprintf(fp2, "%d ", b[i][j]);
  }
 }
 
 /*****************************/
 /* time 측정(start)                  */
 /*****************************/
 printf("program start ... \n");
 start = time(NULL);

 /*****************************/
 /* pthread 생성                      */
 /*****************************/
 for (i = 0; i < atoi(argv[4]); i++)
 {
  arg[i] = i;
  pthread_create(tcb+i, NULL, cal_matrix, arg+i);
 }
 /*****************************/
 /* thread 대기                        */
 /*****************************/
 for (j = 0; j < atoi(argv[4]); j++)
 {
  pthread_join(tcb[i], NULL);
 }

 /*****************************/
 /* 행렬 C 파일에 쓰기             */
 /*****************************/
 for (i = 0; i < N; i++)
 {
  for (j = 0; j < N; j++)
  {
   fprintf(fp3 , "%d ", c[i][j]);
  }
 }

 /*****************************/
 /* time 측정(end)                   */
 /*****************************/
 end = time(NULL);
 printf("time  :  %d  seconds\n", (end-start));
     printf("c[0][0]  :  %d\n",  c[0][0]);
 printf("program end ... \n");

 /*****************************/
 /* 동적공간 반환 & FILE 닫음   */
 /*****************************/
 free(a);
 free(b);
 free(c);
 free(tcb);
 free(arg);

 fclose(fp1);
 fclose(fp2);
 fclose(fp3);
 
 return 0;
}// main

/*****************************/
/* 배열 곱셈하는 함수             */
/*****************************/

void *cal_matrix(void *arg)
{
 int i, j, k;
 int row = row_block * (*((int *)arg));
 int box = row + row_block;
 printf("row=%d row_block=%d\n", row, row_block);
 for(i = 0; i < N ; i++)
 {
  for (j = 0; j < N; j++)
  {
   for (k = row; k < box; k++)
   {    
    c[i][j]  += a[i][k] * b[k][j];
   }//for            
  }// for
 }//for
}// cal_matrix


####### 로직은 맞는데 c 에 값이 제대로 안들어감 (누가 고쳐주셈.ㅡㅜ) #######

[보고서 다운로드]
[다른 완성본 다운로드]

태그 : thread, 행렬곱셈

#include  <stdio.h>
#include  <time.h>
#define  N 2048

main(){
      int  **a,  **b,  **c;
      register int  s,  e,  i,  j,  k, sum,  sum_col1 = 0, sum_col2 = 0;

 a = (int **)calloc(N, sizeof(int));
 b = (int **)calloc(N, sizeof(int));
 c = (int **)calloc(N, sizeof(int));

 for ( i = 0; i < N; i++ )
 {
  a[i] = (int *)calloc(N, sizeof(int));
  b[i] = (int *)calloc(N, sizeof(int));
  c[i] = (int *)calloc(N, sizeof(int));
 }

      printf("Program  Starting....\n");

      for  (i  =  0;  i  <  N;  i++){
            for  (j  =  0;  j  <  N;  j++){
                  a[i][j]  =  1;
                  b[i][j]  =  2;
                  c[i][j]  =  0;
            }
      }

    s  =  clock();
 sum = 0;
   for ( j = 0; j < N; j++ )
      {
            for ( k = j; k < N; k++ )
     {
    if ( j != k)
    {
       sum = b[j][k];
       b[j][k] = b[k][i];
       b[k][i] = sum;
    }
     }
      }
 
      for  (i  =  0;  i  <  N/2;  i++)
   {
            for  (j  =  0;  j  <  N;  j++)
   {
    sum_col1 = 0;
    sum_col2 = 0;

                  for  (k  =  0;  k  <  N/2;  k++)
      {
                       sum_col1 += (a[i*2][k*2]*b[j][k*2]) + (a[i*2][k*2+1]*b[j][k*2+1]);
        sum_col2 += (a[i*2+1][k*2]*b[j][k*2]) + (a[i*2+1][k*2+1]*b[j][k*2+1]);
      }
      c[i*2][j] = sum_col1;
      c[i*2+1][j] = sum_col2;
   }
 }
 
      e  =  clock();
      printf("time  :  %d  clocks \n",  (e-s));
      printf("time  :  %d  seconds \n",  (e-s)/CLOCKS_PER_SEC);
      printf("c[1][1]  :  %d\n",  c[1][1]);
      printf("Program  End....\n");

   for(i = 0; i < N; i++)
  {
   free(a[i]);
   free(b[i]);
   free(c[i]);
     }

     free(a);
     free(b);
     free(c);

}

[보고서 다운로드]

태그 : 배열 곱셈, 캐쉬
 

#include  <stdio.h>

#include  <time.h>

#define  N  512


main(){

      int  a[N][N],  b[N][N],  c[N][N];

      int  s,  e,  i,  j,  k,  sum;


      printf("Program  Starting....n");


      for  (i  =  0;  i  <  N;  i++){

            for  (j  =  0;  j  <  N;  j++){

                  a[i][j]  =  1;

                  b[i][j]  =  2;

                  c[i][j]  =  0;

            }

      }

    s  =  clock();


    //  수정해야할  코드  시작

      sum = 0;

      for ( j = 0; j < N; j++ )

      {

            for ( k = 0; k < N; k++ )

          {

                  sum = b[j][k];

                  b[j][k] = b[k][i];

                  b[k][i] = sum;

          }

      }

      for  (i  =  0;  i  <  N;  i++)

      {

            for  (j  =  0;  j  <  N;  j++)

          {

                  for  (k  =  0;  k  <  N/2;  k++)

                {

                        c[i][j]  += (a[i][k*2]*b[j][k*2]) + (a[i][k*2+1]*b[j][k*2+1]);

                }

          }

      }

               

      //  수정해야할  코드  끝


      e  =  clock();

      printf("time  :  %d  clocksn",  (e-s));

      printf("time  :  %d  secondsn",  (e-s)/CLOCKS_PER_SEC);

      printf("c[1][1]  :  %dn",  c[1][1]);

      printf("Program  End....n");

}


2. 설명


▷ strategy 1

1) b행렬의 가로와 세로행을 바꾼다.

for ( j = 0; j < N; j++ )

      {

            for ( k = 0; k < N; k++ )

          {

                  sum = b[j][k];

                  b[j][k] = b[k][i];

                  b[k][i] = sum;

          }

      }


c[i][j]  += (a[i][k*2]*b[j][k*2]) + (a[i][k*2+1]*b[j][k*2+1]);

2) a행렬의 행과 b행렬의 행을 곱하여 더한다.


이유 : b행렬을 행방향 증가로 바꾸는 이유를 예로 들어 설명하면

만약 a[0][0]을 불러오면 캐쉬에서 miss 가 나면서 메모리에서 불러온다.

이때 캐쉬에는 a[0][0]부터 a[0][512] 까지 행단위로 캐쉬에 잡히게 된다.

(컴퓨터마다 캐쉬의 크기가 조금씩 다르다.)

그래서 행단위로 읽으면 캐쉬에서 miss 가 나지 않기 때문에 좀더 빨리 읽어들일 수 있다.

하지만 열단위로 읽으면 행을 바꿀때마다 miss 가 나면서 캐쉬를 비우고 새로운 행을 캐쉬에 담게 되기 때문이다.


▷ strategy 2

1) k 값을 1/2 로 줄인다.

for  (i  =  0;  i  <  N;  i++)

      {

            for  (j  =  0;  j  <  N;  j++)

          {

                  for  (k  =  0;  k  <  N/2;  k++)

                {

                        c[i][j]  += (a[i][k*2]*b[j][k*2]) + (a[i][k*2+1]*b[j][k*2+1]);

                }

          }

      }


2) for 문을 반만큼 도는 대신에 행을 2개씩 계산한다.


이유 : loop-unrolling 기법으로 for문의 반복횟수를 줄이는 방법이다. for문은 branch의

대표적인 경우로, for문을 반으로 돌리는 대신에 같은 명령을 paste한다면 분기 예측 할

필요를 없애주기 때문에 혹시 있을지 모를 분기 예측 실패율을 막아준다.

(캐쉬와는 크게 상관은 없지만 캐쉬에 담긴 행을 2개씩 계산한다는 점에서 볼 수도 있다.)

태그 : 캐쉬
 «이전 1  다음»