mmap 구현 방식 2가지

ldd 많이 참조함, 옅은 지식으로 쓴 내용이라 틀릴 수 있음

mmap의 역할?

kernel 공간 혹은 하드웨어 장치에 있는 메모리를 복사하는 과정 없이 user 공간에게 제공
rw가 엄청나게 발생하는 경우에 꽤나 의미있는 성능향상을 보여줄 수 있다
c 어플리케이션에서 mmap 함수를 호출하면 device driver의 mmap 함수가 호출된다
그 함수를 구현하는게 목표


어플리케이션 영역

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
          int fd, off_t offset);
int munmap(void *addr, size_t length);

자세한 설명은 man 페이지 참조
flag는 계속 늘어나는데 MAP_HUGETLB 같이 huge page를 쓸 때 필요한 옵션도 있다
그냥 일반적인 flag로는 MAP_SHARED(다른 프로세스랑 같이 공유) 가 있겠다
prot는 page의 권한에 관한 내용이다
페이지가 읽힐 예정인지 쓰거나 실행될련지 등등


mmap 구현

요약 : 아래의 함수들을 잘 조합해서 사용하면 된다

io_remap_pfn_range()
remap_pfn_range()

아니면 fault 를 처리하는 방식으로 구현해도 ok

vm_insert_page()
vm_insert_pfn()

fault 처리에 주로 사용된다

원형

include/linux/fs.h
struct file_operation {
    ...
    int (*mmap) (struct file *, struct vm_area_struct *);
}

remap_pfn_range 사용

remap_pfn_range - remap kernel memory to userspace
system RAM을 맵핑하고 싶을 때 사용
참고 : io_remap_pfn_range은 장치의 I/O memory 즉 phys_addr_t로 표현되는 놈을 매핑하고 싶을 때 사용
nommu가 아닌이상 동일

내가 파악한 바로는(틀릴 수 있지만 ㅠㅜ)

  1. 유저에서 mmap을 호출하면 (사용할 수 있는 커널 메모리 쫌만 주세요. 대신 제가 알아볼 수 있는 주소로요)
  2. 위의 요청 정보가 vma 형태로 driver로 들어오고(메모리 정보)
  3. 인제 개발자는 가지고 있던(미리 할당해두었던) 메로리를 vma랑 붙여주는데
  4. 그 때 사용하는 도구가 remap_pfn_range 인가보다

이 함수는 userspace의 addr ~ addr+size 범위의 virtual address를 위한 page table을 만든다
이 함수는 pfn<<PAGE_SHIFT ~ (pfn<<PAGE_SHIFT) + size 까지 해당하는 물리 메모리 영역에 영향을 준다

include/linux/pfn.h
#define PFN_PHYS(x)     ((phys_addr_t)(x) << PAGE_SHIFT)
#define PHYS_PFN(x)     ((unsigned long)((x) >> PAGE_SHIFT))

int remap_pfn_range(struct vm_area_struct *vma,  
                    unsigned long addr, 
                    unsigned long pfn,
                    unsigned long size, 
                    pgprot_t prot)

vma : 매핑될 virtual memory area - 변태가 아닌 이상 mmap 호출될 때 vma 인자가 같이 딸려오므로 그대로 쓰면 된다. 하지만 변태라면?!
addr : target user address to start at
pfn : page frame number of kernel memory
보통 physical address >> PAGE_SHIFT 한 값이다
이 physical address에 addr(target user address, virtual address)가 매핑되게 된다

size : 바이트 단위의 매핑될 영역 크기. 보통 vma->vm_end - vma->vm_start
prot : page protection flag, 보통 vma->vm_page_prot 값을 사용한다

이 함수를 호출할 때 흔히 다음과 같이 한다(아래 참조)

예시 : drivers/uio/uio.c

static const struct file_operations uio_fops = {
    ...
    .mmap = uio_mmap,
}

static int uio_map(struct file *filep, struct vm_area_struct *vma)
{
    ...
    return uio_mmap_physical(vma);
}

static int uio_mmap_physical(struct vm_area_struct *vma)
{
    ...
    return remap_pfn_range(vma,
                           vma->vm_start,
                           mem->addr >> PAGE_SHIFT,    //미리 할당해두었던 메모리 
                           vma->vm_end - vma->vm_start,
                           vma->vm_page_prot);
}

조금 형태가 다르긴 한데 이런 것도 있다
drivers/vfio/pci/vfio_pci.c

static int vfio_pci_mmap(void *device_data, struct vm_area_struct *vma)
{
    ...


    pgoff = vma->vm_pgoff &
            ((1U << (VFIO_PCI_OFFSET_SHIFT - PAGE_SHIFT)) - 1);    //???

    u64 phys_len = pci_resource_len(pdev, index);    
    req_start = pgoff << PAGE_SHIFT;
    req_len = vma->vm_end - vma->vm_start;

    if(phys_len < PAGE_SIZE || req_start + req_len > phys_len)    //크기 검사
        return -EINVAL;

    vma->vm_pgoff = (pci_resource_start(pdev, index) >> PAGE_SHIFT) + pgoff;

    return remap_pfn_range(vma, vma->start, vma->vm_pgoff,
                            req_len, vma->vm_page_prot);
    //어차피 remap_pfn_range를 호출하면 vma->vm_pgoff = pfn 이 된다
}

아니면 이런 애들도 있다
sound/core/pcm_native.c

int snd_pcm_lib_default_mmap(...)
{
    area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
#ifdef CONFIG_GENERIC_ALLOCATOR
    if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV_IRAM) {
            area->vm_page_prot = pgprot_writecombine(area->vm_page_prot);
            return remap_pfn_range(area, area->vm_start,
                            substream->dma_buffer.addr >> PAGE_SHIFT,
                            area->vm_end - area->vm_start, area->vm_page_prot);
    }    
#endif /* CONFIG_GENERIC_ALLOCATOR */
#ifndef CONFIG_X86 /* for avoiding warnings arch/x86/mm/pat.c */
    if (!substream->ops->page &&
        substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV)
            return dma_mmap_coherent(substream->dma_buffer.dev.dev,
                                     area,
                                     substream->runtime->dma_area,
                                     substream->runtime->dma_addr,
                                     area->vm_end - area->vm_start);
#endif /* CONFIG_X86 */
    /* mmap with fault handler */
    area->vm_ops = &snd_pcm_vm_ops_data_fault;
    return 0;
}

기타 다른 예시
drivers/infiniband/hw/nes/nes_verbs.c

static int nes_mmap(struct ...)
{
    ...
    remap_pfn_range( ...
    virt_to_phys >> PAGE_SHIFT ...)
    ...
}

.pgprot

잘 몰라서 생략
대략 cache 쓸껀지 말껀지 관한 내용인데
pgprot_noncached, pgprot_writecombine, pgprot_writethrough
등이 있다


기타

drivers/media/platform/omap/omap_vout.c
OMAP 24xx camera controller 코드

처음 매몰이 할당 코드

virt_addr = omap_vout_alloc_buffer(vout->buffer_size, &phy_addr);
vout->buf_virt_addr[i] = virt_addr;
vout->buf_phy_addr[i] = phy_addr;

unsigned long omap_vout_alloc_buffer(u32 buf_size, u32 *phys_addr)
{
    u32 order, size;
    unsigned long virt_addr, addr;

    size = PAGE_ALIGN(buf_size);    //참고로 가로px * 세로px * bpp(bits per pixel)
    order = get_order(size);
    virt_addr = __get_free_pages(GFP_KERNEL, order);
    addr = virt_addr;

    *phys_addr = (u32) virt_to_phys((void *) virt_addr);
    return virt_addr;
}

mmap 코드

vm_operations_struct omap_vout_vm_ops
    .open = vout->mmap_count++; 역할 수행(mmap )
    .close =  vout->mmap_count--; 역할 수행

mmap 호출한다고 해서 mmap open까지 같이 호출되지는 않는다
놀랍게도 r/w 둘 다 open을 호출하지 않는다
open은 process가 fork할 때 불려진다
그리고 vma에 대한 새로운 reference를 생성한다

static int omap_vout_mmap(struct file *file, struct vm_area_struct *vma)
{
    unsigned long start = vma->vm_start;
    unsigned long size = vma->vm_end - vm->vm_start;
    ...
    void* pos = (void *)vout->buf_virt_addr[i];            //kernel logical address(virtual address)
    vma->vm_pgoff = virt_to_phys((void *) pos) >> PAGE_SHIFT;    //physical
    vma->vm_ops = &omap_vout_vm_ops;
    ...

    while(size > 0) {
        unsigned long pfn;
        pfn = virt_to_phys((void *) pos) >> PAGE_SHIFT;        //물리 주소 획득
        if(remap_pfn_range(vma, start, pfn, PAGE_SIZE, PAGE_SHARED))
            return -EAGAIN;
        start += PAGE_SIZE;
        pos += PAGE_SIZE;
        size -= PAGE_SIZE;
    }

    vout->mmap_count++;        
}

remap_pfn_range에 size를 한 번에 넣는게 아니라 PAGE_SIZE로 나눠 하는 이유?
pixel size가 크다고 해도 어차피 kmalloc 한계인 MAX_ORDER(2^ 10 ~11, 1024) * PAGE_SIZE(4096B) = 4MB or 8MB 이내 범위로 메모리가 할당되어 있다
왜 이짓을 하냐면 메모리가 cma같은게 아닌이상 크게 할당이 안되고 PAGE_SIZE 크기로 할당해서 일듯?
vma 유저 메몰희랑 pfn 물리 매몰의랑

추가 참고 : http://www.cs.fsu.edu/~baker/devices/notes/ch15.html#(34)

그 외 fault 직접 관리

vm_opeartions_struct 의 fault 함수를 직접 구현해야 함
결론적으로 vmf->page = page 를 해주어 메모리를 제공하면 끝
제공해줄 page는 어디서 얻냐고?
여러가지 구현 방법이 있는데 get_zeroed_page 같은 함수로 미리 얻어놓거나
필요할 때마다 획득하거나 등등

예시 : sound/usb/usx2y/usX2Yhwdep.c, sound/core/memalloc.c
mmap이 호출되면 __get_free_pages 로 shared memory 호출하고
fault가 호출되면 아래의 함수가 호출된다

static int snd_us428ctls_vm_fault(struct vm_area_struct *area, 
                                  struct vm_fault *vmf)
{
    ...
    offset = vmf->pgoff << PAGE_SHIFT;        //physical to kernel logical
    vaddr = (char*)((struct usX2Ydev *)area->vm_private_data)->us428ctls_sharedmem + offset;    
    //미리 얻어놨던 메모리 조금씩 제공
    page = virt_to_page(vaddr);
    vmf->page = page;
    get_page(page);                // 나 이 페이지 쓴다!
}

혹은 다른 예시 - dma 같은게 필요 없는 경우
drivers/uio/uio.c

static int uio_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
    page = vmalloc_to_page(addr);
    get_page(page);
    vmf->page = page;
}

drivers/staging/android/ion/ion.c

static int ion_vm_fault(staruct vm_area_struct *vma, struct vm_fault *vmf)
{
    ...
    vm_insert_pfn(vma, (unsigned long)vmf->virtual_address, pfn);
    ...
}

그 외 dma common mmap

include/linux/dma-mapping.h

struct dma_map_ops {
    ...
    int (*mmap)(struct device *, struct vm_area_struct *,
                    void *, dma_addr_t, size_t, struct dma_attrs *attrs);
}

drivers/base/dma-mapping.c 참고
int dma_common_mmap(struct device *dev, struct vm_area_struct *vma,
                    void *cpu_addr, dma_addr_t dma_addr, size_t size)
{
        int ret = -ENXIO;
#if defined(CONFIG_MMU) && !defined(CONFIG_ARCH_NO_COHERENT_DMA_MMAP)
        unsigned long user_count = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
        unsigned long count = PAGE_ALIGN(size) >> PAGE_SHIFT;
        unsigned long pfn = page_to_pfn(virt_to_page(cpu_addr));
        unsigned long off = vma->vm_pgoff;

        vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
                            include/linux/mm.h 

        if (dma_mmap_from_coherent(dev, vma, cpu_addr, size, &ret))
                return ret;

        if (off < count && user_count <= (count - off)) {
                ret = remap_pfn_range(vma, vma->vm_start,
                                      pfn + off,
                                      user_count << PAGE_SHIFT,
                                      vma->vm_page_prot);
        }   
#endif  /* CONFIG_MMU && !CONFIG_ARCH_NO_COHERENT_DMA_MMAP */

        return ret;
}

http://anster.egloos.com/m/2141358ㅁ

'devel > 개념' 카테고리의 다른 글

gpio pull down switch 연결  (0) 2016.11.16
도요타 캠리 급발진 버그 분석 보고서  (0) 2016.11.10
stm32f4 절전모드  (0) 2016.08.10
__GFP_COMP - compound page  (0) 2016.07.26
ld linker script 예시  (0) 2016.07.25
Posted by 쵸코케키

블로그 이미지
chocokeki
쵸코케키

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.12
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31

최근에 올라온 글

최근에 달린 댓글

글 보관함