1.1 시스템 구성도
◆ 유저 어플리케이션
가장 기본적으로 테스트를 위한 유저 어플리케이션이 필요하다. 유저 어플리케이션은 mmap함수를 통해 특정 메모리 영역을 파일처럼 읽고 쓰는 역할을 한다.
◆ 커널 모듈
DSM을 커널 레벨에서 지원하기 위한 가장 필수적인 요소는 커널 모듈이다. 유저 어플리케이션은 각각 자신의 가상 메모리 영역(virtual memory area)을 나타내는 vm_area_struct를 가지고 있으며, 커널 모듈은 이 가상 메모리 영역을 관리하고 접근 할 수 있는 방법을 제공한다. 특히 현재 메모리에 존재하지 않는 가상 메모리 영역의 페이지를 불러오기 위해 페이지 폴트(page fault)[1]를 발생시킨다. 자세한 내용은 2장에서 설명한다.
◆ 데몬 프로그램
커널 레벨에서 DSM을 지원하기 위해서는 DSM의 모듈이 필요하다. 만약 페이지 폴트가 발생했을 때, 요청한 페이지가 해당 노드에 존재하지 않는다면 모듈은 데몬에 페이지를 요청한다. 데몬은 소켓을 통해 연결된 노드들에 페이지를 요청하고, 페이지가 발견되면 해당 페이지를 원래 노드로 돌려준다. 자세한 내용은 3장에서 설명한다.
그림 1. 시스템 개념도. DSM은 크게 유저 어플리케이션, 데몬 그리고 커널 모듈로 구성된다. 특히 커널 모듈은 가상 주소 공간을 제어하는 역할을 하며, 해당 노드에 있지 않은 페이지는 데몬을 통해 다른 노드에 요청된다.
1.2 시스템 흐름도
그림2 는 페이지 프로세스에서 페이지 폴트를 발생시켜 다른 노드의 페이지를 요청하는 과정을 보이고 있다. 맨 처음 노드 A의 프로세스에서 페이지 폴트가 발생하면, 커널에서는 디바이스 드라이버는 nopage() 함수를 호출한다. 데몬은 디바이스 드라이버로부터 전달된 페이지 정보를 읽기 위해 read함수를 호출하고, 이 정보를 노드 B로 요청한다. 요청받은 노드 B는 페이지 정보를 디바이스 드라이버로 전송하기 위해 요청에 관련된 플래그를 설정하고 데몬으로 전달하며, 데몬은 해당 페이지를 찾아 돌려주고 자신의 메모리에서 해당 페이지를 삭제한다.
그림 3은 다른 노드로부터 요청받은 페이지에 대해 응답하는 과정을 보이고 있다. 노드 B는 read()함수를 통해 디바이스 드라이버로부터 페이지를 읽고 노드 A로 전송한다. 이 페이지 정보를 받은 노드 A의 데몬은 디바이스 드라이버를 통해 페이지를 현재 노드의 메모리에 저장하게 된다.
The DSM driver
2.1 The DSM device data structure
DSM을 제어하기 위한 주요 자료구조와 메모리 구조의 관계는 그림 4와 같다. dsm_vam는 DSM의 가상 메모리 영역과 실제 디바이스의 매핑을 담당하며, 가상 메모리 영역을 가리키는 자료구조는 vm_area_struct[2], 데몬을 통해 페이지를 제어하기 위한 dsm_device를 포함하고 있다. 또한 모든 dsm_vma 구조체는 서로 다른 dsm_vma구조를 가리키며 이중 링크드 리스트로 관리된다.
1: typedef struct dsm_vma {
2: struct dsm_device *devp;
3: struct vm_area_struct *vmap;
4: struct dsm_vma **pprev, *next;
5: } DsmVma;
vm_area_struct는 커널에서 가상 메모리 영역을 가리키기 위해 사용되는 자료구조이다. vma_start와 vma_end로 가상 메모리 영역에서 프로세스에 할당된 메모리의 범위를 가리킨다. 주소 공간에서도 각 데이터는 서로 다른 영역에 할당되고, 실행 시간에 따라 할당되는 시점이 달라지므로 프로세스에서 사용하는 메모리 영역은 가상 메모리 영역에서도 부분적으로 나뉘게 된다. 따라서 각 영역을 가리키는 vm_area_struct는 여러 개가 존재하며, 링크드 리스트로 연결돼있다. 또한 각각 vm_area_struct에서 가리키는 메모리 영역은 모두 다른 크기를 가지고 있다.
메모리 영역을 사용하기 위해서는 가상 메모리 영역뿐만 아니라 실제 페이지에 대한 제어도 필요하다. DSM에서는 이 역할을 모듈과 데몬이 함께 수행하고 있으며, 이 작업을 위한 자료구조는 다음과 같다.
1: /*
2: * Data structure for buffering output to the daemon.
3: */
4: typedef struct dsm_device {
5: wait_queue_head_t readq; /* Reader blocking */
6: waitset_t faultq; /* Page fault waiting */
7: struct semaphore sem; /* mutual exclusion semaphore */
8: DsmPage *pages; /* page info */
9: struct dsm_vma *attaches;
10: /* all the vm_struct_area that are mapped */
11: } DsmDevice;
5~6 : 데이터를 읽는 과정이나 폴트가 발생했을 때, 해당 작업은 데이터가 준비될 때까지 대기 큐로 들어가게 되고 커널은 다른 작업을 수행한다. 데이터가 준비되면 작업은 원래 위치부터 다시 작업을 수행하게 되는데, 이때 필요한 것이 readq와 faultq이다.
7 : 페이지 데이터를 처리하는 과정에서 중간에 데이터가 다른 작업에 의해 변경되는 경우가 발생할 수 있다. 따라서 세마포어를 통해 데이터를 크리티컬 섹션으로 보호한다.
8 : 페이지의 정보를 가져오기 위한 pte정보가 저장돼있다. 이 pte는 가상 메모리 주소를 실제 물리 메모리 주소로 바꿔주는 역할을 한다.
9 : 일반적으로는 가상 메모리 영역에 먼저 접근을 하고, 이 영역이 메모리에 있지 않다면 물리 메모리로부터 읽어오는 작업을 수행한다. 하지만 반대로 물리 메모리에서 가상 메모리 주소를 알아야 하는 경우가 발생한다(revers mapping).
페이지를 가리키는 자료구조는 dsm_alloc_device() 함수를 통해 할당되며, 기본적으로 하나의 노드는 1024개의 페이지를 갖는다. 이 페이지는 여러 노드에 나눠 저장, 사용되고 있기 때문에 페이지의 상태를 나타내는 몇 개의 플래그(flags)가 필요하다.
1: /*
2: * Page information.
3: * Page state.
4: */
5: #define PSTATE_PRESENT 1
6: /* page is present */
7: #define PSTATE_LOCKED 2
8: /* page is locked (it should also be present) */
9: #define PSTATE_FAULTED 4
10: /* page is not present, and it is wanted */
11: #define PSTATE_REQUESTED 8
12: /* page is not present, it is wanted, had been requested */
13: #define PSTATE_DAEMON 16
14: /* page is present, but daemon wants it */
15: typedef struct dsm_page {
16: unsigned flags; /* State of the page */
17: unsigned long pte; /* Memory map info */
18: } DsmPage;
5: PSTAT_PRESENT는 페이지가 물리 메모리에 존재하는 경우 비트가 설정된다.
7: PSTAT_LOCKED는 페이지가 현재 물리 메모리에 "locked"되는 경우 설정된다.
9: PSTAT_FAULTED는 만약 이 비트가 설정 됐다면 페이지는 현재 머신에 존재하지 않으며, 프로세스는 폴트를 발생시킨다. 폴트가 발생한 페이지는 데몬으로 요청이 발생한다.
11: PSTAT_REQUESTED는 만약 이 비트가 설정됐다면, 페이지 폴트가 발생했으며 페이지 요청을 위해
13 : PSTAT_DAEMON는 페이지가 데몬에 의해 요청된 경우 설정된다.
16 : 페이지 상태를 나타내기 위해 위 플래그들을 저장한다.
17 : pte[3]는 페이지 테이블로부터 페이지를 참조할 수 있는 주소를 포함하고 있다.
커널 레벨 DSM의 플래그는 다음과 같이 사용된다.
1) 초기에는 페이지가 존재하지 않으며, 플래그는 0으로 설정된다.
2) 프로세스에서 페이지 폴트가 발생하며 nopage함수가 호출된다. nopage함수는
a) PSTATE_FAULT 비트를 설정
b) readq를 깨운다.
c) faultq를 재운다.
3) 데몬에서 read함수를 통해 디바이스로부터 데이터를 읽는다.
a) page fault 발생을 알린다.
b) PSTATE_REQUESTED 비트를 설정한다.
c) 데몬으로 페이지 요청을 되돌려준다.
4) 페이지를 찾았다면 write함수를 통해 디바이스 드라이버에 쓴다.
a) 새로운 페이지를 할당(alloc_page())한다.
b) 데이터를 이 페이지에 쓰고,
c) 플래그를 PSTATE_PRESENT로 설정한다.
데몬은 다음과 같은 방법으로 페이지를 돌려보내게 된다.
1) 데몬은 요청받은 페이지를 write()함수를 통해 커널로 전달된다. 이때 비트는 PSTATE_DAEMON으로 설정된다.
2) 요청한 페이지는 read 함수를 통해 데몬으로 응답하며, 이때 비트는 PSTATE_DAEMON으로 설정된다.
a) 페이지를 참조하고 있는 페이지 테이블 엔트리(pte)를 무효화하고,
b) TLB를 지운다.
c) 해당 페이지의 플래그를 0으로 초기화 시킨다.
d) 페이지의 데이터를 데몬으로 복사하고,
e) read()의 결과를 되돌려준다.
2.2.1 File operation
리눅스에서는 모든 장치를 하나의 파일로 보고 있으며, 파일 연산을 통해 각종 장치를 제어할 수 있다. DSM의 디바이스 드라이버 모듈을 제어하기 위한 파일 연산은 다음과 같다.
1: /*
2: * File operations for the dsm device.
3: */
4: static struct file_operations dsm_fops = {
5: open: dsm_open,
6: release: dsm_release,
7: llseek: dsm_lseek,
8: read: dsm_read,
9: poll: dsm_poll,
10: write: dsm_write,
11: mmap: dsm_mmap
12: };
5 : 장치를 열 때 호출되는 함수로, 장치사용에 기본적인 초기화 작업을 수행한다.
6 : 장치 사용이 끝나면서 자원 반납을 위해 호출된다.
8 : 장치로부터 데이터를 읽을 때 호출된다.
9 : 다른 데몬과 소켓통신을 하는 경우, select()[4] 함수를 통해 전송받은 데이터를 주기적으로 확인하기 위해 사용된다.
10 : 장치에 데이터를 쓸 때 호출된다.
11 : 장치와 프로세스 주소 공간(메모리)로 연결될 때 호출된다.
2.2.2 dsm_open
dsm_open() 함수에서는 디바이스 장치에서 사용하기 위해 open함수를 사용할 때 호출되는 함수이며, 메모리를 할당하기 위해 dsm_alloc_device()함수를 호출한다. dsm_alloc_device() 함수 내용은 다음과 같다.
1: /*
2: * Allocate a new device.
3: */
4: static DsmDevice *dsm_alloc_device(void)
5: {
6: DsmPage *infop;
7: DsmDevice *devp;
8: pte_t tmp_pte;
9: int i;
10:
11: /* Allocate struct */
12: devp = kmalloc(sizeof(DsmDevice), GFP_KERNEL);
......
13: /* Allocate page info */
14: infop = vmalloc(sizeof(DsmPage) * DSM_PAGES);
......
17: /* Initialize device */
18: memset(devp, 0, sizeof(DsmDevice));
19: init_waitqueue_head(&devp->readq);
20: waitset_init(&devp->faultq);
21: sema_init(&devp->sem, 1);
22: devp->pages = infop;
23: /* Clear the page entries */
24: tmp_pte = __pte(0);
25: for(i = 0; i != DSM_PAGES; i++) {
26: infop->flags = 0;
27: infop->pte = pte_val(tmp_pte);
28: infop++
29: }
30: return devp;
}
12 : 디바이스 장치의 정보를 저장할 메모리 영역을 할당한다. kmalloc() 함수로 물리 메모리에 직접 할당한다.
14 : 디바이스 장치에서 사용하기 위한 메모리 영역을 가리키기 위한 메모리 맵정보(pte)를 저장한다. 하나의 노드당 1024개의 페이지를 갖는다.
23~30 : pte정보를 모두 초기화한다.
2.2.3 dsm_mmap
open을 통해 할당된 메모리 영역을 파일로 매핑하고, 특히 페이지가 메모리에 없을 때 fault를 발생시켜 페이지를 불러오기 위한 연산을 매핑 시킨다.
1: /*
2: * On mmap, set up a handler for nopage.
3: */
4: static int dsm_mmap(struct file *filep, struct vm_area_struct *vmap)
5: {
6: DsmVma *listp;
7: DsmDevice *devp;
8: devp = filep->private_data;
9: /* Don't try to swap out these pages */
10: vmap->vm_flags |= VM_RESERVED | VM_SHARED;
11: vmap->vm_ops = &dsm_vma_ops;
12: /* Set up local info for the vma */
13: listp = kmalloc(sizeof(DsmVma), GFP_KERNEL);
14: if(listp == 0) {
15: printk("Out of memory in dsm_mmap");
16: return -ENOMEM;
17: }
18: listp->vmap = vmap;
19: listp->devp = devp;
20: vmap->vm_private_data = listp;
21: dsm_vma_open(vmap);
22: return 0;
23: }
8 : private_data는 매핑되는 메모리 영역의 디바이스 장치 정보를 가져온다.
10 : 해당 가상 메모리 영역의 옵션을 설정한다. 특히 VM_RESERVED 옵션을 사용해 해당 페이지의 스왑아웃(swap-out)을 막는다.
11 : 가상 메모리를 제어하기 위한 연산자들을 매핑시킨다.
18 : 메모리 영역을 링크드 리스트에 저장한다.
21 : 메모리 관리를 위해 dsm_vma_open()함수를 호출한다. 해당 함수는 DSM Virtual Areas에서 설명한다.
2.2.4 dsm_read
polling에 의해 모듈 혹은 디바이스에서 입력이 발생할 경우 데몬은 해당 이벤트를 처리하는 과정에서 read 함수를 호출하며, 이 함수는 dsm_read()함수에 연결된다. dsm_read()의 역할은 현재 모듈의 페이지 리스트를 탐색하며, 폴트가 발생한 페이지를 데몬의 버퍼로 전송하는 역할을 한다.
1: /*
2: * The daemon _had better_ give us a buffer large enough for
3: * one full command. We reject smaller requests.
4: */
5: static ssize_t dsm_read(struct file *filep, char *bufp, size_t count, loff_t *ppos)
6: {
7: DsmDevice *devp;
8: DsmPage *infop;
9: unsigned flags, i;
......
10: /*
11: * Scan all the pages for action to be taken.
12: * If there is nothing to do, just wait until a process faults.
13: */
14: devp = filep->private_data;
15: while(1) {
16: infop = devp->pages;
17: for(i = 0; i != DSM_PAGES; i++) {
18: flags = infop->flags;
19: if((flags & PSTATE_FAULTED) &&
(flags & PSTATE_REQUESTED) == 0)
20: return ask_for_page_from_daemon
(devp, i, (DsmCommand *) bufp);
21: else if(flags & PSTATE_DAEMON)
22: return give_page_to_daemon
(devp, i, (DsmCommand *) bufp);
23: infop++;
24: }
25: /* Nothing to do, so wait */
26: interruptible_sleep_on(&devp->readq);
27: if(signal_pending(current))
28: return -EINTR;
29: }
30: }
17-24 : read()함수가 호출되면 현재 노드에서 관리 중인 모든 페이지를 확인한다.
19-20 : 읽으려는 페이지가 노드에 저장돼있지 않고(FAULTED), 아직 다른 노드에 페이지 정보를 요청하지 않은 경우 (PSTATE_REQUESTED), 다른 노드에 페이지 정보를 요청하기 위해 ask_for_page_from_daemon() 함수를 호출한다.
21-22 : PSTATE_DAEMON 플래그는 데몬이 페이지를 요청한 경우 설정된다.
26 : interruptible_sleep_on()[5] 함수는 현재 프로세스를 wait_queue_in에 넣고 schedule 함수를 호출하여 다른 프로세스의 실행을 재개한다. 특히 interruptible_sleep_on()함수는 상태를 TASK_INTERRUPTIBLE로 설정해 시그널을 수신해도 프로세스가 깨어날 수 있도록 한다.
ask_for_page_from_daemon() 함수는 dsm_read함수를 통해 호출된다. 페이지지가 현재 노드에 저장돼있지 않고 아직 다른 노드에 페이지정보를 요청하지 않은 경우 다른 노드로부터 페이지를 가져와야 한다. 따라서 모듈에서 데몬으로 전달되는 상태는 커뮤니케이션 플래그는 DSM_READ_PAGE로 설정된다. 커뮤니케이션 플래그는 페이지 플래그와는 다르게 다른 노드와의 통신을 위해 사용하며 자세한 내용은 3장에서 자세히 설명한다.
1: /*
2: * Handle a fault request.
3: */
4: static int ask_for_page_from_daemon(DsmDevice *devp,
unsigned page, DsmCommand *comp)
5: {
6: DsmPage *infop;
7: /* Ask for the page */
8: if(put_user(DSM_READ_PAGE, &comp->command) ||
put_user(page, &comp->page))
9: return -EFAULT;
10: /* Handle a page fault */
11: if(down_interruptible(&devp->sem))
12: return -ERESTARTSYS;
13: /* Mark the page as requested */
14: infop = devp->pages + page;
15: infop->flags |= PSTATE_REQUESTED;
16: /* Release the semaphore and return */
17: up(&devp->sem);
18: return sizeof(DsmCommand);
19: }
8 : ask_for_page_from_daemon함수에서는 put_user()[6] 시스템 함수를 호출하여 command는 DSM_READ_PAGE로 그리고 페이지는 현재 요청 페이지로 설정된다. 데몬은 read를 통해 이 데이터를 읽는다.
11 : 세마포어를 얻기 위해 down_interruptible 함수를 호출하며, 세마포어를 얻는데 실패 할 경우 해당 작업을 task_interruptible 상태로 변경한다.
14 : 디바이스 페이지 들 중 요청 받은 페이지 인덱스의 포인터 정보를 저장한다.
15 : 디바이스 페이지의 플래그를 PSTATE_REQUESTED로 스위칭한다.
give_page_to_daemon()은 페이지 플래그가 PSTATE_DAEMON로 설정된 상태, 즉 데몬은 다른 노드로부터 페이지 요청을 받았으며, 이 요청을 받은 데몬은 다시 커널로 페이지 정보를 요청하기 위해 dsm_read()함수가 호출된 상태이다. 따라서 해당 노드의 커널에서는 데몬이 원하는 페이지 정보를 데몬으로 넘겨주며, 자신의 노드에 있는 메모리의 페이지와 이 페이지를 가리키는 pte를 해제하는 작업을 수행해야 한다.
1: /*
2: * Pass a released page to the daemon.
3: */
4: static int give_page_to_daemon(DsmDevice *devp,
unsigned page, DsmCommand *comp)
5: {
6: struct page *pagep;
7: DsmPage *infop;
8: DsmVma *listp;
9: void *addr;
10: pte_t pte;
11: /* Deliver the page */
12: if(put_user(DSM_WRITE_PAGE, &comp->command)
|| put_user(page, &comp->page))
13: return -EFAULT;
14: /* Handle a page fault */
15: if(down_interruptible(&devp->sem))
16: return -ERESTARTSYS;
17: /* Page must be present */
18: infop = devp->pages + page;
19: if((infop->flags & PSTATE_PRESENT) == 0) {
20: up(&devp->sem);
21: return -EFAULT;
22: }
23: /* Get the page */
24: infop = devp->pages + page;
25: pte = __pte(infop->pte);
26: pagep = pte_page(pte);
27: /* Copy data to the user area */
28: addr = kmap_atomic(pagep, KM_USER0);
29: if(copy_to_user(comp->data, addr, PAGE_SIZE))
30: return -EFAULT;
31: kunmap_atomic(pagep, KM_USER0);
32: /* Release the page */
33: ClearPageReserved(pagep);
34: infop->flags = 0;
35: infop->pte = 0;
36: /* Zap the page table entries in all the attached segments */
37: for(listp = devp->attaches; listp; listp = listp->next) {
38: struct vm_area_struct *vmap = listp->vmap;
39: unsigned long addr = vmap->vm_start + page * PAGE_SIZE;
40: pgd_t *pgd = pgd_offset(vmap->vm_mm, addr);
41: pud_t *pud = pud_offset(pgd, addr);
42: pmd_t *pmd = pmd_offset(pud, addr);
43: pte_t *pte = pte_offset_map(pmd, addr);
44: pte_clear(vmap->vm_mm, addr, pte);
45: }
46: atomic_set(&pagep->_count, 1);
47: __free_page(pagep);
48: /* Release the semaphore and return */
49: up(&devp->sem);
50: return sizeof(DsmCommand);
51: }
12 : 현재 필요한 페이지에 대해 DSM_WRITE_PAGE로 설정하고, 페이지 정보를 커널 레벨로 전달한다.
24~26 : 필요한 페이지 인덱스를 계산하고 pte내에서 페이지 포인터를 얻어온다.
27~31 : copy_to_user() 함수를 통해 얻어온 페이지 정보를 유저레벨로 전달한다. kmap_atomic() 함수와 kunmap_atomic() 함수는 커널의 메모리에 일시적으로 매핑하기 위해 사용하는 함수이다
36~45 : 현재 DSM에서 부여된 모든 pte정보를 구하고, pte_clear() 함수를 통해 초기화시킨다.
47 : pagep를 통해 참조하고 있던 현재 페이지를 초기화 시킨다.
2.2.5 dsm_write
dsm_write() 함수는 write()함수가 호출될 때 사용되며, 데몬은 자신이 받은 요청들을 커널로 보내거나 다른 노드로부터 받은 파일을 자신의 노드에 쓰기 위해 호출되는 함수이다.
1: /*
2: * This is a command from the daemon.
3: * Get the command and execute it.
4: */
5: static ssize_t dsm_write
(struct file *filep, const char *bufp, size_t count, loff_t *ppos)
6: {
7: unsigned page, command;
8: DsmCommand *comp;
9: DsmDevice *devp;
10: int rval;
......
11: /* Handle the command */
12: switch(command) {
13: case DSM_READ_PAGE:
14: rval = daemon_page_request(devp, page);
15: break;
16: case DSM_WRITE_PAGE:
17: rval = get_page_from_daemon(devp, page, comp->data);
18: break;
19: default:
20: rval = -EFAULT;
21: break;
22: }
23: return rval;
24 }
12~21 : 드라이버로 전달된 메시지를 처리한다. 커맨드가 DSM_READ_PAGE 일 경우 다른 노드에서 페이지 요청이 들어온 경우이며 daemond_page_request() 함수를 호출한다. 그렇지 않고 플래그가 DSM_WIRTE_PAGE인 경우 요청했던 페이지가 다른 노드로부터 들어온 경우이며 get_page_from_daemon() 함수를 호출한다.
daemon_page_request() 함수는 다른 데몬으로부터 페이지를 읽기 위한 요청이 있을 경우 호출되는 함수이다. 이 함수는 요청된 페이지가 현재 노드에 존재한다면(PSTATE_PRESENT) 이 페이지의 상태를 데몬으로부터 페이지가 요청된 상태를 나타내는 PSTATE_DEAMON으로 변경한다. PSTATE_DAEMON 플래그가 설정된 페이지는 polling에 의해 read()함수를 호출시킨다.
1: /*
2: * Handle a read request.
3: */
4: static int daemon_page_request(DsmDevice *devp, unsigned page)
5: {
6: DsmPage *infop;
7: int rval;
......
8: /* Check the bounds */
9: if(page >= DSM_PAGES)
10: return -EFAULT;
11: /* Lock the device */
12: if(down_interruptible(&devp->sem))
13: return -ERESTARTSYS;
14: /* Mark the page as released, so the reader will pass it to the daemon */
15: rval = 0;
16: infop = devp->pages + page;
17: if(infop->flags & PSTATE_PRESENT) {
......
18: infop->flags |= PSTATE_DAEMON;
19: rval = sizeof(DsmCommand);
20: }
21: /* Release the critical section before returning */
22: up(&devp->sem);
23: wake_up(&devp->readq);
24: return rval;
25: }
9~10 : 현재 요청 받은 페이지의 범위가 올바른지 확인한다.
12~13 : 세마포어를 획득해 크리티컬 섹션으로 만든다.
14~20 : 요청한 페이지가 현재 노드에 존재(PSTATE_PRESENT)하고 있다면, 요청한 노드로 되돌려주기 위해 플래그를 PSTATE_DAEMON로 설정한다.
21 : 크리티컬 섹션을 해제한다.
get_page_from_daemon() 함수는 전달받거나 존재 하지 않는 페이지를 요청한 노드에서 자신의 메모리에 쓸 경우 호출되는 함수이다.
1: /*
2: * Write some data into a page.
3: */
4: static int get_page_from_daemon(DsmDevice *devp,
unsigned page, const char *bufp)
5: {
6: pte_t pte;
7: void *addr;
8: DsmPage *infop;
9: struct page *pagep;
10: int wake_flag;
......
11: /* Check the bounds */
12: if(page >= DSM_PAGES)
13: return -EFAULT;
14: /* Allocate the page if it is not present */
15: infop = devp->pages + page;
16: if((infop->flags & PSTATE_PRESENT) == 0) {
17: pagep = alloc_pages(GFP_HIGHUSER, 0);
18: if(pagep == 0)
19: return -ENOMEM;
20: SetPageReserved(pagep);
21: /* Build the page-table-entry */
22: pte = mk_pte(pagep, PAGE_SHARED);
23: infop->pte = pte_val(pte);
24: infop->flags |= PSTATE_PRESENT;
25: infop->flags &= ~(PSTATE_FAULTED | PSTATE_REQUESTED);
26: /* Wake up the fault queue when we're done */
27: wake_flag = 1;
28: }
29: else {
30: pagep = pte_page(__pte(infop->pte));
31: wake_flag = 0;
32: }
33: /* Copy the data onto the page */
34: addr = kmap_atomic(pagep, KM_USER0);
35: if(copy_from_user(addr, bufp, PAGE_SIZE))
36: return -EFAULT;
37: kunmap_atomic(pagep, KM_USER0);
38: /* Release faulted processes */
39: if(wake_flag)
40: waitset_wake_up_all(&devp->faultq);
41: return sizeof(DsmCommand);
42: }
12~13 : 페이지의 범위가 올바른지 확인한다.
15 : 페이지의 위치를 계산한다.
16~28 : 페이지가 현재 노드에 존재하지 않는다면 alloc_page함수를 통해 새로운 페이지를 할당하고, pte를 할당한다. 플래그는 현재 노드에 있는 PSTATE_PRESENT로 설정하고 요청된 페이지에 대한 플래그는 해제한다.
29~32 : 존재하는 페이지이므로 포인터만 가져온다.
33~37 : 데이터를 쓰려는 주소를 크리티컬 섹션으로 보호하고 유저로부터 전달된 데이터를 페이지에 저장한다.
38~40 : fault로 인해 잠든 프로세스를 깨워 작업을 재개한다.
2.2.6 dsm_poll
dsm_poll은 select()함수를 통해 폴링(polling)이 수행 될 때 호출되는 함수이다. 일반적으로 리눅스에서 read나 recv함수를 사용할 경우 해당 함수에서 블로킹되는 문제가 발생하며, 이를 해결하는 것이 select이다. 폴링은 이에 사용되는 매커니즘으로 interrupt와는 또 다른 방식으로 이벤트를 처리하기 위해 사용된다. 폴링에 의해 소캣 또는 디바이스 드라이버로부터 입력이 발생할 경우 이에 해당하는 파일 디스크립터(fd)를 처리한다.
1: /*
2: * Polling is used by select.
3: * The device can always be written,
4: * but check if it can be read.
5: */
6: static unsigned dsm_poll(struct file *filep, poll_table *wait)
7: {
8: DsmDevice *devp;
9: DsmPage *infop;
10: unsigned mask, flags;
11: int i;
12: /*
13: * Register the readq.
14: */
15: devp = filep->private_data;
16: poll_wait(filep, &devp->readq, wait);
17: /*
18: * Build the mask.
19: */
20: mask = POLLOUT | POLLWRNORM;
21: infop = devp->pages;
22: for(i = 0; i != DSM_PAGES; i++) {
23: flags = infop->flags;
24: if(((flags & PSTATE_FAULTED) && (flags & PSTATE_REQUESTED) == 0) || (flags & PSTATE_DAEMON)) {
25: mask |= POLLIN | POLLRDNORM;
26: break;
27: }
28: infop++;
29: }
30: return mask;
31: }
16 : 대기큐를 poll_table에 등록해서 커널이 주기적으로 확인
20 : poll mask의 초기 상태를 쓰기 가능 상태(POLLOUT | POLLWRNORM)로 초기화 시킨다.
24 : 페이지를 요청했지만(PSTATE_FAULTED) 아직 페이지가 요청되지 않은 경우(PSTATE_REQUESTED) 혹은 데몬으로부터 요청이 온 경우(PSTATE_DAEMON) 읽기 가능 상태 (POLLIN | POLLRDNORM)를 스위칭 시킨다.
2.2 DSM Virtual Areas
2.2.1 File operation
vma는 실제 어플리케이션에서 사용되는 메모리를 제어하기 위해 필요하다. 특히 nopage는 페이지 폴트가 발생해 물리 디스크 혹은 다른 노드로부터 페이지를 가져와야 하는 경우 호출되는 함수로 가장 필수적인 함수이다. 마찬가지로 파일 연산자에 의해 처리된다.
1: /*
2: * Add open, close, and nopage functions.
3: */
4: static struct vm_operations_struct dsm_vma_ops = {
5: open: dsm_vma_open,
6: close: dsm_vma_close,
7: nopage: dsm_vma_nopage
8: };
5: 메모리가 매핑 될 때 호출되는 함수이다.
6: 메모리 매핑이 해제 될 때 호출되는 함수이다.
7: 유저 어플리케이션에서 페이지를 찾을 수 없어 페이지 폴트가 발생할 경우 호출되는 함수이다.
2.2.2 dsm_vma_open
dsm_vma_open() 함수는 dsm_mmap함수를 통해 메모리를 디바이스와 매핑 할 때 호출되는 함수이다.
1: /*
2: * Open the mmap.
3: * Add the area to the attach list.
4: */
5: static void dsm_vma_open(struct vm_area_struct *vmap)
6: {
7: DsmDevice *devp;
8: DsmVma *listp;
9: listp = vmap->vm_private_data;
10: devp = listp->devp;
11: /* Link the VM area into the list of attached segments */
12: if((listp->next = devp->attaches) != 0)
13: devp->attaches->pprev = &listp->next;
14: devp->attaches = listp;
15: listp->pprev = &devp->attaches;
16: }
9: 전달받은 매개변수(vmap 정보)의 상태정보를 DSM 가상 메모리 영역 리스트에 저장한다.
12~16: DSM의 디바이스 attaches와 가상 메모리 영역 리스트를 연결한다.
2.2.3 dsm_vma_close
dsm_vma_close() 함수는 메모리 매핑이 해제될 때 호출되는 함수이다.
1: static void dsm_vma_close(struct vm_area_struct *vmap)
2: {
3: DsmVma *listp;
4: DsmVma *next;
5: DsmVma **pprev;
......
9: /* Unlink from the list of attached segments */
10: listp = vmap->vm_private_data;
11: next = listp->next;
12: pprev = listp->pprev;
13: *pprev = next;
14: if(next)
15: next->pprev = pprev;
16: vmap->vm_private_data = 0;
17: kfree(listp);
18: }
10~15: 현재 매핑된 vmap을 리스트로부터 제거한다.
2.2.4 dsm_vma_nopage
dsm_vma_nopage() 함수는 페이지 폴트가 발생해 pte리스트를 확인했을 때, 페이지가 현재 노드에 존재하지 않은 경우 발생한다. 현재의 페이지 플래그가(PSTATE_FAULTED)가 PSTATE_PRESENT가 될 때 까지 대기상태에 놓이게 된다.
1: /*
2: * Nopage function.
3: */
4: static struct page *dsm_vma_nopage(
struct vm_area_struct *vmap, unsigned long address, int *write_access)
5: {
6: pte_t pte;
7: struct page *pagep;
8: unsigned long index;
9: DsmDevice *devp;
10: DsmPage *infop;
11: DsmVma *listp;
12: /* Get info */
13: listp = vmap->vm_private_data;
14: devp = listp->devp;
15: /* Get the number of the page that was referenced */
16: index = (address - vmap->vm_start) >> PAGE_SHIFT;
17: if(index >= DSM_PAGES)
18: return NOPAGE_SIGBUS;
19: /* Wait until the page is present */
20: infop = devp->pages + index;
21: while((infop->flags & PSTATE_PRESENT) == 0) {
22: /*
23: * If this is the first time, mark the page as faulted,
24: * and wake up the reader so the fault command will
25: * be passed to the daemon.
26: */
27: if((infop->flags & PSTATE_FAULTED) == 0) {
28: infop->flags |= PSTATE_FAULTED;
29: wake_up(&devp->readq);
30: }
31: /*
32: * Wait until daemon wakes us up.
33: */
34: waitset_wait_on(&devp->faultq);
35: }
36: /* Return the page */
37: pte = __pte(infop->pte);
38: if(!pte_present(pte))
39: return NOPAGE_SIGBUS;
40: pagep = pte_page(pte);
41: atomic_set(&pagep->_count, 1);
42: return pagep;
43: }
16 : 폴트가 발생한 페이지의 주소를 이용해 페이지를 인덱스를 구한다.
21 : 플래그가 PSATATE_PRESENT가 아닌 동안 진행한다.
27-30 : 플래그가 PSTATE_FAULTED가 아니라면 해당 페이지가 다른 노드로부터 도착한 것이므로 PSATATE_FAULTED 플래그를 해제시키고, 프로세스를 깨운다.
34 : 데몬에서 페이지를 받아와 프로세스를 깨울 때 까지 폴트 큐에 넣고 대기한다.
37~40 : 페이지를 받아왔다면 페이지정보를 pte에 저장한다.
Daemon
그림 5은 데몬의 주요 기능을 보이고 있다. 데몬이 실행되면 소켓통신을 위한 기본적인 작업을 수행한 후, server() 함수를 호출해 본격적으로 인터페이스 역할을 수행한다. 일반적으로 해당 노드의 커널 모듈에서 put_user() 함수를 통해 데몬으로 필요한 페이지 정보를 요청할 경우 데몬은 read_device() 함수를 통해 이 데이터를 읽는다. 그리고 해당 페이지가 read()함수 혹은 wrtie 연산에 의해 발생한 여부에 따라 해당 페이지 번호의 플래그(flags)를 설정한다. 그 후 serve_queue() 함수에서는 계속해서 페이지 리스트들의 플래그를 확인한다. 만약 페이지 요청으로 인해 플래그가 변경됐을 경우, 데몬은 다른 노드로 페이지를 요청하게 되며 페이지를 가지고 있는 노드는 페이지 정보를 처음 요청이 들어온 노드로 되돌려주게 된다. 페이지를 요청했던 노드에서는 정상적으로 페이지를 받을 경우, 이 페이지를 이용하며 일정 횟수동안 요청했음에도 페이지 정보가 오지 않을 경우 없는 페이지로 간주하여 새로운 페이지를 생성한다.
3.1 Daemon commnad interface
노드에 페이지 폴트가 발생을 하고, 해당 노드에 페이지가 존재하지 않는다면 모듈은 데몬을 통해 다른 노드로 페이지를 요청해야한다. 따라서 통신을 위한 자료구조와 어떤 연산에 의해 페이지 폴트가 발생했는지 나타낼 플래그가 필요하다.
1: /*
2: * This is the format of commands.
3: */
4: #define DSM_READ_PAGE 0
5: #define DSM_WRITE_PAGE 1
6: #define DSM_ACK_PAGE 2
7: typedef struct dsm_command {
8: unsigned long id;
9: int command;
10: int page;
11: char data[EXEC_PAGESIZE];
12: } DsmCommand;
4: 쓰기 연산에서 DSM_READ_PAGE로 플래그를 설정함으로써 페이지를 요청하게 된다. 데몬이 읽기 결과로 DSM_READ_PAGE를 얻으면 페이지는 폴트되고 드라이버는 페이지를 요청하게 된다.
5: DSM_WRITE_PAGE는 쓰기 연산에서 드라이버에 페이지 내용을 쓰기 위해 사용되며, 읽기 연산에서는 데몬의 요청에 따라 드라이버에 페이지를 데몬에 제공할 때 사용된다.
10~11 : 페이지의 번호와 실제 페이지의 데이터를 저장하기 위해 사용된다.
3.2 read_device
그림 6는 read_device() 함수의 순서도를 보이고 있다. read_device() 함수는 read() 함수를 호출하며, 이와 연결된 dsm_read() 함수가 put_user() 함수를 통해 페이지에 대한 명령어가 데몬으로 복사 된다. 이 명령어에 의해 페이지 요청이 읽기 혹은 쓰기 연산에 따라 fault_page() 함수나 deliver_page() 함수가 호출된다.
fault_page() 함수는 페이지가 아예 없는 경우 새로 할당하거나, 다른 페이지로부터 페이지를 읽어와 저장해야 하므로 페이지를 생성하고 플래그를 PSTATE_FAULTED로 설정하는 역할만 한다.
1: /*
2: * Fault the page for the first time.
3: * Set the page in fault mode.
4: */
5: static void fault_page(Server *servep, DsmCommand *comp)
6: {
7: PageInfo *infop;
......
8: /* Get the page info */
9: infop = find_or_create_page(servep, comp->page);
10: infop->flags |= PSTATE_FAULTED;
11: }
9~10: 읽기 요청에 의해 fault_page() 함수가 호출되면 제일 해당 페이지가 있는지 찾게 된다. 만약 페이지가 존재하지 않는다면 새로운 페이지 자료구조를 할당하며, 현재 페이지의 플래그는 PSTATE_FAULTED로 설정한다.
deliver_page() 함수는 페이지가 없는 경우 새 페이지를 생성해 디바이스 드라이버로 돌려주거나 다른 페이지로 전송하기 위해 페이지 데이터를 커맨드 자료구조에 저장한다.
1: /*
2: * Get a page from the process.
3: */
4: static void deliver_page(Server *servep, DsmCommand *comp)
5: {
6: PageInfo *infop;
......
7: /* Get the page info */
8: infop = find_or_create_page(servep, comp->page);
9: if(infop->dstp == 0) {
10: /* Something broke */
11: fprintf(stderr, "unexpected deliver_page\n");
12: return;
13: }
14: /* Copy the data to the page */
15: memcpy(infop->dstp->command.data, comp->data, EXEC_PAGESIZE);
16: infop->timeout = 0;
17: infop->counter = 0;
18: infop->flags = PSTATE_INDELIVERY;
19: }
9~13 : 쓰기 연산의 경우 목적지의 주소가 필요하므로 dstp 자료구조의 할당 여부를 확인한다.
15 : command의 데이터(실제 데이터)를 할당된 페이지로 복사한다.
16~18 : 통신을 위해 플래그를 PASTATE_INDELIVERY로 설정하고, 페이지가 없는 경우를 처리하기 위해 timeout과 counter를 초기화한다.
3.3 serve_queue
그림 7는 serve_queue() 함수의 흐름도를 보이고 있다. serve_queue() 함수는 지속적으로 해당 노드에서 가지고 있는 page 정보의 리스트를 확인하며, read_device() 함수에 의해 플래그가 설정된 페이지를 검색하고, 플래그에 맞는 처리를 수행한다. 쓰기 연산에 의해 PSTATE_INDELIVERY로 설정 돼있으면 retry_delivery() 함수를, 읽기 연산에 의해 PSTATE_FAULTED 플레그가 설정 돼있으면 retry_request() 함수를 호출하게 된다.
그리고 요청 횟수에 따라 retries 변수가 증가하게 되는데, MAX_RETRIES와 같은 경우(최대 페이지 요청 수만큼 요청했음에도 페이지가 도착하지 않은 경우) 페이지가 다른 노드에 존재하지 않음으로 판단하여 서로 다른 처리를 하게 된다.
retry_delivery() 함수의 경우 다른 노드에 페이지가 존재하지 않고 전송요청이 취소된 경우 write_back() 함수를 호출하고, 다른 노드로부터 페이지가 요청돼 페이지 정보를 전송해야 하는 경우 send_delivery() 함수를 호출한다.
전송을 요청한 횟수가 MAX_RETIRES와 같다면 다른 노드에 존재하지 않는 페이지로 판단하고 생성해둔 페이지를 자신에 노드에 쓰기 위해 write_back() 함수를 호출한다.
1: /*
2: * A delivery has been canceled.
3: */
4: static void write_back(Server *servep, PageInfo *infop)
5: {
6: /* Send the page to the device */
7: DsmCommand *comp;
......
8: /* Send the page to the device */
9: comp = &infop->dstp->command;
10: comp->command = DSM_WRITE_PAGE;
11: comp->page = infop->page;
12: write(servep->devfd, comp, sizeof(DsmCommand));
13: /* Clear storage */
14: free(infop->dstp);
15: infop->dstp = 0;
16: infop->flags = PSTATE_PRESENT;
17: }
9~11 : 커맨드 정보를 설정한다. 이때 자신의 노드에 페이지 정보를 써야하기 때문에 플래그는 DSM_WRITE_PAGE로 설정한다.
12 : write() 함수는 dsm_write() 함수로 매핑되어 있으며, 페이지의 정보와 페이지 내용을 디바이스로 전달한다.
만약 다른 노드로부터 페이지 요청을 받았고, 이에 응답하기 위해 페이지 데이터를 전송하는 경우라면 send_delivery() 함수가 호출된다.
1: /*
2: * Send the message.
3: */
4: static void send_delivery(Server *servep, PageInfo *infop)
5: {
6: DsmCommand *comp;
7: struct sockaddr_in *sinp;
8: dtime now;
......
9: /* Format the command */
10: comp = &infop->dstp->command;
11: comp->id = htonl(servep->id);
12: comp->command = htonl(DSM_WRITE_PAGE);
13: comp->page = htonl(infop->page);
14: /* Send it */
15: sinp = &infop->dstp->sin;
16: if(sendto(servep->socketd, comp, sizeof(*comp), 0,
struct sockaddr *) sinp, sizeof(*sinp)) < 0)
17: perror("sendto: send_delivery");
18: /* Increment the counters */
19: now = get_current_time();
20: infop->timeout = now + (FIRST_TIMEOUT << infop->counter);
21: infop->counter++;
22: }
10~13: 전송할 페이지 정보를 커맨드 정보에 포함한다. 이때 요청한 노드에 쓰여 질 페이지이므로 커맨드의 플래그 정보는 DSM_WRITE_PAGE로 설정한다.
15~16: 보낼 ip주소를 페이지의 목적지(infop->dstp)의 ip로 설정한 후, 전송한다.
20~21: 타임아웃이나 다른 노드에 페이지가 없는 경우를 처리하기 위해 timeout과 counter변수를 증가시킨다.
retry_request() 함수의 경우 다른 노드에도 페이지가 존재하지 않으면 null_page() 함수를 호출하고 그렇지 않으면 계속해서 다른 노드에 페이지를 요청하는 cast_request() 함수를 호출한다.
null_page() 는 페이지를 읽으려 했으나 현재 노드나 다른 노드에도 존재하지 않는 페이지를 읽을 때 처리되는 함수이다. 이 경우에도 미리 마련된 빈 페이지를 현재 노드의 메모리에 쓴다.
1: /*
2: * Install a null page.
3: */
4: static void null_page(Server *servep, PageInfo *infop)
5: {
6: DsmCommand command;
......
7: /* Format the page */
8: command.command = DSM_WRITE_PAGE;
9: command.page = infop->page;
10: memset(command.data, servep->code, EXEC_PAGESIZE);
11: write(servep->devfd, &command, sizeof(command));
12: /* Make the page present */
13: infop->flags = PSTATE_PRESENT;
14: }
7~11: 현재 노드는 물론 다른 노드에도 없는 페이지이므로 페이지 정보를 생성한 후 현재의 노드에 쓰기 연산으로 페이지를 생성한다.
페이지 정보가 필요해 요청해야 하는 경우 cast_request() 함수를 통해 연결된 모든 노드에 브로드캐스트(Broadcast)로 데이터를 전송한다.
1: /*
2: * Broadcast a request for the page.
3: */
4: static void cast_request(Server *servep, PageInfo *infop)
5: {
6: DsmCommand command;
7: dtime now;
......
8: /* Format the request */
9: command.id = htonl(servep->id);
10: command.command = htonl(DSM_READ_PAGE);
11: command.page = htonl(infop->page);
12: /* Send to broadcast address */
13: if(sendto(servep->socketd, &command, sizeof(command), 0,
(struct sockaddr *) &servep->sin_cast,
sizeof(servep->sin_cast)) < 0)
14: perror("sendto: cast_request");
15: /* Increment counters */
16: now = get_current_time();
17: infop->timeout = now + (FIRST_TIMEOUT << infop->counter);
18: infop->counter++;
19: }
9~11: 현재 필요한 페이지 정보를 커맨드 자료구조에 포함하며 이때 플래그는 DSM_READ_PAGE를 사용한다.
13: 연결 가능한 모든 노드에 브로드캐스트로 커맨드 자료구조를 전송한다
17~18: 타임아웃이나 다른 노드에 페이지가 없는 경우를 처리하기 위해 timeout과 counter변수를 증가시킨다.
6.4 read_socket
그림 8은 read_socket() 함수의 흐름도를 보이고 있다. read_socket() 함수는 소캣을 통해 데이터를 요청받을 경우 DSM_READ_PAGE 혹은 DSM_WIRTE_PAGE 플래그에 따라 데이터를 처리해주는 역할을 하고 있다. DSM_READ_PAGE의 경우 handle_page_request() 함수를 호출하여 요청한 페이지를 찾고 맨 처음 페이지를 요구한 노드로 되돌려주며, DSM_WRITE_PAGE의 경우 handle_page_delivery() 함수를 통해 데이터를 처리한다.
handle_page_request() 함수는 다른 노드로부터 요청이 발생한 경우 현재 노드에 페이지가 있는지 확인하고 페이지가 있다면 플래그를 DSM_READ_PAGE로 설정하고 write() 함수를 호출한다.
1: /*
2: * Request a page.
3: * If we don't have it, just ignore it.
4: */
5: static void handle_page_request(Server *servep, DsmCommand *comp, struct sockaddr_in *sinp)
6: {
7: PageInfo *infop;
8: PageDst *dstp;
......
9: infop = find_page(servep, comp->page);
10: if(infop && infop->flags == PSTATE_PRESENT) {
11: /* Save the return address */
12: dstp = infop->dstp;
13: if(dstp == 0) {
14: dstp = (PageDst *) malloc(sizeof(PageDst));
15: if(dstp == 0)
16: return;
17: infop->dstp = dstp;
18: }
19: dstp->sin = *sinp;
20: /* Ask the device for the page */
21: infop->counter = 0;
22: comp->command = DSM_READ_PAGE;
23: comp->page = infop->page;
24: write(servep->devfd, comp, sizeof(*comp));
25: }
26: }
9: 요청받은 페이지를 자신의 페이지 리스트에서 검사한다.
10~19: 페이지를 찾았고, 현재 메모리에 보유 중(PSTATE_PRESENT)일 경우, 요청받은 노드로 전송할 수 있는 상태이다. 이 경우 목적지(dstp)의 정보를 생성하고, 요청받은 노드의 ip주소(sinp)로 목적지의 주소를 설정한다.
20~24: 현재 노드에서 찾은 페이지의 정보를 커맨드 자료구조로 복사한 후, 목적지로 전송한다.
요청했던 페이지를 받아 현재 노드에 저장하는 경우 handle_page_delivery() 함수가 호출된다. 이 함수는 페이지 정보를 자신이 페이지에 저장하기 위해 플래그를 DSM_WRITE_PAGE로 설정하고 자신의 디바이스 드라이버의 fd에 쓴다.
1: /*
2: * Handle a page install.
3: * Respnd to the sender.
4: */
5: static void handle_page_delivery(Server *servep, DsmCommand *comp, struct sockaddr_in *sinp)
6: {
7: PageInfo *infop;
8: unsigned flags;
......
9: /* Get the page */
10: infop = find_page(servep, comp->page);
11: if(infop == 0)
12: return;
13: /* If the page is already present, this is a duplicate delivery */
14: flags = infop->flags;
15: if(flags & PSTATE_FAULTED) {
16: /* Install the page in the device */
17: comp->command = DSM_WRITE_PAGE;
18: comp->page = infop->page;
19: write(servep->devfd, comp, sizeof(*comp));
20: infop->flags = PSTATE_PRESENT;
21: }
22: /* Acknowledge the receipt */
23: comp->id = htonl(servep->id);
24: comp->command = htonl(DSM_ACK_PAGE);
25: comp->page = htonl(infop->page);
26: if(sendto(servep->socketd, comp, sizeof(*comp), 0,
(struct sockaddr *) sinp, sizeof(*sinp)) < 0)
27: perror("sendto: handle_page_delivery");
28: /* Delete the dst buffer */
29: if(infop->dstp) {
30: free(infop->dstp);
31: infop->dstp = 0;
32: }
33: }
10~12: 현재 노드에서 페이지를 찾는다. 페이지가 없을 경우 그냥 종료한다.
14~21: 찾은 페이지의 플래그가 PSTATE_FAULTED일 경우 현재 페이지에 페이지를 쓰고 플래그를 PSTATE_PRESENT로 변경한다.
22~26: 요청에 응답하기 위해 커맨드를 DSM_ACK_PAGE로 변경하고, 페이지 정보를 요청한 곳으로 되돌려준다(하지만 ACK은 실제 시스템에서 사용하지 않는 플래그이다).
정리
그림 9는 앞서 설명한 함수들에 대한 종합적인 흐름도를 보인다.
관련 지식
5.1 page fault
멀티 프로세스에서 하나의 물리 적인 메모리를 여러 어플리케이션이 공유하는 동시에, 어플리케이션은 서로 인식하지 못하고 자신만이 모든 메모리를 사용하고 있는 것으로 인식한다. 또한 실제 물리 메모리보다 더 많은 가상의 주소 공간(Virtual Address Space)을 가질 수 있도록 하는 기술을 ‘가상 메모리(Virtual Memory)’라고 부른다. 이때 프로세스 주소 공간을 일정 한 크기로 나누며, 나눠진 공간을 ‘페이지(Page)‘라고 한다. 페이지들은 필요할 때 주기억장치에 불려오며, 가상 메모리에서는 연속적인 하나의 공간은 주기억장치에서는 여러 조각으로 나뉘어 임의의 위치에 배치된다. 페이지의 가상 주소와 물리 주소는 서로 다르기 때문에 메모리 관리 장치 (MMU :Memory Management Unit)에 의해 변환된다.
어플리케이션에서 데이터를 작업을 수행하던 중 페이지가 물리 메모리에 존재하지 않는다면 보조기억장치로부터 페이지를 가져와야한다. 이때 페이지 폴트가 발생한다. 페이지 폴트가 발생하면 어플리케이션은 수행을 멈추고 운영체제에 페이지를 요청한다. 요청을 받은 페이지는 보조기억장치로부터 페이지를 읽어와 물리 메모리에 적재하고 페이지 테이블을 갱신한다.
(출처 : 응용 운영 체제 개념 Silberschatz, Galvin, Gagne 공저)
5.2 리눅스의 vm_area_struct
그림 11은 리눅스의 가상 메모리 관리를 보이고 있다. 하나의 프로세스는 메모리 관리를 위한 mm 자료구조를 포함하고 있으며, mm은 vma_area_struct 자료구조들을 링크드 리스트로 관리한다. vam_area_struct 자료구조는 가상 메모리 공간을 나누워 가리킨다. 또한 mm 자료구조는 페이지 정보를 관리하기 위한 페이지 테이블을 저장하고 있으며, 필요한 페이지가 발생하면 페이지 테이블을 통해 pte를 참조한다.
(출처 : Linux Kernel Camp 한국 정보과학회 컴퓨터 시스템 소사이어티)
5.3 PTE
프로세스에서 페이지가 필요할 경우 프로세스의 가상 메모리 주소는 MMU를 통해 물리 메모리 주소로 변경하게 되며, 이때 MMU는 페이지 테이블을 참조하여 주소를 변경한다. 그림 12는 페이지 테이블의 역할을 보여준다. 가상 주소에서 요청한 페이지(p)는 페이지 테이블을 참조해 물리 페이지(f, 혹은 프레임)의 번호로 변경된다. 그리고 d를 통해 해당 인덱스로부터 떨어진 주소를 참조하게 된다. 이때 p에서 f로 매핑시키는 페이지 테이블의 하나의 구성요소를 페이지 테이블 엔트리(PTE : Page Table Entry)라고 부른다.
모듈이 작성된 리눅스 커널의 버전 기준(Linux kernel 2.6.15.7)에서 하나의 PTE는 3-level page table을 갖는다.
5.4 I/O Multiplexing (select)
여러 클라이언트와 연결하거나 여러 장치와 입출력이 필요한 경우 단일 I/O를 사용할 경우 read혹은 recv등과 같은 함수에 의해 블록킹(Blocking)이 발생한다. 이런 경우 다른 클라이언트의 데이터를 원활하게 받지 못하거나 성능이 저하되는 문제가 있으며, 이를 해결하기 위한 기법 중 하나가 I/O 멀티플랙싱(I/O Multiplexing)이다. select는 이 멀티 플랙싱 기술 중 하나이다. 멀티 플랙싱은 프로그램에서 여러 파일 디스크립터를 모니터링해서 어떤 종류의 I/O 이벤트가 일어났는지 검사하고 각각의 파일 디스크립터가 준비상태가 되었는지 인지한다. 만약 준비된 파일 디스크립터가 발견되면 해당 디스크립터의 이벤트를 처리한다.
(출처1 : Linux man page)
(출처2 : http://daniel.haxx.se/docs/poll-vs-select.html)
5.5 interruptible_sleep_on
sleep_on함수는 현재 프로세스의 상태를 TASK_UNINTERRUPTIBLE로 설정하고 이를 지정한 대기 큐에 삽입한다. 그런 다음 스케줄러를 호출하여 다른 프로세스의 실행을 재개한다. 잠들어 있던 프로세스가 깨어나면 스케줄러는 sleep_on()에 실행을 재개하여 프로세스를 대기 큐에서 제거한다.
1: void sleep_on(wait_queue_head_t *wq)
2: {
3: wait_queue_t wait;
4: init_waitqueue_entry(&wait, current);
5: current->state = TASK_UNINTERRUPTIBLE;
6: add_wait_queue(wq, &wait);
7: schedule();
8: remove_wait_queue(wq, &wait);
9: }
interruptible_sleep_on은 현재 프로세스의 상태를 TASK_TASK_INTERRUPTIBLE로 설정하여 시그널을 수신해도 프로세스가 깨어날 수 있게 한다는 점을 제외하고 sleep_on과 동일하다.
sleep_on_timeout과 interruptible_sleep_on_timeout함수는 앞에 나온 함수들과 비슷하지만 커널이 프로세스를 깨우기 전에 기다리는 시간을 지정할 수 있다. 이를 위해 이 함수는 schedule()대신 schedule_timeout()함수를 호출한다.
리눅스 2.6에서 도입한 prepare_to_wait(), prepare_to_wait_exclusive(), finish_wait()함수는 현재 잠잘 프로세스를 대기 큐에 넣는 또 하나의 방법을 제시한다. 전형적으로 다음과 같이 사용된다.
1: DEFINE_WAIT(wait);
2: prepare_to_wait_exclusive(&wq, &wait, TASK_INTERRUPTIBLE);
3: /*wq is the head of the wait queue*/
4: ......
5: if( !condition)
6: schedule();
7: finish_wait(&wq, &wait);
prepare_to_wait()과 prepare_to_wait_exclusive()함수는 현재 상태를 세 번째 매개 변수로 전달된 값으로 설정하고, 대기 큐 요소의 배타성을 나타내는 플래그를 0(배타적이지 않은) 또는 1(배타적인)으로 설정한다. 그리고 대기 큐 요소 wait을 대기 큐 머리 wq에 삽입한다.
프로세스가 깨어나면 바로 프로세스 상태를 TASK_RUNNING 상태로 바꾸는 FINISH_WAIT()함수를 수행하고(SCHEDULE() 함수를 호출하기 전에 깨우는 조건이 참인 경우에만 해당한다), 대기 큐 리스트에서 대기 큐 요소를 제거한다(이것은 깨우기 함수에서 요소를 제거하지 않은 경우에만 해당한다).
wait_event와 wait_event_interruptible 매크로는 지정한 조건이 참이 될 때까지 이를 호출한 프로세를 대기 큐에서 잠들게 한다. 예를 들어, wait_event(wq, condition)매크로는 다음과 같은 코드를 만든다.
1: DEFINE_WAIT(__wait);
2: for(;;){
3: prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);
4: if(condition)
5: break;
6: schedule();
7: }
8: finish_wait(&wq, &__wait);
(출처 : 리눅스 커널의 이해 다니엘 보베이, 마르코 처사티 저)
5.6 put_user
디바이스 드라이버의 read()함수는 응용 프로그램에 데이터를 전달하는 것이 목적이다(데이터의 흐름이 디바이스 드라이버에서 유저 어플리케이션이다). 이 데이터는 하드웨어에서 얻은 것일 수도 있고, 디바이스 드라이버 동작 중에서 발생한 것일 수도 있다. 이때 read()함수의 매개변수로는 응용 프로그램의 버퍼 주소이며, 디바이스 드라이버는 커널 메모리 공간에서 동작하기 때문에 디바이스 드라이버는 데이터 버퍼의 주소를 그대로 전달 할 수 없다. 이와 같이 디바이스 드라이버와 응용 프로그램간의 메모리를 전달하기 위해 커널은 put_user함수 또는 copy_to_user()함수를 사용한다.
(출처 : 리눅스 디바이스 드라이버 유영창 저)
'공부 > 커널' 카테고리의 다른 글
qemu + gdb 연동 (0) | 2018.07.11 |
---|---|
커널 소스 분석 1. system call trace (2) | 2016.01.09 |
asmlinkage에 대해 (0) | 2014.09.02 |